diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-03 13:42:47 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:27:51 +0000 |
commit | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (patch) | |
tree | d29d987c4d7b173cf853279b79a51598f104b403 /chromium/chrome/browser | |
parent | 830c9e163d31a9180fadca926b3e1d7dfffb5021 (diff) |
BASELINE: Update Chromium to 66.0.3359.156
Change-Id: I0c9831ad39911a086b6377b16f995ad75a51e441
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/chrome/browser')
1009 files changed, 39128 insertions, 8648 deletions
diff --git a/chromium/chrome/browser/BUILD.gn b/chromium/chrome/browser/BUILD.gn index c61e8b60b4c..957ba49abb8 100644 --- a/chromium/chrome/browser/BUILD.gn +++ b/chromium/chrome/browser/BUILD.gn @@ -5,12 +5,13 @@ import("//build/config/chrome_build.gni") import("//build/config/crypto.gni") import("//build/config/features.gni") +import("//build/config/jumbo.gni") import("//build/config/ui.gni") import("//build/split_static_library.gni") import("//chrome/common/features.gni") import("//components/feature_engagement/features.gni") import("//components/nacl/features.gni") -import("//components/offline_pages/features/features.gni") +import("//components/offline_pages/buildflags/features.gni") import("//components/os_crypt/features.gni") import("//components/signin/features.gni") import("//components/spellcheck/spellcheck_build_features.gni") @@ -24,7 +25,6 @@ import("//rlz/features/features.gni") import("//sandbox/features.gni") import("//third_party/protobuf/proto_library.gni") import("//ui/base/ui_features.gni") -import("//build/buildflag_header.gni") # //build/config/android/rules.gni imports //tools/grit/grit_rule.gni, which # produces a conflict for the "grit" template so we have to only include one. @@ -34,10 +34,6 @@ if (is_android) { import("//tools/grit/grit_rule.gni") } -if (enable_vr) { - import("//chrome/browser/vr/features.gni") -} - additional_modules_list_file = "$root_gen_dir/chrome/browser/internal/additional_modules_list.txt" @@ -52,7 +48,6 @@ if (is_win) { "netapi32.lib", "ndfapi.lib", # Used by browser/net/net_error_diagnostics_dialog_win.h "pdh.lib", # Used by browser/private_working_set_snapshot.h - "msi.lib", # Used by browser/conflicts/msi_util_win.h ] ldflags = [ "/DELAYLOAD:ndfapi.dll", @@ -61,16 +56,9 @@ if (is_win) { } } -if (enable_vr) { - buildflag_header("vr_build_features") { - header = "vr_features.h" - flags = [ "USE_VR_ASSETS_COMPONENT=$use_vr_assets_component" ] - } -} - # Use a static library here because many test binaries depend on this but don't # require many files from it. This makes linking more efficient. -split_static_library("browser") { +jumbo_split_static_library("browser") { # Split into multiple static libraries on Windows builds. We have hit size # limits on Windows official builds and on goma builds when symbol_level = 2 # is selected. Always splitting on Windows builds is simpler than trying to @@ -222,6 +210,8 @@ split_static_library("browser") { "browsing_data/counters/site_data_counting_helper.h", "browsing_data/local_data_container.cc", "browsing_data/local_data_container.h", + "browsing_data/navigation_entry_remover.cc", + "browsing_data/navigation_entry_remover.h", "browsing_data/site_data_size_collector.cc", "browsing_data/site_data_size_collector.h", "budget_service/budget_database.cc", @@ -306,14 +296,10 @@ split_static_library("browser") { "component_updater/supervised_user_whitelist_installer.h", "component_updater/sw_reporter_installer_win.cc", "component_updater/sw_reporter_installer_win.h", - "component_updater/third_party_module_list_component_installer_win.cc", - "component_updater/third_party_module_list_component_installer_win.h", "conflicts/enumerate_input_method_editors_win.cc", "conflicts/enumerate_input_method_editors_win.h", "conflicts/enumerate_shell_extensions_win.cc", "conflicts/enumerate_shell_extensions_win.h", - "conflicts/installed_programs_win.cc", - "conflicts/installed_programs_win.h", "conflicts/module_database_observer_win.h", "conflicts/module_database_win.cc", "conflicts/module_database_win.h", @@ -325,10 +311,6 @@ split_static_library("browser") { "conflicts/module_info_win.h", "conflicts/module_inspector_win.cc", "conflicts/module_inspector_win.h", - "conflicts/module_list_manager_win.cc", - "conflicts/module_list_manager_win.h", - "conflicts/msi_util_win.cc", - "conflicts/msi_util_win.h", "conflicts/third_party_metrics_recorder_win.cc", "conflicts/third_party_metrics_recorder_win.h", "consent_auditor/consent_auditor_factory.cc", @@ -357,6 +339,8 @@ split_static_library("browser") { "custom_handlers/protocol_handler_registry.h", "custom_handlers/protocol_handler_registry_factory.cc", "custom_handlers/protocol_handler_registry_factory.h", + "data_reduction_proxy_util.cc", + "data_reduction_proxy_util.h", "data_usage/tab_id_annotator.cc", "data_usage/tab_id_annotator.h", "data_usage/tab_id_provider.cc", @@ -410,6 +394,7 @@ split_static_library("browser") { "download/download_permission_request.h", "download/download_prefs.cc", "download/download_prefs.h", + "download/download_prompt_status.h", "download/download_query.cc", "download/download_query.h", "download/download_request_limiter.cc", @@ -518,12 +503,8 @@ split_static_library("browser") { "google/google_brand_chromeos.cc", "google/google_brand_chromeos.h", "google/google_update_settings_posix.cc", - "google/google_update_win.cc", - "google/google_update_win.h", "google/google_url_tracker_factory.cc", "google/google_url_tracker_factory.h", - "gpu/gpu_driver_info_manager_android.cc", - "gpu/gpu_driver_info_manager_android.h", "gpu/gpu_mode_manager.cc", "gpu/gpu_mode_manager.h", "gpu/three_d_api_observer.cc", @@ -571,6 +552,7 @@ split_static_library("browser") { "install_verification/win/module_list.h", "install_verification/win/module_verification_common.cc", "install_verification/win/module_verification_common.h", + "installable/installable_ambient_badge_infobar_delegate.h", "installable/installable_data.cc", "installable/installable_data.h", "installable/installable_logging.cc", @@ -661,9 +643,12 @@ split_static_library("browser") { "media/platform_verification_impl.h", "media/router/media_router_feature.cc", "media/router/media_router_feature.h", + "media/single_client_video_capture_host.cc", + "media/single_client_video_capture_host.h", "media/webrtc/desktop_media_list.h", "media/webrtc/desktop_media_list_base.cc", "media/webrtc/desktop_media_list_base.h", + "media/webrtc/desktop_media_picker.cc", "media/webrtc/desktop_media_picker.h", "media/webrtc/desktop_streams_registry.cc", "media/webrtc/desktop_streams_registry.h", @@ -681,10 +666,6 @@ split_static_library("browser") { "media/webrtc/native_desktop_media_list.h", "media/webrtc/permission_bubble_media_access_handler.cc", "media/webrtc/permission_bubble_media_access_handler.h", - - # TODO(brettw) should this go with the webrtc sources? - "media/webrtc/webrtc_log_list.cc", - "media/webrtc/webrtc_log_list.h", "media/webrtc/window_icon_util.h", "media/webrtc/window_icon_util_chromeos.cc", "media/webrtc/window_icon_util_mac.mm", @@ -740,6 +721,8 @@ split_static_library("browser") { "metrics/sampling_metrics_provider.h", "metrics/subprocess_metrics_provider.cc", "metrics/subprocess_metrics_provider.h", + "metrics/testing/metrics_reporting_pref_helper.cc", + "metrics/testing/metrics_reporting_pref_helper.h", "metrics/thread_watcher.cc", "metrics/thread_watcher.h", "metrics/thread_watcher_android.cc", @@ -805,6 +788,8 @@ split_static_library("browser") { "net/quota_policy_channel_id_store.h", "net/referrer.cc", "net/referrer.h", + "net/reporting_permissions_checker.cc", + "net/reporting_permissions_checker.h", "net/safe_search_util.cc", "net/safe_search_util.h", "net/service_providers_win.cc", @@ -860,6 +845,8 @@ split_static_library("browser") { "notifications/persistent_notification_handler.h", "notifications/platform_notification_service_impl.cc", "notifications/platform_notification_service_impl.h", + "notifications/system_notification_helper.cc", + "notifications/system_notification_helper.h", "ntp_snippets/bookmark_last_visit_updater.cc", "ntp_snippets/bookmark_last_visit_updater.h", "ntp_snippets/content_suggestions_notifier_service_factory.cc", @@ -940,6 +927,8 @@ split_static_library("browser") { "page_load_metrics/observers/previews_ukm_observer.h", "page_load_metrics/observers/protocol_page_load_metrics_observer.cc", "page_load_metrics/observers/protocol_page_load_metrics_observer.h", + "page_load_metrics/observers/security_state_page_load_metrics_observer.cc", + "page_load_metrics/observers/security_state_page_load_metrics_observer.h", "page_load_metrics/observers/service_worker_page_load_metrics_observer.cc", "page_load_metrics/observers/service_worker_page_load_metrics_observer.h", "page_load_metrics/observers/subresource_filter_metrics_observer.cc", @@ -967,6 +956,8 @@ split_static_library("browser") { "page_load_metrics/user_input_tracker.h", "password_manager/chrome_password_manager_client.cc", "password_manager/chrome_password_manager_client.h", + "password_manager/password_manager_util_linux.cc", + "password_manager/password_manager_util_linux.h", "password_manager/password_manager_util_mac.h", "password_manager/password_manager_util_mac.mm", "password_manager/password_manager_util_win.cc", @@ -977,6 +968,9 @@ split_static_library("browser") { "password_manager/password_store_mac.h", "password_manager/password_store_win.cc", "password_manager/password_store_win.h", + "password_manager/reauth_purpose.h", + "payments/payment_handler_permission_context.cc", + "payments/payment_handler_permission_context.h", "payments/ssl_validity_checker.cc", "performance_monitor/performance_monitor.cc", "performance_monitor/performance_monitor.h", @@ -1091,8 +1085,6 @@ split_static_library("browser") { "predictors/resource_prefetch_predictor_tab_helper.h", "predictors/resource_prefetch_predictor_tables.cc", "predictors/resource_prefetch_predictor_tables.h", - "predictors/resource_prefetcher.cc", - "predictors/resource_prefetcher.h", "prefs/browser_prefs.cc", "prefs/browser_prefs.h", "prefs/chrome_command_line_pref_store.cc", @@ -1351,6 +1343,8 @@ split_static_library("browser") { "signin/signin_tracker_factory.h", "signin/signin_util.cc", "signin/signin_util.h", + "signin/unified_consent_helper.cc", + "signin/unified_consent_helper.h", "site_details.cc", "site_details.h", "speech/chrome_speech_recognition_manager_delegate.cc", @@ -1388,6 +1382,8 @@ split_static_library("browser") { "ssl/chrome_ssl_host_state_delegate_factory.h", "ssl/common_name_mismatch_handler.cc", "ssl/common_name_mismatch_handler.h", + "ssl/connection_help_tab_helper.cc", + "ssl/connection_help_tab_helper.h", "ssl/insecure_sensitive_input_driver.cc", "ssl/insecure_sensitive_input_driver.h", "ssl/insecure_sensitive_input_driver_factory.cc", @@ -1523,6 +1519,8 @@ split_static_library("browser") { "webshare/webshare_target.h", "win/app_icon.cc", "win/app_icon.h", + "win/automation_controller.cc", + "win/automation_controller.h", "win/browser_util.cc", "win/browser_util.h", "win/chrome_elf_init.cc", @@ -1547,15 +1545,10 @@ split_static_library("browser") { "win/taskbar_icon_finder.h", "win/titlebar_config.cc", "win/titlebar_config.h", + "win/ui_automation_util.cc", + "win/ui_automation_util.h", ] - if (enable_downloadable_strings) { - sources += [ - "component_updater/downloadable_strings_component_installer.cc", - "component_updater/downloadable_strings_component_installer.h", - ] - } - configs += [ "//build/config/compiler:wexit_time_destructors", "//build/config:precompiled_headers", @@ -1573,7 +1566,7 @@ split_static_library("browser") { "//base", "//chrome/common", "//components/autofill/core/browser", - "//components/nacl/common:features", + "//components/nacl/common:buildflags", "//components/payments/core", "//components/sync", "//content/public/browser", @@ -1584,7 +1577,7 @@ split_static_library("browser") { ":active_use_util", ":resource_prefetch_predictor_proto", "//base:i18n", - "//base/allocator:features", + "//base/allocator:buildflags", "//cc", "//chrome:extra_resources", "//chrome:resources", @@ -1639,12 +1632,13 @@ split_static_library("browser") { "//components/domain_reliability", "//components/download/content/factory", "//components/download/downloader/in_progress", - "//components/download/public", + "//components/download/public/background_service:public", "//components/error_page/common", "//components/favicon/content", "//components/favicon/core", "//components/favicon_base", "//components/feature_engagement", + "//components/filename_generation", "//components/flags_ui", "//components/gcm_driver", "//components/google/core/browser", @@ -1672,7 +1666,7 @@ split_static_library("browser") { "//components/ntp_snippets", "//components/ntp_tiles", "//components/offline_items_collection/core", - "//components/offline_pages/features:features", + "//components/offline_pages/buildflags", "//components/omnibox/browser", "//components/optimization_guide", "//components/os_crypt", @@ -1722,15 +1716,15 @@ split_static_library("browser") { "//components/sync_bookmarks", "//components/sync_preferences", "//components/sync_sessions", - "//components/task_scheduler_util/browser", "//components/task_scheduler_util/common", "//components/tracing:startup_tracing", "//components/translate/content/browser", "//components/translate/core/browser", "//components/translate/core/common", "//components/ukm:observers", - "//components/ukm/debug_page", + "//components/ukm/content/debug_page", "//components/undo", + "//components/unzip_service/public/interfaces", "//components/update_client", "//components/upload_list", "//components/url_formatter", @@ -1752,20 +1746,20 @@ split_static_library("browser") { "//content/public/common:feature_h264_with_openh264_ffmpeg", "//content/public/common:features", "//content/public/common:service_names", - "//content/public/network", "//courgette:courgette_lib", "//crypto", "//crypto:platform", "//device/base", "//device/geolocation", "//device/usb/mojo", - "//device/usb/public/interfaces", + "//device/usb/public/mojom", "//device/vr/features", "//extensions/features", "//google_apis", "//gpu/config", "//media", "//media:media_features", + "//media/capture", "//media/cast:net", "//media/midi", "//media/mojo:features", @@ -1783,28 +1777,30 @@ split_static_library("browser") { "//rlz/features", "//services/data_decoder/public/cpp", "//services/device/public/cpp:device_features", - "//services/device/public/interfaces", + "//services/device/public/mojom", "//services/identity:lib", "//services/identity/public/cpp", "//services/metrics/public/cpp:ukm_builders", - "//services/network/public/interfaces", + "//services/network:network_service", + "//services/network/public/cpp", + "//services/network/public/mojom", "//services/preferences/public/cpp", "//services/preferences/public/cpp:service_main", "//services/preferences/public/cpp/tracked", - "//services/preferences/public/interfaces", + "//services/preferences/public/mojom", "//services/preferences/tracked", - "//services/proxy_resolver/public/interfaces", + "//services/proxy_resolver/public/mojom", "//services/resource_coordinator/public/cpp:resource_coordinator_cpp", "//services/service_manager/public/cpp", - "//services/shape_detection/public/interfaces", + "//services/shape_detection/public/mojom", "//skia", "//sql", "//storage/browser", "//storage/common", - "//third_party/WebKit/common:blink_common", "//third_party/WebKit/public:features", "//third_party/WebKit/public:resources", "//third_party/WebKit/public:scaled_resources", + "//third_party/WebKit/public/common", "//third_party/cacheinvalidation", "//third_party/icu", "//third_party/leveldatabase", @@ -1864,6 +1860,8 @@ split_static_library("browser") { "android/browsing_data/browsing_data_counter_bridge.h", "android/browsing_data/url_filter_bridge.cc", "android/browsing_data/url_filter_bridge.h", + "android/byte_array_int_callback.cc", + "android/byte_array_int_callback.h", "android/chrome_backup_agent.cc", "android/chrome_backup_agent.h", "android/chrome_backup_watcher.cc", @@ -1913,6 +1911,7 @@ split_static_library("browser") { "android/compositor/scene_layer/toolbar_scene_layer.h", "android/compositor/tab_content_manager.cc", "android/compositor/tab_content_manager.h", + "android/consent_auditor/consent_auditor_bridge.cc", "android/content/content_utils.cc", "android/contextualsearch/contextual_search_context.cc", "android/contextualsearch/contextual_search_context.h", @@ -1934,6 +1933,8 @@ split_static_library("browser") { "android/cookies/cookies_fetcher.h", "android/crash/pure_java_exception_handler.cc", "android/crash/pure_java_exception_handler.h", + "android/customtabs/detached_resource_request.cc", + "android/customtabs/detached_resource_request.h", "android/customtabs/origin_verifier.cc", "android/customtabs/origin_verifier.h", "android/data_usage/data_use_matcher.cc", @@ -1973,6 +1974,8 @@ split_static_library("browser") { "android/download/download_controller.h", "android/download/download_controller_base.cc", "android/download/download_controller_base.h", + "android/download/download_location_dialog_bridge.cc", + "android/download/download_location_dialog_bridge.h", "android/download/download_manager_service.cc", "android/download/download_manager_service.h", "android/download/duplicate_download_infobar_delegate.cc", @@ -1991,6 +1994,7 @@ split_static_library("browser") { "android/feature_utilities.cc", "android/feature_utilities.h", "android/feedback/connectivity_checker.cc", + "android/feedback/process_id_feedback_source.cc", "android/feedback/screenshot_task.cc", "android/feedback/system_info_feedback_source.cc", "android/find_in_page/find_in_page_bridge.cc", @@ -2048,14 +2052,14 @@ split_static_library("browser") { "android/metrics/variations_session.cc", "android/mojo/chrome_interface_registrar_android.cc", "android/mojo/chrome_interface_registrar_android.h", - "android/net/external_estimate_provider_android.cc", - "android/net/external_estimate_provider_android.h", "android/ntp/android_content_suggestions_notifier.cc", "android/ntp/android_content_suggestions_notifier.h", "android/ntp/content_suggestions_notifier.cc", "android/ntp/content_suggestions_notifier.h", "android/ntp/content_suggestions_notifier_service.cc", "android/ntp/content_suggestions_notifier_service.h", + "android/ntp/get_remote_suggestions_scheduler.cc", + "android/ntp/get_remote_suggestions_scheduler.h", "android/ntp/most_visited_sites_bridge.cc", "android/ntp/most_visited_sites_bridge.h", "android/ntp/new_tab_page_url_handler.cc", @@ -2144,6 +2148,7 @@ split_static_library("browser") { "android/signin/signin_manager_android.h", "android/signin/signin_promo_util_android.cc", "android/signin/signin_promo_util_android.h", + "android/subresource_filter/test_subresource_filter_publisher.cc", "android/tab_android.cc", "android/tab_android.h", "android/tab_state.cc", @@ -2158,8 +2163,6 @@ split_static_library("browser") { "android/url_utilities.cc", "android/usb/web_usb_chooser_service_android.cc", "android/usb/web_usb_chooser_service_android.h", - "android/voice_search_tab_helper.cc", - "android/voice_search_tab_helper.h", "android/warmup_manager.cc", "android/web_contents_factory.cc", "android/webapk/chrome_webapk_host.cc", @@ -2199,6 +2202,8 @@ split_static_library("browser") { "banners/app_banner_infobar_delegate_android.h", "banners/app_banner_manager_android.cc", "banners/app_banner_manager_android.h", + "banners/app_banner_ui_delegate_android.cc", + "banners/app_banner_ui_delegate_android.h", "chrome_browser_field_trials_mobile.cc", "chrome_browser_field_trials_mobile.h", "dom_distiller/dom_distiller_service_factory_android.cc", @@ -2214,6 +2219,7 @@ split_static_library("browser") { "history/android/bookmark_model_sql_handler.h", "history/android/sqlite_cursor.cc", "history/android/sqlite_cursor.h", + "installable/installable_ambient_badge_infobar_delegate.cc", "invalidation/invalidation_service_factory_android.cc", "invalidation/invalidation_service_factory_android.h", "lifetime/application_lifetime_android.cc", @@ -2299,7 +2305,6 @@ split_static_library("browser") { "sync/glue/synced_window_delegates_getter_android.h", "sync/profile_sync_service_android.cc", "sync/profile_sync_service_android.h", - "sync/sessions/sync_sessions_metrics_android.cc", ] deps += [ ":client_discourse_context_proto", @@ -2536,8 +2541,6 @@ split_static_library("browser") { "net/firefox_proxy_settings.h", "notifications/message_center_notification_manager.cc", "notifications/message_center_notification_manager.h", - "notifications/message_center_stats_collector.cc", - "notifications/message_center_stats_collector.h", "notifications/notification_system_observer.cc", "notifications/notification_system_observer.h", "notifications/notification_ui_manager_desktop.cc", @@ -2551,6 +2554,8 @@ split_static_library("browser") { "page_load_metrics/observers/session_restore_page_load_metrics_observer.h", "pdf/pdf_extension_util.cc", "pdf/pdf_extension_util.h", + "permissions/attestation_permission_request.cc", + "permissions/attestation_permission_request.h", "picture_in_picture/picture_in_picture_window_controller.cc", "picture_in_picture/picture_in_picture_window_controller.h", "policy/local_sync_policy_handler.cc", @@ -2628,6 +2633,8 @@ split_static_library("browser") { "resource_coordinator/tab_manager_stats_collector.h", "resource_coordinator/tab_manager_web_contents_data.cc", "resource_coordinator/tab_manager_web_contents_data.h", + "resource_coordinator/tab_metrics_logger.cc", + "resource_coordinator/tab_metrics_logger.h", "resource_coordinator/tab_stats.cc", "resource_coordinator/tab_stats.h", "resource_coordinator/time.cc", @@ -2774,6 +2781,7 @@ split_static_library("browser") { "//chrome/app/vector_icons", "//chrome/browser/policy:path_parser", "//chrome/browser/profile_resetter:profile_reset_report_proto", + "//chrome/browser/resource_coordinator:tab_metrics_event_proto", "//chrome/browser/resources:component_extension_resources", "//chrome/browser/search:generated", "//chrome/common/importer:interfaces", @@ -2851,11 +2859,11 @@ split_static_library("browser") { ] deps += [ "//ash", + "//ash/components/quick_launch/public/mojom:constants", "//ash/public/cpp", "//chrome/browser/chromeos", "//components/font_service:lib", "//components/font_service/public/interfaces", - "//mash/quick_launch/public/interfaces:constants", "//services/ui/public/cpp/input_devices", "//services/ui/public/cpp/input_devices:input_device_controller", "//services/ui/public/interfaces", @@ -2892,8 +2900,6 @@ split_static_library("browser") { "downgrade/user_data_downgrade.cc", "downgrade/user_data_downgrade.h", "first_run/upgrade_util.cc", - "notifications/mock_itoastnotification.cc", - "notifications/mock_itoastnotification.h", "notifications/notification_image_retainer.cc", "notifications/notification_image_retainer.h", "notifications/notification_template_builder.cc", @@ -2910,14 +2916,13 @@ split_static_library("browser") { "//chrome/common:metrics_constants_util_win", "//chrome/common:version_header", "//chrome/install_static:install_static_util", - "//chrome/services/util_win/public/interfaces", + "//chrome/services/util_win/public/mojom", "//chrome_elf:blacklist", "//chrome_elf:constants", "//chrome_elf:dll_hash", "//components/browser_watcher", "//components/browser_watcher:browser_watcher_client", "//components/browser_watcher:stability_client", - "//google_update", "//third_party/crashpad/crashpad/client:client", "//third_party/iaccessible2", "//third_party/isimpledom", @@ -2929,14 +2934,38 @@ split_static_library("browser") { if (enable_native_notifications) { sources += [ + "notifications/notification_launch_id.cc", + "notifications/notification_launch_id.h", "notifications/notification_platform_bridge_win.cc", "notifications/notification_platform_bridge_win.h", ] } - if (!is_chrome_branded) { - deps -= [ "//google_update" ] - sources -= [ + if (is_chrome_branded) { + deps += [ + ":conflicts_module_list_proto", + "//google_update", + ] + libs += [ "msi.lib" ] + sources += [ + "component_updater/third_party_module_list_component_installer_win.cc", + "component_updater/third_party_module_list_component_installer_win.h", + "conflicts/installed_programs_win.cc", + "conflicts/installed_programs_win.h", + "conflicts/module_list_filter_win.cc", + "conflicts/module_list_filter_win.h", + "conflicts/msi_util_win.cc", + "conflicts/msi_util_win.h", + "conflicts/problematic_programs_updater_win.cc", + "conflicts/problematic_programs_updater_win.h", + "conflicts/registry_key_watcher_win.cc", + "conflicts/registry_key_watcher_win.h", + "conflicts/third_party_conflicts_manager_win.cc", + "conflicts/third_party_conflicts_manager_win.h", + "conflicts/token_util_win.cc", + "conflicts/token_util_win.h", + "conflicts/uninstall_application_win.cc", + "conflicts/uninstall_application_win.h", "google/google_update_win.cc", "google/google_update_win.h", ] @@ -3298,7 +3327,7 @@ split_static_library("browser") { ] } if (is_win || enable_print_preview) { - deps += [ "//chrome/services/printing/public/interfaces" ] + deps += [ "//chrome/services/printing/public/mojom" ] } if (enable_print_preview) { # Full printing on top of the above. @@ -3610,7 +3639,7 @@ split_static_library("browser") { "//components/guest_view/browser", "//extensions/components/javascript_dialog_extensions_client", "//media/cast:net", - "//services/device/public/interfaces", + "//services/device/public/mojom", ] } @@ -3859,7 +3888,7 @@ split_static_library("browser") { "//media:media_features", "//ppapi/features", "//ppapi/proxy:ipc", - "//services/device/public/interfaces", + "//services/device/public/mojom", "//third_party/adobe/flash:flapper_version_h", ] } @@ -3945,6 +3974,7 @@ split_static_library("browser") { sources += [ "spellchecker/spell_check_host_chrome_impl.cc", "spellchecker/spell_check_host_chrome_impl.h", + "spellchecker/spell_check_host_chrome_impl_mac.cc", "spellchecker/spellcheck_custom_dictionary.cc", "spellchecker/spellcheck_custom_dictionary.h", "spellchecker/spellcheck_factory.cc", @@ -3953,7 +3983,6 @@ split_static_library("browser") { "spellchecker/spellcheck_hunspell_dictionary.h", "spellchecker/spellcheck_language_policy_handler.cc", "spellchecker/spellcheck_language_policy_handler.h", - "spellchecker/spellcheck_message_filter_platform_mac.cc", "spellchecker/spellcheck_service.cc", "spellchecker/spellcheck_service.h", ] @@ -4076,20 +4105,16 @@ split_static_library("browser") { if (enable_vr) { if (enable_gvr_services) { - deps += [ "android/vr_shell:vr_android" ] + deps += [ "android/vr:vr_android" ] configs += [ "//third_party/gvr-android-sdk:libgvr_config" ] - allow_circular_includes_from += [ "android/vr_shell:vr_android" ] + allow_circular_includes_from += [ "android/vr:vr_android" ] } sources += [ "component_updater/vr_assets_component_installer.cc", "component_updater/vr_assets_component_installer.h", - "vr_features.h", - ] - deps += [ - ":vr_build_features", - "//chrome/browser/vr:vr_common", ] + deps += [ "//chrome/browser/vr:vr_common" ] } if (enable_wayland_server) { @@ -4107,8 +4132,6 @@ split_static_library("browser") { sources += [ "media/webrtc/audio_debug_recordings_handler.cc", "media/webrtc/audio_debug_recordings_handler.h", - - # TODO(brettw) should webrtc_log_list.cc go here? "media/webrtc/webrtc_log_uploader.cc", "media/webrtc/webrtc_log_uploader.h", "media/webrtc/webrtc_log_util.cc", @@ -4123,7 +4146,9 @@ split_static_library("browser") { "media/webrtc/webrtc_text_log_handler.h", ] deps += [ - "//third_party/webrtc/modules/desktop_capture", + "//components/webrtc_logging/browser", + "//components/webrtc_logging/common", + "//services/audio/public/cpp", "//third_party/webrtc_overrides", "//third_party/webrtc_overrides:init_webrtc", ] @@ -4185,7 +4210,6 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/IntentHelper.java", "../android/java/src/org/chromium/chrome/browser/JavascriptAppModalDialog.java", "../android/java/src/org/chromium/chrome/browser/NearOomMonitor.java", - "../android/java/src/org/chromium/chrome/browser/PasswordUIView.java", "../android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java", "../android/java/src/org/chromium/chrome/browser/SearchGeolocationDisclosureTabHelper.java", "../android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java", @@ -4206,6 +4230,7 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java", "../android/java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java", "../android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java", + "../android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java", "../android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java", "../android/java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java", "../android/java/src/org/chromium/chrome/browser/browsing_data/UrlFilterBridge.java", @@ -4222,6 +4247,7 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabListSceneLayer.java", "../android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java", "../android/java/src/org/chromium/chrome/browser/compositor/scene_layer/ToolbarSceneLayer.java", + "../android/java/src/org/chromium/chrome/browser/consent_auditor/ConsentAuditorBridge.java", "../android/java/src/org/chromium/chrome/browser/content/ContentUtils.java", "../android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java", "../android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuParams.java", @@ -4245,6 +4271,7 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/download/DownloadController.java", "../android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java", "../android/java/src/org/chromium/chrome/browser/download/DownloadItem.java", + "../android/java/src/org/chromium/chrome/browser/download/DownloadLocationDialogBridge.java", "../android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java", "../android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java", "../android/java/src/org/chromium/chrome/browser/download/service/DownloadBackgroundTask.java", @@ -4254,6 +4281,7 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/favicon/LargeIconBridge.java", "../android/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java", "../android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java", + "../android/java/src/org/chromium/chrome/browser/feedback/ProcessIdFeedbackSource.java", "../android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java", "../android/java/src/org/chromium/chrome/browser/feedback/SystemInfoFeedbackSource.java", "../android/java/src/org/chromium/chrome/browser/findinpage/FindInPageBridge.java", @@ -4271,6 +4299,7 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/infobar/GeneratedPasswordSavedInfoBarDelegate.java", "../android/java/src/org/chromium/chrome/browser/infobar/InfoBar.java", "../android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java", + "../android/java/src/org/chromium/chrome/browser/infobar/InstallableAmbientBadgeInfoBar.java", "../android/java/src/org/chromium/chrome/browser/infobar/InstantAppsInfoBar.java", "../android/java/src/org/chromium/chrome/browser/infobar/InstantAppsInfoBarDelegate.java", "../android/java/src/org/chromium/chrome/browser/infobar/NearOomInfoBar.java", @@ -4299,7 +4328,6 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java", "../android/java/src/org/chromium/chrome/browser/metrics/VariationsSession.java", "../android/java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java", - "../android/java/src/org/chromium/chrome/browser/net/qualityprovider/ExternalEstimateProviderAndroid.java", "../android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java", "../android/java/src/org/chromium/chrome/browser/notifications/ActionInfo.java", "../android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java", @@ -4354,6 +4382,8 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/preferences/PrefServiceBridge.java", "../android/java/src/org/chromium/chrome/browser/preferences/PreferencesLauncher.java", "../android/java/src/org/chromium/chrome/browser/preferences/autofill/AutofillProfileBridge.java", + "../android/java/src/org/chromium/chrome/browser/preferences/password/ByteArrayIntCallback.java", + "../android/java/src/org/chromium/chrome/browser/preferences/password/PasswordUIView.java", "../android/java/src/org/chromium/chrome/browser/preferences/privacy/BrowsingDataBridge.java", "../android/java/src/org/chromium/chrome/browser/preferences/privacy/BrowsingDataCounterBridge.java", "../android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePreferenceBridge.java", @@ -4377,12 +4407,12 @@ if (is_android) { "../android/java/src/org/chromium/chrome/browser/snackbar/smartlockautosignin/AutoSigninSnackbarController.java", "../android/java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java", "../android/java/src/org/chromium/chrome/browser/ssl/SecurityStateModel.java", + "../android/java/src/org/chromium/chrome/browser/subresource_filter/TestSubresourceFilterPublisher.java", "../android/java/src/org/chromium/chrome/browser/suggestions/MostVisitedSites.java", "../android/java/src/org/chromium/chrome/browser/suggestions/MostVisitedSitesBridge.java", "../android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java", "../android/java/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProvider.java", "../android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java", - "../android/java/src/org/chromium/chrome/browser/sync/SyncSessionsMetrics.java", "../android/java/src/org/chromium/chrome/browser/tab/Tab.java", "../android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java", "../android/java/src/org/chromium/chrome/browser/tabmodel/SingleTabModel.java", @@ -4505,10 +4535,10 @@ grit("resources") { "//chrome/browser/ui/webui/interventions_internals:mojo_bindings_js", "//chrome/browser/ui/webui/omnibox:mojo_bindings_js", "//chrome/browser/ui/webui/usb_internals:mojo_bindings_js", - "//device/bluetooth/public/interfaces:deprecated_experimental_interfaces_js", - "//device/bluetooth/public/interfaces:interfaces_js", - "//url/mojo:url_mojom_gurl_js", - "//url/mojo:url_mojom_origin_js", + "//device/bluetooth/public/mojom:deprecated_experimental_interfaces_js", + "//device/bluetooth/public/mojom:mojom_js", + "//url/mojom:url_mojom_gurl_js", + "//url/mojom:url_mojom_origin_js", ] if (is_win || is_mac || is_desktop_linux || is_chromeos) { @@ -4693,6 +4723,10 @@ static_library("test_support") { "sessions/session_restore_test_helper.h", "sessions/session_service_test_helper.cc", "sessions/session_service_test_helper.h", + "ui/tabs/tab_activity_simulator.cc", + "ui/tabs/tab_activity_simulator.h", + "ui/tabs/tab_ukm_test_helper.cc", + "ui/tabs/tab_ukm_test_helper.h", ] } @@ -4718,6 +4752,8 @@ static_library("test_support") { "chromeos/login/test/js_checker.h", "chromeos/login/test/oobe_screen_waiter.cc", "chromeos/login/test/oobe_screen_waiter.h", + "chromeos/login/ui/fake_login_display_host.cc", + "chromeos/login/ui/fake_login_display_host.h", "chromeos/login/ui/mock_login_display.cc", "chromeos/login/ui/mock_login_display.h", "chromeos/login/ui/mock_login_display_host.cc", @@ -4756,6 +4792,13 @@ static_library("test_support") { ] } + if (is_win) { + sources += [ + "notifications/mock_itoastnotification.cc", + "notifications/mock_itoastnotification.h", + ] + } + if (enable_app_list) { sources += [ "ui/app_list/test/chrome_app_list_test_support.cc", @@ -4781,8 +4824,6 @@ static_library("test_support") { "extensions/test_blacklist.h", "extensions/test_blacklist_state_fetcher.cc", "extensions/test_blacklist_state_fetcher.h", - "extensions/test_extension_dir.cc", - "extensions/test_extension_dir.h", "extensions/test_extension_environment.cc", "extensions/test_extension_environment.h", "extensions/test_extension_prefs.cc", @@ -4798,6 +4839,7 @@ static_library("test_support") { "//components/drive:test_support", "//components/storage_monitor:test_support", "//extensions:test_support", + "//services/data_decoder/public/cpp:test_support", ] } diff --git a/chromium/chrome/browser/android/vr_shell/BUILD.gn b/chromium/chrome/browser/android/vr/BUILD.gn index 0150f161693..9e8263952a3 100644 --- a/chromium/chrome/browser/android/vr_shell/BUILD.gn +++ b/chromium/chrome/browser/android/vr/BUILD.gn @@ -27,14 +27,16 @@ static_library("vr_android") { "gvr_util.h", "mailbox_to_surface_bridge.cc", "mailbox_to_surface_bridge.h", - "vr_compositor.cc", - "vr_compositor.h", "vr_controller.cc", "vr_controller.h", "vr_core_info.cc", "vr_core_info.h", + "vr_dialog.cc", + "vr_dialog.h", "vr_gl_thread.cc", "vr_gl_thread.h", + "vr_input_connection.cc", + "vr_input_connection.h", "vr_metrics_util.cc", "vr_metrics_util.h", "vr_shell.cc", @@ -60,7 +62,7 @@ static_library("vr_android") { "//content/public/common", "//device/gamepad", "//device/vr", - "//services/device/public/interfaces", + "//services/device/public/mojom", "//services/metrics/public/cpp:ukm_builders", "//services/ui/public/cpp/gpu", "//ui/android", @@ -71,7 +73,7 @@ static_library("vr_android") { ] public_deps = [ - "//device/vr:mojo_bindings", + "//device/vr/public/mojom", ] libs = [ @@ -90,9 +92,11 @@ generate_jni("vr_shell_jni_headers") { "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/AndroidUiGestureTarget.java", "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/AndroidVSyncHelper.java", "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrCoreInfo.java", + "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrInputConnection.java", "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java", "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java", "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java", + "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/TextEditAction.java", ] jni_package = "vr_shell" } diff --git a/chromium/chrome/browser/browser_resources.grd b/chromium/chrome/browser/browser_resources.grd index c25812b62a3..5c8745bc214 100644 --- a/chromium/chrome/browser/browser_resources.grd +++ b/chromium/chrome/browser/browser_resources.grd @@ -8,11 +8,6 @@ </outputs> <release seq="1"> <structures> - <if expr="enable_app_list"> - <structure name="IDR_APP_LIST_START_PAGE_CSS" file="resources\app_list\start_page.css" flattenhtml="true" type="chrome_html" /> - <structure name="IDR_APP_LIST_START_PAGE_HTML" file="resources\app_list\start_page.html" flattenhtml="true" type="chrome_html" /> - <structure name="IDR_APP_LIST_START_PAGE_JS" file="resources\app_list\start_page.js" flattenhtml="true" type="chrome_html" /> - </if> <if expr="enable_extensions"> <structure name="IDR_EXTENSIONS_HTML" file="resources\extensions\extensions.html" flattenhtml="true" type="chrome_html" /> </if> @@ -53,10 +48,6 @@ <structure name="IDR_CUSTOM_ELEMENTS_USER_POD_HTML" file="resources\chromeos\login\custom_elements_user_pod.html" flattenhtml="true" type="chrome_html" /> <structure name="IDR_CUSTOM_ELEMENTS_LOGIN_HTML" file="resources\chromeos\login\custom_elements_login.html" flattenhtml="true" type="chrome_html" /> <structure name="IDR_CUSTOM_ELEMENTS_LOGIN_JS" file="resources\chromeos\login\custom_elements_login.js" flattenhtml="true" type="chrome_html" /> - <structure name="IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_HTML" file="resources\chromeos\quick_unlock\pin_keyboard.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" /> - <structure name="IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_JS" file="resources\chromeos\quick_unlock\pin_keyboard.js" type="chrome_html" /> - <structure name="IDR_MD_CUSTOM_ELEMENTS_PIN_KEYBOARD_HTML" file="resources\chromeos\quick_unlock\md_pin_keyboard.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" /> - <structure name="IDR_MD_CUSTOM_ELEMENTS_PIN_KEYBOARD_JS" file="resources\chromeos\quick_unlock\md_pin_keyboard.js" type="chrome_html" /> </if> <structure name="IDR_SIGNIN_SHARED_CSS_HTML" file="resources\signin\signin_shared_css.html" preprocess="true" allowexternalscript="true" type="chrome_html" /> <if expr="not is_android and not chromeos"> @@ -100,9 +91,9 @@ <include name="IDR_ABOUT_SYS_HTML" file="resources\about_sys\about_sys.html" flattenhtml="true" type="BINDATA" /> </if> <include name="IDR_AD_NETWORK_HASHES" file="resources\ad_networks.dat" type="BINDATA" /> - <include name="IDR_BLUETOOTH_ADAPTER_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\interfaces\adapter.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> - <include name="IDR_BLUETOOTH_DEVICE_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\interfaces\device.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> - <include name="IDR_BLUETOOTH_UUID_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\interfaces\uuid.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> + <include name="IDR_BLUETOOTH_ADAPTER_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\mojom\adapter.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> + <include name="IDR_BLUETOOTH_DEVICE_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\mojom\device.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> + <include name="IDR_BLUETOOTH_UUID_MOJO_JS" file="${root_gen_dir}\device\bluetooth\public\mojom\uuid.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> <include name="IDR_BLUETOOTH_INTERNALS_CSS" file="resources\bluetooth_internals\bluetooth_internals.css" type="BINDATA" compress="gzip" /> <include name="IDR_BLUETOOTH_INTERNALS_ADAPTER_BROKER_JS" file="resources\bluetooth_internals\adapter_broker.js" type="BINDATA" compress="gzip" /> <include name="IDR_BLUETOOTH_INTERNALS_ADAPTER_PAGE_JS" file="resources\bluetooth_internals\adapter_page.js" type="BINDATA" compress="gzip" /> @@ -135,6 +126,7 @@ <include name="IDR_CLOUDPRINT_MANIFEST" file="resources\cloud_print_app\manifest.json" type="BINDATA" /> <include name="IDR_PDF_COMPOSITOR_MANIFEST" file="..\..\components\printing\service\pdf_compositor_manifest.json" type="BINDATA" /> </if> + <include name="IDR_CHROME_RENDERER_SERVICE_MANIFEST" file="..\app\chrome_renderer_manifest.json" type="BINDATA" /> <include name="IDR_DEVTOOLS_DISCOVERY_PAGE_HTML" file="devtools\frontend\devtools_discovery_page.html" type="BINDATA"/> <include name="IDR_DOMAIN_RELIABILITY_INTERNALS_HTML" file="resources\domain_reliability_internals.html" compress="gzip" type="BINDATA" /> <include name="IDR_DOMAIN_RELIABILITY_INTERNALS_CSS" file="resources\domain_reliability_internals.css" compress="gzip" type="BINDATA" /> @@ -353,7 +345,7 @@ <include name="IDR_OMNIBOX_CSS" file="resources\omnibox\omnibox.css" type="BINDATA" compress="gzip" /> <include name="IDR_OMNIBOX_JS" file="resources\omnibox\omnibox.js" type="BINDATA" compress="gzip" /> <include name="IDR_OMNIBOX_MOJO_JS" file="${root_gen_dir}\chrome\browser\ui\webui\omnibox\omnibox.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> - <include name="IDR_ORIGIN_MOJO_JS" file="${root_gen_dir}\url\mojo\origin.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip"/> + <include name="IDR_ORIGIN_MOJO_JS" file="${root_gen_dir}\url\mojom\origin.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip"/> <include name="IDR_COMPONENTS_HTML" file="resources\components.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" /> <include name="IDR_COMPONENTS_JS" file="resources\components.js" type="BINDATA" compress="gzip" /> <include name="IDR_MEMORY_INTERNALS_HTML" file="resources\memory_internals.html" flattenhtml="true" type="BINDATA" compress="gzip" /> @@ -408,8 +400,6 @@ file="resources\print_preview\images\google_doc.png" type="BINDATA" /> <include name="IDR_PRINT_PREVIEW_IMAGES_PDF" file="resources\print_preview\images\pdf.png" type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_IMAGES_THIRD_PARTY" - file="resources\print_preview\images\third_party.png" type="BINDATA" /> <include name="IDR_PRINT_PREVIEW_IMAGES_MOBILE" file="resources\print_preview\images\mobile.png" type="BINDATA" /> <include name="IDR_PRINT_PREVIEW_IMAGES_MOBILE_SHARED" @@ -418,7 +408,7 @@ <include name="IDR_SITE_ENGAGEMENT_HTML" file="resources\engagement\site_engagement.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" /> <include name="IDR_SITE_ENGAGEMENT_JS" file="resources\engagement\site_engagement.js" flattenhtml="true" type="BINDATA" compress="gzip" /> <include name="IDR_SITE_ENGAGEMENT_MOJO_JS" file="${root_gen_dir}\chrome\browser\engagement\site_engagement_details.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> - <include name="IDR_URL_MOJO_JS" file="${root_gen_dir}\url\mojo\url.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> + <include name="IDR_URL_MOJO_JS" file="${root_gen_dir}\url\mojom\url.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" /> <include name="IDR_SYNC_CONFIRMATION_CSS" file="resources\signin\sync_confirmation\sync_confirmation.css" type="BINDATA" /> <include name="IDR_SYNC_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" /> <include name="IDR_SYNC_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_confirmation.js" type="BINDATA" /> diff --git a/chromium/chrome/browser/chrome_content_browser_manifest_overlay.json b/chromium/chrome/browser/chrome_content_browser_manifest_overlay.json index 19cb2b02d4c..b016c0546d9 100644 --- a/chromium/chrome/browser/chrome_content_browser_manifest_overlay.json +++ b/chromium/chrome/browser/chrome_content_browser_manifest_overlay.json @@ -10,6 +10,7 @@ "chrome::mojom::CacheStatsRecorder", "chrome::mojom::NetBenchmarking", "extensions::StashService", + "metrics::mojom::CallStackProfileCollector", "metrics::mojom::LeakDetector", "mojom::ModuleEventSink", "rappor::mojom::RapporRecorder", @@ -25,7 +26,7 @@ ] }, "requires": { - "accessibility_autoclick": [ "ash:autoclick" ], + "autoclick_app": [ "chromeos:autoclick" ], "ash": [ "system_ui", "test", "display" ], // Only used in classic ash case. "ash_pref_connector": [ "pref_connector" ], @@ -49,7 +50,7 @@ "patch": [ "patch_file" ], "pdf_compositor": [ "compositor" ], "profile_import": [ "import" ], - "profiling": [ "profiling" ], + "profiling": [ "profiling", "heap_profiler" ], "proxy_resolver": [ "factory" ], "preferences": [ "pref_client", "pref_control" ], "removable_storage_writer": [ "removable_storage_writer" ], @@ -59,6 +60,7 @@ "input_device_controller", "window_manager" ], + "unzip": [ "unzip_file" ], "util_win" : [ "shell_util_win" ], "wifi_util_win": [ "wifi_credentials" ] } @@ -84,6 +86,7 @@ "media_router::mojom::MediaRouter", "page_load_metrics::mojom::PageLoadMetrics", "password_manager::mojom::CredentialManager", + "safe_browsing::mojom::PhishingDetectorClient", "translate::mojom::ContentTranslateDriver", // TODO(beng): These should be moved to a separate capability. diff --git a/chromium/chrome/browser/chrome_content_gpu_manifest_overlay.json b/chromium/chrome/browser/chrome_content_gpu_manifest_overlay.json index 0d7c7c15121..9f8e9f0355b 100644 --- a/chromium/chrome/browser/chrome_content_gpu_manifest_overlay.json +++ b/chromium/chrome/browser/chrome_content_gpu_manifest_overlay.json @@ -9,7 +9,6 @@ "arc::mojom::VideoDecodeClient", "arc::mojom::VideoEncodeAccelerator", "arc::mojom::VideoEncodeClient", - "chrome::mojom::ResourceUsageReporter", "profiling::mojom::ProfilingClient" ] } diff --git a/chromium/chrome/browser/chrome_content_plugin_manifest_overlay.json b/chromium/chrome/browser/chrome_content_plugin_manifest_overlay.json index 6d04b5f4a8e..c47eb05bb7e 100644 --- a/chromium/chrome/browser/chrome_content_plugin_manifest_overlay.json +++ b/chromium/chrome/browser/chrome_content_plugin_manifest_overlay.json @@ -4,7 +4,6 @@ "service_manager:connector": { "provides": { "browser": [ - "chrome::mojom::ResourceUsageReporter" ] } } diff --git a/chromium/chrome/browser/chrome_content_renderer_manifest_overlay.json b/chromium/chrome/browser/chrome_content_renderer_manifest_overlay.json index d5b3e090519..6e9f7efb631 100644 --- a/chromium/chrome/browser/chrome_content_renderer_manifest_overlay.json +++ b/chromium/chrome/browser/chrome_content_renderer_manifest_overlay.json @@ -5,14 +5,9 @@ "service_manager:connector": { "provides": { "browser": [ - "chrome::mojom::ResourceUsageReporter", "chrome::mojom::SearchBouncer", - "spellcheck::mojom::SpellChecker", "profiling::mojom::ProfilingClient" ] - }, - "requires": { - "chrome": [ "renderer" ] } }, "navigation:frame": { @@ -25,8 +20,11 @@ "chrome::mojom::ChromeRenderFrame", "chrome::mojom::ContentSettingsRenderer", "chrome::mojom::PrerenderDispatcher", + "chrome::mojom::SandboxStatusExtension", "dom_distiller::mojom::DistillerPageNotifierService", "extensions::mojom::AppWindow", + "safe_browsing::mojom::ThreatReporter", + "safe_browsing::mojom::PhishingDetector", "spellcheck::mojom::SpellCheckPanel" ] } diff --git a/chromium/chrome/browser/chrome_content_utility_manifest_overlay.json b/chromium/chrome/browser/chrome_content_utility_manifest_overlay.json index b0eec0e6e40..5bd06f880e3 100644 --- a/chromium/chrome/browser/chrome_content_utility_manifest_overlay.json +++ b/chromium/chrome/browser/chrome_content_utility_manifest_overlay.json @@ -6,7 +6,6 @@ "browser": [ "chrome::mojom::DialDeviceDescriptionParser", "chrome::mojom::ProfileImport", - "chrome::mojom::ResourceUsageReporter", "chrome::mojom::ShellHandler", "extensions::mojom::ExtensionUnpacker", "extensions::mojom::ManifestParser", diff --git a/chromium/chrome/browser/chrome_notification_types.h b/chromium/chrome/browser/chrome_notification_types.h index 6ca55171b3b..baaa3be6440 100644 --- a/chromium/chrome/browser/chrome_notification_types.h +++ b/chromium/chrome/browser/chrome_notification_types.h @@ -318,8 +318,6 @@ enum NotificationType { // NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE // 4. Boot into retail mode // NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE - // 5. Boot into kiosk mode - // NOTIFICATION_KIOSK_APP_LAUNCHED NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, // Send when kiosk auto-launch warning screen is visible. @@ -340,9 +338,6 @@ enum NotificationType { // Sent when kiosk app list is loaded in UI. NOTIFICATION_KIOSK_APPS_LOADED, - // Sent when a kiosk app is launched. - NOTIFICATION_KIOSK_APP_LAUNCHED, - // Sent when the user list has changed. NOTIFICATION_USER_LIST_CHANGED, diff --git a/chromium/chrome/browser/chromeos/BUILD.gn b/chromium/chrome/browser/chromeos/BUILD.gn index c484e216a46..bc72201a1ca 100644 --- a/chromium/chrome/browser/chromeos/BUILD.gn +++ b/chromium/chrome/browser/chromeos/BUILD.gn @@ -4,6 +4,7 @@ import("//build/config/features.gni") import("//build/config/ui.gni") +import("//chromeos/assistant/assistant.gni") import("//extensions/features/features.gni") import("//media/media_options.gni") import("//printing/features/features.gni") @@ -40,12 +41,12 @@ source_set("chromeos") { deps = [ # TODO(tbarzic): Cleanup this list. ":attestation_proto", + ":backdrop_wallpaper_proto", ":device_policy_remover_generated", ":user_activity_event_proto", "//apps", "//ash", "//ash:ash_with_content", - "//ash/autoclick/mus/public/interfaces", "//ash/public/cpp", "//cc/paint", "//chrome/app:command_ids", @@ -53,6 +54,7 @@ source_set("chromeos") { "//chrome/browser:rlz", "//chrome/browser/devtools", "//chrome/browser/extensions", + "//chrome/browser/resource_coordinator:tab_metrics_event_proto", "//chrome/browser/safe_browsing:chunk_proto", "//chrome/common", "//chrome/common/extensions/api", @@ -66,12 +68,14 @@ source_set("chromeos") { "//chromeos:biod_proto", "//chromeos:cryptohome_proto", "//chromeos:cryptohome_signkey_proto", + "//chromeos/assistant:buildflags", "//chromeos/components/tether", "//components/arc", "//components/browser_sync", "//components/captive_portal", "//components/certificate_reporting:cert_logger_proto", "//components/component_updater:crl_set_remover", + "//components/consent_auditor:consent_auditor", "//components/constrained_window", "//components/content_settings/core/browser", "//components/crash/content/app", @@ -92,6 +96,7 @@ source_set("chromeos") { "//components/keep_alive_registry", "//components/keyed_service/content", "//components/keyed_service/core", + "//components/language/core/common", "//components/login", "//components/metrics:serialization", "//components/metrics/leak_detector", @@ -122,7 +127,10 @@ source_set("chromeos") { "//components/tracing:startup_tracing", "//components/ukm/content", "//components/user_manager", + "//components/viz/host", + "//services/identity/public/cpp", "//services/metrics/public/cpp:ukm_builders", + "//services/resource_coordinator/public/cpp:resource_coordinator_cpp", "//third_party/fontconfig", "//third_party/metrics_proto", @@ -143,21 +151,21 @@ source_set("chromeos") { "//device/bluetooth", "//device/media_transfer_protocol", "//device/usb/public/cpp", - "//device/usb/public/interfaces", + "//device/usb/public/mojom", "//extensions/browser", "//extensions/browser/kiosk", "//gpu", "//gpu/ipc/host", "//gpu/ipc/service", - "//mash/public/interfaces", + "//mash/public/mojom", "//media", "//media/mojo/interfaces", "//mojo/common", "//net", "//ppapi/proxy:ipc", # For PpapiMsg_LoadPlugin "//services/data_decoder/public/cpp", - "//services/device/public/interfaces", - "//services/preferences/public/interfaces", + "//services/device/public/mojom", + "//services/preferences/public/mojom", "//services/service_manager/public/cpp", "//services/service_manager/runner/common", "//services/ui/public/interfaces/display", @@ -170,7 +178,7 @@ source_set("chromeos") { "//skia", "//storage/browser", "//storage/common", - "//third_party/WebKit/common:blink_common", + "//third_party/WebKit/public/common", "//third_party/adobe/flash:flapper_version_h", "//third_party/cacheinvalidation", "//third_party/icu", @@ -213,10 +221,6 @@ source_set("chromeos") { "//url", ] - data_deps = [ - "//ash/autoclick/mus:accessibility_autoclick", - ] - allow_circular_includes_from = [ "//chrome/browser/extensions" ] sources = [ @@ -230,8 +234,6 @@ source_set("chromeos") { "../supervised_user/chromeos/supervised_user_password_service_factory.h", "accessibility/accessibility_extension_loader.cc", "accessibility/accessibility_extension_loader.h", - "accessibility/accessibility_highlight_manager.cc", - "accessibility/accessibility_highlight_manager.h", "accessibility/accessibility_manager.cc", "accessibility/accessibility_manager.h", "accessibility/chromevox_panel.cc", @@ -303,8 +305,6 @@ source_set("chromeos") { "arc/accessibility/arc_accessibility_helper_bridge.h", "arc/accessibility/ax_tree_source_arc.cc", "arc/accessibility/ax_tree_source_arc.h", - "arc/arc_auth_notification.cc", - "arc/arc_auth_notification.h", "arc/arc_migration_constants.h", "arc/arc_migration_guide_notification.cc", "arc/arc_migration_guide_notification.h", @@ -425,6 +425,10 @@ source_set("chromeos") { "arc/process/arc_process.h", "arc/process/arc_process_service.cc", "arc/process/arc_process_service.h", + "arc/screen_capture/arc_screen_capture_bridge.cc", + "arc/screen_capture/arc_screen_capture_bridge.h", + "arc/screen_capture/arc_screen_capture_session.cc", + "arc/screen_capture/arc_screen_capture_session.h", "arc/tracing/arc_tracing_bridge.cc", "arc/tracing/arc_tracing_bridge.h", "arc/tts/arc_tts_service.cc", @@ -457,8 +461,6 @@ source_set("chromeos") { "attestation/platform_verification_flow.h", "authpolicy/auth_policy_credentials_manager.cc", "authpolicy/auth_policy_credentials_manager.h", - "background/ash_wallpaper_delegate.cc", - "background/ash_wallpaper_delegate.h", "base/file_flusher.cc", "base/file_flusher.h", "base/locale_util.cc", @@ -504,6 +506,8 @@ source_set("chromeos") { "dbus/chrome_proxy_resolution_service_provider_delegate.h", "dbus/chrome_virtual_file_request_service_provider_delegate.cc", "dbus/chrome_virtual_file_request_service_provider_delegate.h", + "dbus/finch_features_service_provider_delegate.cc", + "dbus/finch_features_service_provider_delegate.h", "dbus/kiosk_info_service_provider.cc", "dbus/kiosk_info_service_provider.h", "dbus/screen_lock_service_provider.cc", @@ -520,6 +524,8 @@ source_set("chromeos") { "display/output_protection_delegate.h", "display/quirks_manager_delegate_impl.cc", "display/quirks_manager_delegate_impl.h", + "docked_magnifier/docked_magnifier_client.cc", + "docked_magnifier/docked_magnifier_client.h", "drive/debug_info_collector.cc", "drive/debug_info_collector.h", "drive/download_handler.cc", @@ -1025,8 +1031,6 @@ source_set("chromeos") { "login/screens/wrong_hwid_screen.cc", "login/screens/wrong_hwid_screen.h", "login/screens/wrong_hwid_screen_view.h", - "login/session/app_terminating_stack_dumper.cc", - "login/session/app_terminating_stack_dumper.h", "login/session/chrome_session_manager.cc", "login/session/chrome_session_manager.h", "login/session/user_session_manager.cc", @@ -1068,8 +1072,6 @@ source_set("chromeos") { "login/startup_utils.h", "login/supervised/supervised_user_authentication.cc", "login/supervised/supervised_user_authentication.h", - "login/supervised/supervised_user_authenticator.cc", - "login/supervised/supervised_user_authenticator.h", "login/supervised/supervised_user_constants.cc", "login/supervised/supervised_user_constants.h", "login/supervised/supervised_user_creation_controller.cc", @@ -1086,12 +1088,16 @@ source_set("chromeos") { "login/ui/captive_portal_view.h", "login/ui/captive_portal_window_proxy.cc", "login/ui/captive_portal_window_proxy.h", + "login/ui/gaia_dialog_delegate.cc", + "login/ui/gaia_dialog_delegate.h", "login/ui/input_events_blocker.cc", "login/ui/input_events_blocker.h", "login/ui/login_display.cc", "login/ui/login_display.h", "login/ui/login_display_host.cc", "login/ui/login_display_host.h", + "login/ui/login_display_host_common.cc", + "login/ui/login_display_host_common.h", "login/ui/login_display_host_views.cc", "login/ui/login_display_host_views.h", "login/ui/login_display_host_webui.cc", @@ -1150,8 +1156,6 @@ source_set("chromeos") { "login/users/supervised_user_manager_impl.cc", "login/users/supervised_user_manager_impl.h", "login/users/user_manager_interface.h", - "login/users/wallpaper/wallpaper_manager.cc", - "login/users/wallpaper/wallpaper_manager.h", "login/version_info_updater.cc", "login/version_info_updater.h", "login/wizard_controller.cc", @@ -1232,6 +1236,18 @@ source_set("chromeos") { "policy/affiliated_invalidation_service_provider_impl.h", "policy/android_management_client.cc", "policy/android_management_client.h", + "policy/app_install_event_log.cc", + "policy/app_install_event_log.h", + "policy/app_install_event_log_collector.cc", + "policy/app_install_event_log_collector.h", + "policy/app_install_event_log_manager.cc", + "policy/app_install_event_log_manager.h", + "policy/app_install_event_log_manager_wrapper.cc", + "policy/app_install_event_log_manager_wrapper.h", + "policy/app_install_event_log_uploader.cc", + "policy/app_install_event_log_uploader.h", + "policy/app_install_event_logger.cc", + "policy/app_install_event_logger.h", "policy/auto_enrollment_client.cc", "policy/auto_enrollment_client.h", "policy/bluetooth_policy_handler.cc", @@ -1342,14 +1358,16 @@ source_set("chromeos") { "policy/server_backed_device_state.h", "policy/server_backed_state_keys_broker.cc", "policy/server_backed_state_keys_broker.h", + "policy/single_app_install_event_log.cc", + "policy/single_app_install_event_log.h", "policy/status_uploader.cc", "policy/status_uploader.h", "policy/system_log_uploader.cc", "policy/system_log_uploader.h", + "policy/temp_certs_cache_nss.cc", + "policy/temp_certs_cache_nss.h", "policy/ticl_device_settings_provider.cc", "policy/ticl_device_settings_provider.h", - "policy/untrusted_authority_certs_cache.cc", - "policy/untrusted_authority_certs_cache.h", "policy/upload_job.h", "policy/upload_job_impl.cc", "policy/upload_job_impl.h", @@ -1381,8 +1399,13 @@ source_set("chromeos") { "power/idle_action_warning_dialog_view.h", "power/idle_action_warning_observer.cc", "power/idle_action_warning_observer.h", + "power/ml/boot_clock.h", "power/ml/idle_event_notifier.cc", "power/ml/idle_event_notifier.h", + "power/ml/real_boot_clock.cc", + "power/ml/real_boot_clock.h", + "power/ml/recent_events_counter.cc", + "power/ml/recent_events_counter.h", "power/ml/user_activity_logger.cc", "power/ml/user_activity_logger.h", "power/ml/user_activity_logger_delegate.h", @@ -1504,6 +1527,8 @@ source_set("chromeos") { "smb_client/smb_service.h", "smb_client/smb_service_factory.cc", "smb_client/smb_service_factory.h", + "smb_client/temp_file_manager.cc", + "smb_client/temp_file_manager.h", "status/network_menu.cc", "status/network_menu.h", "system/automatic_reboot_manager.cc", @@ -1544,8 +1569,8 @@ source_set("chromeos") { "system_logs/single_debug_daemon_log_source.h", "system_logs/single_log_file_log_source.cc", "system_logs/single_log_file_log_source.h", + "system_logs/touch_log_source.cc", "system_logs/touch_log_source.h", - "system_logs/touch_log_source_ozone.cc", "tether/fake_tether_service.cc", "tether/fake_tether_service.h", "tether/tether_service.cc", @@ -1571,8 +1596,12 @@ source_set("chromeos") { "ui/screen_capture_notification_ui_chromeos.h", "upgrade_detector_chromeos.cc", "upgrade_detector_chromeos.h", + "virtual_machines/virtual_machines_util.cc", + "virtual_machines/virtual_machines_util.h", # Extension API implementations. + "extensions/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.cc", + "extensions/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.h", "extensions/echo_private_api.cc", "extensions/echo_private_api.h", "extensions/file_manager/device_event_router.cc", @@ -1628,12 +1657,17 @@ source_set("chromeos") { "extensions/wallpaper_api.h", "extensions/wallpaper_function_base.cc", "extensions/wallpaper_function_base.h", - "extensions/wallpaper_manager_util.cc", - "extensions/wallpaper_manager_util.h", "extensions/wallpaper_private_api.cc", "extensions/wallpaper_private_api.h", ] + if (enable_cros_assistant) { + deps += [ + "//chromeos/services/assistant:lib", + "//chromeos/services/assistant/public/mojom", + ] + } + if (use_cups) { sources += [ "printing/cups_print_job_manager_impl.cc", @@ -1731,6 +1765,7 @@ source_set("unit_tests") { "accessibility/spoken_feedback_event_rewriter_unittest.cc", "app_mode/startup_app_launcher_unittest.cc", "arc/accessibility/arc_accessibility_helper_bridge_unittest.cc", + "arc/accessibility/ax_tree_source_arc_unittest.cc", "arc/arc_play_store_enabled_preference_handler_unittest.cc", "arc/arc_session_manager_unittest.cc", "arc/arc_support_host_unittest.cc", @@ -1885,8 +1920,6 @@ source_set("unit_tests") { "login/users/affiliation_unittest.cc", "login/users/multi_profile_user_controller_unittest.cc", "login/users/user_manager_unittest.cc", - "login/users/wallpaper/wallpaper_manager_test_utils.cc", - "login/users/wallpaper/wallpaper_manager_test_utils.h", "mobile/mobile_activator_unittest.cc", "mobile_config_unittest.cc", "net/cert_verify_proc_chromeos_unittest.cc", @@ -1906,6 +1939,12 @@ source_set("unit_tests") { "policy/affiliated_cloud_policy_invalidator_unittest.cc", "policy/affiliated_invalidation_service_provider_impl_unittest.cc", "policy/android_management_client_unittest.cc", + "policy/app_install_event_log_collector_unittest.cc", + "policy/app_install_event_log_manager_unittest.cc", + "policy/app_install_event_log_manager_wrapper_unittest.cc", + "policy/app_install_event_log_unittest.cc", + "policy/app_install_event_log_uploader_unittest.cc", + "policy/app_install_event_logger_unittest.cc", "policy/auto_enrollment_client_unittest.cc", "policy/bluetooth_policy_handler_unittest.cc", "policy/cached_policy_key_loader_chromeos_unittest.cc", @@ -1935,15 +1974,20 @@ source_set("unit_tests") { "policy/remote_commands/device_command_set_volume_job_unittest.cc", "policy/secondary_google_account_signin_policy_handler_unittest.cc", "policy/server_backed_state_keys_broker_unittest.cc", + "policy/single_app_install_event_log_unittest.cc", "policy/status_uploader_unittest.cc", "policy/system_log_uploader_unittest.cc", - "policy/untrusted_authority_certs_cache_unittest.cc", + "policy/temp_certs_cache_nss_unittest.cc", "policy/upload_job_unittest.cc", "policy/user_cloud_policy_manager_chromeos_unittest.cc", "policy/user_cloud_policy_store_chromeos_unittest.cc", "power/cpu_data_collector_unittest.cc", "power/extension_event_observer_unittest.cc", + "power/ml/fake_boot_clock.cc", + "power/ml/fake_boot_clock.h", "power/ml/idle_event_notifier_unittest.cc", + "power/ml/real_boot_clock_unittest.cc", + "power/ml/recent_events_counter_unittest.cc", "power/ml/user_activity_logger_delegate_ukm_unittest.cc", "power/ml/user_activity_logger_unittest.cc", "power/power_data_collector_unittest.cc", @@ -1958,7 +2002,6 @@ source_set("unit_tests") { "printing/printers_sync_bridge_unittest.cc", "printing/specifics_translation_unittest.cc", "printing/synced_printers_manager_unittest.cc", - "printing/usb_printer_detector_unittest.cc", "printing/zeroconf_printer_detector_unittest.cc", "profiles/profile_list_chromeos_unittest.cc", "proxy_config_service_impl_unittest.cc", @@ -1974,11 +2017,13 @@ source_set("unit_tests") { "settings/shutdown_policy_handler_unittest.cc", "settings/stub_cros_settings_provider_unittest.cc", "smb_client/smb_service_unittest.cc", + "smb_client/temp_file_manager_unittest.cc", "system/automatic_reboot_manager_unittest.cc", "system/device_disabling_manager_unittest.cc", "system_logs/single_debug_daemon_log_source_unittest.cc", "system_logs/single_log_file_log_source_unittest.cc", "tether/tether_service_unittest.cc", + "tpm_firmware_update_unittest.cc", "ui/idle_app_name_notification_view_unittest.cc", "ui/low_disk_notification_unittest.cc", @@ -2095,6 +2140,13 @@ proto_library("user_activity_event_proto") { ] } +proto_library("backdrop_wallpaper_proto") { + sources = [ + "extensions/backdrop_wallpaper_handlers/backdrop_wallpaper.proto", + ] + generate_python = false +} + service_manifest("ash_pref_connector_manifest") { name = "ash_pref_connector" source = "prefs/ash_pref_connector_manifest.json" diff --git a/chromium/chrome/browser/custom_handlers/OWNERS b/chromium/chrome/browser/custom_handlers/OWNERS index 9fd573abbf2..f4a612fe88c 100644 --- a/chromium/chrome/browser/custom_handlers/OWNERS +++ b/chromium/chrome/browser/custom_handlers/OWNERS @@ -1 +1,2 @@ benwells@chromium.org +mgiuca@chromium.org diff --git a/chromium/chrome/browser/custom_handlers/protocol_handler_registry.cc b/chromium/chrome/browser/custom_handlers/protocol_handler_registry.cc index 608da379d81..d3de61c47ba 100644 --- a/chromium/chrome/browser/custom_handlers/protocol_handler_registry.cc +++ b/chromium/chrome/browser/custom_handlers/protocol_handler_registry.cc @@ -567,6 +567,11 @@ bool ProtocolHandlerRegistry::IsHandledProtocol( void ProtocolHandlerRegistry::RemoveHandler( const ProtocolHandler& handler) { + if (IsIgnored(handler)) { + RemoveIgnoredHandler(handler); + return; + } + DCHECK_CURRENTLY_ON(BrowserThread::UI); ProtocolHandlerList& handlers = protocol_handlers_[handler.protocol()]; bool erase_success = false; @@ -751,7 +756,7 @@ base::Value* ProtocolHandlerRegistry::EncodeRegisteredHandlers() { j != i->second.end(); ++j) { std::unique_ptr<base::DictionaryValue> encoded = j->Encode(); if (IsDefault(*j)) { - encoded->Set("default", base::MakeUnique<base::Value>(true)); + encoded->Set("default", std::make_unique<base::Value>(true)); } protocol_handlers->Append(std::move(encoded)); } diff --git a/chromium/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc b/chromium/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc index 5b47bd09cd8..10a4f8545bf 100644 --- a/chromium/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc +++ b/chromium/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc @@ -115,7 +115,7 @@ void AssertWillHandle( std::unique_ptr<base::DictionaryValue> GetProtocolHandlerValue( const std::string& protocol, const std::string& url) { - auto value = base::MakeUnique<base::DictionaryValue>(); + auto value = std::make_unique<base::DictionaryValue>(); value->SetString("protocol", protocol); value->SetString("url", url); return value; @@ -492,6 +492,14 @@ TEST_F(ProtocolHandlerRegistryTest, TestRemoveHandler) { registry()->RemoveHandler(ph1); ASSERT_FALSE(registry()->IsRegistered(ph1)); ASSERT_FALSE(registry()->IsHandledProtocol("test")); + + registry()->OnIgnoreRegisterProtocolHandler(ph1); + ASSERT_FALSE(registry()->IsRegistered(ph1)); + ASSERT_TRUE(registry()->IsIgnored(ph1)); + + registry()->RemoveHandler(ph1); + ASSERT_FALSE(registry()->IsRegistered(ph1)); + ASSERT_FALSE(registry()->IsIgnored(ph1)); } TEST_F(ProtocolHandlerRegistryTest, TestIsRegistered) { @@ -992,6 +1000,70 @@ TEST_F(ProtocolHandlerRegistryTest, TestPrefPolicyOverlapIgnore) { ASSERT_EQ(InMemoryIgnoredHandlerCount(), 4); } +TEST_F(ProtocolHandlerRegistryTest, TestURIPercentEncoding) { + ProtocolHandler ph = + CreateProtocolHandler("web+custom", GURL("https://test.com/url=%s")); + registry()->OnAcceptRegisterProtocolHandler(ph); + + // Normal case. + GURL translated_url = ph.TranslateUrl(GURL("web+custom://custom/handler")); + ASSERT_EQ(translated_url, + GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom%2Fhandler")); + + // Percent-encoding. + translated_url = ph.TranslateUrl(GURL("web+custom://custom/%20handler")); + ASSERT_EQ( + translated_url, + GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom%2F%2520handler")); + + // Space character. + translated_url = ph.TranslateUrl(GURL("web+custom://custom handler")); + // TODO(mgiuca): Check whether this(' ') should be encoded as '%20'. + ASSERT_EQ(translated_url, + GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom+handler")); + + // Query parameters. + translated_url = ph.TranslateUrl(GURL("web+custom://custom?foo=bar&bar=baz")); + ASSERT_EQ(translated_url, + GURL("https://test.com/" + "url=web%2Bcustom%3A%2F%2Fcustom%3Ffoo%3Dbar%26bar%3Dbaz")); + + // Non-ASCII characters. + translated_url = ph.TranslateUrl(GURL("web+custom://custom/<>`{}#?\"'😂")); + ASSERT_EQ(translated_url, GURL("https://test.com/" + "url=web%2Bcustom%3A%2F%2Fcustom%2F%3C%3E%60%" + "7B%7D%23%3F%22'%25F0%259F%2598%2582")); + + // C0 characters. GURL constructor encodes U+001F as "%1F" first, because + // U+001F is an illegal char. Then the protocol handler translator encodes it + // to "%251F" again. That's why the expected result has double-encoded URL. + translated_url = ph.TranslateUrl(GURL("web+custom://custom/\x1fhandler")); + ASSERT_EQ( + translated_url, + GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom%2F%251Fhandler")); + + // Control characters. + // TODO(crbug.com/809852): Check why non-special URLs don't encode any + // characters above U+001F. + translated_url = ph.TranslateUrl(GURL("web+custom://custom/\x7Fhandler")); + ASSERT_EQ( + translated_url, + GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom%2F%7Fhandler")); + + // Path percent-encode set. + translated_url = + ph.TranslateUrl(GURL("web+custom://custom/handler=#download")); + ASSERT_EQ(translated_url, + GURL("https://test.com/" + "url=web%2Bcustom%3A%2F%2Fcustom%2Fhandler%3D%23download")); + + // Userinfo percent-encode set. + translated_url = ph.TranslateUrl(GURL("web+custom://custom/handler:@id=")); + ASSERT_EQ(translated_url, + GURL("https://test.com/" + "url=web%2Bcustom%3A%2F%2Fcustom%2Fhandler%3A%40id%3D")); +} + TEST_F(ProtocolHandlerRegistryTest, TestMultiplePlaceholders) { ProtocolHandler ph = CreateProtocolHandler("test", GURL("http://example.com/%s/url=%s")); diff --git a/chromium/chrome/browser/devtools/OWNERS b/chromium/chrome/browser/devtools/OWNERS index 8d426acaa95..1329473478e 100644 --- a/chromium/chrome/browser/devtools/OWNERS +++ b/chromium/chrome/browser/devtools/OWNERS @@ -1,5 +1,6 @@ dgozman@chromium.org pfeldman@chromium.org +caseq@chromium.org per-file devtools_embedder_message_dispatcher.*=set noparent per-file devtools_embedder_message_dispatcher.*=file://ipc/SECURITY_OWNERS diff --git a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc index 22a76735980..35ee33fd445 100644 --- a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc +++ b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc @@ -132,10 +132,10 @@ bool ChromeDevToolsManagerDelegate::HandleCommand( std::string ChromeDevToolsManagerDelegate::GetTargetType( content::WebContents* web_contents) { - for (TabContentsIterator it; !it.done(); it.Next()) { - if (*it == web_contents) - return DevToolsAgentHost::kTypePage; - } + auto& all_tabs = AllTabContentses(); + auto it = std::find(all_tabs.begin(), all_tabs.end(), web_contents); + if (it != all_tabs.end()) + return DevToolsAgentHost::kTypePage; std::string extension_name; std::string extension_type; @@ -184,9 +184,8 @@ std::string ChromeDevToolsManagerDelegate::GetDiscoveryPageHTML() { .as_string(); } -std::string ChromeDevToolsManagerDelegate::GetFrontendResource( - const std::string& path) { - return content::DevToolsFrontendHost::GetFrontendResource(path).as_string(); +bool ChromeDevToolsManagerDelegate::HasBundledFrontendResources() { + return true; } void ChromeDevToolsManagerDelegate::DevToolsAgentHostAttached( diff --git a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h index 9e837b163f2..6b81036cbe9 100644 --- a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h +++ b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h @@ -50,7 +50,7 @@ class ChromeDevToolsManagerDelegate : scoped_refptr<content::DevToolsAgentHost> CreateNewTarget( const GURL& url) override; std::string GetDiscoveryPageHTML() override; - std::string GetFrontendResource(const std::string& path) override; + bool HasBundledFrontendResources() override; // content::DevToolsAgentHostObserver overrides. void DevToolsAgentHostAttached( diff --git a/chromium/chrome/browser/devtools/chrome_devtools_session.cc b/chromium/chrome/browser/devtools/chrome_devtools_session.cc index 6dd16ded0f9..2ee5b2a5d63 100644 --- a/chromium/chrome/browser/devtools/chrome_devtools_session.cc +++ b/chromium/chrome/browser/devtools/chrome_devtools_session.cc @@ -20,7 +20,8 @@ ChromeDevToolsSession::ChromeDevToolsSession( client_(client), dispatcher_(std::make_unique<protocol::UberDispatcher>(this)) { dispatcher_->setFallThroughForNotFound(true); - if (agent_host->GetWebContents()) { + if (agent_host->GetWebContents() && + agent_host->GetType() == content::DevToolsAgentHost::kTypePage) { page_handler_ = std::make_unique<PageHandler>(agent_host->GetWebContents(), dispatcher_.get()); } diff --git a/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc index 2b91e7dc1cf..f91488647fc 100644 --- a/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc +++ b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc @@ -29,6 +29,7 @@ #include "net/log/net_log_source.h" #include "net/socket/stream_socket.h" #include "net/socket/tcp_server_socket.h" +#include "net/traffic_annotation/network_traffic_annotation_test_helper.h" using content::BrowserThread; @@ -343,9 +344,9 @@ void SimpleHttpServer::Connection::WriteData() { output_buffer_->offset() + bytes_to_write_) << "Overflow"; int write_result = socket_->Write( - output_buffer_.get(), - bytes_to_write_, - base::Bind(&Connection::OnDataWritten, base::Unretained(this))); + output_buffer_.get(), bytes_to_write_, + base::Bind(&Connection::OnDataWritten, base::Unretained(this)), + TRAFFIC_ANNOTATION_FOR_TESTS); if (write_result != net::ERR_IO_PENDING) OnDataWritten(write_result); @@ -444,7 +445,7 @@ class AdbParser : public SimpleHttpServer::Parser, Send("FAIL", "device offline (x)"); } else { mock_connection_ = - base::MakeUnique<MockAndroidConnection>(this, serial_, command); + std::make_unique<MockAndroidConnection>(this, serial_, command); } } diff --git a/chromium/chrome/browser/devtools/device/android_device_manager.cc b/chromium/chrome/browser/devtools/device/android_device_manager.cc index 18327ea762f..6f1d6059e0d 100644 --- a/chromium/chrome/browser/devtools/device/android_device_manager.cc +++ b/chromium/chrome/browser/devtools/device/android_device_manager.cc @@ -35,7 +35,7 @@ static const char kModelOffline[] = "Offline"; static const char kRequestLineFormat[] = "GET %s HTTP/1.1"; -net::NetworkTrafficAnnotationTag kTrafficAnnotation = +net::NetworkTrafficAnnotationTag kAndroidDeviceManagerTrafficAnnotation = net::DefineNetworkTrafficAnnotation("android_device_manager_socket", R"( semantics { sender: "Android Device Manager" @@ -85,7 +85,7 @@ static void PostHttpUpgradeCallback( std::unique_ptr<net::StreamSocket> socket) { response_task_runner->PostTask( FROM_HERE, base::BindOnce(callback, result, extensions, body_head, - base::Passed(&socket))); + std::move(socket))); } class HttpRequest { @@ -167,7 +167,7 @@ class HttpRequest { result = socket_->Write( request_.get(), request_->BytesRemaining(), base::Bind(&HttpRequest::DoSendRequest, base::Unretained(this)), - kTrafficAnnotation); + kAndroidDeviceManagerTrafficAnnotation); } } @@ -357,7 +357,7 @@ class DevicesRequest : public base::RefCountedThreadSafe<DevicesRequest> { friend class base::RefCountedThreadSafe<DevicesRequest>; ~DevicesRequest() { response_task_runner_->PostTask( - FROM_HERE, base::BindOnce(callback_, base::Passed(&descriptors_))); + FROM_HERE, base::BindOnce(callback_, std::move(descriptors_))); } typedef std::vector<std::string> Serials; diff --git a/chromium/chrome/browser/devtools/device/android_web_socket.cc b/chromium/chrome/browser/devtools/device/android_web_socket.cc index 3130eb55da4..bc307ab2f8e 100644 --- a/chromium/chrome/browser/devtools/device/android_web_socket.cc +++ b/chromium/chrome/browser/devtools/device/android_web_socket.cc @@ -26,7 +26,7 @@ namespace { const int kBufferSize = 16 * 1024; const char kCloseResponse[] = "\x88\x80\x2D\x0E\x1E\xFA"; -net::NetworkTrafficAnnotationTag kTrafficAnnotation = +net::NetworkTrafficAnnotationTag kAndroidWebSocketTrafficAnnotation = net::DefineNetworkTrafficAnnotation("android_web_socket", R"( semantics { sender: "Android Web Socket" @@ -160,7 +160,7 @@ class AndroidDeviceManager::AndroidWebSocket::WebSocketImpl { result = socket_->Write(buffer.get(), buffer->size(), base::Bind(&WebSocketImpl::SendPendingRequests, weak_factory_.GetWeakPtr()), - kTrafficAnnotation); + kAndroidWebSocketTrafficAnnotation); if (result != net::ERR_IO_PENDING) SendPendingRequests(result); } diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider.cc b/chromium/chrome/browser/devtools/device/cast_device_provider.cc index 53d8e6d02dd..5a15374c740 100644 --- a/chromium/chrome/browser/devtools/device/cast_device_provider.cc +++ b/chromium/chrome/browser/devtools/device/cast_device_provider.cc @@ -102,30 +102,33 @@ class CastDeviceProvider::DeviceListerDelegate if (device_lister_) return; service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance(); - device_lister_.reset(new ServiceDiscoveryDeviceLister( - this, service_discovery_client_.get(), kCastServiceType)); + device_lister_ = ServiceDiscoveryDeviceLister::Create( + this, service_discovery_client_.get(), kCastServiceType); device_lister_->Start(); device_lister_->DiscoverNewDevices(); } // ServiceDiscoveryDeviceLister::Delegate implementation: - void OnDeviceChanged(bool added, + void OnDeviceChanged(const std::string& service_type, + bool added, const ServiceDescription& service_description) override { - runner_->PostTask(FROM_HERE, - base::BindOnce(&CastDeviceProvider::OnDeviceChanged, - provider_, added, service_description)); + runner_->PostTask( + FROM_HERE, + base::BindOnce(&CastDeviceProvider::OnDeviceChanged, provider_, + service_type, added, service_description)); } - void OnDeviceRemoved(const std::string& service_name) override { + void OnDeviceRemoved(const std::string& service_type, + const std::string& service_name) override { runner_->PostTask(FROM_HERE, base::BindOnce(&CastDeviceProvider::OnDeviceRemoved, - provider_, service_name)); + provider_, service_type, service_name)); } - void OnDeviceCacheFlushed() override { - runner_->PostTask( - FROM_HERE, - base::BindOnce(&CastDeviceProvider::OnDeviceCacheFlushed, provider_)); + void OnDeviceCacheFlushed(const std::string& service_type) override { + runner_->PostTask(FROM_HERE, + base::BindOnce(&CastDeviceProvider::OnDeviceCacheFlushed, + provider_, service_type)); } private: @@ -173,6 +176,7 @@ void CastDeviceProvider::OpenSocket(const std::string& serial, } void CastDeviceProvider::OnDeviceChanged( + const std::string& service_type, bool added, const ServiceDescription& service_description) { VLOG(1) << "Device " << (added ? "added: " : "changed: ") @@ -190,7 +194,8 @@ void CastDeviceProvider::OnDeviceChanged( device_info_map_[host] = ServiceDescriptionToDeviceInfo(service_description); } -void CastDeviceProvider::OnDeviceRemoved(const std::string& service_name) { +void CastDeviceProvider::OnDeviceRemoved(const std::string& service_type, + const std::string& service_name) { VLOG(1) << "Device removed: " << service_name; auto it = service_hostname_map_.find(service_name); if (it == service_hostname_map_.end()) @@ -200,7 +205,7 @@ void CastDeviceProvider::OnDeviceRemoved(const std::string& service_name) { service_hostname_map_.erase(it); } -void CastDeviceProvider::OnDeviceCacheFlushed() { +void CastDeviceProvider::OnDeviceCacheFlushed(const std::string& service_type) { VLOG(1) << "Device cache flushed"; service_hostname_map_.clear(); device_info_map_.clear(); diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider.h b/chromium/chrome/browser/devtools/device/cast_device_provider.h index f0462cabaa8..f2d81461403 100644 --- a/chromium/chrome/browser/devtools/device/cast_device_provider.h +++ b/chromium/chrome/browser/devtools/device/cast_device_provider.h @@ -35,10 +35,12 @@ class CastDeviceProvider // ServiceDiscoveryDeviceLister::Delegate implementation: void OnDeviceChanged( + const std::string& service_type, bool added, const local_discovery::ServiceDescription& service_description) override; - void OnDeviceRemoved(const std::string& service_name) override; - void OnDeviceCacheFlushed() override; + void OnDeviceRemoved(const std::string& service_type, + const std::string& service_name) override; + void OnDeviceCacheFlushed(const std::string& service_type) override; private: class DeviceListerDelegate; diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc b/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc index 936d7e56099..26ce4d4b019 100644 --- a/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc +++ b/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc @@ -52,7 +52,7 @@ TEST(CastDeviceProviderTest, ServiceDiscovery) { cast_service.metadata.push_back("md=" + cast_service_model); ASSERT_TRUE(cast_service.ip_address.AssignFromIPLiteral("192.168.1.101")); - device_provider_->OnDeviceChanged(true, cast_service); + device_provider_->OnDeviceChanged(cast_service_type, true, cast_service); BrowserInfo exp_browser_info; exp_browser_info.socket_name = "9222"; @@ -91,7 +91,7 @@ TEST(CastDeviceProviderTest, ServiceDiscovery) { base::Bind(&DummyCallback, &was_run)); ASSERT_FALSE(was_run); - device_provider_->OnDeviceChanged(true, other_service); + device_provider_->OnDeviceChanged(cast_service_type, true, other_service); // Callback should not be run, since non-cast services are not discovered by // this device provider. @@ -100,7 +100,8 @@ TEST(CastDeviceProviderTest, ServiceDiscovery) { ASSERT_FALSE(was_run); // Remove the cast service. - device_provider_->OnDeviceRemoved(cast_service.service_name); + device_provider_->OnDeviceRemoved(cast_service_type, + cast_service.service_name); // Callback should not be run, since the cast service has been removed. device_provider_->QueryDeviceInfo(cast_service.address.host(), diff --git a/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc b/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc index 36e084ec663..8148747b721 100644 --- a/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc +++ b/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc @@ -36,8 +36,8 @@ #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/devtools/remote_debugging_server.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_switches.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/prefs/pref_service.h" @@ -72,8 +72,6 @@ bool BrowserIdFromString(const std::string& browser_id_str, return true; } -static void NoOp(int, const std::string&) {} - } // namespace // static @@ -146,7 +144,7 @@ void DevToolsAndroidBridge::OpenRemotePage(scoped_refptr<RemoteBrowser> browser, std::string query = net::EscapeQueryParamValue(url, false /* use_plus */); std::string request = base::StringPrintf(kNewPageRequestWithURL, query.c_str()); - SendJsonRequest(browser->GetId(), request, base::Bind(&NoOp)); + SendJsonRequest(browser->GetId(), request, base::DoNothing()); } DevToolsAndroidBridge::DevToolsAndroidBridge( diff --git a/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc b/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc index d0cf7a70db3..178a6467b3b 100644 --- a/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc +++ b/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc @@ -42,8 +42,6 @@ const char kPageReloadCommand[] = "Page.reload"; const char kWebViewSocketPrefix[] = "webview_devtools_remote"; -static void NoOp(int, const std::string&) {} - static void ScheduleTaskDefault(const base::Closure& task) { BrowserThread::PostDelayedTask( BrowserThread::UI, @@ -340,7 +338,7 @@ std::string AgentHostDelegate::GetFrontendURL() { bool AgentHostDelegate::Activate() { std::string request = base::StringPrintf(kActivatePageRequest, remote_id_.c_str()); - device_->SendJsonRequest(browser_id_, request, base::Bind(&NoOp)); + device_->SendJsonRequest(browser_id_, request, base::DoNothing()); return true; } @@ -352,7 +350,7 @@ void AgentHostDelegate::Reload() { bool AgentHostDelegate::Close() { std::string request = base::StringPrintf(kClosePageRequest, remote_id_.c_str()); - device_->SendJsonRequest(browser_id_, request, base::Bind(&NoOp)); + device_->SendJsonRequest(browser_id_, request, base::DoNothing()); return true; } diff --git a/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc b/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc index 6fa683b864b..55e64f29bf4 100644 --- a/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc +++ b/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc @@ -45,7 +45,7 @@ enum { kStatusOK = 0, }; -net::NetworkTrafficAnnotationTag kTrafficAnnotation = +net::NetworkTrafficAnnotationTag kPortForwardingControllerTrafficAnnotation = net::DefineNetworkTrafficAnnotation("port_forwarding_controller_socket", R"( semantics { @@ -162,7 +162,7 @@ class SocketTunnel { result = to->Write(drainable.get(), total, base::Bind(&SocketTunnel::OnWritten, base::Unretained(this), drainable, from, to), - kTrafficAnnotation); + kPortForwardingControllerTrafficAnnotation); if (result != net::ERR_IO_PENDING) OnWritten(drainable, from, to, result); } @@ -184,7 +184,7 @@ class SocketTunnel { to->Write(drainable.get(), drainable->BytesRemaining(), base::Bind(&SocketTunnel::OnWritten, base::Unretained(this), drainable, from, to), - kTrafficAnnotation); + kPortForwardingControllerTrafficAnnotation); if (result != net::ERR_IO_PENDING) OnWritten(drainable, from, to, result); return; diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc b/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc index 84c364f5653..d2abbfb33cd 100644 --- a/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc @@ -305,7 +305,7 @@ class MockUsbDeviceHandle : public UsbDeviceHandle { AdbMessage::kCommandOKAY, std::string()); local_sockets_[current_message_->arg0] = - base::MakeUnique<MockLocalSocket>( + std::make_unique<MockLocalSocket>( base::Bind(&MockUsbDeviceHandle::WriteResponse, base::Unretained(this), last_local_socket_, current_message_->arg0), @@ -563,7 +563,7 @@ class AndroidUsbCountTest : public AndroidUsbDiscoveryTest { class AndroidUsbTraitsTest : public AndroidUsbDiscoveryTest { protected: std::unique_ptr<MockUsbService> CreateMockService() override { - return base::MakeUnique<MockUsbServiceForCheckingTraits>(); + return std::make_unique<MockUsbServiceForCheckingTraits>(); } }; diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc b/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc index 6b2615bd800..3697b0c4cf4 100644 --- a/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc @@ -379,7 +379,7 @@ void AndroidUsbDevice::InitOnCallerThread() { if (task_runner_) return; task_runner_ = base::ThreadTaskRunnerHandle::Get(); - Queue(base::MakeUnique<AdbMessage>(AdbMessage::kCommandCNXN, kVersion, + Queue(std::make_unique<AdbMessage>(AdbMessage::kCommandCNXN, kVersion, kMaxPayload, kHostConnectMessage)); ReadHeader(); } @@ -398,7 +398,7 @@ void AndroidUsbDevice::Send(uint32_t command, uint32_t arg0, uint32_t arg1, const std::string& body) { - auto message = base::MakeUnique<AdbMessage>(command, arg0, arg1, body); + auto message = std::make_unique<AdbMessage>(command, arg0, arg1, body); // Delay open request if not yet connected. if (!is_connected_) { pending_messages_.push_back(std::move(message)); @@ -524,12 +524,11 @@ void AndroidUsbDevice::ParseHeader(UsbTransferStatus status, if (data_length == 0) { task_runner_->PostTask( FROM_HERE, base::BindOnce(&AndroidUsbDevice::HandleIncoming, this, - base::Passed(&message))); + std::move(message))); } else { task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&AndroidUsbDevice::ReadBody, this, - base::Passed(&message), data_length, data_check)); + FROM_HERE, base::BindOnce(&AndroidUsbDevice::ReadBody, this, + std::move(message), data_length, data_check)); } } @@ -559,9 +558,8 @@ void AndroidUsbDevice::ParseBody(std::unique_ptr<AdbMessage> message, if (status == UsbTransferStatus::TIMEOUT) { task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&AndroidUsbDevice::ReadBody, this, - base::Passed(&message), data_length, data_check)); + FROM_HERE, base::BindOnce(&AndroidUsbDevice::ReadBody, this, + std::move(message), data_length, data_check)); return; } @@ -580,7 +578,7 @@ void AndroidUsbDevice::ParseBody(std::unique_ptr<AdbMessage> message, task_runner_->PostTask(FROM_HERE, base::BindOnce(&AndroidUsbDevice::HandleIncoming, this, - base::Passed(&message))); + std::move(message))); } void AndroidUsbDevice::HandleIncoming(std::unique_ptr<AdbMessage> message) { @@ -591,18 +589,18 @@ void AndroidUsbDevice::HandleIncoming(std::unique_ptr<AdbMessage> message) { { DCHECK_EQ(message->arg0, static_cast<uint32_t>(AdbMessage::kAuthToken)); if (signature_sent_) { - Queue(base::MakeUnique<AdbMessage>( + Queue(std::make_unique<AdbMessage>( AdbMessage::kCommandAUTH, AdbMessage::kAuthRSAPublicKey, 0, AndroidRSAPublicKey(rsa_key_.get()))); } else { signature_sent_ = true; std::string signature = AndroidRSASign(rsa_key_.get(), message->body); if (!signature.empty()) { - Queue(base::MakeUnique<AdbMessage>(AdbMessage::kCommandAUTH, + Queue(std::make_unique<AdbMessage>(AdbMessage::kCommandAUTH, AdbMessage::kAuthSignature, 0, signature)); } else { - Queue(base::MakeUnique<AdbMessage>( + Queue(std::make_unique<AdbMessage>( AdbMessage::kCommandAUTH, AdbMessage::kAuthRSAPublicKey, 0, AndroidRSAPublicKey(rsa_key_.get()))); } diff --git a/chromium/chrome/browser/devtools/devtools_eye_dropper.cc b/chromium/chrome/browser/devtools/devtools_eye_dropper.cc index 14b8b6e7860..742b7521876 100644 --- a/chromium/chrome/browser/devtools/devtools_eye_dropper.cc +++ b/chromium/chrome/browser/devtools/devtools_eye_dropper.cc @@ -95,8 +95,8 @@ void DevToolsEyeDropper::UpdateFrame() { gfx::Size should_be_rendering_size = host_->GetView()->GetViewBounds().size(); host_->GetView()->CopyFromSurface( gfx::Rect(), should_be_rendering_size, - base::Bind(&DevToolsEyeDropper::FrameUpdated, weak_factory_.GetWeakPtr()), - kN32_SkColorType); + base::BindOnce(&DevToolsEyeDropper::FrameUpdated, + weak_factory_.GetWeakPtr())); } void DevToolsEyeDropper::ResetFrame() { @@ -105,12 +105,11 @@ void DevToolsEyeDropper::ResetFrame() { last_cursor_y_ = -1; } -void DevToolsEyeDropper::FrameUpdated(const SkBitmap& bitmap, - content::ReadbackResponse response) { - if (response == content::READBACK_SUCCESS) { - frame_ = bitmap; - UpdateCursor(); - } +void DevToolsEyeDropper::FrameUpdated(const SkBitmap& bitmap) { + if (bitmap.drawsNothing()) + return; + frame_ = bitmap; + UpdateCursor(); } bool DevToolsEyeDropper::HandleMouseEvent(const blink::WebMouseEvent& event) { diff --git a/chromium/chrome/browser/devtools/devtools_eye_dropper.h b/chromium/chrome/browser/devtools/devtools_eye_dropper.h index 8d92d53c4e0..4956dce6c86 100644 --- a/chromium/chrome/browser/devtools/devtools_eye_dropper.h +++ b/chromium/chrome/browser/devtools/devtools_eye_dropper.h @@ -7,7 +7,6 @@ #include "base/callback.h" #include "base/macros.h" -#include "content/public/browser/readback_types.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/web_contents_observer.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -37,7 +36,7 @@ class DevToolsEyeDropper : public content::WebContentsObserver { void UpdateFrame(); void ResetFrame(); - void FrameUpdated(const SkBitmap&, content::ReadbackResponse); + void FrameUpdated(const SkBitmap&); bool HandleMouseEvent(const blink::WebMouseEvent& event); void UpdateCursor(); diff --git a/chromium/chrome/browser/devtools/devtools_file_helper.cc b/chromium/chrome/browser/devtools/devtools_file_helper.cc index 6e848f38c9a..bd03a162387 100644 --- a/chromium/chrome/browser/devtools/devtools_file_helper.cc +++ b/chromium/chrome/browser/devtools/devtools_file_helper.cc @@ -368,7 +368,7 @@ void DevToolsFileHelper::AddUserConfirmedFileSystem(const std::string& type, prefs::kDevToolsFileSystemPaths); base::DictionaryValue* file_systems_paths_value = update.Get(); file_systems_paths_value->SetWithoutPathExpansion( - file_system_path, base::MakeUnique<base::Value>(type)); + file_system_path, std::make_unique<base::Value>(type)); } void DevToolsFileHelper::FailedToAddFileSystem(const std::string& error) { diff --git a/chromium/chrome/browser/devtools/devtools_file_system_indexer_unittest.cc b/chromium/chrome/browser/devtools/devtools_file_system_indexer_unittest.cc index 029c360f206..eddbc3a809b 100644 --- a/chromium/chrome/browser/devtools/devtools_file_system_indexer_unittest.cc +++ b/chromium/chrome/browser/devtools/devtools_file_system_indexer_unittest.cc @@ -5,6 +5,7 @@ #include <set> #include "base/bind.h" +#include "base/bind_helpers.h" #include "base/files/file_util.h" #include "base/path_service.h" #include "base/run_loop.h" @@ -49,8 +50,8 @@ TEST_F(DevToolsFileSystemIndexerTest, BasicUsage) { .Append(FILE_PATH_LITERAL("indexer")); scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob> job = - indexer_->IndexPath(index_path.AsUTF8Unsafe(), base::Bind([](int) {}), - base::Bind([](int) {}), + indexer_->IndexPath(index_path.AsUTF8Unsafe(), base::DoNothing(), + base::DoNothing(), base::Bind(&DevToolsFileSystemIndexerTest::SetDone, base::Unretained(this))); diff --git a/chromium/chrome/browser/devtools/devtools_protocol.cc b/chromium/chrome/browser/devtools/devtools_protocol.cc index 1a2e6ccbc1e..3eb8251c266 100644 --- a/chromium/chrome/browser/devtools/devtools_protocol.cc +++ b/chromium/chrome/browser/devtools/devtools_protocol.cc @@ -48,7 +48,7 @@ DevToolsProtocol::CreateInvalidParamsResponse(int command_id, const std::string& param) { std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); response->SetInteger(kIdParam, command_id); - auto error_object = base::MakeUnique<base::DictionaryValue>(); + auto error_object = std::make_unique<base::DictionaryValue>(); error_object->SetInteger(kErrorCodeParam, kErrorInvalidParams); error_object->SetString(kErrorMessageParam, base::StringPrintf("Missing or invalid '%s' parameter", param.c_str())); @@ -63,7 +63,7 @@ std::unique_ptr<base::DictionaryValue> DevToolsProtocol::CreateErrorResponse( const std::string& error_message) { std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); response->SetInteger(kIdParam, command_id); - auto error_object = base::MakeUnique<base::DictionaryValue>(); + auto error_object = std::make_unique<base::DictionaryValue>(); error_object->SetInteger(kErrorCodeParam, kErrorServerError); error_object->SetString(kErrorMessageParam, error_message); response->Set(kErrorParam, std::move(error_object)); @@ -79,7 +79,7 @@ std::unique_ptr<base::DictionaryValue> DevToolsProtocol::CreateSuccessResponse( response->SetInteger(kIdParam, command_id); response->Set(kResultParam, result ? std::move(result) - : base::MakeUnique<base::DictionaryValue>()); + : std::make_unique<base::DictionaryValue>()); return response; } diff --git a/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc index a47a3d0b56a..e35920bf95e 100644 --- a/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc +++ b/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc @@ -17,6 +17,7 @@ #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" +#include "base/optional.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" @@ -33,7 +34,6 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/extensions/unpacked_installer.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/profiles/profile.h" @@ -77,6 +77,7 @@ #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/common/switches.h" #include "extensions/common/value_builder.h" +#include "extensions/test/test_extension_dir.h" #include "net/dns/mock_host_resolver.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "net/test/url_request/url_request_mock_http_job.h" @@ -330,6 +331,20 @@ class DevToolsSanityTest : public InProcessBrowserTest { DevToolsWindow* window_; }; +class SitePerProcessDevToolsSanityTest : public DevToolsSanityTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + DevToolsSanityTest::SetUpCommandLine(command_line); + content::IsolateAllSitesForTesting(command_line); + }; + + void SetUpOnMainThread() override { + DevToolsSanityTest::SetUpOnMainThread(); + content::SetupCrossSiteRedirector(embedded_test_server()); + ASSERT_TRUE(embedded_test_server()->Start()); + } +}; + // Used to block until a dev tools window gets beforeunload event. class DevToolsWindowBeforeUnloadObserver : public content::WebContentsObserver { @@ -504,7 +519,7 @@ class DevToolsExtensionTest : public DevToolsSanityTest, const std::string& devtools_page, const std::string& panel_iframe_src) { test_extension_dirs_.push_back( - base::MakeUnique<extensions::TestExtensionDir>()); + std::make_unique<extensions::TestExtensionDir>()); extensions::TestExtensionDir* dir = test_extension_dirs_.back().get(); extensions::DictionaryBuilder manifest; @@ -2088,3 +2103,78 @@ IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestRawHeadersWithRedirectAndHSTS) { redirect_url.spec().c_str()); CloseDevToolsWindow(); } + +// Tests that OpenInNewTab filters URLs. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestOpenInNewTabFilter) { + OpenDevToolsWindow(kDebuggerTestPage, false); + DevToolsUIBindings::Delegate* bindings_delegate_ = + static_cast<DevToolsUIBindings::Delegate*>(window_); + std::string test_url = + spawned_test_server()->GetURL(kDebuggerTestPage).spec(); + const std::string self_blob_url = + base::StringPrintf("blob:%s", test_url.c_str()); + const std::string self_filesystem_url = + base::StringPrintf("filesystem:%s", test_url.c_str()); + + // Pairs include a URL string and boolean whether it should be allowed. + std::vector<std::pair<const std::string, const std::string>> tests = { + {test_url, test_url}, + {"data:,foo", "data:,foo"}, + {"about://inspect", "about:blank"}, + {"chrome://inspect", "about:blank"}, + {"chrome://inspect/#devices", "about:blank"}, + {self_blob_url, self_blob_url}, + {"blob:chrome://inspect", "about:blank"}, + {self_filesystem_url, self_filesystem_url}, + {"filesystem:chrome://inspect", "about:blank"}, + {"view-source:http://chromium.org", "about:blank"}, + {"file:///", "about:blank"}, + {"about://gpu", "about:blank"}, + {"chrome://gpu", "about:blank"}, + {"chrome://crash", "about:blank"}, + {"", "about:blank"}, + }; + + TabStripModel* tabs = browser()->tab_strip_model(); + int i = 0; + for (const std::pair<const std::string, const std::string> pair : tests) { + bindings_delegate_->OpenInNewTab(pair.first); + i++; + + std::string opened_url = tabs->GetWebContentsAt(i)->GetVisibleURL().spec(); + SCOPED_TRACE( + base::StringPrintf("while testing URL: %s", pair.first.c_str())); + EXPECT_EQ(opened_url, pair.second); + } +} + +IN_PROC_BROWSER_TEST_F(SitePerProcessDevToolsSanityTest, InspectElement) { + GURL url(embedded_test_server()->GetURL("a.com", "/devtools/oopif.html")); + ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url, 2); + + std::vector<RenderFrameHost*> frames = GetInspectedTab()->GetAllFrames(); + ASSERT_EQ(2u, frames.size()); + ASSERT_NE(frames[0]->GetProcess(), frames[1]->GetProcess()); + RenderFrameHost* frame_host = frames[0]->GetParent() ? frames[0] : frames[1]; + + DevToolsWindowCreationObserver observer; + DevToolsWindow::InspectElement(frame_host, 100, 100); + observer.WaitForLoad(); + DevToolsWindow* window = observer.devtools_window(); + + DispatchOnTestSuite(window, "testInspectedElementIs", "INSPECTED-DIV"); + DevToolsWindowTesting::CloseDevToolsWindowSync(window); +} + +IN_PROC_BROWSER_TEST_F(SitePerProcessDevToolsSanityTest, + InputDispatchEventsToOOPIF) { + GURL url( + embedded_test_server()->GetURL("a.com", "/devtools/oopif-input.html")); + ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url, 2); + for (auto* frame : GetInspectedTab()->GetAllFrames()) + content::WaitForChildFrameSurfaceReady(frame); + DevToolsWindow* window = + DevToolsWindowTesting::OpenDevToolsWindowSync(GetInspectedTab(), false); + RunTestFunction(window, "testInputDispatchEventsToOOPIF"); + DevToolsWindowTesting::CloseDevToolsWindowSync(window); +} diff --git a/chromium/chrome/browser/devtools/devtools_targets_ui.cc b/chromium/chrome/browser/devtools/devtools_targets_ui.cc index 1776c366826..a5b599c5910 100644 --- a/chromium/chrome/browser/devtools/devtools_targets_ui.cc +++ b/chromium/chrome/browser/devtools/devtools_targets_ui.cc @@ -243,7 +243,7 @@ void AdbTargetsUIHandler::DeviceListChanged( kAdbDeviceIdFormat, device->serial().c_str()); device_data->SetString(kTargetIdField, device_id); - auto browser_list = base::MakeUnique<base::ListValue>(); + auto browser_list = std::make_unique<base::ListValue>(); DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers(); for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit = @@ -263,7 +263,7 @@ void AdbTargetsUIHandler::DeviceListChanged( browser_data->SetString(kTargetIdField, browser_id); browser_data->SetString(kTargetSourceField, source_id()); - auto page_list = base::MakeUnique<base::ListValue>(); + auto page_list = std::make_unique<base::ListValue>(); remote_browsers_[browser_id] = browser; for (const auto& page : browser->pages()) { scoped_refptr<DevToolsAgentHost> host = page->CreateTarget(); @@ -338,7 +338,7 @@ DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) { std::unique_ptr<base::DictionaryValue> DevToolsTargetsUIHandler::Serialize( DevToolsAgentHost* host) { - auto target_data = base::MakeUnique<base::DictionaryValue>(); + auto target_data = std::make_unique<base::DictionaryValue>(); target_data->SetString(kTargetSourceField, source_id_); target_data->SetString(kTargetIdField, host->GetId()); target_data->SetString(kTargetTypeField, host->GetType()); @@ -382,14 +382,14 @@ void PortForwardingStatusSerializer::PortStatusChanged( base::DictionaryValue result; for (ForwardingStatus::const_iterator sit = status.begin(); sit != status.end(); ++sit) { - auto port_status_dict = base::MakeUnique<base::DictionaryValue>(); + auto port_status_dict = std::make_unique<base::DictionaryValue>(); const PortStatusMap& port_status_map = sit->second; for (PortStatusMap::const_iterator it = port_status_map.begin(); it != port_status_map.end(); ++it) { port_status_dict->SetInteger(base::IntToString(it->first), it->second); } - auto device_status_dict = base::MakeUnique<base::DictionaryValue>(); + auto device_status_dict = std::make_unique<base::DictionaryValue>(); device_status_dict->Set(kPortForwardingPorts, std::move(port_status_dict)); device_status_dict->SetString(kPortForwardingBrowserId, sit->first->GetId()); diff --git a/chromium/chrome/browser/devtools/devtools_ui_bindings.cc b/chromium/chrome/browser/devtools/devtools_ui_bindings.cc index 6f54f8b2bc5..d9d36269208 100644 --- a/chromium/chrome/browser/devtools/devtools_ui_bindings.cc +++ b/chromium/chrome/browser/devtools/devtools_ui_bindings.cc @@ -112,7 +112,7 @@ base::LazyInstance<DevToolsUIBindingsList>::Leaky std::unique_ptr<base::DictionaryValue> CreateFileSystemValue( DevToolsFileHelper::FileSystem file_system) { - auto file_system_value = base::MakeUnique<base::DictionaryValue>(); + auto file_system_value = std::make_unique<base::DictionaryValue>(); file_system_value->SetString("type", file_system.type); file_system_value->SetString("fileSystemName", file_system.file_system_name); file_system_value->SetString("rootURL", file_system.root_url); @@ -1072,7 +1072,7 @@ void DevToolsUIBindings::OnURLFetchComplete(const net::URLFetcher* source) { DCHECK(it != pending_requests_.end()); base::DictionaryValue response; - auto headers = base::MakeUnique<base::DictionaryValue>(); + auto headers = std::make_unique<base::DictionaryValue>(); net::HttpResponseHeaders* rh = source->GetResponseHeaders(); response.SetInteger("statusCode", rh ? rh->response_code() : 200); diff --git a/chromium/chrome/browser/devtools/devtools_window.cc b/chromium/chrome/browser/devtools/devtools_window.cc index 7e1ccbc335e..29fbdebd839 100644 --- a/chromium/chrome/browser/devtools/devtools_window.cc +++ b/chromium/chrome/browser/devtools/devtools_window.cc @@ -22,6 +22,7 @@ #include "chrome/browser/file_select_helper.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/browser/task_manager/web_contents_tags.h" #include "chrome/browser/ui/browser.h" @@ -43,6 +44,7 @@ #include "components/zoom/page_zoom.h" #include "components/zoom/zoom_controller.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/keyboard_event_processing_result.h" #include "content/public/browser/native_web_keyboard_event.h" @@ -83,6 +85,16 @@ base::LazyInstance<std::vector<base::Callback<void(DevToolsWindow*)>>>::Leaky static const char kKeyUpEventName[] = "keyup"; static const char kKeyDownEventName[] = "keydown"; +static const char kDefaultFrontendURL[] = + "chrome-devtools://devtools/bundled/devtools_app.html"; +static const char kNodeFrontendURL[] = + "chrome-devtools://devtools/bundled/node_app.html"; +static const char kWorkerFrontendURL[] = + "chrome-devtools://devtools/bundled/worker_app.html"; +static const char kJSFrontendURL[] = + "chrome-devtools://devtools/bundled/js_app.html"; +static const char kFallbackFrontendURL[] = + "chrome-devtools://devtools/bundled/inspector.html"; bool FindInspectedBrowserAndTabIndex( WebContents* inspected_web_contents, Browser** browser, int* tab) { @@ -523,6 +535,21 @@ void DevToolsWindow::OpenDevToolsWindow( void DevToolsWindow::OpenDevToolsWindow( scoped_refptr<content::DevToolsAgentHost> agent_host, Profile* profile) { + OpenDevToolsWindow(agent_host, profile, false /* use_bundled_frontend */); +} + +// static +void DevToolsWindow::OpenDevToolsWindowWithBundledFrontend( + scoped_refptr<content::DevToolsAgentHost> agent_host, + Profile* profile) { + OpenDevToolsWindow(agent_host, profile, true /* use_bundled_frontend */); +} + +// static +void DevToolsWindow::OpenDevToolsWindow( + scoped_refptr<content::DevToolsAgentHost> agent_host, + Profile* profile, + bool use_bundled_frontend) { if (!profile) profile = Profile::FromBrowserContext(agent_host->GetBrowserContext()); @@ -535,14 +562,8 @@ void DevToolsWindow::OpenDevToolsWindow( type == DevToolsAgentHost::kTypeSharedWorker; if (!agent_host->GetFrontendURL().empty()) { - FrontendType frontend_type = kFrontendRemote; - if (is_worker) { - frontend_type = kFrontendWorker; - } else if (type == "node") { - frontend_type = kFrontendV8; - } DevToolsWindow::OpenExternalFrontend(profile, agent_host->GetFrontendURL(), - agent_host, frontend_type); + agent_host, use_bundled_frontend); return; } @@ -605,18 +626,35 @@ void DevToolsWindow::OpenExternalFrontend( Profile* profile, const std::string& frontend_url, const scoped_refptr<content::DevToolsAgentHost>& agent_host, - FrontendType frontend_type) { + bool use_bundled_frontend) { DevToolsWindow* window = FindDevToolsWindow(agent_host.get()); - if (!window) { - window = Create(profile, nullptr, frontend_type, - DevToolsUI::GetProxyURL(frontend_url).spec(), false, - std::string(), std::string(), agent_host->IsAttached()); - if (!window) - return; - window->bindings_->AttachTo(agent_host); - window->close_on_detach_ = false; + if (window) { + window->ScheduleShow(DevToolsToggleAction::Show()); + return; } + std::string type = agent_host->GetType(); + if (type == "node") { + // Direct node targets will always open using ToT front-end. + window = Create(profile, nullptr, kFrontendV8, std::string(), false, + std::string(), std::string(), agent_host->IsAttached()); + } else { + bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker || + type == DevToolsAgentHost::kTypeSharedWorker; + + FrontendType frontend_type = + is_worker ? kFrontendRemoteWorker : kFrontendRemote; + std::string effective_frontend_url = + use_bundled_frontend ? kFallbackFrontendURL + : DevToolsUI::GetProxyURL(frontend_url).spec(); + window = + Create(profile, nullptr, frontend_type, effective_frontend_url, false, + std::string(), std::string(), agent_host->IsAttached()); + } + if (!window) + return; + window->bindings_->AttachTo(agent_host); + window->close_on_detach_ = false; window->ScheduleShow(DevToolsToggleAction::Show()); } @@ -695,17 +733,15 @@ void DevToolsWindow::InspectElement( WebContents::FromRenderFrameHost(inspected_frame_host); scoped_refptr<DevToolsAgentHost> agent( DevToolsAgentHost::GetOrCreateFor(web_contents)); + agent->InspectElement(inspected_frame_host, x, y); bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL; base::TimeTicks start_time = base::TimeTicks::Now(); // TODO(loislo): we should initiate DevTools window opening from within // renderer. Otherwise, we still can hit a race condition here. OpenDevToolsWindow(web_contents, DevToolsToggleAction::ShowElementsPanel()); DevToolsWindow* window = FindDevToolsWindow(agent.get()); - if (window) { - agent->InspectElement(window->bindings_, x, y); - if (should_measure_time) - window->inspect_element_start_time_ = start_time; - } + if (window && should_measure_time) + window->inspect_element_start_time_ = start_time; } // static @@ -953,6 +989,14 @@ DevToolsWindow* DevToolsWindow::Create( base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) return nullptr; +#if defined(OS_CHROMEOS) + // Do not create DevTools if it's disabled for primary profile. + const Profile* primary_profile = ProfileManager::GetPrimaryUserProfile(); + if (primary_profile && + primary_profile->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled)) + return nullptr; +#endif + if (inspected_web_contents) { // Check for a place to dock. Browser* browser = nullptr; @@ -989,37 +1033,50 @@ GURL DevToolsWindow::GetDevToolsURL(Profile* profile, bool can_dock, const std::string& panel, bool has_other_clients) { - std::string url(!frontend_url.empty() ? frontend_url - : chrome::kChromeUIDevToolsURL); - std::string url_string(url + - ((url.find("?") == std::string::npos) ? "?" : "&")); + std::string url; + +// Modules are always bundled in CrOS. +#if defined(OS_CHROMEOS) + std::string remote_base = "?"; +#else + std::string remote_base = + "?remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec(); +#endif + + // remoteFrontend is here for backwards compatibility only. + std::string remote_frontend = + frontend_url + ((frontend_url.find("?") == std::string::npos) + ? "?remoteFrontend=true" + : "&remoteFrontend=true"); switch (frontend_type) { - case kFrontendRemote: - url_string += "&remoteFrontend=true"; + case kFrontendDefault: + url = kDefaultFrontendURL + remote_base; + if (can_dock) + url += "&can_dock=true"; + if (panel.size()) + url += "&panel=" + panel; break; case kFrontendWorker: - url_string += "&isSharedWorker=true"; + url = kWorkerFrontendURL + remote_base; break; - case kFrontendNode: - url_string += "&nodeFrontend=true"; - // Fall through case kFrontendV8: - url_string += "&v8only=true"; + url = kJSFrontendURL + remote_base; break; - case kFrontendDefault: - default: + case kFrontendNode: + url = kNodeFrontendURL + remote_base; + break; + case kFrontendRemote: + url = remote_frontend; + break; + case kFrontendRemoteWorker: + // isSharedWorker is here for backwards compatibility only. + url = remote_frontend + "&isSharedWorker=true"; break; } - if (frontend_url.empty()) - url_string += "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec(); - if (can_dock) - url_string += "&can_dock=true"; - if (panel.size()) - url_string += "&panel=" + panel; if (has_other_clients) - url_string += "&hasOtherClients=true"; - return DevToolsUIBindings::SanitizeFrontendURL(GURL(url_string)); + url += "&hasOtherClients=true"; + return DevToolsUIBindings::SanitizeFrontendURL(GURL(url)); } // static @@ -1284,13 +1341,27 @@ void DevToolsWindow::SetIsDocked(bool dock_requested) { } void DevToolsWindow::OpenInNewTab(const std::string& url) { - content::OpenURLParams params(GURL(url), content::Referrer(), + GURL fixed_url(url); + WebContents* inspected_web_contents = GetInspectedWebContents(); + int child_id = content::ChildProcessHost::kInvalidUniqueID; + if (inspected_web_contents) { + content::RenderViewHost* render_view_host = + inspected_web_contents->GetRenderViewHost(); + if (render_view_host) + child_id = render_view_host->GetProcess()->GetID(); + } + // Use about:blank instead of an empty GURL. The browser treats an empty GURL + // as navigating to the home page, which may be privileged (chrome://newtab/). + if (!content::ChildProcessSecurityPolicy::GetInstance()->CanRequestURL( + child_id, fixed_url)) + fixed_url = GURL(url::kAboutBlankURL); + + content::OpenURLParams params(fixed_url, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false); - WebContents* inspected_web_contents = GetInspectedWebContents(); if (!inspected_web_contents || !inspected_web_contents->OpenURL(params)) { chrome::ScopedTabbedBrowserDisplayer displayer(profile_); - chrome::AddSelectedTabWithURL(displayer.browser(), GURL(url), + chrome::AddSelectedTabWithURL(displayer.browser(), fixed_url, ui::PAGE_TRANSITION_LINK); } } @@ -1444,7 +1515,7 @@ void DevToolsWindow::CreateDevToolsBrowser() { if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) { DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement); base::DictionaryValue* wp_prefs = update.Get(); - auto dev_tools_defaults = base::MakeUnique<base::DictionaryValue>(); + auto dev_tools_defaults = std::make_unique<base::DictionaryValue>(); dev_tools_defaults->SetInteger("left", 100); dev_tools_defaults->SetInteger("top", 100); dev_tools_defaults->SetInteger("right", 740); diff --git a/chromium/chrome/browser/devtools/devtools_window.h b/chromium/chrome/browser/devtools/devtools_window.h index 9aca0915dc1..928fea3dfab 100644 --- a/chromium/chrome/browser/devtools/devtools_window.h +++ b/chromium/chrome/browser/devtools/devtools_window.h @@ -84,6 +84,10 @@ class DevToolsWindow : public DevToolsUIBindings::Delegate, static void OpenDevToolsWindow( scoped_refptr<content::DevToolsAgentHost> host, Profile* profile); + // Similar to previous one, but forces the bundled frontend to be used. + static void OpenDevToolsWindowWithBundledFrontend( + scoped_refptr<content::DevToolsAgentHost> host, + Profile* profile); // Perform specified action for current WebContents inside a |browser|. // This may close currently open DevTools window. @@ -241,10 +245,11 @@ class DevToolsWindow : public DevToolsUIBindings::Delegate, enum FrontendType { kFrontendDefault, - kFrontendRemote, kFrontendWorker, kFrontendV8, - kFrontendNode + kFrontendNode, + kFrontendRemote, + kFrontendRemoteWorker, }; DevToolsWindow(FrontendType frontend_type, @@ -259,7 +264,10 @@ class DevToolsWindow : public DevToolsUIBindings::Delegate, Profile* profile, const std::string& frontend_uri, const scoped_refptr<content::DevToolsAgentHost>& agent_host, - FrontendType frontend_type); + bool use_bundled_frontend); + static void OpenDevToolsWindow(scoped_refptr<content::DevToolsAgentHost> host, + Profile* profile, + bool use_bundled_frontend); static DevToolsWindow* Create(Profile* profile, content::WebContents* inspected_web_contents, diff --git a/chromium/chrome/browser/devtools/global_confirm_info_bar_browsertest.cc b/chromium/chrome/browser/devtools/global_confirm_info_bar_browsertest.cc index 3860fef06c7..6f37575942e 100644 --- a/chromium/chrome/browser/devtools/global_confirm_info_bar_browsertest.cc +++ b/chromium/chrome/browser/devtools/global_confirm_info_bar_browsertest.cc @@ -84,7 +84,7 @@ IN_PROC_BROWSER_TEST_F(GlobalConfirmInfoBarTest, MultipleTabs) { for (int i = 0; i < tab_strip_model->count(); i++) EXPECT_EQ(0u, GetInfoBarServiceFromTabIndex(i)->infobar_count()); - auto delegate = base::MakeUnique<TestConfirmInfoBarDelegate>(); + auto delegate = std::make_unique<TestConfirmInfoBarDelegate>(); TestConfirmInfoBarDelegate* delegate_ptr = delegate.get(); base::WeakPtr<GlobalConfirmInfoBar> global_confirm_info_bar = @@ -116,7 +116,7 @@ IN_PROC_BROWSER_TEST_F(GlobalConfirmInfoBarTest, UserInteraction) { for (int i = 0; i < tab_strip_model->count(); i++) EXPECT_EQ(0u, GetInfoBarServiceFromTabIndex(i)->infobar_count()); - auto delegate = base::MakeUnique<TestConfirmInfoBarDelegate>(); + auto delegate = std::make_unique<TestConfirmInfoBarDelegate>(); TestConfirmInfoBarDelegate* delegate_ptr = delegate.get(); base::WeakPtr<GlobalConfirmInfoBar> global_confirm_info_bar = diff --git a/chromium/chrome/browser/devtools/protocol_string.cc b/chromium/chrome/browser/devtools/protocol_string.cc index 822108467bd..b48641a7e2b 100644 --- a/chromium/chrome/browser/devtools/protocol_string.cc +++ b/chromium/chrome/browser/devtools/protocol_string.cc @@ -74,7 +74,7 @@ std::unique_ptr<base::Value> toBaseValue(protocol::Value* value, int depth) { if (!value || !depth) return nullptr; if (value->type() == protocol::Value::TypeNull) - return base::MakeUnique<base::Value>(); + return std::make_unique<base::Value>(); if (value->type() == protocol::Value::TypeBoolean) { bool inner; value->asBoolean(&inner); diff --git a/chromium/chrome/browser/devtools/remote_debugging_server.cc b/chromium/chrome/browser/devtools/remote_debugging_server.cc index e07fe4cb62a..3650e41ce99 100644 --- a/chromium/chrome/browser/devtools/remote_debugging_server.cc +++ b/chromium/chrome/browser/devtools/remote_debugging_server.cc @@ -19,6 +19,7 @@ #include "chrome/common/chrome_paths.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/devtools_socket_factory.h" +#include "content/public/common/content_switches.h" #include "net/base/net_errors.h" #include "net/log/net_log_source.h" #include "net/socket/tcp_server_socket.h" @@ -36,10 +37,8 @@ const int kBackLog = 10; class TCPServerSocketFactory : public content::DevToolsSocketFactory { public: - TCPServerSocketFactory(const std::string& address, uint16_t port) - : address_(address), - port_(port), - last_tethering_port_(kMinTetheringPort) {} + explicit TCPServerSocketFactory(uint16_t port) + : port_(port), last_tethering_port_(kMinTetheringPort) {} private: std::unique_ptr<net::ServerSocket> CreateLocalHostServerSocket(int port) { @@ -57,11 +56,7 @@ class TCPServerSocketFactory std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { std::unique_ptr<net::ServerSocket> socket( new net::TCPServerSocket(nullptr, net::NetLogSource())); - if (address_.empty()) - return CreateLocalHostServerSocket(port_); - if (socket->ListenWithAddressAndPort(address_, port_, kBackLog) == net::OK) - return socket; - return std::unique_ptr<net::ServerSocket>(); + return CreateLocalHostServerSocket(port_); } std::unique_ptr<net::ServerSocket> CreateForTethering( @@ -90,8 +85,20 @@ void RemoteDebuggingServer::EnableTetheringForDebug() { g_tethering_enabled.Get() = true; } -RemoteDebuggingServer::RemoteDebuggingServer(const std::string& ip, - uint16_t port) { +RemoteDebuggingServer::RemoteDebuggingServer() { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kRemoteDebuggingPipe)) { + content::DevToolsAgentHost::StartRemoteDebuggingPipeHandler(); + return; + } + + std::string port_str = + command_line.GetSwitchValueASCII(::switches::kRemoteDebuggingPort); + int port; + if (!base::StringToInt(port_str, &port) || port < 0 || port >= 65535) + return; + base::FilePath output_dir; if (!port) { // The client requested an ephemeral port. Must write the selected @@ -107,8 +114,8 @@ RemoteDebuggingServer::RemoteDebuggingServer(const std::string& ip, #endif content::DevToolsAgentHost::StartRemoteDebuggingServer( - base::MakeUnique<TCPServerSocketFactory>(ip, port), std::string(), - output_dir, debug_frontend_dir); + std::make_unique<TCPServerSocketFactory>(port), output_dir, + debug_frontend_dir); } RemoteDebuggingServer::~RemoteDebuggingServer() { @@ -116,4 +123,5 @@ RemoteDebuggingServer::~RemoteDebuggingServer() { // accesses it during shutdown. DCHECK(g_browser_process->profile_manager()); content::DevToolsAgentHost::StopRemoteDebuggingServer(); + content::DevToolsAgentHost::StopRemoteDebuggingPipeHandler(); } diff --git a/chromium/chrome/browser/devtools/remote_debugging_server.h b/chromium/chrome/browser/devtools/remote_debugging_server.h index e2ba3e78440..0f5a2f80621 100644 --- a/chromium/chrome/browser/devtools/remote_debugging_server.h +++ b/chromium/chrome/browser/devtools/remote_debugging_server.h @@ -16,9 +16,7 @@ class RemoteDebuggingServer { public: static void EnableTetheringForDebug(); - // Bind remote debugging service to the given |ip| and |port|. - // Empty |ip| stands for 127.0.0.1 or ::1. - RemoteDebuggingServer(const std::string& ip, uint16_t port); + RemoteDebuggingServer(); virtual ~RemoteDebuggingServer(); private: diff --git a/chromium/chrome/browser/devtools/serialize_host_descriptions.cc b/chromium/chrome/browser/devtools/serialize_host_descriptions.cc index 26aa0206369..e058819935b 100644 --- a/chromium/chrome/browser/devtools/serialize_host_descriptions.cc +++ b/chromium/chrome/browser/devtools/serialize_host_descriptions.cc @@ -24,7 +24,7 @@ base::DictionaryValue Serialize( base::DictionaryValue* root, const std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>& children) { - auto children_list = base::MakeUnique<base::ListValue>(); + auto children_list = std::make_unique<base::ListValue>(); auto child_it = children.find(root); if (child_it != children.end()) { for (base::DictionaryValue* child : child_it->second) { diff --git a/chromium/chrome/browser/engagement/BUILD.gn b/chromium/chrome/browser/engagement/BUILD.gn index 3bca05363d5..9bd92b61df9 100644 --- a/chromium/chrome/browser/engagement/BUILD.gn +++ b/chromium/chrome/browser/engagement/BUILD.gn @@ -10,7 +10,7 @@ mojom("mojo_bindings") { ] public_deps = [ - "//url/mojo:url_mojom_gurl", + "//url/mojom:url_mojom_gurl", ] # TODO(crbug.com/714018): Convert the implementation to use OnceCallback. diff --git a/chromium/chrome/browser/engagement/site_engagement_details.mojom b/chromium/chrome/browser/engagement/site_engagement_details.mojom index ec705aeae4a..42549b019ea 100644 --- a/chromium/chrome/browser/engagement/site_engagement_details.mojom +++ b/chromium/chrome/browser/engagement/site_engagement_details.mojom @@ -4,7 +4,7 @@ module mojom; -import "url/mojo/url.mojom"; +import "url/mojom/url.mojom"; struct SiteEngagementDetails { url.mojom.Url origin; diff --git a/chromium/chrome/browser/extensions/BUILD.gn b/chromium/chrome/browser/extensions/BUILD.gn index 6e19b623bae..cc5ef3e33eb 100644 --- a/chromium/chrome/browser/extensions/BUILD.gn +++ b/chromium/chrome/browser/extensions/BUILD.gn @@ -179,10 +179,6 @@ static_library("extensions") { "api/easy_unlock_private/easy_unlock_private_connection_manager.h", "api/easy_unlock_private/easy_unlock_private_crypto_delegate.h", "api/easy_unlock_private/easy_unlock_private_crypto_delegate_chromeos.cc", - "api/experience_sampling_private/experience_sampling.cc", - "api/experience_sampling_private/experience_sampling.h", - "api/experience_sampling_private/experience_sampling_private_api.cc", - "api/experience_sampling_private/experience_sampling_private_api.h", "api/extension_action/extension_action_api.cc", "api/extension_action/extension_action_api.h", "api/extension_action/extension_page_actions_api_constants.cc", @@ -339,8 +335,6 @@ static_library("extensions") { "api/resources_private/resources_private_api.h", "api/runtime/chrome_runtime_api_delegate.cc", "api/runtime/chrome_runtime_api_delegate.h", - "api/screenlock_private/screenlock_private_api.cc", - "api/screenlock_private/screenlock_private_api.h", "api/sessions/session_id.cc", "api/sessions/session_id.h", "api/sessions/sessions_api.cc", @@ -462,7 +456,6 @@ static_library("extensions") { "bookmark_app_helper.h", "bookmark_app_navigation_throttle.cc", "bookmark_app_navigation_throttle.h", - "browser_action_test_util.h", "browser_context_keyed_service_factories.cc", "browser_context_keyed_service_factories.h", "browser_extension_window_controller.cc", @@ -504,6 +497,8 @@ static_library("extensions") { "chrome_process_manager_delegate.h", "chrome_url_request_util.cc", "chrome_url_request_util.h", + "chrome_zipfile_installer.cc", + "chrome_zipfile_installer.h", "clipboard_extension_helper_chromeos.cc", "clipboard_extension_helper_chromeos.h", "component_extensions_whitelist/whitelist.cc", @@ -558,10 +553,6 @@ static_library("extensions") { "extension_context_menu_model.h", "extension_cookie_notifier.cc", "extension_cookie_notifier.h", - "extension_creator.cc", - "extension_creator.h", - "extension_creator_filter.cc", - "extension_creator_filter.h", "extension_disabled_ui.cc", "extension_disabled_ui.h", "extension_error_controller.cc", @@ -775,8 +766,6 @@ static_library("extensions") { "window_controller_list.cc", "window_controller_list.h", "window_controller_list_observer.h", - "zipfile_installer.cc", - "zipfile_installer.h", ] configs += [ @@ -815,7 +804,7 @@ static_library("extensions") { "//chrome/common/extensions/api:extensions_features", "//chrome/common/safe_browsing:proto", "//chrome/services/media_gallery_util/public/cpp", - "//chrome/services/removable_storage_writer/public/interfaces", + "//chrome/services/removable_storage_writer/public/mojom", "//components/app_modal", "//components/autofill/content/browser", "//components/bookmarks/browser", @@ -825,11 +814,11 @@ static_library("extensions") { "//components/bubble", "//components/content_settings/core/browser", "//components/crx_file", - "//components/crx_file:crx_creator", "//components/cryptauth", "//components/data_reduction_proxy/core/browser", "//components/dom_distiller/core", "//components/download/content/public", + "//components/download/public/common:public", "//components/drive", "//components/favicon/content", "//components/feedback", @@ -838,8 +827,9 @@ static_library("extensions") { "//components/history/core/browser", "//components/infobars/core", "//components/keyed_service/content", - "//components/language/core/browser:browser", - "//components/nacl/common:features", + "//components/language/core/browser", + "//components/language/core/common", + "//components/nacl/common:buildflags", "//components/navigation_interception", "//components/net_log", "//components/omnibox/browser", @@ -892,12 +882,13 @@ static_library("extensions") { "//ppapi/features", "//printing/features", "//rlz/features", + "//services/audio/public/cpp", "//services/data_decoder/public/cpp", - "//services/device/public/interfaces", - "//services/identity/public/interfaces", - "//services/network/public/interfaces", + "//services/device/public/mojom", + "//services/identity/public/mojom", + "//services/network/public/mojom", "//services/service_manager/public/cpp", - "//services/service_manager/public/interfaces", + "//services/service_manager/public/mojom", "//skia", "//sql", "//storage/browser", @@ -909,9 +900,8 @@ static_library("extensions") { "//third_party/leveldatabase", "//third_party/libaddressinput:util", "//third_party/re2", - "//third_party/webrtc/modules/desktop_capture", "//third_party/zlib/google:zip", - "//ui/accessibility:ax_gen", + "//ui/accessibility:ax_enums_mojo", "//ui/base", "//ui/base/ime", "//ui/display/manager", @@ -957,6 +947,8 @@ static_library("extensions") { "api/platform_keys/platform_keys_api.h", "api/platform_keys/verify_trust_api.cc", "api/platform_keys/verify_trust_api.h", + "api/screenlock_private/screenlock_private_api.cc", + "api/screenlock_private/screenlock_private_api.h", "api/settings_private/chromeos_resolve_time_zone_by_geolocation_method_short.cc", "api/settings_private/chromeos_resolve_time_zone_by_geolocation_method_short.h", "api/settings_private/chromeos_resolve_time_zone_by_geolocation_on_off.cc", @@ -1092,7 +1084,7 @@ static_library("extensions") { if (is_win) { deps += [ - "//chrome/services/wifi_util_win/public/interfaces", + "//chrome/services/wifi_util_win/public/mojom", "//third_party/iaccessible2", "//third_party/isimpledom", ] @@ -1119,6 +1111,7 @@ static_library("extensions") { "api/cloud_print_private/cloud_print_private_api.cc", "api/cloud_print_private/cloud_print_private_api.h", ] + deps += [ "//chrome/common:service_process_mojom" ] } if (enable_service_discovery) { diff --git a/chromium/chrome/browser/extensions/api/DEPS b/chromium/chrome/browser/extensions/api/DEPS index fea8d34597f..e6dcb249500 100644 --- a/chromium/chrome/browser/extensions/api/DEPS +++ b/chromium/chrome/browser/extensions/api/DEPS @@ -8,7 +8,7 @@ include_rules = [ # Enable remote assistance on Chrome OS "+remoting/base", "+remoting/host", - "+services/network/public/interfaces", + "+services/network", ] specific_include_rules = { diff --git a/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc b/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc index 81b27277daa..12183657a08 100644 --- a/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc +++ b/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc @@ -120,7 +120,13 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, Actions) { << message_; } -IN_PROC_BROWSER_TEST_F(AutomationApiTest, Location) { +// TODO(https://crbug.com/622387): Disabled due to flakiness. +#if defined(OS_CHROMEOS) && defined(NDEBUG) +#define MAYBE_Location DISABLED_Location +#else +#define MAYBE_Location Location +#endif +IN_PROC_BROWSER_TEST_F(AutomationApiTest, MAYBE_Location) { StartEmbeddedTestServer(); ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html")) << message_; @@ -174,14 +180,14 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationHostsPermissions) { } #if defined(USE_AURA) -// Flaky, see http://crbug.com/637525 +// TODO(https://crbug.com/754870): Disabled due to flakiness. IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Desktop) { ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html")) << message_; } #if defined(OS_CHROMEOS) -// TODO(crbug.com/615908): Flaky on CrOS sanitizers. +// TODO(https://crbug.com/754870): Flaky on CrOS sanitizers. IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_DesktopInitialFocus) { ASSERT_TRUE( RunExtensionSubtest("automation/tests/desktop", "initial_focus.html")) @@ -194,8 +200,7 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopFocusWeb) { << message_; } -// Flaky, see https://crbug.com/724923. -IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_DesktopFocusIframe) { +IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopFocusIframe) { StartEmbeddedTestServer(); ASSERT_TRUE( RunExtensionSubtest("automation/tests/desktop", "focus_iframe.html")) @@ -247,7 +252,7 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopHitTest) { << message_; } -// Flaky, see http://crbug.com/435449 +// TODO(https://crbug.com/754870): flaky. IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_DesktopLoadTabs) { ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html")) << message_; @@ -280,13 +285,19 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) { << message_; } -// TODO(crbug.com/725420) Flaky IN_PROC_BROWSER_TEST_F(AutomationApiTest, Attributes) { StartEmbeddedTestServer(); ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "attributes.html")) << message_; } +IN_PROC_BROWSER_TEST_F(AutomationApiTest, ReverseRelations) { + StartEmbeddedTestServer(); + ASSERT_TRUE( + RunExtensionSubtest("automation/tests/tabs", "reverse_relations.html")) + << message_; +} + IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) { StartEmbeddedTestServer(); ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html")) diff --git a/chromium/chrome/browser/extensions/api/automation_internal/automation_event_router.cc b/chromium/chrome/browser/extensions/api/automation_internal/automation_event_router.cc index b54484f9b58..b87d67a8227 100644 --- a/chromium/chrome/browser/extensions/api/automation_internal/automation_event_router.cc +++ b/chromium/chrome/browser/extensions/api/automation_internal/automation_event_router.cc @@ -23,7 +23,7 @@ #include "extensions/browser/event_router.h" #include "extensions/common/extension.h" #include "ui/accessibility/ax_action_data.h" -#include "ui/accessibility/ax_enums.h" +#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc b/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc index c468f587436..e8309a03a77 100644 --- a/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc +++ b/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc @@ -15,6 +15,7 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" +#include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -37,6 +38,7 @@ #include "extensions/common/extension_messages.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/accessibility/ax_action_data.h" +#include "ui/accessibility/ax_enum_util.h" #include "ui/accessibility/ax_host_delegate.h" #include "ui/accessibility/ax_tree_id_registry.h" @@ -208,7 +210,7 @@ class AutomationWebContentsObserver std::vector<content::AXEventNotificationDetails> details; content::AXEventNotificationDetails detail; detail.ax_tree_id = id.first->GetAXTreeID(); - detail.event_type = ui::AX_EVENT_MEDIA_STARTED_PLAYING; + detail.event_type = ax::mojom::Event::kMediaStartedPlaying; details.push_back(detail); AccessibilityEventReceived(details); } @@ -220,7 +222,7 @@ class AutomationWebContentsObserver std::vector<content::AXEventNotificationDetails> details; content::AXEventNotificationDetails detail; detail.ax_tree_id = id.first->GetAXTreeID(); - detail.event_type = ui::AX_EVENT_MEDIA_STOPPED_PLAYING; + detail.event_type = ax::mojom::Event::kMediaStoppedPlaying; details.push_back(detail); AccessibilityEventReceived(details); } @@ -239,7 +241,7 @@ class AutomationWebContentsObserver content::AXEventNotificationDetails detail; detail.ax_tree_id = rfh->GetAXTreeID(); - detail.event_type = ui::AX_EVENT_MEDIA_STARTED_PLAYING; + detail.event_type = ax::mojom::Event::kMediaStartedPlaying; details.push_back(detail); AccessibilityEventReceived(details); } @@ -261,18 +263,19 @@ AutomationInternalEnableTabFunction::Run() { content::WebContents* contents = NULL; if (params->args.tab_id.get()) { int tab_id = *params->args.tab_id; - if (!ExtensionTabUtil::GetTabById(tab_id, - GetProfile(), - include_incognito(), - NULL, /* browser out param*/ - NULL, /* tab_strip out param */ - &contents, - NULL /* tab_index out param */)) { + if (!ExtensionTabUtil::GetTabById( + tab_id, browser_context(), include_incognito(), + NULL, /* browser out param*/ + NULL, /* tab_strip out param */ + &contents, NULL /* tab_index out param */)) { return RespondNow( Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id))); } } else { - contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents(); + contents = ChromeExtensionFunctionDetails(this) + .GetCurrentBrowser() + ->tab_strip_model() + ->GetActiveWebContents(); if (!contents) return RespondNow(Error("No active tab")); } @@ -336,17 +339,17 @@ AutomationInternalPerformActionFunction::ConvertToAXActionData( action->request_id = request_id ? *request_id : -1; switch (params->args.action_type) { case api::automation_internal::ACTION_TYPE_DODEFAULT: - action->action = ui::AX_ACTION_DO_DEFAULT; + action->action = ax::mojom::Action::kDoDefault; break; case api::automation_internal::ACTION_TYPE_FOCUS: - action->action = ui::AX_ACTION_FOCUS; + action->action = ax::mojom::Action::kFocus; break; case api::automation_internal::ACTION_TYPE_GETIMAGEDATA: { api::automation_internal::GetImageDataParams get_image_data_params; EXTENSION_FUNCTION_VALIDATE( api::automation_internal::GetImageDataParams::Populate( params->opt_args.additional_properties, &get_image_data_params)); - action->action = ui::AX_ACTION_GET_IMAGE_DATA; + action->action = ax::mojom::Action::kGetImageData; action->target_rect = gfx::Rect(0, 0, get_image_data_params.max_width, get_image_data_params.max_height); break; @@ -356,34 +359,34 @@ AutomationInternalPerformActionFunction::ConvertToAXActionData( EXTENSION_FUNCTION_VALIDATE( api::automation_internal::HitTestParams::Populate( params->opt_args.additional_properties, &hit_test_params)); - action->action = ui::AX_ACTION_HIT_TEST; + action->action = ax::mojom::Action::kHitTest; action->target_point = gfx::Point(hit_test_params.x, hit_test_params.y); action->hit_test_event_to_fire = - ui::ParseAXEvent(hit_test_params.event_to_fire); - if (action->hit_test_event_to_fire == ui::AX_EVENT_NONE) + ui::ParseEvent(hit_test_params.event_to_fire.c_str()); + if (action->hit_test_event_to_fire == ax::mojom::Event::kNone) return RespondNow(NoArguments()); break; } case api::automation_internal::ACTION_TYPE_MAKEVISIBLE: - action->action = ui::AX_ACTION_SCROLL_TO_MAKE_VISIBLE; + action->action = ax::mojom::Action::kScrollToMakeVisible; break; case api::automation_internal::ACTION_TYPE_SCROLLBACKWARD: - action->action = ui::AX_ACTION_SCROLL_BACKWARD; + action->action = ax::mojom::Action::kScrollBackward; break; case api::automation_internal::ACTION_TYPE_SCROLLFORWARD: - action->action = ui::AX_ACTION_SCROLL_FORWARD; + action->action = ax::mojom::Action::kScrollForward; break; case api::automation_internal::ACTION_TYPE_SCROLLUP: - action->action = ui::AX_ACTION_SCROLL_UP; + action->action = ax::mojom::Action::kScrollUp; break; case api::automation_internal::ACTION_TYPE_SCROLLDOWN: - action->action = ui::AX_ACTION_SCROLL_DOWN; + action->action = ax::mojom::Action::kScrollDown; break; case api::automation_internal::ACTION_TYPE_SCROLLLEFT: - action->action = ui::AX_ACTION_SCROLL_LEFT; + action->action = ax::mojom::Action::kScrollLeft; break; case api::automation_internal::ACTION_TYPE_SCROLLRIGHT: - action->action = ui::AX_ACTION_SCROLL_RIGHT; + action->action = ax::mojom::Action::kScrollRight; break; case api::automation_internal::ACTION_TYPE_SETSELECTION: { api::automation_internal::SetSelectionParams selection_params; @@ -394,17 +397,17 @@ AutomationInternalPerformActionFunction::ConvertToAXActionData( action->anchor_offset = selection_params.anchor_offset; action->focus_node_id = selection_params.focus_node_id; action->focus_offset = selection_params.focus_offset; - action->action = ui::AX_ACTION_SET_SELECTION; + action->action = ax::mojom::Action::kSetSelection; break; } case api::automation_internal::ACTION_TYPE_SHOWCONTEXTMENU: { - action->action = ui::AX_ACTION_SHOW_CONTEXT_MENU; + action->action = ax::mojom::Action::kShowContextMenu; break; } case api::automation_internal:: ACTION_TYPE_SETSEQUENTIALFOCUSNAVIGATIONSTARTINGPOINT: { action->action = - ui::AX_ACTION_SET_SEQUENTIAL_FOCUS_NAVIGATION_STARTING_POINT; + ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint; break; } case api::automation_internal::ACTION_TYPE_CUSTOMACTION: { @@ -414,7 +417,7 @@ AutomationInternalPerformActionFunction::ConvertToAXActionData( api::automation_internal::PerformCustomActionParams::Populate( params->opt_args.additional_properties, &perform_custom_action_params)); - action->action = ui::AX_ACTION_CUSTOM_ACTION; + action->action = ax::mojom::Action::kCustomAction; action->custom_action_id = perform_custom_action_params.custom_action_id; break; } diff --git a/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.h b/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.h index ffce688fc36..2971910dca3 100644 --- a/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.h +++ b/chromium/chrome/browser/extensions/api/automation_internal/automation_internal_api.h @@ -7,9 +7,9 @@ #include <string> -#include "chrome/browser/extensions/chrome_extension_function.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" +#include "extensions/browser/extension_function.h" namespace extensions { @@ -29,8 +29,7 @@ struct AXActionData; namespace extensions { // Implementation of the chrome.automation API. -class AutomationInternalEnableTabFunction - : public ChromeUIThreadExtensionFunction { +class AutomationInternalEnableTabFunction : public UIThreadExtensionFunction { DECLARE_EXTENSION_FUNCTION("automationInternal.enableTab", AUTOMATIONINTERNAL_ENABLETAB) protected: diff --git a/chromium/chrome/browser/extensions/api/autotest_private/DEPS b/chromium/chrome/browser/extensions/api/autotest_private/DEPS new file mode 100644 index 00000000000..b21e182ab37 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/autotest_private/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/message_center", +] diff --git a/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc b/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc index 7a2179a278f..8bdc5a72567 100644 --- a/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc +++ b/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc @@ -37,7 +37,7 @@ #include "chromeos/printing/printer_configuration.h" #include "components/user_manager/user_manager.h" #include "ui/message_center/message_center.h" -#include "ui/message_center/notification.h" +#include "ui/message_center/public/cpp/notification.h" #endif namespace extensions { @@ -396,6 +396,10 @@ AutotestPrivateGetVisibleNotificationsFunction::Run() { DVLOG(1) << "AutotestPrivateGetVisibleNotificationsFunction"; std::unique_ptr<base::ListValue> values(new base::ListValue); #if defined(OS_CHROMEOS) + // TODO(estade): we can't rely on the message center being available in the + // browser process (in mash). Make autotests that use it fail loudly. See + // crbug.com/804570 + CHECK(message_center::MessageCenter::Get()); for (auto* notification : message_center::MessageCenter::Get()->GetVisibleNotifications()) { auto result = std::make_unique<base::DictionaryValue>(); diff --git a/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.h b/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.h index 7f55a387113..7eb44055070 100644 --- a/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.h +++ b/chromium/chrome/browser/extensions/api/autotest_private/autotest_private_api.h @@ -10,7 +10,7 @@ #include "base/compiler_specific.h" #include "chrome/browser/extensions/chrome_extension_function.h" #include "extensions/browser/browser_context_keyed_api_factory.h" -#include "ui/message_center/notification_types.h" +#include "ui/message_center/public/cpp/notification_types.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/printing/cups_printers_manager.h" diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc index 8717bf94ac3..c4cdaf3aa3b 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc @@ -21,13 +21,7 @@ using bookmarks::BookmarkModel; -// Flaky on Windows and Linux. http://crbug.com/383452 -#if defined(OS_WIN) || defined(OS_LINUX) -#define MAYBE_Bookmarks DISABLED_Bookmarks -#else -#define MAYBE_Bookmarks Bookmarks -#endif -IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_Bookmarks) { +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Bookmarks) { // Add test managed bookmarks to verify that the bookmarks API can read them // and can't modify them. Profile* profile = browser()->profile(); diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc index 397e6f924bf..1e93c03bfd7 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc @@ -555,7 +555,7 @@ bool BookmarksSearchFunction::RunOnReady() { return true; } -bool BookmarksRemoveFunction::RunOnReady() { +bool BookmarksRemoveFunctionBase::RunOnReady() { if (!EditBookmarksEnabled()) return false; @@ -567,15 +567,21 @@ bool BookmarksRemoveFunction::RunOnReady() { if (!GetBookmarkIdAsInt64(params->id, &id)) return false; - bool recursive = false; - if (name() == BookmarksRemoveTreeFunction::function_name()) - recursive = true; - BookmarkModel* model = GetBookmarkModel(); ManagedBookmarkService* managed = GetManagedBookmarkService(); - if (!bookmark_api_helpers::RemoveNode(model, managed, id, recursive, &error_)) + if (!bookmark_api_helpers::RemoveNode(model, managed, id, is_recursive(), + &error_)) { return false; + } + + return true; +} + +bool BookmarksRemoveFunction::is_recursive() const { + return false; +} +bool BookmarksRemoveTreeFunction::is_recursive() const { return true; } diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h index f67548f71c8..610c6410c80 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h @@ -244,23 +244,36 @@ class BookmarksSearchFunction : public BookmarksFunction { bool RunOnReady() override; }; -class BookmarksRemoveFunction : public BookmarksFunction { +class BookmarksRemoveFunctionBase : public BookmarksFunction { + protected: + ~BookmarksRemoveFunctionBase() override {} + + virtual bool is_recursive() const = 0; + + // BookmarksFunction: + bool RunOnReady() override; +}; + +class BookmarksRemoveFunction : public BookmarksRemoveFunctionBase { public: - DECLARE_EXTENSION_FUNCTION("bookmarks.remove", BOOKMARKS_REMOVE) + DECLARE_EXTENSION_FUNCTION("bookmarks.remove", BOOKMARKS_REMOVE); protected: ~BookmarksRemoveFunction() override {} - // BookmarksFunction: - bool RunOnReady() override; + // BookmarksRemoveFunctionBase: + bool is_recursive() const override; }; -class BookmarksRemoveTreeFunction : public BookmarksRemoveFunction { +class BookmarksRemoveTreeFunction : public BookmarksRemoveFunctionBase { public: DECLARE_EXTENSION_FUNCTION("bookmarks.removeTree", BOOKMARKS_REMOVETREE) protected: ~BookmarksRemoveTreeFunction() override {} + + // BookmarksRemoveFunctionBase: + bool is_recursive() const override; }; class BookmarksCreateFunction : public BookmarksFunction { diff --git a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc index c16ed76a1da..244be025170 100644 --- a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc +++ b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc @@ -136,11 +136,8 @@ bool BrowsingDataSettingsFunction::isDataTypeSelected( ExtensionFunction::ResponseAction BrowsingDataSettingsFunction::Run() { prefs_ = Profile::FromBrowserContext(browser_context())->GetPrefs(); - ClearBrowsingDataTab tab = - base::FeatureList::IsEnabled(features::kTabsInCbd) - ? static_cast<ClearBrowsingDataTab>(prefs_->GetInteger( - browsing_data::prefs::kLastClearBrowsingDataTab)) - : ClearBrowsingDataTab::ADVANCED; + ClearBrowsingDataTab tab = static_cast<ClearBrowsingDataTab>( + prefs_->GetInteger(browsing_data::prefs::kLastClearBrowsingDataTab)); // Fill origin types. // The "cookies" and "hosted apps" UI checkboxes both map to diff --git a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_test.cc b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_test.cc index d777b98d233..f0e724e64a3 100644 --- a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_test.cc +++ b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_test.cc @@ -9,7 +9,6 @@ #include "base/memory/ref_counted.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" -#include "base/test/scoped_feature_list.h" #include "base/values.h" #include "chrome/browser/browsing_data/browsing_data_helper.h" #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h" @@ -17,7 +16,6 @@ #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" -#include "chrome/common/chrome_features.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "components/browsing_data/core/browsing_data_utils.h" @@ -62,7 +60,6 @@ class ExtensionBrowsingDataTest : public InProcessBrowserTest { protected: void SetUp() override { - feature_list_.InitAndEnableFeature(features::kTabsInCbd); InProcessBrowserTest::SetUp(); } @@ -297,7 +294,6 @@ class ExtensionBrowsingDataTest : public InProcessBrowserTest { } private: - base::test::ScopedFeatureList feature_list_; // Cached pointer to BrowsingDataRemover for access to testing methods. content::BrowsingDataRemover* remover_; }; diff --git a/chromium/chrome/browser/extensions/api/cast_streaming/performance_test.cc b/chromium/chrome/browser/extensions/api/cast_streaming/performance_test.cc index 8a53d8a19aa..3a6d2f4f736 100644 --- a/chromium/chrome/browser/extensions/api/cast_streaming/performance_test.cc +++ b/chromium/chrome/browser/extensions/api/cast_streaming/performance_test.cc @@ -68,13 +68,9 @@ constexpr size_t kTrimEvents = 24; // 1 sec at 24fps, or 0.4 sec at 60 fps. constexpr size_t kMinDataPoints = 100; // 1 sec of audio, or ~5 sec at 24fps. enum TestFlags { - // TODO(miu): Remove kUseGpu (since the GPU is required), and maybe - // kDisableVsync. http://crbug.com/567848 kUseGpu = 1 << 0, // Only execute test if --enable-gpu was given // on the command line. This is required for // tests that run on GPU. - kDisableVsync = 1 << 1, // Do not limit framerate to vertical refresh. - // when on GPU, nor to 60hz when not on GPU. kSmallWindow = 1 << 2, // Window size: 1 = 800x600, 0 = 2000x1000 k24fps = 1 << 3, // Use 24 fps video. k30fps = 1 << 4, // Use 30 fps video. @@ -368,8 +364,6 @@ class CastV2PerformanceTest std::string suffix; if (HasFlag(kUseGpu)) suffix += "_gpu"; - if (HasFlag(kDisableVsync)) - suffix += "_novsync"; if (HasFlag(kSmallWindow)) suffix += "_small"; if (HasFlag(k24fps)) @@ -406,6 +400,8 @@ class CastV2PerformanceTest void SetUp() override { EnablePixelOutput(); + if (!HasFlag(kUseGpu)) + UseSoftwareCompositing(); ExtensionApiTest::SetUp(); } @@ -425,12 +421,10 @@ class CastV2PerformanceTest if (!HasFlag(kUseGpu)) command_line->AppendSwitch(switches::kDisableGpu); - if (HasFlag(kDisableVsync)) - command_line->AppendSwitch(switches::kDisableGpuVsync); - command_line->AppendSwitchASCII( extensions::switches::kWhitelistedExtensionID, kExtensionId); + ExtensionApiTest::SetUpCommandLine(command_line); } @@ -721,7 +715,6 @@ INSTANTIATE_TEST_CASE_P( testing::Values(kUseGpu | k24fps, kUseGpu | k30fps, kUseGpu | k60fps, - kUseGpu | k24fps | kDisableVsync, kUseGpu | k30fps | kProxyWifi, kUseGpu | k30fps | kProxyBad, kUseGpu | k30fps | kSlowClock, diff --git a/chromium/chrome/browser/extensions/api/cloud_print_private/cloud_print_private_api.cc b/chromium/chrome/browser/extensions/api/cloud_print_private/cloud_print_private_api.cc index 90c1889cb5e..17dbddd0d73 100644 --- a/chromium/chrome/browser/extensions/api/cloud_print_private/cloud_print_private_api.cc +++ b/chromium/chrome/browser/extensions/api/cloud_print_private/cloud_print_private_api.cc @@ -7,7 +7,6 @@ #include <memory> #include <string> -#include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h" #include "chrome/common/extensions/api/cloud_print_private.h" diff --git a/chromium/chrome/browser/extensions/api/commands/command_service.cc b/chromium/chrome/browser/extensions/api/commands/command_service.cc index 3f3661f362e..362c8e4bf5a 100644 --- a/chromium/chrome/browser/extensions/api/commands/command_service.cc +++ b/chromium/chrome/browser/extensions/api/commands/command_service.cc @@ -133,8 +133,8 @@ void CommandService::RegisterProfilePrefs( CommandService::CommandService(content::BrowserContext* context) : profile_(Profile::FromBrowserContext(context)), extension_registry_observer_(this) { - ExtensionFunctionRegistry::GetInstance()-> - RegisterFunction<GetAllCommandsFunction>(); + ExtensionFunctionRegistry::GetInstance() + .RegisterFunction<GetAllCommandsFunction>(); extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); } @@ -177,16 +177,16 @@ bool CommandService::GetBrowserActionCommand(const std::string& extension_id, QueryType type, Command* command, bool* active) const { - return GetExtensionActionCommand( - extension_id, type, command, active, BROWSER_ACTION); + return GetExtensionActionCommand(extension_id, type, command, active, + Command::Type::kBrowserAction); } bool CommandService::GetPageActionCommand(const std::string& extension_id, QueryType type, Command* command, bool* active) const { - return GetExtensionActionCommand( - extension_id, type, command, active, PAGE_ACTION); + return GetExtensionActionCommand(extension_id, type, command, active, + Command::Type::kPageAction); } bool CommandService::GetNamedCommands(const std::string& extension_id, @@ -403,8 +403,7 @@ Command CommandService::FindCommandByName(const std::string& extension_id, bool CommandService::GetSuggestedExtensionCommand( const std::string& extension_id, const ui::Accelerator& accelerator, - Command* command, - ExtensionCommandType* command_type) const { + Command* command) const { const Extension* extension = ExtensionRegistry::Get(profile_) ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED); @@ -419,8 +418,6 @@ bool CommandService::GetSuggestedExtensionCommand( accelerator == prospective_command.accelerator()) { if (command) *command = prospective_command; - if (command_type) - *command_type = BROWSER_ACTION; return true; } else if (GetPageActionCommand(extension_id, CommandService::SUGGESTED, @@ -429,8 +426,6 @@ bool CommandService::GetSuggestedExtensionCommand( accelerator == prospective_command.accelerator()) { if (command) *command = prospective_command; - if (command_type) - *command_type = PAGE_ACTION; return true; } else if (GetNamedCommands(extension_id, CommandService::SUGGESTED, @@ -442,8 +437,6 @@ bool CommandService::GetSuggestedExtensionCommand( if (accelerator == it->second.accelerator()) { if (command) *command = it->second; - if (command_type) - *command_type = NAMED; return true; } } @@ -454,11 +447,10 @@ bool CommandService::GetSuggestedExtensionCommand( bool CommandService::RequestsBookmarkShortcutOverride( const Extension* extension) const { return RemovesBookmarkShortcut(extension) && - GetSuggestedExtensionCommand( - extension->id(), - chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE), - NULL, - NULL); + GetSuggestedExtensionCommand( + extension->id(), + chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE), + nullptr); } void CommandService::AddObserver(Observer* observer) { @@ -858,7 +850,7 @@ bool CommandService::GetExtensionActionCommand( QueryType query_type, Command* command, bool* active, - ExtensionCommandType action_type) const { + Command::Type action_type) const { const ExtensionSet& extensions = ExtensionRegistry::Get(profile_)->enabled_extensions(); const Extension* extension = extensions.GetByID(extension_id); @@ -869,13 +861,13 @@ bool CommandService::GetExtensionActionCommand( const Command* requested_command = NULL; switch (action_type) { - case BROWSER_ACTION: + case Command::Type::kBrowserAction: requested_command = CommandsInfo::GetBrowserActionCommand(extension); break; - case PAGE_ACTION: + case Command::Type::kPageAction: requested_command = CommandsInfo::GetPageActionCommand(extension); break; - case NAMED: + case Command::Type::kNamed: NOTREACHED(); return false; } diff --git a/chromium/chrome/browser/extensions/api/commands/command_service.h b/chromium/chrome/browser/extensions/api/commands/command_service.h index ebff2853236..378922ae5fe 100644 --- a/chromium/chrome/browser/extensions/api/commands/command_service.h +++ b/chromium/chrome/browser/extensions/api/commands/command_service.h @@ -68,13 +68,6 @@ class CommandService : public BrowserContextKeyedAPI, ANY_SCOPE, // All commands, regardless of scope (used when querying). }; - // An enum specifying the types of commands that can be used by an extension. - enum ExtensionCommandType { - NAMED, - BROWSER_ACTION, - PAGE_ACTION - }; - class Observer { public: // Called when an extension command is added. @@ -186,8 +179,7 @@ class CommandService : public BrowserContextKeyedAPI, // assigns the type to *|command_type| if non-null. bool GetSuggestedExtensionCommand(const std::string& extension_id, const ui::Accelerator& accelerator, - Command* command, - ExtensionCommandType* command_type) const; + Command* command) const; // Returns true if |extension| requests to override the bookmark shortcut key // and should be allowed to do so. @@ -265,7 +257,7 @@ class CommandService : public BrowserContextKeyedAPI, QueryType query_type, Command* command, bool* active, - ExtensionCommandType action_type) const; + Command::Type type) const; // A weak pointer to the profile we are associated with. Not owned by us. Profile* profile_; diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc b/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc index f77080331df..13790f2f9eb 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc @@ -33,7 +33,7 @@ #include "extensions/common/permissions/permissions_data.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_constants.h" -#include "services/network/public/interfaces/network_service.mojom.h" +#include "services/network/public/mojom/network_service.mojom.h" using content::BrowserThread; @@ -87,7 +87,8 @@ network::mojom::CookieManager* ParseStoreCookieManager( // GetCurrentBrowser() already takes into account incognito settings. // TODO(rdevlin.cronin): Relying on the current execution context is // almost never the right answer; clean this up. - Browser* current_browser = function->GetCurrentBrowser(); + Browser* current_browser = + ChromeExtensionFunctionDetails(function).GetCurrentBrowser(); if (!current_browser) { function->SetError(keys::kNoCookieStoreFoundError); return nullptr; diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_api.h b/chromium/chrome/browser/extensions/api/cookies/cookies_api.h index b5a5ad044f1..991fb466e53 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_api.h +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_api.h @@ -22,7 +22,7 @@ #include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/event_router.h" #include "net/cookies/canonical_cookie.h" -#include "services/network/public/interfaces/cookie_manager.mojom.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" #include "url/gurl.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_helpers.h b/chromium/chrome/browser/extensions/api/cookies/cookies_helpers.h index ec287ffbce9..0b21eb056db 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_helpers.h +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_helpers.h @@ -18,7 +18,7 @@ #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_options.h" -#include "services/network/public/interfaces/cookie_manager.mojom.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" class Browser; class Profile; diff --git a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc index 6e99d232a39..6b95797c994 100644 --- a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc +++ b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc @@ -10,20 +10,18 @@ #include "base/memory/ptr_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" -#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/extensions/extension_tab_util.h" -#include "chrome/browser/permissions/permission_request.h" +#include "chrome/browser/permissions/attestation_permission_request.h" #include "chrome/browser/permissions/permission_request_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_features.h" #include "chrome/common/pref_names.h" -#include "chrome/grit/generated_resources.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_service.h" #include "crypto/sha2.h" #include "extensions/common/error_utils.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" -#include "ui/base/l10n/l10n_util.h" +#include "url/origin.h" namespace { @@ -57,52 +55,6 @@ bool ContainsAppIdByHash(const base::ListValue& list, return false; } -// AttestationPermissionRequest is a delegate class that provides information -// and callbacks to the PermissionRequestManager. -// -// PermissionRequestManager has a reference to this object and so this object -// must outlive it. Since attestation requests are never canceled, -// PermissionRequestManager guarentees that |RequestFinished| will always, -// eventually, be called. This object uses that fact to delete itself during -// |RequestFinished| and thus owns itself. -class AttestationPermissionRequest : public PermissionRequest { - public: - AttestationPermissionRequest(const GURL& app_id, - base::OnceCallback<void(bool)> callback) - : app_id_(app_id), callback_(std::move(callback)) {} - - PermissionRequest::IconId GetIconId() const override { - return kUsbSecurityKeyIcon; - } - - base::string16 GetMessageTextFragment() const override { - return l10n_util::GetStringUTF16( - IDS_SECURITY_KEY_ATTESTATION_PERMISSION_FRAGMENT); - } - GURL GetOrigin() const override { return app_id_; } - void PermissionGranted() override { std::move(callback_).Run(true); } - void PermissionDenied() override { std::move(callback_).Run(false); } - void Cancelled() override { std::move(callback_).Run(false); } - - void RequestFinished() override { - if (callback_) - std::move(callback_).Run(false); - delete this; - } - - PermissionRequestType GetPermissionRequestType() const override { - return PermissionRequestType::PERMISSION_SECURITY_KEY_ATTESTATION; - } - - private: - ~AttestationPermissionRequest() override = default; - - const GURL app_id_; - base::OnceCallback<void(bool)> callback_; - - DISALLOW_COPY_AND_ASSIGN(AttestationPermissionRequest); -}; - } // namespace namespace extensions { @@ -241,8 +193,9 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { } // The created AttestationPermissionRequest deletes itself once complete. - permission_request_manager->AddRequest(new AttestationPermissionRequest( - app_id_url, + const url::Origin origin(url::Origin::Create(app_id_url)); + permission_request_manager->AddRequest(NewAttestationPermissionRequest( + origin, base::BindOnce( &CryptotokenPrivateCanAppIdGetAttestationFunction::Complete, this))); return RespondLater(); diff --git a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc index a88874bd102..3c912638980 100644 --- a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc @@ -69,8 +69,7 @@ class CryptoTokenPrivateApiTest : public extensions::ExtensionApiUnittest { args->AppendString(app_id); if (!extension_function_test_utils::RunFunction( - function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)) { + function.get(), std::move(args), browser(), api_test_utils::NONE)) { return false; } @@ -89,7 +88,7 @@ class CryptoTokenPrivateApiTest : public extensions::ExtensionApiUnittest { if (!extension_function_test_utils::RunFunction( function.get(), base::ListValue::From(std::move(args)), browser(), - extension_function_test_utils::NONE)) { + api_test_utils::NONE)) { return false; } @@ -214,8 +213,7 @@ class CryptoTokenPermissionTest : public ExtensionApiUnittest { auto args_list = base::ListValue::From(std::move(args)); extension_function_test_utils::RunFunction( - function.get(), std::move(args_list), browser(), - extension_function_test_utils::NONE); + function.get(), std::move(args_list), browser(), api_test_utils::NONE); return GetSingleBooleanResult(function.get(), out_result); } diff --git a/chromium/chrome/browser/extensions/api/dashboard_private/dashboard_private_api.cc b/chromium/chrome/browser/extensions/api/dashboard_private/dashboard_private_api.cc index c7d8abdb460..c2bb77b851e 100644 --- a/chromium/chrome/browser/extensions/api/dashboard_private/dashboard_private_api.cc +++ b/chromium/chrome/browser/extensions/api/dashboard_private/dashboard_private_api.cc @@ -78,7 +78,8 @@ DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::Run() { if (!icon_url.is_empty()) { loader_factory = content::BrowserContext::GetDefaultStoragePartition(browser_context()) - ->GetURLLoaderFactoryForBrowserProcess(); + ->GetURLLoaderFactoryForBrowserProcess() + .get(); } scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( diff --git a/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc index 615ee3a7276..92fae3336a0 100644 --- a/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc +++ b/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc @@ -126,20 +126,20 @@ testing::AssertionResult DebuggerApiTest::RunAttachFunctionOnTarget( attach_function->set_extension(extension_.get()); std::string actual_error; - if (!RunFunction(attach_function.get(), - base::StringPrintf("[%s, \"1.1\"]", debuggee_target.c_str()), - browser(), - extension_function_test_utils::NONE)) { + if (!extension_function_test_utils::RunFunction( + attach_function.get(), + base::StringPrintf("[%s, \"1.1\"]", debuggee_target.c_str()), + browser(), api_test_utils::NONE)) { actual_error = attach_function->GetError(); } else { // Clean up and detach. scoped_refptr<DebuggerDetachFunction> detach_function = new DebuggerDetachFunction(); detach_function->set_extension(extension_.get()); - if (!RunFunction(detach_function.get(), - base::StringPrintf("[%s]", debuggee_target.c_str()), - browser(), - extension_function_test_utils::NONE)) { + if (!extension_function_test_utils::RunFunction( + detach_function.get(), + base::StringPrintf("[%s]", debuggee_target.c_str()), browser(), + api_test_utils::NONE)) { return testing::AssertionFailure() << "Could not detach from " << debuggee_target << " : " << detach_function->GetError(); } @@ -212,10 +212,10 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Attach should create infobars in both browsers. attach_function = new DebuggerAttachFunction(); attach_function->set_extension(extension()); - ASSERT_TRUE( - RunFunction(attach_function.get(), - base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + attach_function.get(), + base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), browser(), + api_test_utils::NONE)); EXPECT_EQ(1u, service1->infobar_count()); EXPECT_EQ(1u, service2->infobar_count()); EXPECT_EQ(1u, service3->infobar_count()); @@ -223,10 +223,10 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Second attach should not create infobars. attach_function = new DebuggerAttachFunction(); attach_function->set_extension(extension()); - ASSERT_TRUE( - RunFunction(attach_function.get(), - base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id2), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + attach_function.get(), + base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id2), browser(), + api_test_utils::NONE)); EXPECT_EQ(1u, service1->infobar_count()); EXPECT_EQ(1u, service2->infobar_count()); EXPECT_EQ(1u, service3->infobar_count()); @@ -234,9 +234,9 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Detach from one of the tabs should not remove infobars. detach_function = new DebuggerDetachFunction(); detach_function->set_extension(extension()); - ASSERT_TRUE(RunFunction(detach_function.get(), - base::StringPrintf("[{\"tabId\": %d}]", tab_id2), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id2), + browser(), api_test_utils::NONE)); EXPECT_EQ(1u, service1->infobar_count()); EXPECT_EQ(1u, service2->infobar_count()); EXPECT_EQ(1u, service3->infobar_count()); @@ -244,9 +244,9 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Detach should remove all infobars. detach_function = new DebuggerDetachFunction(); detach_function->set_extension(extension()); - ASSERT_TRUE(RunFunction(detach_function.get(), - base::StringPrintf("[{\"tabId\": %d}]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id), + browser(), api_test_utils::NONE)); EXPECT_EQ(0u, service1->infobar_count()); EXPECT_EQ(0u, service2->infobar_count()); EXPECT_EQ(0u, service3->infobar_count()); @@ -254,10 +254,10 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Attach again. attach_function = new DebuggerAttachFunction(); attach_function->set_extension(extension()); - ASSERT_TRUE( - RunFunction(attach_function.get(), - base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + attach_function.get(), + base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), browser(), + api_test_utils::NONE)); EXPECT_EQ(1u, service1->infobar_count()); EXPECT_EQ(1u, service2->infobar_count()); EXPECT_EQ(1u, service3->infobar_count()); @@ -275,17 +275,17 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { detach_function = new DebuggerDetachFunction(); detach_function->set_extension(extension()); // Cannot detach again. - ASSERT_FALSE(RunFunction(detach_function.get(), - base::StringPrintf("[{\"tabId\": %d}]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_FALSE(extension_function_test_utils::RunFunction( + detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id), + browser(), api_test_utils::NONE)); // And again... attach_function = new DebuggerAttachFunction(); attach_function->set_extension(extension()); - ASSERT_TRUE( - RunFunction(attach_function.get(), - base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + attach_function.get(), + base::StringPrintf("[{\"tabId\": %d}, \"1.1\"]", tab_id), browser(), + api_test_utils::NONE)); EXPECT_EQ(1u, service1->infobar_count()); EXPECT_EQ(1u, service2->infobar_count()); EXPECT_EQ(1u, service3->infobar_count()); @@ -305,9 +305,9 @@ IN_PROC_BROWSER_TEST_F(DebuggerApiTest, InfoBar) { // Detach should remove the remaining infobar. detach_function = new DebuggerDetachFunction(); detach_function->set_extension(extension()); - ASSERT_TRUE(RunFunction(detach_function.get(), - base::StringPrintf("[{\"tabId\": %d}]", tab_id), - browser(), extension_function_test_utils::NONE)); + ASSERT_TRUE(extension_function_test_utils::RunFunction( + detach_function.get(), base::StringPrintf("[{\"tabId\": %d}]", tab_id), + browser(), api_test_utils::NONE)); EXPECT_EQ(0u, service1->infobar_count()); } diff --git a/chromium/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar.cc b/chromium/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar.cc index 6b187e3a93c..41b8582f234 100644 --- a/chromium/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar.cc +++ b/chromium/chrome/browser/extensions/api/debugger/extension_dev_tools_infobar.cc @@ -26,7 +26,6 @@ class ExtensionDevToolsInfoBarDelegate : public ConfirmInfoBarDelegate { ~ExtensionDevToolsInfoBarDelegate() override; // ConfirmInfoBarDelegate: - Type GetInfoBarType() const override; infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override; bool ShouldExpire(const NavigationDetails& details) const override; void InfoBarDismissed() override; @@ -50,11 +49,6 @@ ExtensionDevToolsInfoBarDelegate::ExtensionDevToolsInfoBarDelegate( ExtensionDevToolsInfoBarDelegate::~ExtensionDevToolsInfoBarDelegate() {} -infobars::InfoBarDelegate::Type -ExtensionDevToolsInfoBarDelegate::GetInfoBarType() const { - return WARNING_TYPE; -} - infobars::InfoBarDelegate::InfoBarIdentifier ExtensionDevToolsInfoBarDelegate::GetIdentifier() const { return EXTENSION_DEV_TOOLS_INFOBAR_DELEGATE; diff --git a/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc b/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc index 21fe2a8754a..24f23e44620 100644 --- a/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc @@ -13,7 +13,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/test/thread_test_helper.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/ui_test_utils.h" @@ -24,6 +23,7 @@ #include "extensions/browser/extension_prefs.h" #include "extensions/common/extension.h" #include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/test_extension_dir.h" using content::BrowserThread; diff --git a/chromium/chrome/browser/extensions/api/declarative/rules_registry_with_cache_unittest.cc b/chromium/chrome/browser/extensions/api/declarative/rules_registry_with_cache_unittest.cc index d9059dbe147..53906599f6b 100644 --- a/chromium/chrome/browser/extensions/api/declarative/rules_registry_with_cache_unittest.cc +++ b/chromium/chrome/browser/extensions/api/declarative/rules_registry_with_cache_unittest.cc @@ -44,7 +44,8 @@ const int kRulesRegistryID = RulesRegistryService::kDefaultRulesRegistryID; class RulesRegistryWithCacheTest : public testing::Test { public: RulesRegistryWithCacheTest() - : cache_delegate_(/*log_storage_init_delay=*/false), + : cache_delegate_(RulesCacheDelegate::Type::kPersistent, + /*log_storage_init_delay=*/false), registry_(new TestRulesRegistry(profile(), /*event_name=*/"", content::BrowserThread::UI, @@ -233,8 +234,8 @@ TEST_F(RulesRegistryWithCacheTest, DeclarativeRulesStored) { const std::string rules_stored_key( RulesCacheDelegate::GetRulesStoredKey( event_name, profile()->IsOffTheRecord())); - std::unique_ptr<RulesCacheDelegate> cache_delegate( - new RulesCacheDelegate(false)); + auto cache_delegate = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kPersistent, false); scoped_refptr<RulesRegistry> registry( new TestRulesRegistry(profile(), event_name, content::BrowserThread::UI, cache_delegate.get(), kRulesRegistryID)); @@ -252,9 +253,11 @@ TEST_F(RulesRegistryWithCacheTest, DeclarativeRulesStored) { EXPECT_TRUE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); // 2. Test writing behavior. - std::unique_ptr<base::ListValue> value(new base::ListValue); - value->AppendBoolean(true); - cache_delegate->WriteToStorage(extension1_->id(), std::move(value)); + { + base::Value value(base::Value::Type::LIST); + value.GetList().push_back(base::Value(true)); + cache_delegate->UpdateRules(extension1_->id(), std::move(value)); + } EXPECT_TRUE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); content::RunAllTasksUntilIdle(); TestingValueStore* store = env_.GetExtensionSystem()->value_store(); @@ -262,17 +265,21 @@ TEST_F(RulesRegistryWithCacheTest, DeclarativeRulesStored) { EXPECT_EQ(1, store->write_count()); int write_count = store->write_count(); - value.reset(new base::ListValue); - cache_delegate->WriteToStorage(extension1_->id(), std::move(value)); - EXPECT_FALSE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); + { + base::Value value = base::Value(base::Value::Type::LIST); + cache_delegate->UpdateRules(extension1_->id(), std::move(value)); + EXPECT_FALSE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); + } content::RunAllTasksUntilIdle(); // No rules currently, but previously there were, so we expect a write. EXPECT_EQ(write_count + 1, store->write_count()); write_count = store->write_count(); - value.reset(new base::ListValue); - cache_delegate->WriteToStorage(extension1_->id(), std::move(value)); - EXPECT_FALSE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); + { + base::Value value = base::Value(base::Value::Type::LIST); + cache_delegate->UpdateRules(extension1_->id(), std::move(value)); + EXPECT_FALSE(cache_delegate->GetDeclarativeRulesStored(extension1_->id())); + } content::RunAllTasksUntilIdle(); EXPECT_EQ(write_count, store->write_count()); @@ -291,6 +298,18 @@ TEST_F(RulesRegistryWithCacheTest, DeclarativeRulesStored) { EXPECT_EQ(read_count + 1, store->read_count()); } +TEST_F(RulesRegistryWithCacheTest, EphemeralCacheIsEphemeral) { + auto cache_delegate = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kEphemeral, false); + base::Value value(base::Value::Type::LIST); + value.GetList().push_back(base::Value(true)); + cache_delegate->UpdateRules(extension1_->id(), std::move(value)); + content::RunAllTasksUntilIdle(); + TestingValueStore* store = env_.GetExtensionSystem()->value_store(); + ASSERT_TRUE(store); + EXPECT_EQ(0, store->write_count()); +} + // Test that each registry has its own "are some rules stored" flag. TEST_F(RulesRegistryWithCacheTest, RulesStoredFlagMultipleRegistries) { ExtensionPrefs* extension_prefs = env_.GetExtensionPrefs(); @@ -303,14 +322,14 @@ TEST_F(RulesRegistryWithCacheTest, RulesStoredFlagMultipleRegistries) { const std::string rules_stored_key2( RulesCacheDelegate::GetRulesStoredKey( event_name2, profile()->IsOffTheRecord())); - std::unique_ptr<RulesCacheDelegate> cache_delegate1( - new RulesCacheDelegate(false)); + auto cache_delegate1 = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kPersistent, false); scoped_refptr<RulesRegistry> registry1( new TestRulesRegistry(profile(), event_name1, content::BrowserThread::UI, cache_delegate1.get(), kRulesRegistryID)); - std::unique_ptr<RulesCacheDelegate> cache_delegate2( - new RulesCacheDelegate(false)); + auto cache_delegate2 = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kPersistent, false); scoped_refptr<RulesRegistry> registry2( new TestRulesRegistry(profile(), event_name2, content::BrowserThread::UI, cache_delegate2.get(), kRulesRegistryID)); @@ -351,8 +370,8 @@ TEST_F(RulesRegistryWithCacheTest, RulesPreservedAcrossRestart) { env_.GetExtensionSystem()->SetReady(); // 2. First run, adding a rule for the extension. - std::unique_ptr<RulesCacheDelegate> cache_delegate( - new RulesCacheDelegate(false)); + auto cache_delegate = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kPersistent, false); scoped_refptr<TestRulesRegistry> registry( new TestRulesRegistry(profile(), "testEvent", content::BrowserThread::UI, cache_delegate.get(), kRulesRegistryID)); @@ -364,7 +383,8 @@ TEST_F(RulesRegistryWithCacheTest, RulesPreservedAcrossRestart) { EXPECT_EQ(1, GetNumberOfRules(extension1_->id(), registry.get())); // 3. Restart the TestRulesRegistry and see the rule still there. - cache_delegate.reset(new RulesCacheDelegate(false)); + cache_delegate = std::make_unique<RulesCacheDelegate>( + RulesCacheDelegate::Type::kPersistent, false); registry = new TestRulesRegistry(profile(), "testEvent", content::BrowserThread::UI, cache_delegate.get(), kRulesRegistryID); diff --git a/chromium/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc b/chromium/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc index 88a0d6da572..4f2b43e14fd 100644 --- a/chromium/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc @@ -16,7 +16,6 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tab_util.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -26,6 +25,7 @@ #include "extensions/browser/extension_system.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/test_extension_dir.h" #include "testing/gmock/include/gmock/gmock.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/declarative_content/request_content_script_apitest.cc b/chromium/chrome/browser/extensions/api/declarative_content/request_content_script_apitest.cc index ffa680bbcd2..42c42b760ce 100644 --- a/chromium/chrome/browser/extensions/api/declarative_content/request_content_script_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_content/request_content_script_apitest.cc @@ -6,12 +6,12 @@ #include "base/macros.h" #include "base/strings/stringprintf.h" #include "chrome/browser/extensions/extension_browsertest.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/test/browser_test_utils.h" #include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/test_extension_dir.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chromium/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc b/chromium/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc index 5a91c7c006c..72a79c3313d 100644 --- a/chromium/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc @@ -5,11 +5,11 @@ #include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_tab_util.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "components/version_info/version_info.h" #include "extensions/common/features/feature_channel.h" #include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/test_extension_dir.h" #include "ui/gfx/image/image.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc index 3ae612a992c..7efb7086919 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc @@ -41,10 +41,13 @@ #include "extensions/browser/extension_util.h" #include "extensions/common/api/declarative_net_request/constants.h" #include "extensions/common/api/declarative_net_request/test_utils.h" +#include "extensions/common/constants.h" #include "extensions/common/extension_id.h" +#include "extensions/common/url_pattern.h" #include "net/dns/mock_host_resolver.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "net/test/test_data_directory.h" +#include "services/network/public/cpp/features.h" namespace extensions { namespace declarative_net_request { @@ -154,8 +157,11 @@ class DeclarativeNetRequestBrowserTest // Loads an extension with the given declarative |rules| in the given // |directory|. Generates a fatal failure if the extension failed to load. + // |hosts| specifies the host permissions, the extensions should + // have. void LoadExtensionWithRules(const std::vector<TestRule>& rules, - const std::string& directory) { + const std::string& directory, + const std::vector<std::string>& hosts) { base::ScopedAllowBlockingForTesting scoped_allow_blocking; base::HistogramTester tester; @@ -163,12 +169,13 @@ class DeclarativeNetRequestBrowserTest EXPECT_TRUE(base::CreateDirectory(extension_dir)); WriteManifestAndRuleset(extension_dir, kJSONRulesetFilepath, - kJSONRulesFilename, rules); + kJSONRulesFilename, rules, hosts); const Extension* extension = nullptr; switch (GetParam()) { case ExtensionLoadType::PACKED: - extension = InstallExtension(extension_dir, 1 /* expected_change */); + extension = InstallExtensionWithPermissionsGranted( + extension_dir, 1 /* expected_change */); break; case ExtensionLoadType::UNPACKED: extension = LoadExtension(extension_dir); @@ -196,7 +203,8 @@ class DeclarativeNetRequestBrowserTest } void LoadExtensionWithRules(const std::vector<TestRule>& rules) { - LoadExtensionWithRules(rules, "test_extension"); + LoadExtensionWithRules(rules, "test_extension", + {URLPattern::kAllUrlsPattern}); } private: @@ -635,8 +643,10 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, rules_2.push_back(rule); } - ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(rules_1, "extension_1")); - ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(rules_2, "extension_2")); + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules( + rules_1, "extension_1", {URLPattern::kAllUrlsPattern})); + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules( + rules_2, "extension_2", {URLPattern::kAllUrlsPattern})); struct { std::string host; @@ -688,7 +698,8 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, // url. for (size_t i = 1; i <= kNumExtensions; ++i) { rule.action->redirect_url = redirect_url_for_extension_number(i); - ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules({rule}, std::to_string(i))); + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules( + {rule}, std::to_string(i), {URLPattern::kAllUrlsPattern})); // Verify that the install time of this extension is greater than the last // extension. @@ -906,7 +917,8 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, } } -// Ensure extensions can't intercept chrome:// urls. +// Ensure extensions can't intercept chrome:// urls, even after explicitly +// requesting access to <all_urls>. IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, ChromeURLS) { // Have the extension block all chrome:// urls. TestRule rule = CreateGenericRule(); @@ -976,7 +988,12 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, RendererCacheCleared) { ui_test_utils::NavigateToURL(browser(), url); EXPECT_EQ(content::PAGE_TYPE_NORMAL, GetPageType()); EXPECT_TRUE(WasFrameWithScriptLoaded(GetMainFrame())); - EXPECT_TRUE(script_monitor.GetAndResetRequestSeen(false)); + + // NOTE: When the Network Service is enabled, the RulesetMatcher will not see + // network requests if no rulesets are active. + EXPECT_TRUE( + base::FeatureList::IsEnabled(network::features::kNetworkService) || + script_monitor.GetAndResetRequestSeen(false)); // Another request to |url| should not cause a network request for // script.js since it will be served by the renderer's in-memory @@ -1010,7 +1027,9 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, RendererCacheCleared) { ui_test_utils::NavigateToURL(browser(), url); EXPECT_EQ(content::PAGE_TYPE_NORMAL, GetPageType()); EXPECT_TRUE(WasFrameWithScriptLoaded(GetMainFrame())); - EXPECT_TRUE(script_monitor.GetAndResetRequestSeen(false)); + EXPECT_TRUE( + base::FeatureList::IsEnabled(network::features::kNetworkService) || + script_monitor.GetAndResetRequestSeen(false)); // Clear RulesetManager's observer. content::BrowserThread::PostTask( @@ -1049,6 +1068,164 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, EXPECT_EQ(content::PAGE_TYPE_NORMAL, GetPageType()); } +// Ensure that an extension can intercept its own resources, but not those of +// other extensions. +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, + InterceptExtensionScheme) { + // Load two extensions. One blocks all urls, and the other blocks urls with + // "google.com" as a substring. + std::vector<TestRule> rules_1; + TestRule rule = CreateGenericRule(); + rule.condition->url_filter = std::string("*"); + rules_1.push_back(rule); + + std::vector<TestRule> rules_2; + rule = CreateGenericRule(); + rule.condition->url_filter = std::string("google.com"); + rules_2.push_back(rule); + + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules( + rules_1, "extension_1", {URLPattern::kAllUrlsPattern})); + const std::string extension_id_1 = last_loaded_extension_id(); + + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules( + rules_2, "extension_2", {URLPattern::kAllUrlsPattern})); + const std::string extension_id_2 = last_loaded_extension_id(); + + auto get_manifest_url = [](const std::string& extension_id) { + return GURL(base::StringPrintf("%s://%s/manifest.json", + extensions::kExtensionScheme, + extension_id.c_str())); + }; + + // Extension 1 should be able to block the request to its own + // manifest.json. + ui_test_utils::NavigateToURL(browser(), get_manifest_url(extension_id_1)); + GURL final_url = web_contents()->GetLastCommittedURL(); + EXPECT_EQ(content::PAGE_TYPE_ERROR, GetPageType()); + + // But it should not be able to intercept requests to the second extensions's + // resources, even with "<all_urls>" host permissions. + ui_test_utils::NavigateToURL(browser(), get_manifest_url(extension_id_2)); + EXPECT_EQ(content::PAGE_TYPE_NORMAL, GetPageType()); +} + +// Test fixture to verify that host permissions for the request url and the +// request initiator are properly checked. Loads an example.com url with four +// sub-frames named frame_[1..4] from hosts frame_[1..4].com. The initiator for +// these frames will be example.com. Loads an extension set to block all sub- +// frames. Verifies that the correct frames are blocked depending on the host +// permissions for the extension. +class DeclarativeNetRequestHostPermissionsBrowserTest + : public DeclarativeNetRequestBrowserTest { + public: + DeclarativeNetRequestHostPermissionsBrowserTest() {} + + protected: + struct FrameLoadResult { + std::string child_frame_name; + bool expect_frame_loaded; + }; + + void LoadExtensionWithHostPermissions(const std::vector<std::string>& hosts) { + std::vector<TestRule> rules; + + // Block all sub-frame requests. + TestRule rule = CreateGenericRule(); + rule.condition->url_filter = std::string("*"); + rule.condition->resource_types = std::vector<std::string>({"sub_frame"}); + rules.push_back(rule); + + ASSERT_NO_FATAL_FAILURE( + LoadExtensionWithRules(rules, "test_extension", hosts)); + } + + void RunTests(const std::vector<FrameLoadResult>& expected_results) { + ASSERT_EQ(4u, expected_results.size()); + + GURL url = embedded_test_server()->GetURL("example.com", + "/page_with_four_frames.html"); + ui_test_utils::NavigateToURL(browser(), url); + ASSERT_TRUE(WasFrameWithScriptLoaded(GetMainFrame())); + + for (const auto& frame_result : expected_results) { + SCOPED_TRACE(base::StringPrintf("Testing child frame named %s", + frame_result.child_frame_name.c_str())); + + content::RenderFrameHost* child = + GetFrameByName(frame_result.child_frame_name); + EXPECT_TRUE(child); + EXPECT_EQ(frame_result.expect_frame_loaded, + WasFrameWithScriptLoaded(child)); + } + } + + std::string GetMatchPatternForDomain(const std::string& domain) const { + return "*://*." + domain + ".com/*"; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DeclarativeNetRequestHostPermissionsBrowserTest); +}; + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestHostPermissionsBrowserTest, + AllURLs1) { + // All frames should be blocked since the extension has access to all hosts. + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithHostPermissions({"<all_urls>"})); + RunTests({{"frame_1", false}, + {"frame_2", false}, + {"frame_3", false}, + {"frame_4", false}}); +} + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestHostPermissionsBrowserTest, + AllURLs2) { + // All frames should be blocked since the extension has access to all hosts. + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithHostPermissions({"*://*/*"})); + RunTests({{"frame_1", false}, + {"frame_2", false}, + {"frame_3", false}, + {"frame_4", false}}); +} + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestHostPermissionsBrowserTest, + NoPermissions) { + // The extension has no host permissions. No frames should be blocked. + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithHostPermissions({})); + RunTests({{"frame_1", true}, + {"frame_2", true}, + {"frame_3", true}, + {"frame_4", true}}); +} + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestHostPermissionsBrowserTest, + SubframesWithNoInitiatorPermissions) { + // The extension has access to requests to "frame_1.com" and "frame_2.com", + // but not the initiator of those requests (example.com). No frames should be + // blocked. + ASSERT_NO_FATAL_FAILURE( + LoadExtensionWithHostPermissions({GetMatchPatternForDomain("frame_1"), + GetMatchPatternForDomain("frame_2")})); + RunTests({{"frame_1", true}, + {"frame_2", true}, + {"frame_3", true}, + {"frame_4", true}}); +} + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestHostPermissionsBrowserTest, + SubframesWithInitiatorPermission) { + // The extension has access to requests to "frame_1.com" and "frame_4.com", + // and also the initiator of those requests (example.com). Hence |frame_1| and + // |frame_4| should be blocked. + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithHostPermissions( + {GetMatchPatternForDomain("frame_1"), GetMatchPatternForDomain("frame_4"), + GetMatchPatternForDomain("example")})); + RunTests({{"frame_1", false}, + {"frame_2", true}, + {"frame_3", true}, + {"frame_4", false}}); +} + // Fixture to test the "resourceTypes" and "excludedResourceTypes" fields of a // declarative rule condition. class DeclarativeNetRequestResourceTypeBrowserTest @@ -1220,6 +1397,10 @@ INSTANTIATE_TEST_CASE_P(, ExtensionLoadType::UNPACKED)); INSTANTIATE_TEST_CASE_P(, + DeclarativeNetRequestHostPermissionsBrowserTest, + ::testing::Values(ExtensionLoadType::PACKED, + ExtensionLoadType::UNPACKED)); +INSTANTIATE_TEST_CASE_P(, DeclarativeNetRequestResourceTypeBrowserTest, ::testing::Values(ExtensionLoadType::PACKED, ExtensionLoadType::UNPACKED)); diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc index e8b3fffeca9..0e5c1f20ae2 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc @@ -24,6 +24,7 @@ #include "extensions/common/file_util.h" #include "extensions/common/install_warning.h" #include "extensions/common/manifest_constants.h" +#include "extensions/common/url_pattern.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { @@ -129,10 +130,12 @@ class RuleIndexingTest : public DNRTestBase { if (rules_value_) { WriteManifestAndRuleset(extension_dir_, kJSONRulesetFilepath, - kJSONRulesFilename, *rules_value_); + kJSONRulesFilename, *rules_value_, + {URLPattern::kAllUrlsPattern}); } else { WriteManifestAndRuleset(extension_dir_, kJSONRulesetFilepath, - kJSONRulesFilename, rules_list_); + kJSONRulesFilename, rules_list_, + {URLPattern::kAllUrlsPattern}); } // Overwrite the JSON rules file with some invalid json. diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc index eec02539495..932c7add27b 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc @@ -6,6 +6,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/optional.h" #include "chrome/browser/extensions/api/declarative_net_request/dnr_test_base.h" #include "chrome/browser/extensions/chrome_test_extension_loader.h" #include "chrome/browser/extensions/extension_util.h" @@ -19,10 +20,12 @@ #include "extensions/common/api/declarative_net_request/constants.h" #include "extensions/common/api/declarative_net_request/test_utils.h" #include "extensions/common/file_util.h" +#include "extensions/common/url_pattern.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" +#include "url/origin.h" namespace extensions { namespace declarative_net_request { @@ -40,14 +43,16 @@ class RulesetManagerTest : public DNRTestBase { // Helper to create a ruleset matcher instance for the given |rules|. void CreateMatcherForRules(const std::vector<TestRule>& rules, const std::string& extension_dirname, - std::unique_ptr<RulesetMatcher>* matcher) { + std::unique_ptr<RulesetMatcher>* matcher, + const std::vector<std::string>& host_permissions = + {URLPattern::kAllUrlsPattern}) { base::FilePath extension_dir = temp_dir().GetPath().AppendASCII(extension_dirname); // Create extension directory. ASSERT_TRUE(base::CreateDirectory(extension_dir)); WriteManifestAndRuleset(extension_dir, kJSONRulesetFilepath, - kJSONRulesFilename, rules); + kJSONRulesFilename, rules, host_permissions); last_loaded_extension_ = CreateExtensionLoader()->LoadExtension(extension_dir); @@ -229,23 +234,46 @@ TEST_P(RulesetManagerTest, Redirect) { rule.action->redirect_url = std::string("http://google.com"); std::unique_ptr<RulesetMatcher> matcher; ASSERT_NO_FATAL_FAILURE( - CreateMatcherForRules({rule}, "test_extension", &matcher)); + CreateMatcherForRules({rule}, "test_extension", &matcher, + {"*://example.com/*", "*://abc.com/*"})); manager->AddRuleset(last_loaded_extension()->id(), std::move(matcher)); + // Create a request to "example.com" with an empty initiator. It should be + // redirected to "google.com". const bool is_incognito_context = false; - GURL redirect_url; + GURL redirect_url1; std::unique_ptr<net::URLRequest> request = GetRequestForURL("http://example.com"); + request->set_initiator(base::nullopt); extensions::WebRequestInfo request_info1(request.get()); EXPECT_TRUE(manager->ShouldRedirectRequest( - request_info1, is_incognito_context, &redirect_url)); - EXPECT_EQ(GURL("http://google.com"), redirect_url); + request_info1, is_incognito_context, &redirect_url1)); + EXPECT_EQ(GURL("http://google.com"), redirect_url1); + + // Change the initiator to "xyz.com". It should not be redirected since we + // don't have host permissions to the request initiator. + GURL redirect_url2; + request->set_initiator(url::Origin::Create(GURL("http://xyz.com"))); + extensions::WebRequestInfo request_info2(request.get()); + EXPECT_FALSE(manager->ShouldRedirectRequest( + request_info2, is_incognito_context, &redirect_url2)); + + // Change the initiator to "abc.com". It should be redirected since we have + // the required host permissions. + GURL redirect_url3; + request->set_initiator(url::Origin::Create(GURL("http://abc.com"))); + extensions::WebRequestInfo request_info3(request.get()); + EXPECT_TRUE(manager->ShouldRedirectRequest( + request_info3, is_incognito_context, &redirect_url3)); + EXPECT_EQ(GURL("http://google.com"), redirect_url3); // Ensure web-socket requests are not redirected. + GURL redirect_url4; request = GetRequestForURL("ws://example.com"); - extensions::WebRequestInfo request_info2(request.get()); + request->set_initiator(base::nullopt); + extensions::WebRequestInfo request_info4(request.get()); EXPECT_FALSE(manager->ShouldRedirectRequest( - request_info2, is_incognito_context, &redirect_url)); + request_info4, is_incognito_context, &redirect_url4)); } INSTANTIATE_TEST_CASE_P(, diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_matcher_unittest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_matcher_unittest.cc index 700f504554a..90209860ec6 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_matcher_unittest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/ruleset_matcher_unittest.cc @@ -18,6 +18,7 @@ #include "extensions/common/api/declarative_net_request/constants.h" #include "extensions/common/api/declarative_net_request/test_utils.h" #include "extensions/common/file_util.h" +#include "extensions/common/url_pattern.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" @@ -44,7 +45,8 @@ class RulesetMatcherTest : public DNRTestBase { // Create extension directory. ASSERT_TRUE(base::CreateDirectory(extension_dir)); WriteManifestAndRuleset(extension_dir, kJSONRulesetFilepath, - kJSONRulesFilename, rules); + kJSONRulesFilename, rules, + {URLPattern::kAllUrlsPattern}); extension_ = CreateExtensionLoader()->LoadExtension(extension_dir); ASSERT_TRUE(extension_); diff --git a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_apitest.cc b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_apitest.cc index 8bfaa066863..c6c75b48921 100644 --- a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_apitest.cc +++ b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_apitest.cc @@ -44,6 +44,8 @@ struct TestFlags { bool picker_deleted; }; +// TODO(crbug.com/805145): Uncomment this when test is re-enabled. +#if 0 DesktopMediaID MakeFakeWebContentsMediaId(bool audio_share) { DesktopMediaID media_id(DesktopMediaID::TYPE_WEB_CONTENTS, DesktopMediaID::kNullId, @@ -52,6 +54,7 @@ DesktopMediaID MakeFakeWebContentsMediaId(bool audio_share) { media_id.audio_share = audio_share; return media_id; } +#endif class FakeDesktopMediaPicker : public DesktopMediaPicker { public: @@ -63,13 +66,8 @@ class FakeDesktopMediaPicker : public DesktopMediaPicker { ~FakeDesktopMediaPicker() override { expectation_->picker_deleted = true; } // DesktopMediaPicker interface. - void Show(content::WebContents* web_contents, - gfx::NativeWindow context, - gfx::NativeWindow parent, - const base::string16& app_name, - const base::string16& target_name, + void Show(const DesktopMediaPicker::Params& params, std::vector<std::unique_ptr<DesktopMediaList>> source_lists, - bool request_audio, const DoneCallback& done_callback) override { bool show_screens = false; bool show_windows = false; @@ -93,7 +91,8 @@ class FakeDesktopMediaPicker : public DesktopMediaPicker { 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, request_audio); + 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. @@ -187,7 +186,8 @@ class DesktopCaptureApiTest : public ExtensionApiTest { // Flaky on Windows: http://crbug.com/301887 // Fails on Chrome OS: http://crbug.com/718512 -#if defined(OS_WIN) || defined(OS_CHROMEOS) +// Flaky on macOS: http://crbug.com/804897 +#if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_MACOSX) #define MAYBE_ChooseDesktopMedia DISABLED_ChooseDesktopMedia #else #define MAYBE_ChooseDesktopMedia ChooseDesktopMedia @@ -196,52 +196,55 @@ IN_PROC_BROWSER_TEST_F(DesktopCaptureApiTest, MAYBE_ChooseDesktopMedia) { // Each element in the following array corresponds to one test in // chrome/test/data/extensions/api_test/desktop_capture/test.js . TestFlags test_flags[] = { - // pickerUiCanceled() - {true, true, false, false, DesktopMediaID()}, - // chooseMedia() - {true, true, false, false, - DesktopMediaID(DesktopMediaID::TYPE_SCREEN, DesktopMediaID::kNullId)}, - // screensOnly() - {true, false, false, false, DesktopMediaID()}, - // WindowsOnly() - {false, true, false, false, DesktopMediaID()}, - // tabOnly() - {false, false, true, false, DesktopMediaID()}, - // audioShareNoApproval() - {true, true, true, true, - DesktopMediaID(DesktopMediaID::TYPE_WEB_CONTENTS, 123, false)}, - // audioShareApproval() - {true, true, true, true, - DesktopMediaID(DesktopMediaID::TYPE_WEB_CONTENTS, 123, true)}, - // chooseMediaAndGetStream() - {true, true, false, false, - DesktopMediaID(DesktopMediaID::TYPE_SCREEN, - webrtc::kFullDesktopScreenId)}, - // chooseMediaAndTryGetStreamWithInvalidId() - {true, true, false, false, - DesktopMediaID(DesktopMediaID::TYPE_SCREEN, - webrtc::kFullDesktopScreenId)}, - // cancelDialog() - {true, true, false, false, DesktopMediaID(), true}, + // pickerUiCanceled() + {true, true, false, false, DesktopMediaID()}, + // chooseMedia() + {true, true, false, false, + DesktopMediaID(DesktopMediaID::TYPE_SCREEN, DesktopMediaID::kNullId)}, + // screensOnly() + {true, false, false, false, DesktopMediaID()}, + // WindowsOnly() + {false, true, false, false, DesktopMediaID()}, + // tabOnly() + {false, false, true, false, DesktopMediaID()}, + // audioShareNoApproval() + {true, true, true, true, + DesktopMediaID(DesktopMediaID::TYPE_WEB_CONTENTS, 123, false)}, + // audioShareApproval() + {true, true, true, true, + DesktopMediaID(DesktopMediaID::TYPE_WEB_CONTENTS, 123, true)}, + // chooseMediaAndGetStream() + {true, true, false, false, + DesktopMediaID(DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId)}, + // chooseMediaAndTryGetStreamWithInvalidId() + {true, true, false, false, + DesktopMediaID(DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId)}, + // cancelDialog() + {true, true, false, false, DesktopMediaID(), true}, +// TODO(crbug.com/805145): Test fails; invalid device IDs being generated. +#if 0 // tabShareWithAudioGetStream() {false, false, true, true, MakeFakeWebContentsMediaId(true)}, - // windowShareWithAudioGetStream() - {false, true, false, true, - DesktopMediaID(DesktopMediaID::TYPE_WINDOW, DesktopMediaID::kFakeId, - true)}, - // screenShareWithAudioGetStream() - {true, false, false, true, - DesktopMediaID(DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId, - true)}, +#endif + // windowShareWithAudioGetStream() + {false, true, false, true, + DesktopMediaID(DesktopMediaID::TYPE_WINDOW, DesktopMediaID::kFakeId, + true)}, + // screenShareWithAudioGetStream() + {true, false, false, true, + DesktopMediaID(DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId, + true)}, +// TODO(crbug.com/805145): Test fails; invalid device IDs being generated. +#if 0 // tabShareWithoutAudioGetStream() {false, false, true, true, MakeFakeWebContentsMediaId(false)}, - // windowShareWithoutAudioGetStream() - {false, true, false, true, - DesktopMediaID(DesktopMediaID::TYPE_WINDOW, DesktopMediaID::kFakeId)}, - // screenShareWithoutAudioGetStream() - {true, false, false, true, - DesktopMediaID(DesktopMediaID::TYPE_SCREEN, - webrtc::kFullDesktopScreenId)}, +#endif + // windowShareWithoutAudioGetStream() + {false, true, false, true, + DesktopMediaID(DesktopMediaID::TYPE_WINDOW, DesktopMediaID::kFakeId)}, + // screenShareWithoutAudioGetStream() + {true, false, false, true, + DesktopMediaID(DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId)}, }; picker_factory_.SetTestFlags(test_flags, arraysize(test_flags)); ASSERT_TRUE(RunExtensionTest("desktop_capture")) << message_; diff --git a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc index 392ecad385f..4e8b6cc9ffa 100644 --- a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc +++ b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc @@ -26,8 +26,6 @@ #include "content/public/browser/web_contents.h" #include "extensions/common/manifest.h" #include "extensions/common/switches.h" -#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" -#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h" #include "ui/base/l10n/l10n_util.h" using extensions::api::desktop_capture::ChooseDesktopMedia::Results::Options; @@ -123,8 +121,7 @@ bool DesktopCaptureChooseDesktopMediaFunctionBase::Execute( #else // !defined(OS_CHROMEOS) screen_list = std::make_unique<NativeDesktopMediaList>( content::DesktopMediaID::TYPE_SCREEN, - webrtc::DesktopCapturer::CreateScreenCapturer( - content::CreateDesktopCaptureOptions())); + content::desktop_capture::CreateScreenCapturer()); #endif // !defined(OS_CHROMEOS) } have_screen_list = true; @@ -151,8 +148,7 @@ bool DesktopCaptureChooseDesktopMediaFunctionBase::Execute( // used on multiple threads concurrently. window_list = std::make_unique<NativeDesktopMediaList>( content::DesktopMediaID::TYPE_WINDOW, - webrtc::DesktopCapturer::CreateWindowCapturer( - content::CreateDesktopCaptureOptions())); + content::desktop_capture::CreateWindowCapturer()); #endif // !defined(OS_CHROMEOS) } have_window_list = true; @@ -208,10 +204,14 @@ bool DesktopCaptureChooseDesktopMediaFunctionBase::Execute( DesktopMediaPicker::DoneCallback callback = base::Bind( &DesktopCaptureChooseDesktopMediaFunctionBase::OnPickerDialogResults, this); - - picker_->Show(web_contents, parent_window, parent_window, - base::UTF8ToUTF16(GetCallerDisplayName()), target_name, - std::move(source_lists), request_audio, callback); + DesktopMediaPicker::Params picker_params; + picker_params.web_contents = web_contents; + picker_params.context = parent_window; + picker_params.parent = parent_window; + picker_params.app_name = base::UTF8ToUTF16(GetCallerDisplayName()); + picker_params.target_name = target_name; + picker_params.request_audio = request_audio; + picker_->Show(picker_params, std::move(source_lists), callback); origin_ = origin; return true; } diff --git a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc index dfa6519275e..0cdd23224d9 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc @@ -21,7 +21,6 @@ #include "chrome/browser/extensions/extension_service_test_with_install.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/scripting_permissions_modifier.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/extensions/unpacked_installer.h" #include "chrome/browser/ui/browser.h" @@ -55,6 +54,7 @@ #include "extensions/common/feature_switch.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/value_builder.h" +#include "extensions/test/test_extension_dir.h" using testing::Return; using testing::_; @@ -156,8 +156,7 @@ bool DeveloperPrivateApiUnitTest::RunFunction( const scoped_refptr<UIThreadExtensionFunction>& function, const base::ListValue& args) { return extension_function_test_utils::RunFunction( - function.get(), args.CreateDeepCopy(), browser(), - extension_function_test_utils::NONE); + function.get(), args.CreateDeepCopy(), browser(), api_test_utils::NONE); } const Extension* DeveloperPrivateApiUnitTest::LoadUnpackedExtension() { diff --git a/chromium/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc b/chromium/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc index a8a5fa6acda..aaa7aced75d 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc @@ -73,7 +73,7 @@ IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectAppWindowView) { base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]", window_view->render_view_id, window_view->render_process_id), - browser(), extension_function_test_utils::NONE); + browser(), api_test_utils::NONE); // Verify that dev tools opened. std::list<AppWindow*> app_windows = @@ -119,7 +119,7 @@ IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectEmbeddedOptionsPage) { function.get(), base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]", view.render_view_id, view.render_process_id), - browser(), extension_function_test_utils::NONE); + browser(), api_test_utils::NONE); // Verify that dev tools opened. content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( diff --git a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.cc b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.cc index 53965e17392..15b031d88d6 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.cc @@ -262,7 +262,7 @@ void ExtensionInfoGenerator::CreateExtensionInfo( if (pending_image_loads_ == 0) { // Don't call the callback re-entrantly. base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(callback, base::Passed(&list_))); + FROM_HERE, base::BindOnce(callback, std::move(list_))); list_.clear(); } else { callback_ = callback; @@ -300,7 +300,7 @@ void ExtensionInfoGenerator::CreateExtensionsInfo( if (pending_image_loads_ == 0) { // Don't call the callback re-entrantly. base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(callback, base::Passed(&list_))); + FROM_HERE, base::BindOnce(callback, std::move(list_))); list_.clear(); } else { callback_ = callback; diff --git a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc index 8eb8f33bd4f..72c1bc79a1a 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc @@ -82,7 +82,7 @@ class ExtensionInfoGeneratorUnitTest : public ExtensionServiceTestBase { EXPECT_EQ(1u, list.size()); if (!list.empty()) info_out->reset(new developer::ExtensionInfo(std::move(list[0]))); - base::ResetAndReturn(&quit_closure_).Run(); + std::move(quit_closure_).Run(); } std::unique_ptr<developer::ExtensionInfo> GenerateExtensionInfo( @@ -103,7 +103,7 @@ class ExtensionInfoGeneratorUnitTest : public ExtensionServiceTestBase { void OnInfosGenerated(ExtensionInfoGenerator::ExtensionInfoList* out, ExtensionInfoGenerator::ExtensionInfoList list) { *out = std::move(list); - base::ResetAndReturn(&quit_closure_).Run(); + std::move(quit_closure_).Run(); } ExtensionInfoGenerator::ExtensionInfoList GenerateExtensionsInfo() { @@ -204,7 +204,7 @@ class ExtensionInfoGeneratorUnitTest : public ExtensionServiceTestBase { } private: - base::Closure quit_closure_; + base::OnceClosure quit_closure_; DISALLOW_COPY_AND_ASSIGN(ExtensionInfoGeneratorUnitTest); }; diff --git a/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc b/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc index e77604705a4..bb87fbcbfbd 100644 --- a/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc +++ b/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc @@ -18,7 +18,7 @@ #include "extensions/browser/api/hid/hid_device_manager.h" #include "extensions/browser/extension_prefs.h" #include "extensions/common/extension.h" -#include "services/device/public/interfaces/hid.mojom.h" +#include "services/device/public/mojom/hid.mojom.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chromium/chrome/browser/extensions/api/dial/dial_api.cc b/chromium/chrome/browser/extensions/api/dial/dial_api.cc index a5922dc9eed..04c086f843e 100644 --- a/chromium/chrome/browser/extensions/api/dial/dial_api.cc +++ b/chromium/chrome/browser/extensions/api/dial/dial_api.cc @@ -255,7 +255,7 @@ void DialFetchDeviceDescriptionFunction::MaybeStartFetch(const GURL& url) { } device_description_fetcher_ = std::make_unique<DeviceDescriptionFetcher>( - url, Profile::FromBrowserContext(browser_context())->GetRequestContext(), + url, base::BindOnce(&DialFetchDeviceDescriptionFunction::OnFetchComplete, this), base::BindOnce(&DialFetchDeviceDescriptionFunction::OnFetchError, this)); diff --git a/chromium/chrome/browser/extensions/api/dial/dial_apitest.cc b/chromium/chrome/browser/extensions/api/dial/dial_apitest.cc index 67049da9bf4..1a8413a289c 100644 --- a/chromium/chrome/browser/extensions/api/dial/dial_apitest.cc +++ b/chromium/chrome/browser/extensions/api/dial/dial_apitest.cc @@ -9,6 +9,8 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/media/router/discovery/dial/dial_registry.h" +#include "chrome/browser/media/router/providers/cast/dual_media_sink_service.h" +#include "chrome/browser/media/router/test/noop_dual_media_sink_service.h" #include "extensions/common/switches.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" @@ -33,6 +35,13 @@ class DialAPITest : public ExtensionApiTest { extensions::switches::kWhitelistedExtensionID, "ddchlicdkolnonkihahngkmmmjnjlkkf"); } + + void SetUp() override { + // Stub out DualMediaSinkService so it does not interfere with the test. + media_router::DualMediaSinkService::SetInstanceForTest( + new media_router::NoopDualMediaSinkService()); + ExtensionApiTest::SetUp(); + } }; } // namespace diff --git a/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc b/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc index e4fd3fb38a5..5e70f755f52 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc @@ -31,9 +31,9 @@ #include "base/strings/string16.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" #include "base/task/cancelable_task_tracker.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/time/time_to_iso8601.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" @@ -56,11 +56,10 @@ #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/common/extensions/api/downloads.h" +#include "components/download/public/common/download_interrupt_reasons.h" +#include "components/download/public/common/download_item.h" +#include "components/download/public/common/download_url_parameters.h" #include "components/web_modal/web_contents_modal_dialog_manager.h" -#include "content/public/browser/download_interrupt_reasons.h" -#include "content/public/browser/download_item.h" -#include "content/public/browser/download_save_info.h" -#include "content/public/browser/download_url_parameters.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" @@ -87,7 +86,7 @@ using content::BrowserContext; using content::BrowserThread; -using content::DownloadItem; +using download::DownloadItem; using content::DownloadManager; namespace download_extension_errors { @@ -193,7 +192,7 @@ const char* const kDangerStrings[] = { kDangerHost, kDangerUnwanted }; -static_assert(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX, +static_assert(arraysize(kDangerStrings) == download::DOWNLOAD_DANGER_TYPE_MAX, "kDangerStrings should have DOWNLOAD_DANGER_TYPE_MAX elements"); // Note: Any change to the state strings, should be accompanied by a @@ -204,38 +203,40 @@ const char* const kStateStrings[] = { kStateInterrupted, kStateInterrupted, }; -static_assert(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE, +static_assert(arraysize(kStateStrings) == + download::DownloadItem::MAX_DOWNLOAD_STATE, "kStateStrings should have MAX_DOWNLOAD_STATE elements"); -const char* DangerString(content::DownloadDangerType danger) { +const char* DangerString(download::DownloadDangerType danger) { DCHECK(danger >= 0); - DCHECK(danger < static_cast<content::DownloadDangerType>( - arraysize(kDangerStrings))); - if (danger < 0 || danger >= static_cast<content::DownloadDangerType>( - arraysize(kDangerStrings))) + DCHECK(danger < + static_cast<download::DownloadDangerType>(arraysize(kDangerStrings))); + if (danger < 0 || danger >= static_cast<download::DownloadDangerType>( + arraysize(kDangerStrings))) return ""; return kDangerStrings[danger]; } -content::DownloadDangerType DangerEnumFromString(const std::string& danger) { +download::DownloadDangerType DangerEnumFromString(const std::string& danger) { for (size_t i = 0; i < arraysize(kDangerStrings); ++i) { if (danger == kDangerStrings[i]) - return static_cast<content::DownloadDangerType>(i); + return static_cast<download::DownloadDangerType>(i); } - return content::DOWNLOAD_DANGER_TYPE_MAX; + return download::DOWNLOAD_DANGER_TYPE_MAX; } -const char* StateString(DownloadItem::DownloadState state) { +const char* StateString(download::DownloadItem::DownloadState state) { DCHECK(state >= 0); - DCHECK(state < static_cast<DownloadItem::DownloadState>( - arraysize(kStateStrings))); - if (state < 0 || state >= static_cast<DownloadItem::DownloadState>( - arraysize(kStateStrings))) + DCHECK(state < static_cast<download::DownloadItem::DownloadState>( + arraysize(kStateStrings))); + if (state < 0 || state >= static_cast<download::DownloadItem::DownloadState>( + arraysize(kStateStrings))) return ""; return kStateStrings[state]; } -DownloadItem::DownloadState StateEnumFromString(const std::string& state) { +download::DownloadItem::DownloadState StateEnumFromString( + const std::string& state) { for (size_t i = 0; i < arraysize(kStateStrings); ++i) { if ((kStateStrings[i] != NULL) && (state == kStateStrings[i])) return static_cast<DownloadItem::DownloadState>(i); @@ -243,15 +244,6 @@ DownloadItem::DownloadState StateEnumFromString(const std::string& state) { return DownloadItem::MAX_DOWNLOAD_STATE; } -std::string TimeToISO8601(const base::Time& t) { - base::Time::Exploded exploded; - t.UTCExplode(&exploded); - return base::StringPrintf( - "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month, - exploded.day_of_month, exploded.hour, exploded.minute, exploded.second, - exploded.millisecond); -} - std::unique_ptr<base::DictionaryValue> DownloadItemToJSON( DownloadItem* download_item, content::BrowserContext* browser_context) { @@ -273,25 +265,27 @@ std::unique_ptr<base::DictionaryValue> DownloadItemToJSON( json->SetBoolean(kCanResumeKey, download_item->CanResume()); json->SetBoolean(kPausedKey, download_item->IsPaused()); json->SetString(kMimeKey, download_item->GetMimeType()); - json->SetString(kStartTimeKey, TimeToISO8601(download_item->GetStartTime())); + json->SetString(kStartTimeKey, + base::TimeToISO8601(download_item->GetStartTime())); json->SetDouble(kBytesReceivedKey, download_item->GetReceivedBytes()); json->SetDouble(kTotalBytesKey, download_item->GetTotalBytes()); json->SetBoolean(kIncognitoKey, browser_context->IsOffTheRecord()); if (download_item->GetState() == DownloadItem::INTERRUPTED) { - json->SetString(kErrorKey, - content::DownloadInterruptReasonToString( - download_item->GetLastReason())); + json->SetString(kErrorKey, download::DownloadInterruptReasonToString( + download_item->GetLastReason())); } else if (download_item->GetState() == DownloadItem::CANCELLED) { json->SetString(kErrorKey, - content::DownloadInterruptReasonToString( - content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED)); + download::DownloadInterruptReasonToString( + download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED)); } if (!download_item->GetEndTime().is_null()) - json->SetString(kEndTimeKey, TimeToISO8601(download_item->GetEndTime())); + json->SetString(kEndTimeKey, + base::TimeToISO8601(download_item->GetEndTime())); base::TimeDelta time_remaining; if (download_item->TimeRemaining(&time_remaining)) { base::Time now = base::Time::Now(); - json->SetString(kEstimatedEndTimeKey, TimeToISO8601(now + time_remaining)); + json->SetString(kEstimatedEndTimeKey, + base::TimeToISO8601(now + time_remaining)); } DownloadedByExtension* by_ext = DownloadedByExtension::Get(download_item); if (by_ext) { @@ -563,9 +557,9 @@ void RunDownloadQuery( std::string danger_string = downloads::ToString(query_in.danger); if (!danger_string.empty()) { - content::DownloadDangerType danger_type = DangerEnumFromString( - danger_string); - if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) { + download::DownloadDangerType danger_type = + DangerEnumFromString(danger_string); + if (danger_type == download::DOWNLOAD_DANGER_TYPE_MAX) { *error = errors::kInvalidDangerType; return; } @@ -768,11 +762,10 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { // Returns false if this |extension_id| was not expected or if this // |extension_id| has already reported. The caller is responsible for // validating |filename|. - bool DeterminerCallback( - Profile* profile, - const std::string& extension_id, - const base::FilePath& filename, - downloads::FilenameConflictAction conflict_action) { + bool DeterminerCallback(content::BrowserContext* browser_context, + const std::string& extension_id, + const base::FilePath& filename, + downloads::FilenameConflictAction conflict_action) { DCHECK_CURRENTLY_ON(BrowserThread::UI); bool found_info = false; for (size_t index = 0; index < determiners_.size(); ++index) { @@ -800,7 +793,7 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { &determined_conflict_action_, &warnings); if (!warnings.empty()) - WarningService::NotifyWarningsOnUI(profile, warnings); + WarningService::NotifyWarningsOnUI(browser_context, warnings); if (winner_extension_id == determiners_[index].extension_id) determiner_ = determiners_[index]; } @@ -965,18 +958,16 @@ const char DownloadedByExtension::kKey[] = "DownloadItem DownloadedByExtension"; DownloadedByExtension* DownloadedByExtension::Get( - content::DownloadItem* item) { + download::DownloadItem* item) { base::SupportsUserData::Data* data = item->GetUserData(kKey); return (data == NULL) ? NULL : static_cast<DownloadedByExtension*>(data); } -DownloadedByExtension::DownloadedByExtension( - content::DownloadItem* item, - const std::string& id, - const std::string& name) - : id_(id), - name_(name) { +DownloadedByExtension::DownloadedByExtension(download::DownloadItem* item, + const std::string& id, + const std::string& name) + : id_(id), name_(name) { item->SetUserData(kKey, base::WrapUnique(this)); } @@ -1030,8 +1021,8 @@ bool DownloadsDownloadFunction::RunAsync() { } } })"); - std::unique_ptr<content::DownloadUrlParameters> download_params( - new content::DownloadUrlParameters( + std::unique_ptr<download::DownloadUrlParameters> download_params( + new download::DownloadUrlParameters( download_url, render_frame_host()->GetProcess()->GetID(), render_frame_host()->GetRenderViewHost()->GetRoutingID(), render_frame_host()->GetRoutingID(), @@ -1082,14 +1073,18 @@ bool DownloadsDownloadFunction::RunAsync() { downloads::ToString(options.method); if (!method_string.empty()) download_params->set_method(method_string); - if (options.body.get()) - download_params->set_post_body(*options.body); + if (options.body.get()) { + download_params->set_post_body( + network::ResourceRequestBody::CreateFromBytes(options.body->data(), + options.body->size())); + } + download_params->set_callback(base::Bind( &DownloadsDownloadFunction::OnStarted, this, creator_suggested_filename, options.conflict_action)); // Prevent login prompts for 401/407 responses. download_params->set_do_not_prompt_for_login(true); - download_params->set_download_source(content::DownloadSource::EXTENSION_API); + download_params->set_download_source(download::DownloadSource::EXTENSION_API); DownloadManager* manager = BrowserContext::GetDownloadManager( current_profile); @@ -1102,11 +1097,11 @@ void DownloadsDownloadFunction::OnStarted( const base::FilePath& creator_suggested_filename, downloads::FilenameConflictAction creator_conflict_action, DownloadItem* item, - content::DownloadInterruptReason interrupt_reason) { + download::DownloadInterruptReason interrupt_reason) { DCHECK_CURRENTLY_ON(BrowserThread::UI); VLOG(1) << __func__ << " " << item << " " << interrupt_reason; if (item) { - DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); + DCHECK_EQ(download::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); SetResult(std::make_unique<base::Value>(static_cast<int>(item->GetId()))); if (!creator_suggested_filename.empty() || (creator_conflict_action != @@ -1124,8 +1119,8 @@ void DownloadsDownloadFunction::OnStarted( new DownloadedByExtension(item, extension()->id(), extension()->name()); item->UpdateObservers(); } else { - DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); - error_ = content::DownloadInterruptReasonToString(interrupt_reason); + DCHECK_NE(download::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); + error_ = download::DownloadInterruptReasonToString(interrupt_reason); } SendResponse(error_.empty()); } @@ -1562,10 +1557,8 @@ bool DownloadsGetFileIconFunction::RunAsync() { float scale = 1.0; content::WebContents* web_contents = dispatcher()->GetVisibleWebContents(); - if (web_contents) { - scale = ui::GetScaleFactorForNativeView( - web_contents->GetRenderWidgetHostView()->GetNativeView()); - } + if (web_contents && web_contents->GetRenderWidgetHostView()) + scale = web_contents->GetRenderWidgetHostView()->GetDeviceScaleFactor(); EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath( download_item->GetTargetFilePath(), scale, @@ -1733,7 +1726,7 @@ void ExtensionDownloadsEventRouter::DetermineFilenameInternal( } bool ExtensionDownloadsEventRouter::DetermineFilename( - Profile* profile, + content::BrowserContext* browser_context, bool include_incognito, const std::string& ext_id, int download_id, @@ -1742,7 +1735,8 @@ bool ExtensionDownloadsEventRouter::DetermineFilename( std::string* error) { DCHECK_CURRENTLY_ON(BrowserThread::UI); RecordApiFunctions(DOWNLOADS_FUNCTION_DETERMINE_FILENAME); - DownloadItem* item = GetDownload(profile, include_incognito, download_id); + DownloadItem* item = + GetDownload(browser_context, include_incognito, download_id); ExtensionDownloadsEventRouterData* data = item ? ExtensionDownloadsEventRouterData::Get(item) : NULL; // maxListeners=1 in downloads.idl and suggestCallback in @@ -1766,8 +1760,8 @@ bool ExtensionDownloadsEventRouter::DetermineFilename( base::FilePath()); // If the invalid filename check is moved to before DeterminerCallback(), then // it will block forever waiting for this ext_id to report. - if (Fault(!data->DeterminerCallback( - profile, ext_id, filename, conflict_action), + if (Fault(!data->DeterminerCallback(browser_context, ext_id, filename, + conflict_action), errors::kUnexpectedDeterminer, error) || Fault((!const_filename.empty() && !valid_filename), errors::kInvalidFilename, error)) diff --git a/chromium/chrome/browser/extensions/api/downloads/downloads_api.h b/chromium/chrome/browser/extensions/api/downloads/downloads_api.h index 084d39b0bc1..cd6e5759ce8 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api.h +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api.h @@ -69,9 +69,9 @@ namespace extensions { class DownloadedByExtension : public base::SupportsUserData::Data { public: - static DownloadedByExtension* Get(content::DownloadItem* item); + static DownloadedByExtension* Get(download::DownloadItem* item); - DownloadedByExtension(content::DownloadItem* item, + DownloadedByExtension(download::DownloadItem* item, const std::string& id, const std::string& name); @@ -100,8 +100,8 @@ class DownloadsDownloadFunction : public ChromeAsyncExtensionFunction { void OnStarted(const base::FilePath& creator_suggested_filename, extensions::api::downloads::FilenameConflictAction creator_conflict_action, - content::DownloadItem* item, - content::DownloadInterruptReason interrupt_reason); + download::DownloadItem* item, + download::DownloadInterruptReason interrupt_reason); DISALLOW_COPY_AND_ASSIGN(DownloadsDownloadFunction); }; @@ -330,7 +330,7 @@ class ExtensionDownloadsEventRouter // existing files, then |overwrite| will be true. Returns true on success, // false otherwise. static bool DetermineFilename( - Profile* profile, + content::BrowserContext* browser_context, bool include_incognito, const std::string& ext_id, int download_id, @@ -352,19 +352,18 @@ class ExtensionDownloadsEventRouter // an extension wants to change the target filename, then |change| will be // called with the new filename and a flag indicating whether the new file // should overwrite any old files of the same name. - void OnDeterminingFilename( - content::DownloadItem* item, - const base::FilePath& suggested_path, - const base::Closure& no_change, - const FilenameChangedCallback& change); + void OnDeterminingFilename(download::DownloadItem* item, + const base::FilePath& suggested_path, + const base::Closure& no_change, + const FilenameChangedCallback& change); // AllDownloadItemNotifier::Observer. void OnDownloadCreated(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; void OnDownloadUpdated(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; void OnDownloadRemoved(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; // extensions::EventRouter::Observer. void OnListenerRemoved(const extensions::EventListenerInfo& details) override; diff --git a/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc index ee607987815..618452122d6 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc @@ -26,28 +26,29 @@ #include "chrome/browser/download/download_file_icon_extractor.h" #include "chrome/browser/download/download_test_file_activity_observer.h" #include "chrome/browser/extensions/api/downloads/downloads_api.h" -#include "chrome/browser/extensions/browser_action_test_util.h" +#include "chrome/browser/extensions/api/downloads_internal/downloads_internal_api.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/platform_util_internal.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/extensions/browser_action_test_util.h" #include "chrome/common/extensions/api/downloads.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/download/public/common/download_item.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/download_item.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_features.h" -#include "content/public/test/controllable_http_response.h" #include "content/public/test/download_test_observer.h" #include "content/public/test/test_download_http_response.h" #include "content/public/test/test_utils.h" @@ -55,6 +56,7 @@ #include "extensions/browser/notification_types.h" #include "net/base/data_url.h" #include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/controllable_http_response.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/url_request/url_request_slow_download_job.h" #include "net/url_request/url_request.h" @@ -69,7 +71,7 @@ using content::BrowserContext; using content::BrowserThread; -using content::DownloadItem; +using download::DownloadItem; using content::DownloadManager; namespace errors = download_extension_errors; @@ -270,7 +272,7 @@ class DownloadExtensionTest : public ExtensionApiTest { // Danger type for the download. Only use DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS // and DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT. - content::DownloadDangerType danger_type; + download::DownloadDangerType danger_type; }; void LoadExtension(const char* name) { @@ -329,10 +331,12 @@ class DownloadExtensionTest : public ExtensionApiTest { DownloadTestFileActivityObserver observer(current_browser()->profile()); observer.EnableFileChooser(false); - first_download_ = std::make_unique<content::ControllableHttpResponse>( - embedded_test_server(), kFirstDownloadUrl); - second_download_ = std::make_unique<content::ControllableHttpResponse>( - embedded_test_server(), kSecondDownloadUrl); + first_download_ = + std::make_unique<net::test_server::ControllableHttpResponse>( + embedded_test_server(), kFirstDownloadUrl); + second_download_ = + std::make_unique<net::test_server::ControllableHttpResponse>( + embedded_test_server(), kSecondDownloadUrl); host_resolver()->AddRule("*", "127.0.0.1"); } @@ -354,10 +358,9 @@ class DownloadExtensionTest : public ExtensionApiTest { current_browser()->profile(), event_name, json_args); } - bool WaitForInterruption( - DownloadItem* item, - content::DownloadInterruptReason expected_error, - const std::string& on_created_event) { + bool WaitForInterruption(DownloadItem* item, + download::DownloadInterruptReason expected_error, + const std::string& on_created_event) { if (!WaitFor(downloads::OnCreated::kEventName, on_created_event)) return false; // Now, onCreated is always fired before interruption. @@ -370,7 +373,7 @@ class DownloadExtensionTest : public ExtensionApiTest { " \"previous\": \"in_progress\"," " \"current\": \"interrupted\"}}]", item->GetId(), - content::DownloadInterruptReasonToString(expected_error).c_str())); + download::DownloadInterruptReasonToString(expected_error).c_str())); } void ClearEvents() { @@ -426,7 +429,7 @@ class DownloadExtensionTest : public ExtensionApiTest { url_chain.push_back(GURL()); for (size_t i = 0; i < count; ++i) { DownloadItem* item = GetOnRecordManager()->CreateDownloadItem( - base::GenerateGUID(), content::DownloadItem::kInvalidId + 1 + i, + base::GenerateGUID(), download::DownloadItem::kInvalidId + 1 + i, downloads_directory().Append(history_info[i].filename), downloads_directory().Append(history_info[i].filename), url_chain, GURL(), GURL(), GURL(), GURL(), std::string(), @@ -440,9 +443,9 @@ class DownloadExtensionTest : public ExtensionApiTest { std::string(), // hash history_info[i].state, // state history_info[i].danger_type, - (history_info[i].state != content::DownloadItem::CANCELLED - ? content::DOWNLOAD_INTERRUPT_REASON_NONE - : content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED), + (history_info[i].state != download::DownloadItem::CANCELLED + ? download::DOWNLOAD_INTERRUPT_REASON_NONE + : download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED), false, // opened current, // last_access_time false, std::vector<DownloadItem::ReceivedSlice>()); @@ -477,7 +480,7 @@ class DownloadExtensionTest : public ExtensionApiTest { } DownloadItem* CreateSlowTestDownload( - content::ControllableHttpResponse* response, + net::test_server::ControllableHttpResponse* response, const std::string& path) { if (!embedded_test_server()->Started()) StartEmbeddedTestServer(); @@ -515,7 +518,8 @@ class DownloadExtensionTest : public ExtensionApiTest { FinishSlowDownloads(second_download_.get()); } - void FinishSlowDownloads(content::ControllableHttpResponse* response) { + void FinishSlowDownloads( + net::test_server::ControllableHttpResponse* response) { std::unique_ptr<content::DownloadTestObserver> observer( CreateDownloadObserver(1)); response->Done(); @@ -547,10 +551,10 @@ class DownloadExtensionTest : public ExtensionApiTest { return result; } - extension_function_test_utils::RunFunctionFlags GetFlags() { - return current_browser()->profile()->IsOffTheRecord() ? - extension_function_test_utils::INCLUDE_INCOGNITO : - extension_function_test_utils::NONE; + api_test_utils::RunFunctionFlags GetFlags() { + return current_browser()->profile()->IsOffTheRecord() + ? api_test_utils::INCLUDE_INCOGNITO + : api_test_utils::NONE; } // extension_function_test_utils::RunFunction*() only uses browser for its @@ -619,8 +623,8 @@ class DownloadExtensionTest : public ExtensionApiTest { Browser* current_browser_; std::unique_ptr<DownloadsEventsListener> events_listener_; - std::unique_ptr<content::ControllableHttpResponse> first_download_; - std::unique_ptr<content::ControllableHttpResponse> second_download_; + std::unique_ptr<net::test_server::ControllableHttpResponse> first_download_; + std::unique_ptr<net::test_server::ControllableHttpResponse> second_download_; DISALLOW_COPY_AND_ASSIGN(DownloadExtensionTest); }; @@ -786,30 +790,30 @@ bool ItemIsInterrupted(DownloadItem* item) { return item->GetState() == DownloadItem::INTERRUPTED; } -content::DownloadInterruptReason InterruptReasonExtensionToContent( +download::DownloadInterruptReason InterruptReasonExtensionToComponent( downloads::InterruptReason error) { switch (error) { case downloads::INTERRUPT_REASON_NONE: - return content::DOWNLOAD_INTERRUPT_REASON_NONE; + return download::DOWNLOAD_INTERRUPT_REASON_NONE; #define INTERRUPT_REASON(name, value) \ case downloads::INTERRUPT_REASON_##name: \ - return content::DOWNLOAD_INTERRUPT_REASON_##name; -#include "content/public/browser/download_interrupt_reason_values.h" + return download::DOWNLOAD_INTERRUPT_REASON_##name; +#include "components/download/public/common/download_interrupt_reason_values.h" #undef INTERRUPT_REASON } NOTREACHED(); - return content::DOWNLOAD_INTERRUPT_REASON_NONE; + return download::DOWNLOAD_INTERRUPT_REASON_NONE; } downloads::InterruptReason InterruptReasonContentToExtension( - content::DownloadInterruptReason error) { + download::DownloadInterruptReason error) { switch (error) { - case content::DOWNLOAD_INTERRUPT_REASON_NONE: + case download::DOWNLOAD_INTERRUPT_REASON_NONE: return downloads::INTERRUPT_REASON_NONE; -#define INTERRUPT_REASON(name, value) \ - case content::DOWNLOAD_INTERRUPT_REASON_##name: \ +#define INTERRUPT_REASON(name, value) \ + case download::DOWNLOAD_INTERRUPT_REASON_##name: \ return downloads::INTERRUPT_REASON_##name; -#include "content/public/browser/download_interrupt_reason_values.h" +#include "components/download/public/common/download_interrupt_reason_values.h" #undef INTERRUPT_REASON } NOTREACHED(); @@ -1063,13 +1067,10 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_FileIcon_History) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("real.txt"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("fake.txt"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; + {FILE_PATH_LITERAL("real.txt"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}, + {FILE_PATH_LITERAL("fake.txt"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}}; DownloadManager::DownloadVector all_downloads; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), &all_downloads)); @@ -1123,6 +1124,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadsShowDefaultFolderFunction) { + platform_util::internal::DisableShellOperationsForTesting(); ScopedCancellingItem item(CreateFirstSlowTestDownload()); ASSERT_TRUE(item.get()); @@ -1144,13 +1146,10 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchFilenameRegex) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("foobar"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; + {FILE_PATH_LITERAL("foobar"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}, + {FILE_PATH_LITERAL("baz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}}; DownloadManager::DownloadVector all_downloads; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), &all_downloads)); @@ -1223,13 +1222,10 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchOrderBy) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; + {FILE_PATH_LITERAL("zzz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}, + {FILE_PATH_LITERAL("baz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}}; DownloadManager::DownloadVector items; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), &items)); @@ -1256,13 +1252,10 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchOrderByEmpty) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; + {FILE_PATH_LITERAL("zzz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}, + {FILE_PATH_LITERAL("baz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}}; DownloadManager::DownloadVector items; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), &items)); @@ -1293,13 +1286,10 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchDanger) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS } - }; + {FILE_PATH_LITERAL("zzz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT}, + {FILE_PATH_LITERAL("baz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}}; DownloadManager::DownloadVector items; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), &items)); @@ -1379,15 +1369,12 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadExtensionTest_SearchPlural) { const HistoryDownloadInfo kHistoryInfo[] = { - { FILE_PATH_LITERAL("aaa"), - DownloadItem::CANCELLED, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS }, - { FILE_PATH_LITERAL("zzz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, - { FILE_PATH_LITERAL("baz"), - DownloadItem::COMPLETE, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT }, + {FILE_PATH_LITERAL("aaa"), DownloadItem::CANCELLED, + download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS}, + {FILE_PATH_LITERAL("zzz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT}, + {FILE_PATH_LITERAL("baz"), DownloadItem::COMPLETE, + download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT}, }; DownloadManager::DownloadVector items; ASSERT_TRUE(CreateHistoryDownloads(kHistoryInfo, arraysize(kHistoryInfo), @@ -1767,7 +1754,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, EXPECT_EQ(GetExtensionURL(), item->GetSiteUrl().spec()); item->SimulateErrorForTesting( - content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); + download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); embedded_test_server_io_runner->PostTask(FROM_HERE, complete_callback); ASSERT_TRUE(WaitFor(downloads::OnChanged::kEventName, @@ -1991,7 +1978,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, DownloadItem* item = GetCurrentManager()->GetDownload(result_id); ASSERT_TRUE(item); ASSERT_TRUE(WaitForInterruption( - item, content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, + item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, "[{\"state\": \"in_progress\"," " \"url\": \"javascript:document.write(\\\"hello\\\");\"}]")); @@ -2003,7 +1990,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, item = GetCurrentManager()->GetDownload(result_id); ASSERT_TRUE(item); ASSERT_TRUE(WaitForInterruption( - item, content::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, + item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, "[{\"state\": \"in_progress\"," " \"url\": \"javascript:return false;\"}]")); @@ -2015,7 +2002,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, item = GetCurrentManager()->GetDownload(result_id); ASSERT_TRUE(item); ASSERT_TRUE(WaitForInterruption( - item, content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, "[{\"state\": \"in_progress\"," " \"url\": \"ftp://example.com/example.txt\"}]")); } @@ -2274,8 +2261,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); ASSERT_TRUE(WaitForInterruption( - item, - content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED, + item, download::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED, base::StringPrintf("[{\"danger\": \"safe\"," " \"incognito\": false," " \"paused\": false," @@ -2367,8 +2353,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); ASSERT_TRUE(WaitForInterruption( - item, - content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + item, download::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, base::StringPrintf("[{\"danger\": \"safe\"," " \"incognito\": false," " \"bytesReceived\": 0.0," @@ -2509,16 +2494,14 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); ASSERT_TRUE(WaitForInterruption( - item, - content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + item, download::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, base::StringPrintf("[{\"danger\": \"safe\"," " \"incognito\": false," " \"mime\": \"\"," " \"paused\": false," " \"id\": %d," " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); + result_id, download_url.c_str()))); } // Test that downloadPostSuccess would fail if the resource requires the POST @@ -2552,16 +2535,14 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); ASSERT_TRUE(WaitForInterruption( - item, - content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + item, download::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, base::StringPrintf("[{\"danger\": \"safe\"," " \"incognito\": false," " \"mime\": \"\"," " \"paused\": false," " \"id\": %d," " \"url\": \"%s\"}]", - result_id, - download_url.c_str()))); + result_id, download_url.c_str()))); } // Test that cancel()ing an in-progress download causes its state to transition @@ -2886,6 +2867,51 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, result_id))); } +// Tests downloadsInternal.determineFilename. +// Regression test for https://crbug.com/815362. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadsInternalDetermineFilename) { + GoOnTheRecord(); + LoadExtension("downloads_split"); + AddFilenameDeterminer(); + ASSERT_TRUE(StartEmbeddedTestServer()); + std::string download_url = embedded_test_server()->GetURL("/slow?0").spec(); + + // Start downloading a file. + std::unique_ptr<base::Value> result(RunFunctionAndReturnResult( + new DownloadsDownloadFunction(), + base::StringPrintf(R"([{"url": "%s"}])", download_url.c_str()))); + ASSERT_TRUE(result.get()); + int result_id = result->GetInt(); + DownloadItem* item = GetCurrentManager()->GetDownload(result_id); + ASSERT_TRUE(item); + ScopedCancellingItem canceller(item); + ASSERT_EQ(download_url, item->GetOriginalUrl().spec()); + + // Wait for the onCreated and onDeterminingFilename events. + ASSERT_TRUE(WaitFor(downloads::OnCreated::kEventName, + base::StringPrintf(R"([{ + "danger": "safe", + "incognito": false, + "id": %d, + "mime": "text/plain", + "paused": false, + "url": "%s" + }])", + result_id, download_url.c_str()))); + ASSERT_TRUE( + WaitFor(downloads::OnDeterminingFilename::kEventName, + base::StringPrintf( + R"([{"id": %d, "filename": "slow.txt"}])", result_id))); + ASSERT_TRUE(item->GetTargetFilePath().empty()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + std::unique_ptr<base::Value> determine_result(RunFunctionAndReturnResult( + new DownloadsInternalDetermineFilenameFunction(), + base::StringPrintf(R"([%d, "", "uniquify"])", result_id))); + EXPECT_FALSE(determine_result.get()); // No return value. +} + IN_PROC_BROWSER_TEST_F( DownloadExtensionTest, DownloadExtensionTest_OnDeterminingFilename_DangerousOverride) { @@ -4315,14 +4341,14 @@ IN_PROC_BROWSER_TEST_F(DownloadsApiTest, DownloadsApiTest) { TEST(DownloadInterruptReasonEnumsSynced, DownloadInterruptReasonEnumsSynced) { -#define INTERRUPT_REASON(name, value) \ - EXPECT_EQ(InterruptReasonContentToExtension( \ - content::DOWNLOAD_INTERRUPT_REASON_##name), \ - downloads::INTERRUPT_REASON_##name); \ - EXPECT_EQ( \ - InterruptReasonExtensionToContent(downloads::INTERRUPT_REASON_##name), \ - content::DOWNLOAD_INTERRUPT_REASON_##name); -#include "content/public/browser/download_interrupt_reason_values.h" // NOLINT +#define INTERRUPT_REASON(name, value) \ + EXPECT_EQ(InterruptReasonContentToExtension( \ + download::DOWNLOAD_INTERRUPT_REASON_##name), \ + downloads::INTERRUPT_REASON_##name); \ + EXPECT_EQ( \ + InterruptReasonExtensionToComponent(downloads::INTERRUPT_REASON_##name), \ + download::DOWNLOAD_INTERRUPT_REASON_##name); +#include "components/download/public/common/download_interrupt_reason_values.h" #undef INTERRUPT_REASON } diff --git a/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.cc b/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.cc index 109d0b84a3e..db6580819bf 100644 --- a/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.cc +++ b/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.cc @@ -19,21 +19,21 @@ DownloadsInternalDetermineFilenameFunction:: typedef extensions::api::downloads_internal::DetermineFilename::Params DetermineFilenameParams; -bool DownloadsInternalDetermineFilenameFunction::RunAsync() { +ExtensionFunction::ResponseAction +DownloadsInternalDetermineFilenameFunction::Run() { std::unique_ptr<DetermineFilenameParams> params( DetermineFilenameParams::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); base::FilePath::StringType filename; EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filename)); - return ExtensionDownloadsEventRouter::DetermineFilename( - GetProfile(), - include_incognito(), - extension()->id(), - params->download_id, - base::FilePath(filename), + std::string error; + bool result = ExtensionDownloadsEventRouter::DetermineFilename( + browser_context(), include_incognito(), extension()->id(), + params->download_id, base::FilePath(filename), extensions::api::downloads::ParseFilenameConflictAction( params->conflict_action), - &error_); + &error); + return RespondNow(result ? NoArguments() : Error(error)); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.h b/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.h index f7da2b58564..4d6acf505cf 100644 --- a/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.h +++ b/chromium/chrome/browser/extensions/api/downloads_internal/downloads_internal_api.h @@ -6,17 +6,17 @@ #define CHROME_BROWSER_EXTENSIONS_API_DOWNLOADS_INTERNAL_DOWNLOADS_INTERNAL_API_H_ #include "base/macros.h" -#include "chrome/browser/extensions/chrome_extension_function.h" +#include "extensions/browser/extension_function.h" namespace extensions { class DownloadsInternalDetermineFilenameFunction - : public ChromeAsyncExtensionFunction { + : public UIThreadExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("downloadsInternal.determineFilename", DOWNLOADSINTERNAL_DETERMINEFILENAME); DownloadsInternalDetermineFilenameFunction(); - bool RunAsync() override; + ResponseAction Run() override; protected: ~DownloadsInternalDetermineFilenameFunction() override; diff --git a/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc b/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc index 7f41f2d8534..c8dc598aa2b 100644 --- a/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc +++ b/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc @@ -43,6 +43,7 @@ #include "components/proximity_auth/screenlock_state.h" #include "components/proximity_auth/switches.h" #include "components/signin/core/account_id/account_id.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/browser_context_keyed_api_factory.h" #include "ui/base/l10n/l10n_util.h" @@ -157,9 +158,8 @@ ExtensionFunction::ResponseAction EasyUnlockPrivateGetStringsFunction::Run() { #endif // defined(OS_CHROMEOS) // Common strings. - strings->SetString( - "learnMoreLinkTitle", - l10n_util::GetStringUTF16(IDS_EASY_UNLOCK_LEARN_MORE_LINK_TITLE)); + strings->SetString("learnMoreLinkTitle", + l10n_util::GetStringUTF16(IDS_LEARN_MORE)); strings->SetString("deviceType", device_type); // Setup notification strings. @@ -302,10 +302,8 @@ ExtensionFunction::ResponseAction EasyUnlockPrivateGetStringsFunction::Run() { "setupAndroidSmartLockDoneButtonText", l10n_util::GetStringUTF16( IDS_EASY_UNLOCK_SETUP_ANDROID_SMART_LOCK_DONE_BUTTON_LABEL)); - strings->SetString( - "setupAndroidSmartLockAboutLinkText", - l10n_util::GetStringUTF16( - IDS_EASY_UNLOCK_SETUP_ANDROID_SMART_LOCK_ABOUT_LINK_TEXT)); + strings->SetString("setupAndroidSmartLockAboutLinkText", + l10n_util::GetStringUTF16(IDS_LEARN_MORE)); // Step 3: Setup completed successfully. strings->SetString( "setupCompleteHeaderTitle", diff --git a/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api_chromeos_unittest.cc b/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api_chromeos_unittest.cc index 290b81d3093..69a148dabf9 100644 --- a/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api_chromeos_unittest.cc +++ b/chromium/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api_chromeos_unittest.cc @@ -184,10 +184,7 @@ TEST_F(EasyUnlockPrivateApiTest, GenerateEcP256KeyPair) { function->set_has_callback(true); ASSERT_TRUE(extension_function_test_utils::RunFunction( - function.get(), - "[]", - browser(), - extension_function_test_utils::NONE)); + function.get(), "[]", browser(), extensions::api_test_utils::NONE)); const base::ListValue* result_list = function->GetResultList(); ASSERT_TRUE(result_list); @@ -231,7 +228,7 @@ TEST_F(EasyUnlockPrivateApiTest, PerformECDHKeyAgreement) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -277,7 +274,7 @@ TEST_F(EasyUnlockPrivateApiTest, CreateSecureMessage) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -307,7 +304,7 @@ TEST_F(EasyUnlockPrivateApiTest, CreateSecureMessage_EmptyOptions) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -346,7 +343,7 @@ TEST_F(EasyUnlockPrivateApiTest, CreateSecureMessage_AsymmetricSign) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -384,7 +381,7 @@ TEST_F(EasyUnlockPrivateApiTest, UnwrapSecureMessage) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -414,7 +411,7 @@ TEST_F(EasyUnlockPrivateApiTest, UnwrapSecureMessage_EmptyOptions) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -450,7 +447,7 @@ TEST_F(EasyUnlockPrivateApiTest, UnwrapSecureMessage_AsymmetricSign) { ASSERT_TRUE(extension_function_test_utils::RunFunction( function.get(), std::move(args), browser(), - extension_function_test_utils::NONE)); + extensions::api_test_utils::NONE)); EXPECT_EQ(expected_result, GetSingleBinaryResultAsString(function.get())); } @@ -500,10 +497,8 @@ TEST_F(EasyUnlockPrivateApiTest, AutoPairing) { scoped_refptr<EasyUnlockPrivateSetAutoPairingResultFunction> function( new EasyUnlockPrivateSetAutoPairingResultFunction()); ASSERT_TRUE(extension_function_test_utils::RunFunction( - function.get(), - "[{\"success\":false, \"errorMessage\":\"fake_error\"}]", - browser(), - extension_function_test_utils::NONE)); + function.get(), "[{\"success\":false, \"errorMessage\":\"fake_error\"}]", + browser(), extensions::api_test_utils::NONE)); EXPECT_FALSE(result.success); EXPECT_EQ("fake_error", result.error); @@ -512,10 +507,8 @@ TEST_F(EasyUnlockPrivateApiTest, AutoPairing) { base::Unretained(&result))); function = new EasyUnlockPrivateSetAutoPairingResultFunction(); ASSERT_TRUE(extension_function_test_utils::RunFunction( - function.get(), - "[{\"success\":true}]", - browser(), - extension_function_test_utils::NONE)); + function.get(), "[{\"success\":true}]", browser(), + extensions::api_test_utils::NONE)); EXPECT_TRUE(result.success); EXPECT_TRUE(result.error.empty()); } diff --git a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc index 51661075426..35679e37510 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc @@ -11,6 +11,8 @@ #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/enterprise_device_attributes.h" +#include "chromeos/system/statistics_provider.h" +#include "components/user_manager/user.h" #include "components/user_manager/user_manager.h" namespace extensions { @@ -18,23 +20,13 @@ namespace extensions { namespace { // Checks for the current browser context if the user is affiliated. -bool IsPermittedToGetDeviceId(content::BrowserContext* context) { +bool IsPermittedToGetDeviceAttributes(content::BrowserContext* context) { const user_manager::User* user = chromeos::ProfileHelper::Get()->GetUserByProfile( Profile::FromBrowserContext(context)); return user->IsAffiliated(); } -// Returns the directory device id for the permitted extensions or an empty -// string. -std::string GetDirectoryDeviceId(content::BrowserContext* context) { - return IsPermittedToGetDeviceId(context) - ? g_browser_process->platform_part() - ->browser_policy_connector_chromeos() - ->GetDirectoryApiID() - : std::string(); -} - } // namespace EnterpriseDeviceAttributesGetDirectoryDeviceIdFunction:: @@ -45,10 +37,71 @@ EnterpriseDeviceAttributesGetDirectoryDeviceIdFunction:: ExtensionFunction::ResponseAction EnterpriseDeviceAttributesGetDirectoryDeviceIdFunction::Run() { - const std::string device_id = GetDirectoryDeviceId(browser_context()); + std::string device_id; + if (IsPermittedToGetDeviceAttributes(browser_context())) { + device_id = g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->GetDirectoryApiID(); + } return RespondNow(ArgumentList( api::enterprise_device_attributes::GetDirectoryDeviceId::Results::Create( device_id))); } +EnterpriseDeviceAttributesGetDeviceSerialNumberFunction:: + EnterpriseDeviceAttributesGetDeviceSerialNumberFunction() {} + +EnterpriseDeviceAttributesGetDeviceSerialNumberFunction:: + ~EnterpriseDeviceAttributesGetDeviceSerialNumberFunction() {} + +ExtensionFunction::ResponseAction +EnterpriseDeviceAttributesGetDeviceSerialNumberFunction::Run() { + std::string serial_number; + if (IsPermittedToGetDeviceAttributes(browser_context())) { + serial_number = chromeos::system::StatisticsProvider::GetInstance() + ->GetEnterpriseMachineID(); + } + return RespondNow(ArgumentList( + api::enterprise_device_attributes::GetDeviceSerialNumber::Results::Create( + serial_number))); +} + +EnterpriseDeviceAttributesGetDeviceAssetIdFunction:: + EnterpriseDeviceAttributesGetDeviceAssetIdFunction() {} + +EnterpriseDeviceAttributesGetDeviceAssetIdFunction:: + ~EnterpriseDeviceAttributesGetDeviceAssetIdFunction() {} + +ExtensionFunction::ResponseAction +EnterpriseDeviceAttributesGetDeviceAssetIdFunction::Run() { + std::string asset_id; + if (IsPermittedToGetDeviceAttributes(browser_context())) { + asset_id = g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->GetDeviceAssetID(); + } + return RespondNow(ArgumentList( + api::enterprise_device_attributes::GetDeviceAssetId::Results::Create( + asset_id))); +} + +EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction:: + EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction() {} + +EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction:: + ~EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction() {} + +ExtensionFunction::ResponseAction +EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction::Run() { + std::string annotated_location; + if (IsPermittedToGetDeviceAttributes(browser_context())) { + annotated_location = g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->GetDeviceAnnotatedLocation(); + } + return RespondNow(ArgumentList( + api::enterprise_device_attributes::GetDeviceAnnotatedLocation::Results:: + Create(annotated_location))); +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h index ca1d02370c7..3f993c2174d 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h +++ b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h @@ -24,5 +24,52 @@ class EnterpriseDeviceAttributesGetDirectoryDeviceIdFunction ENTERPRISE_DEVICEATTRIBUTES_GETDIRECTORYDEVICEID); }; +class EnterpriseDeviceAttributesGetDeviceSerialNumberFunction + : public UIThreadExtensionFunction { + public: + EnterpriseDeviceAttributesGetDeviceSerialNumberFunction(); + + protected: + ~EnterpriseDeviceAttributesGetDeviceSerialNumberFunction() override; + + ResponseAction Run() override; + + private: + DECLARE_EXTENSION_FUNCTION( + "enterprise.deviceAttributes.getDeviceSerialNumber", + ENTERPRISE_DEVICEATTRIBUTES_GETDEVICESERIALNUMBER); +}; + +class EnterpriseDeviceAttributesGetDeviceAssetIdFunction + : public UIThreadExtensionFunction { + public: + EnterpriseDeviceAttributesGetDeviceAssetIdFunction(); + + protected: + ~EnterpriseDeviceAttributesGetDeviceAssetIdFunction() override; + + ResponseAction Run() override; + + private: + DECLARE_EXTENSION_FUNCTION("enterprise.deviceAttributes.getDeviceAssetId", + ENTERPRISE_DEVICEATTRIBUTES_GETDEVICEASSETID); +}; + +class EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction + : public UIThreadExtensionFunction { + public: + EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction(); + + protected: + ~EnterpriseDeviceAttributesGetDeviceAnnotatedLocationFunction() override; + + ResponseAction Run() override; + + private: + DECLARE_EXTENSION_FUNCTION( + "enterprise.deviceAttributes.getDeviceAnnotatedLocation", + ENTERPRISE_DEVICEATTRIBUTES_GETDEVICEANNOTATEDLOCATION); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_DEVICE_ATTRIBUTES_ENTERPRISE_DEVICE_ATTRIBUTES_API_H_ diff --git a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc index b4422a1d396..84603c4d929 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc @@ -4,7 +4,9 @@ #include <memory> +#include "base/json/json_writer.h" #include "base/strings/stringprintf.h" +#include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/policy/affiliation_test_helper.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" @@ -14,6 +16,8 @@ #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/test/base/ui_test_utils.h" #include "chromeos/dbus/fake_session_manager_client.h" +#include "chromeos/system/fake_statistics_provider.h" +#include "chromeos/system/statistics_provider.h" #include "components/policy/core/common/cloud/device_management_service.h" #include "components/policy/core/common/mock_configuration_policy_provider.h" #include "components/policy/policy_constants.h" @@ -31,6 +35,9 @@ namespace { constexpr char kDeviceId[] = "device_id"; +constexpr char kSerialNumber[] = "serial_number"; +constexpr char kAssetId[] = "asset_id"; +constexpr char kAnnotatedLocation[] = "annotated_location"; constexpr base::FilePath::CharType kTestExtensionDir[] = FILE_PATH_LITERAL("extensions/api_test/enterprise_device_attributes"); constexpr base::FilePath::CharType kUpdateManifestFileName[] = @@ -51,6 +58,21 @@ struct Params { bool affiliated_; }; +base::Value BuildCustomArg(const std::string& expected_directory_device_id, + const std::string& expected_serial_number, + const std::string& expected_asset_id, + const std::string& expected_annotated_location) { + base::Value custom_arg(base::Value::Type::DICTIONARY); + custom_arg.SetKey("expectedDirectoryDeviceId", + base::Value(expected_directory_device_id)); + custom_arg.SetKey("expectedSerialNumber", + base::Value(expected_serial_number)); + custom_arg.SetKey("expectedAssetId", base::Value(expected_asset_id)); + custom_arg.SetKey("expectedAnnotatedLocation", + base::Value(expected_annotated_location)); + return custom_arg; +} + } // namespace namespace extensions { @@ -60,6 +82,8 @@ class EnterpriseDeviceAttributesTest : public ::testing::WithParamInterface<Params> { public: EnterpriseDeviceAttributesTest() { + fake_statistics_provider_.SetMachineStatistic( + chromeos::system::kSerialNumberKey, kSerialNumber); set_exit_when_last_browser_closes(false); set_chromeos_user_ = false; } @@ -110,6 +134,8 @@ class EnterpriseDeviceAttributesTest : policy::DevicePolicyBuilder* device_policy = test_helper_.device_policy(); device_policy->SetDefaultSigningKey(); device_policy->policy_data().set_directory_api_id(kDeviceId); + device_policy->policy_data().set_annotated_asset_id(kAssetId); + device_policy->policy_data().set_annotated_location(kAnnotatedLocation); device_policy->Build(); fake_session_manager_client->set_device_policy(device_policy->GetBlob()); @@ -170,9 +196,15 @@ class EnterpriseDeviceAttributesTest : // only very little functionality from RunExtensionSubtest(). Thus so that // don't make RunExtensionSubtest() to complex we just introduce a new // function. - bool TestExtension(Browser* browser, const std::string& page_url) { + bool TestExtension(Browser* browser, + const std::string& page_url, + const base::Value& custom_arg_value) { DCHECK(!page_url.empty()) << "page_url cannot be empty"; + std::string custom_arg; + base::JSONWriter::Write(custom_arg_value, &custom_arg); + SetCustomArg(custom_arg); + extensions::ResultCatcher catcher; ui_test_utils::NavigateToURL(browser, GURL(page_url)); @@ -190,6 +222,7 @@ class EnterpriseDeviceAttributesTest : private: policy::MockConfigurationPolicyProvider policy_provider_; policy::DevicePolicyCrosTestHelper test_helper_; + chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_; }; IN_PROC_BROWSER_TEST_P(EnterpriseDeviceAttributesTest, PRE_Success) { @@ -206,13 +239,21 @@ IN_PROC_BROWSER_TEST_P(EnterpriseDeviceAttributesTest, Success) { EXPECT_EQ(GetParam().affiliated_, user_manager::UserManager::Get()-> FindUser(affiliated_account_id_)->IsAffiliated()); - // Device ID is available only for affiliated user. - std::string device_id = GetParam().affiliated_ ? kDeviceId : ""; + // Device attributes are available only for affiliated user. + std::string expected_directory_device_id = + GetParam().affiliated_ ? kDeviceId : ""; + std::string expected_serial_number = + GetParam().affiliated_ ? kSerialNumber : ""; + std::string expected_asset_id = GetParam().affiliated_ ? kAssetId : ""; + std::string expected_annotated_location = + GetParam().affiliated_ ? kAnnotatedLocation : ""; // Pass the expected value (device_id) to test. - ASSERT_TRUE(TestExtension(CreateBrowser(profile()), - base::StringPrintf("chrome-extension://%s/basic.html?%s", - kTestExtensionID, device_id.c_str()))) + ASSERT_TRUE(TestExtension( + CreateBrowser(profile()), + base::StringPrintf("chrome-extension://%s/basic.html", kTestExtensionID), + BuildCustomArg(expected_directory_device_id, expected_serial_number, + expected_asset_id, expected_annotated_location))) << message_; } diff --git a/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc index 6ca6aa5a0ef..d974bf51ac0 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc @@ -181,7 +181,8 @@ class EPKChallengeKeyTestBase : public BrowserWithTestWindowTest { std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function, std::unique_ptr<base::ListValue> args, Browser* browser) { - utils::RunFunction(function, std::move(args), browser, utils::NONE); + utils::RunFunction(function, std::move(args), browser, + extensions::api_test_utils::NONE); EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type()); return function->GetError(); } @@ -195,7 +196,8 @@ class EPKChallengeKeyTestBase : public BrowserWithTestWindowTest { scoped_refptr<ExtensionFunction> function_owner(function); // Without a callback the function will not generate a result. function->set_has_callback(true); - utils::RunFunction(function, std::move(args), browser, utils::NONE); + utils::RunFunction(function, std::move(args), browser, + extensions::api_test_utils::NONE); EXPECT_TRUE(function->GetError().empty()) << "Unexpected error: " << function->GetError(); const base::Value* single_result = NULL; @@ -324,16 +326,16 @@ TEST_F(EPKChallengeMachineKeyTest, KeyExists) { // GetCertificate must not be called if the key exists. EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)).Times(0); - EXPECT_TRUE( - utils::RunFunction(func_.get(), CreateArgs(), browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(), + extensions::api_test_utils::NONE)); } TEST_F(EPKChallengeMachineKeyTest, KeyNotRegisteredByDefault) { EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _)) .Times(0); - EXPECT_TRUE( - utils::RunFunction(func_.get(), CreateArgs(), browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(), + extensions::api_test_utils::NONE)); } TEST_F(EPKChallengeMachineKeyTest, KeyNotRegistered) { @@ -341,7 +343,7 @@ TEST_F(EPKChallengeMachineKeyTest, KeyNotRegistered) { .Times(0); EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgsNoRegister(), browser(), - utils::NONE)); + extensions::api_test_utils::NONE)); } TEST_F(EPKChallengeMachineKeyTest, Success) { @@ -514,8 +516,8 @@ TEST_F(EPKChallengeUserKeyTest, KeyExists) { // GetCertificate must not be called if the key exists. EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)).Times(0); - EXPECT_TRUE( - utils::RunFunction(func_.get(), CreateArgs(), browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(), + extensions::api_test_utils::NONE)); } TEST_F(EPKChallengeUserKeyTest, KeyNotRegistered) { @@ -523,7 +525,7 @@ TEST_F(EPKChallengeUserKeyTest, KeyNotRegistered) { .Times(0); EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgsNoRegister(), browser(), - utils::NONE)); + extensions::api_test_utils::NONE)); } TEST_F(EPKChallengeUserKeyTest, PersonalDevice) { diff --git a/chromium/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc index f81fd5c960b..03541c19880 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc @@ -320,7 +320,8 @@ TEST_F(EPKPChallengeMachineKeyTest, KeyExists) { EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)) .Times(0); - EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(), + extensions::api_test_utils::NONE)); } TEST_F(EPKPChallengeMachineKeyTest, AttestationNotPrepared) { @@ -363,7 +364,7 @@ TEST_P(EPKPChallengeMachineKeyAllProfilesTest, Success) { .Times(1); std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult( - func_.get(), kArgs, browser(), utils::NONE)); + func_.get(), kArgs, browser(), extensions::api_test_utils::NONE)); std::string response; value->GetAsString(&response); @@ -490,15 +491,16 @@ TEST_F(EPKPChallengeUserKeyTest, KeyExists) { EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)) .Times(0); - EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(), + extensions::api_test_utils::NONE)); } TEST_F(EPKPChallengeUserKeyTest, KeyNotRegistered) { EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _)) .Times(0); - EXPECT_TRUE(utils::RunFunction( - func_.get(), "[\"Y2hhbGxlbmdl\", false]", browser(), utils::NONE)); + EXPECT_TRUE(utils::RunFunction(func_.get(), "[\"Y2hhbGxlbmdl\", false]", + browser(), extensions::api_test_utils::NONE)); } TEST_F(EPKPChallengeUserKeyTest, PersonalDevice) { @@ -533,7 +535,7 @@ TEST_F(EPKPChallengeUserKeyTest, Success) { .Times(1); std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult( - func_.get(), kArgs, browser(), utils::NONE)); + func_.get(), kArgs, browser(), extensions::api_test_utils::NONE)); std::string response; value->GetAsString(&response); diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/OWNERS b/chromium/chrome/browser/extensions/api/experience_sampling_private/OWNERS deleted file mode 100644 index 885f0ab7205..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/OWNERS +++ /dev/null @@ -1 +0,0 @@ -felt@chromium.org diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.cc b/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.cc deleted file mode 100644 index ebf4250039f..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.cc +++ /dev/null @@ -1,113 +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. - -#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h" - -#include <utility> - -#include "base/values.h" -#include "chrome/browser/browser_process.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/common/extensions/api/experience_sampling_private.h" -#include "extensions/browser/event_router.h" -#include "url/gurl.h" - -namespace extensions { - -// static -const char ExperienceSamplingEvent::kProceed[] = "proceed"; -const char ExperienceSamplingEvent::kDeny[] = "deny"; -const char ExperienceSamplingEvent::kIgnore[] = "ignore"; -const char ExperienceSamplingEvent::kCancel[] = "cancel"; -const char ExperienceSamplingEvent::kReload[] = "reload"; - -// static -const char ExperienceSamplingEvent::kMaliciousDownload[] = - "download_warning_malicious"; -const char ExperienceSamplingEvent::kDangerousDownload[] = - "download_warning_dangerous"; -const char ExperienceSamplingEvent::kDownloadDangerPrompt[] = - "download_danger_prompt"; -const char ExperienceSamplingEvent::kExtensionInstallDialog[] = - "extension_install_dialog_"; - -// static -std::unique_ptr<ExperienceSamplingEvent> ExperienceSamplingEvent::Create( - const std::string& element_name, - const GURL& destination, - const GURL& referrer) { - Profile* profile = NULL; - if (g_browser_process->profile_manager()) - profile = g_browser_process->profile_manager()->GetLastUsedProfile(); - if (!profile) - return std::unique_ptr<ExperienceSamplingEvent>(); - return std::unique_ptr<ExperienceSamplingEvent>(new ExperienceSamplingEvent( - element_name, destination, referrer, profile)); -} - -// static -std::unique_ptr<ExperienceSamplingEvent> ExperienceSamplingEvent::Create( - const std::string& element_name) { - return ExperienceSamplingEvent::Create(element_name, GURL(), GURL()); -} - -ExperienceSamplingEvent::ExperienceSamplingEvent( - const std::string& element_name, - const GURL& destination, - const GURL& referrer, - content::BrowserContext* browser_context) - : has_viewed_details_(false), - has_viewed_learn_more_(false), - browser_context_(browser_context) { - ui_element_.name = element_name; - ui_element_.destination = destination.GetAsReferrer().possibly_invalid_spec(); - ui_element_.referrer = referrer.GetAsReferrer().possibly_invalid_spec(); - ui_element_.time = base::Time::Now().ToJsTime(); -} - -ExperienceSamplingEvent::~ExperienceSamplingEvent() { -} - -void ExperienceSamplingEvent::CreateUserDecisionEvent( - const std::string& decision_name) { - // Check if this is from an incognito context. If it is, don't create and send - // any events. - if (browser_context_ && browser_context_->IsOffTheRecord()) - return; - api::experience_sampling_private::UserDecision decision; - decision.name = decision_name; - decision.learn_more = has_viewed_learn_more(); - decision.details = has_viewed_details(); - decision.time = base::Time::Now().ToJsTime(); - - std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append(ui_element_.ToValue()); - args->Append(decision.ToValue()); - std::unique_ptr<Event> event( - new Event(events::EXPERIENCE_SAMPLING_PRIVATE_ON_DECISION, - api::experience_sampling_private::OnDecision::kEventName, - std::move(args))); - EventRouter* router = EventRouter::Get(browser_context_); - if (router) - router->BroadcastEvent(std::move(event)); -} - -void ExperienceSamplingEvent::CreateOnDisplayedEvent() { - // Check if this is from an incognito context. If it is, don't create and send - // any events. - if (browser_context_ && browser_context_->IsOffTheRecord()) - return; - std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append(ui_element_.ToValue()); - std::unique_ptr<Event> event( - new Event(events::EXPERIENCE_SAMPLING_PRIVATE_ON_DISPLAYED, - api::experience_sampling_private::OnDisplayed::kEventName, - std::move(args))); - EventRouter* router = EventRouter::Get(browser_context_); - if (router) - router->BroadcastEvent(std::move(event)); -} - -} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h b/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h deleted file mode 100644 index 30f6a68f0d2..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h +++ /dev/null @@ -1,79 +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. - -#ifndef CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_H_ -#define CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_H_ - -#include <memory> - -#include "base/macros.h" -#include "chrome/common/extensions/api/experience_sampling_private.h" - -namespace content { -class BrowserContext; -} - -class GURL; - -namespace extensions { - -using api::experience_sampling_private::UIElement; - -class ExperienceSamplingEvent { - public: - // String constants for user decision events. - static const char kProceed[]; - static const char kDeny[]; - static const char kIgnore[]; - static const char kCancel[]; - static const char kReload[]; - - // String constants for event names. - static const char kMaliciousDownload[]; - static const char kDangerousDownload[]; - static const char kDownloadDangerPrompt[]; - static const char kExtensionInstallDialog[]; - - // The Create() functions can return an empty scoped_ptr if they cannot find - // the BrowserContext. Code using them should check the scoped pointer using - // scoped_ptr::get(). - static std::unique_ptr<ExperienceSamplingEvent> Create( - const std::string& element_name, - const GURL& destination, - const GURL& referrer); - - static std::unique_ptr<ExperienceSamplingEvent> Create( - const std::string& element_name); - - ExperienceSamplingEvent(const std::string& element_name, - const GURL& destination, - const GURL& referrer, - content::BrowserContext* browser_context); - ~ExperienceSamplingEvent(); - - // Sends an extension API event for the user seeing this event. - void CreateOnDisplayedEvent(); - // Sends an extension API event for the user making a decision about this - // event. - void CreateUserDecisionEvent(const std::string& decision_name); - - bool has_viewed_details() const { return has_viewed_details_; } - void set_has_viewed_details(bool viewed) { has_viewed_details_ = viewed; } - bool has_viewed_learn_more() const { return has_viewed_learn_more_; } - void set_has_viewed_learn_more(bool viewed) { - has_viewed_learn_more_ = viewed; - } - - private: - bool has_viewed_details_; - bool has_viewed_learn_more_; - content::BrowserContext* browser_context_; - UIElement ui_element_; - - DISALLOW_COPY_AND_ASSIGN(ExperienceSamplingEvent); -}; - -} // namespace extensions - -#endif // CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_H_ diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.cc b/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.cc deleted file mode 100644 index f212226b785..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.cc +++ /dev/null @@ -1,26 +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. - -#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.h" - -#include "base/metrics/field_trial.h" -#include "chrome/common/extensions/api/experience_sampling_private.h" - -namespace sampling = extensions::api::experience_sampling_private; - -namespace extensions { - -bool ExperienceSamplingPrivateGetBrowserInfoFunction::RunAsync() { - std::string field_trials; - sampling::BrowserInfo info; - - base::FieldTrialList::StatesToString(&field_trials); - info.variations = field_trials; - - SetResult(info.ToValue()); - SendResponse(true /* success */); - return true; -} - -} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.h b/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.h deleted file mode 100644 index e199f288704..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.h +++ /dev/null @@ -1,27 +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. - -#ifndef CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_PRIVATE_API_H_ -#define CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_PRIVATE_API_H_ - -#include "chrome/browser/extensions/chrome_extension_function.h" - -namespace extensions { - -class ExperienceSamplingPrivateGetBrowserInfoFunction - : public ChromeAsyncExtensionFunction { - protected: - ~ExperienceSamplingPrivateGetBrowserInfoFunction() override {} - - // ExtensionFuction: - bool RunAsync() override; - - private: - DECLARE_EXTENSION_FUNCTION("experienceSamplingPrivate.getBrowserInfo", - EXPERIENCESAMPLINGPRIVATE_GETBROWSERINFO); -}; - -} // namespace extensions - -#endif // CHROME_BROWSER_EXTENSIONS_API_EXPERIENCE_SAMPLING_PRIVATE_EXPERIENCE_SAMPLING_PRIVATE_API_H_ diff --git a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api_unittest.cc deleted file mode 100644 index e71b6037c51..00000000000 --- a/chromium/chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api_unittest.cc +++ /dev/null @@ -1,43 +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. - -#include "base/metrics/field_trial.h" -#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling_private_api.h" -#include "chrome/browser/extensions/extension_api_unittest.h" -#include "chrome/browser/extensions/extension_function_test_utils.h" - - -namespace utils = extension_function_test_utils; - -namespace extensions { - -typedef ExtensionApiUnittest ExperienceSamplingPrivateTest; - -// Tests that chrome.experienceSamplingPrivate.getBrowserInfo() returns expected -// field trials and groups. -TEST_F(ExperienceSamplingPrivateTest, GetBrowserInfoTest) { - // Start with an empty FieldTrialList. - std::unique_ptr<base::FieldTrialList> trial_list( - new base::FieldTrialList(nullptr)); - std::unique_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary( - new ExperienceSamplingPrivateGetBrowserInfoFunction(), "[]")); - ASSERT_TRUE(result->HasKey("variations")); - std::string trials_string; - EXPECT_TRUE(result->GetStringWithoutPathExpansion("variations", - &trials_string)); - ASSERT_EQ("", trials_string); - - // Set field trials using a string. - base::FieldTrialList::CreateTrialsFromString( - "*Some name/Winner/*xxx/yyyy/*zzz/default/", - std::set<std::string>()); - result = RunFunctionAndReturnDictionary( - new ExperienceSamplingPrivateGetBrowserInfoFunction(), "[]"); - ASSERT_TRUE(result->HasKey("variations")); - EXPECT_TRUE(result->GetStringWithoutPathExpansion("variations", - &trials_string)); - ASSERT_EQ("Some name/Winner/xxx/yyyy/zzz/default/", trials_string); -} - -} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc index c057b5bebfb..35432dbe223 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc @@ -12,7 +12,6 @@ #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" -#include "chrome/browser/extensions/browser_action_test_util.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_icon_factory.h" #include "chrome/browser/extensions/extension_action_manager.h" @@ -26,6 +25,7 @@ #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_navigator_params.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/browser_action_test_util.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" @@ -1020,7 +1020,7 @@ IN_PROC_BROWSER_TEST_F(NavigatingExtensionPopupBrowserTest, DownloadViaPost) { downloads_observer.WaitForFinished(); EXPECT_EQ(0u, downloads_observer.NumDangerousDownloadsSeen()); EXPECT_EQ(1u, downloads_observer.NumDownloadsSeenInState( - content::DownloadItem::COMPLETE)); + download::DownloadItem::COMPLETE)); EXPECT_TRUE(base::PathExists(downloads_directory->GetPath().AppendASCII( "download-test3-attachment.gif"))); diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc index d2a853e242d..f709e6c9f33 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc @@ -7,7 +7,6 @@ #include "base/run_loop.h" #include "base/test/test_timeouts.h" #include "build/build_config.h" -#include "chrome/browser/extensions/browser_action_test_util.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_apitest.h" @@ -18,6 +17,7 @@ #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/browser_action_test_util.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model.h" #include "chrome/test/base/interactive_test_utils.h" @@ -134,16 +134,23 @@ class BrowserActionInteractiveTest : public ExtensionApiTest { } // Open an extension popup via the chrome.browserAction.openPopup API. + // If |will_reply| is true, then the listener is responsible for having a + // test message listener that replies to the extension. Otherwise, this + // method will create a listener and reply to the extension before returning + // to avoid leaking an API function while waiting for a reply. void OpenPopupViaAPI(bool will_reply) { // Setup the notification observer to wait for the popup to finish loading. content::WindowedNotificationObserver frame_observer( content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, content::NotificationService::AllSources()); - ExtensionTestMessageListener listener("ready", will_reply); + std::unique_ptr<ExtensionTestMessageListener> listener; + if (!will_reply) + listener = std::make_unique<ExtensionTestMessageListener>("ready", false); // Show first popup in first window and expect it to have loaded. ASSERT_TRUE(RunExtensionSubtest("browser_action/open_popup", "open_popup_succeeds.html")) << message_; - EXPECT_TRUE(listener.WaitUntilSatisfied()); + if (listener) + EXPECT_TRUE(listener->WaitUntilSatisfied()); frame_observer.Wait(); EnsurePopupActive(); } @@ -446,21 +453,20 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, } #if defined(OS_WIN) -// Test that forcibly closing the browser and popup HWND does not cause a crash. -// http://crbug.com/400646 -IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, - DISABLED_DestroyHWNDDoesNotCrash) { +// Forcibly closing a browser HWND with a popup should not cause a crash. +IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, DestroyHWNDDoesNotCrash) { if (!ShouldRunPopupTest()) return; OpenPopupViaAPI(false); BrowserActionTestUtil test_util(browser()); - const gfx::NativeView view = test_util.GetPopupNativeView(); - EXPECT_NE(static_cast<gfx::NativeView>(NULL), view); - const HWND hwnd = views::HWNDForNativeView(view); - EXPECT_EQ(hwnd, - views::HWNDForNativeView(browser()->window()->GetNativeWindow())); - EXPECT_EQ(TRUE, ::IsWindow(hwnd)); + const gfx::NativeView popup_view = test_util.GetPopupNativeView(); + EXPECT_NE(static_cast<gfx::NativeView>(nullptr), popup_view); + const HWND popup_hwnd = views::HWNDForNativeView(popup_view); + EXPECT_EQ(TRUE, ::IsWindow(popup_hwnd)); + const HWND browser_hwnd = + views::HWNDForNativeView(browser()->window()->GetNativeWindow()); + EXPECT_EQ(TRUE, ::IsWindow(browser_hwnd)); // Create a new browser window to prevent the message loop from terminating. browser()->OpenURL(content::OpenURLParams(GURL("about:"), content::Referrer(), @@ -468,9 +474,10 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, ui::PAGE_TRANSITION_TYPED, false)); // Forcibly closing the browser HWND should not cause a crash. - EXPECT_EQ(TRUE, ::CloseWindow(hwnd)); - EXPECT_EQ(TRUE, ::DestroyWindow(hwnd)); - EXPECT_EQ(FALSE, ::IsWindow(hwnd)); + EXPECT_EQ(TRUE, ::CloseWindow(browser_hwnd)); + EXPECT_EQ(TRUE, ::DestroyWindow(browser_hwnd)); + EXPECT_EQ(FALSE, ::IsWindow(browser_hwnd)); + EXPECT_EQ(FALSE, ::IsWindow(popup_hwnd)); } #endif // OS_WIN diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc index 017614b8028..6466d63c75f 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc @@ -96,31 +96,31 @@ static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI>>:: ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context) : browser_context_(context), extension_prefs_(nullptr) { - ExtensionFunctionRegistry* registry = + ExtensionFunctionRegistry& registry = ExtensionFunctionRegistry::GetInstance(); // Browser Actions - registry->RegisterFunction<BrowserActionSetIconFunction>(); - registry->RegisterFunction<BrowserActionSetTitleFunction>(); - registry->RegisterFunction<BrowserActionSetBadgeTextFunction>(); - registry->RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>(); - registry->RegisterFunction<BrowserActionSetPopupFunction>(); - registry->RegisterFunction<BrowserActionGetTitleFunction>(); - registry->RegisterFunction<BrowserActionGetBadgeTextFunction>(); - registry->RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>(); - registry->RegisterFunction<BrowserActionGetPopupFunction>(); - registry->RegisterFunction<BrowserActionEnableFunction>(); - registry->RegisterFunction<BrowserActionDisableFunction>(); - registry->RegisterFunction<BrowserActionOpenPopupFunction>(); + registry.RegisterFunction<BrowserActionSetIconFunction>(); + registry.RegisterFunction<BrowserActionSetTitleFunction>(); + registry.RegisterFunction<BrowserActionSetBadgeTextFunction>(); + registry.RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>(); + registry.RegisterFunction<BrowserActionSetPopupFunction>(); + registry.RegisterFunction<BrowserActionGetTitleFunction>(); + registry.RegisterFunction<BrowserActionGetBadgeTextFunction>(); + registry.RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>(); + registry.RegisterFunction<BrowserActionGetPopupFunction>(); + registry.RegisterFunction<BrowserActionEnableFunction>(); + registry.RegisterFunction<BrowserActionDisableFunction>(); + registry.RegisterFunction<BrowserActionOpenPopupFunction>(); // Page Actions - registry->RegisterFunction<PageActionShowFunction>(); - registry->RegisterFunction<PageActionHideFunction>(); - registry->RegisterFunction<PageActionSetIconFunction>(); - registry->RegisterFunction<PageActionSetTitleFunction>(); - registry->RegisterFunction<PageActionSetPopupFunction>(); - registry->RegisterFunction<PageActionGetTitleFunction>(); - registry->RegisterFunction<PageActionGetPopupFunction>(); + registry.RegisterFunction<PageActionShowFunction>(); + registry.RegisterFunction<PageActionHideFunction>(); + registry.RegisterFunction<PageActionSetIconFunction>(); + registry.RegisterFunction<PageActionSetTitleFunction>(); + registry.RegisterFunction<PageActionSetPopupFunction>(); + registry.RegisterFunction<PageActionGetTitleFunction>(); + registry.RegisterFunction<PageActionGetPopupFunction>(); } ExtensionActionAPI::~ExtensionActionAPI() { @@ -226,8 +226,9 @@ void ExtensionActionAPI::DispatchExtensionActionClicked( if (event_name) { std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append( - ExtensionTabUtil::CreateTabObject(web_contents, extension)->ToValue()); + args->Append(ExtensionTabUtil::CreateTabObject( + web_contents, ExtensionTabUtil::kScrubTab, extension) + ->ToValue()); DispatchEventToExtension(web_contents->GetBrowserContext(), extension_action.extension_id(), histogram_value, diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc index abc8ca885cc..8aaa5170ffd 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc @@ -11,12 +11,12 @@ #include "base/strings/stringprintf.h" #include "chrome/browser/extensions/browsertest_util.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "extensions/browser/state_store.h" #include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/test_extension_dir.h" namespace extensions { namespace { diff --git a/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.cc b/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.cc index af719794f36..0815a0b5907 100644 --- a/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.cc +++ b/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.cc @@ -19,10 +19,10 @@ #include "chrome/grit/generated_resources.h" #include "extensions/common/extension.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/message_center/notification.h" -#include "ui/message_center/notification_delegate.h" -#include "ui/message_center/notification_types.h" -#include "ui/message_center/notifier_id.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notification_delegate.h" +#include "ui/message_center/public/cpp/notification_types.h" +#include "ui/message_center/public/cpp/notifier_id.h" using file_manager::Volume; using message_center::Notification; diff --git a/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc b/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc index 7c80a0cf2c0..373c8081e5f 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc +++ b/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc @@ -518,8 +518,8 @@ class IdentityGetAccountsFunctionTest : public IdentityTestWithSignin { new IdentityGetAccountsFunction); func->set_extension( ExtensionBuilder("Test").SetID(kExtensionId).Build().get()); - if (!utils::RunFunction( - func.get(), std::string("[]"), browser(), utils::NONE)) { + if (!utils::RunFunction(func.get(), std::string("[]"), browser(), + api_test_utils::NONE)) { return GenerateFailureResult(accounts, NULL) << "getAccounts did not return a result."; } @@ -819,7 +819,7 @@ class GetAuthTokenFunctionTest const OAuth2TokenService::ScopeSet& scopes) override { if (on_access_token_requested_.is_null()) return; - base::ResetAndReturn(&on_access_token_requested_).Run(); + std::move(on_access_token_requested_).Run(); } std::string extension_id_; @@ -2020,10 +2020,8 @@ class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest { func->set_extension( ExtensionBuilder("Test").SetID(kExtensionId).Build().get()); return utils::RunFunction( - func.get(), - std::string("[{\"token\": \"") + kAccessToken + "\"}]", - browser(), - extension_function_test_utils::NONE); + func.get(), std::string("[{\"token\": \"") + kAccessToken + "\"}]", + browser(), api_test_utils::NONE); } IdentityAPI* id_api() { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc b/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc index 6281011f2f0..00eb5916020 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc @@ -10,8 +10,8 @@ #include "components/signin/core/browser/profile_management_switches.h" #include "content/public/browser/browser_context.h" #include "content/public/common/service_manager_connection.h" -#include "services/identity/public/interfaces/account.mojom.h" -#include "services/identity/public/interfaces/constants.mojom.h" +#include "services/identity/public/mojom/account.mojom.h" +#include "services/identity/public/mojom/constants.mojom.h" #include "services/service_manager/public/cpp/connector.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.h b/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.h index 1e3973c8d5d..9f9aab7b280 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.h +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_accounts_function.h @@ -7,7 +7,7 @@ #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_function_histogram_value.h" -#include "services/identity/public/interfaces/identity_manager.mojom.h" +#include "services/identity/public/mojom/identity_manager.mojom.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc b/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc index 3d31a6e98d9..ad7069a0f30 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc @@ -28,7 +28,7 @@ #include "extensions/common/extension_l10n_util.h" #include "google_apis/gaia/gaia_urls.h" #include "services/identity/public/cpp/scope_set.h" -#include "services/identity/public/interfaces/constants.mojom.h" +#include "services/identity/public/mojom/constants.mojom.h" #include "services/service_manager/public/cpp/connector.h" #if defined(OS_CHROMEOS) diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h b/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h index bfaca4ed14e..ed99f975a37 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h @@ -13,7 +13,7 @@ #include "google_apis/gaia/oauth2_mint_token_flow.h" #include "google_apis/gaia/oauth2_token_service.h" #include "services/identity/public/cpp/account_state.h" -#include "services/identity/public/interfaces/identity_manager.mojom.h" +#include "services/identity/public/mojom/identity_manager.mojom.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc b/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc index 085f4c7b41c..b5bf6028018 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc @@ -10,7 +10,7 @@ #include "content/public/common/service_manager_connection.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/permissions_data.h" -#include "services/identity/public/interfaces/constants.mojom.h" +#include "services/identity/public/mojom/constants.mojom.h" #include "services/service_manager/public/cpp/connector.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h b/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h index 17a201c0649..72d93ab920a 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h +++ b/chromium/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h @@ -9,7 +9,7 @@ #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_function_histogram_value.h" #include "services/identity/public/cpp/account_state.h" -#include "services/identity/public/interfaces/identity_manager.mojom.h" +#include "services/identity/public/mojom/identity_manager.mojom.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc index 2c4dc2e4cf4..ddcd1a3b317 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc @@ -69,7 +69,6 @@ class ImageWriterPrivateApiTest : public ExtensionApiTest { protected: - base::MessageLoopForUI message_loop_; image_writer::ImageWriterTestUtils test_utils_; }; diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc index 7d81d3c4a6e..8510dfde7a0 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc @@ -11,7 +11,7 @@ #include "base/optional.h" #include "base/threading/thread_restrictions.h" #include "chrome/grit/generated_resources.h" -#include "chrome/services/removable_storage_writer/public/interfaces/constants.mojom.h" +#include "chrome/services/removable_storage_writer/public/mojom/constants.mojom.h" #include "content/public/browser/browser_thread.h" #include "mojo/public/cpp/bindings/binding.h" #include "services/service_manager/public/cpp/connector.h" diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h index 3ab0136b017..6ac29f5ab5e 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h @@ -15,7 +15,7 @@ #include "base/memory/ref_counted.h" #include "base/sequence_checker.h" #include "base/sequenced_task_runner.h" -#include "chrome/services/removable_storage_writer/public/interfaces/removable_storage_writer.mojom.h" +#include "chrome/services/removable_storage_writer/public/mojom/removable_storage_writer.mojom.h" namespace service_manager { class Connector; diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client_browsertest.cc b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client_browsertest.cc index 68e923f6766..5c0a2ce7983 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client_browsertest.cc @@ -15,7 +15,7 @@ #include "base/task_scheduler/post_task.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/extensions/api/image_writer_private/operation.h" -#include "chrome/services/removable_storage_writer/public/interfaces/removable_storage_writer.mojom.h" +#include "chrome/services/removable_storage_writer/public/mojom/removable_storage_writer.mojom.h" #include "chrome/test/base/in_process_browser_test.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/service_manager_connection.h" diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/operation.cc b/chromium/chrome/browser/extensions/api/image_writer_private/operation.cc index 8c89c0334bb..58739bb2437 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/operation.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/operation.cc @@ -251,8 +251,8 @@ void Operation::GetMD5SumOfFile( } } - PostTask(base::BindOnce(&Operation::MD5Chunk, this, Passed(std::move(file)), - 0, file_size, progress_offset, progress_scale, + PostTask(base::BindOnce(&Operation::MD5Chunk, this, std::move(file), 0, + file_size, progress_offset, progress_scale, std::move(callback))); } @@ -294,10 +294,9 @@ void Operation::MD5Chunk( progress_offset; SetProgress(percent_curr); - PostTask(base::BindOnce(&Operation::MD5Chunk, this, - Passed(std::move(file)), bytes_processed + len, - bytes_total, progress_offset, progress_scale, - std::move(callback))); + PostTask(base::BindOnce( + &Operation::MD5Chunk, this, std::move(file), bytes_processed + len, + bytes_total, progress_offset, progress_scale, std::move(callback))); // Skip closing the file. return; } else { diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc b/chromium/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc index fcff500fa4c..3f0a8bceef5 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc @@ -260,7 +260,7 @@ TEST_F(ImageWriterOperationTest, VerifyFileFailure) { test_utils_.GetDevicePath(), kDevicePattern, kTestFileSize); operation_->Start(); - operation_->VerifyWrite(base::Bind(&base::DoNothing)); + operation_->VerifyWrite(base::DoNothing()); content::RunAllTasksUntilIdle(); } #endif // !defined(OS_CHROMEOS) diff --git a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.cc b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.cc index efd57030204..6d1fd598890 100644 --- a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.cc +++ b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.cc @@ -13,6 +13,7 @@ #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "extensions/browser/extension_registry.h" +#include "ui/base/ime/ime_bridge.h" #include "ui/keyboard/keyboard_util.h" namespace input_ime = extensions::api::input_ime; @@ -413,6 +414,9 @@ InputImeAPI::~InputImeAPI() = default; void InputImeAPI::Shutdown() { EventRouter::Get(browser_context_)->UnregisterObserver(this); registrar_.RemoveAll(); + if (observer_ && ui::IMEBridge::Get()) { + ui::IMEBridge::Get()->SetObserver(nullptr); + } } static base::LazyInstance<BrowserContextKeyedAPIFactory<InputImeAPI>>:: diff --git a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.h b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.h index 511b1fc2dfb..6aba7251fcf 100644 --- a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.h +++ b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api.h @@ -24,6 +24,7 @@ #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_registry_observer.h" #include "extensions/common/extension.h" +#include "ui/base/ime/ime_bridge_observer.h" #include "ui/base/ime/ime_engine_handler_interface.h" #include "ui/base/ime/text_input_flags.h" @@ -62,7 +63,6 @@ class ImeObserver : public input_method::InputMethodEngineBase::Observer { int cursor_pos, int anchor_pos, int offset_pos) override; - void OnRequestEngineSwitch() override {} protected: // Helper function used to forward the given event to the |profile_|'s event @@ -176,6 +176,7 @@ class InputImeAPI : public BrowserContextKeyedAPI, // BrowserContextKeyedAPI implementation. static BrowserContextKeyedAPIFactory<InputImeAPI>* GetFactoryInstance(); + void Shutdown() override; // ExtensionRegistryObserver implementation. @@ -210,6 +211,8 @@ class InputImeAPI : public BrowserContextKeyedAPI, extension_registry_observer_; content::NotificationRegistrar registrar_; + + std::unique_ptr<ui::IMEBridgeObserver> observer_; }; InputImeEventRouter* GetInputImeEventRouter(Profile* profile); diff --git a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_nonchromeos.cc b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_nonchromeos.cc index 11bccc7e86d..76519202074 100644 --- a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_nonchromeos.cc +++ b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_nonchromeos.cc @@ -63,6 +63,20 @@ bool IsInputImeEnabled() { switches::kDisableInputImeAPI); } +class ImeBridgeObserver : public ui::IMEBridgeObserver { + public: + void OnRequestSwitchEngine() override { + Browser* browser = chrome::FindLastActive(); + if (!browser) + return; + extensions::InputImeEventRouter* router = + extensions::GetInputImeEventRouter(browser->profile()); + if (!router) + return; + ui::IMEBridge::Get()->SetCurrentEngineHandler(router->active_engine()); + } +}; + class ImeObserverNonChromeOS : public ui::ImeObserver { public: ImeObserverNonChromeOS(const std::string& extension_id, Profile* profile) @@ -94,17 +108,6 @@ class ImeObserverNonChromeOS : public ui::ImeObserver { OnCompositionBoundsChanged::kEventName, std::move(args)); } - void OnRequestEngineSwitch() override { - Browser* browser = chrome::FindLastActive(); - if (!browser) - return; - extensions::InputImeEventRouter* router = - extensions::GetInputImeEventRouter(browser->profile()); - if (!router) - return; - ui::IMEBridge::Get()->SetCurrentEngineHandler(router->active_engine()); - } - private: // ImeObserver overrides. void DispatchEventToExtension( @@ -145,6 +148,10 @@ void InputImeAPI::OnExtensionLoaded(content::BrowserContext* browser_context, const Extension* extension) { // No-op if called multiple times. ui::IMEBridge::Initialize(); + if (!observer_) { + observer_ = std::make_unique<ImeBridgeObserver>(); + ui::IMEBridge::Get()->SetObserver(observer_.get()); + } // Set the preference kPrefNeverActivatedSinceLoaded true to indicate // input.ime.activate API has been never called since loaded. diff --git a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc index e42d686bd00..59177c3676e 100644 --- a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc +++ b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc @@ -12,6 +12,7 @@ #include <utility> #include <vector> +#include "base/containers/flat_set.h" #include "base/feature_list.h" #include "base/stl_util.h" #include "base/strings/string16.h" @@ -32,6 +33,7 @@ #include "chrome/common/extensions/api/language_settings_private.h" #include "chrome/common/pref_names.h" #include "components/language/core/browser/language_model.h" +#include "components/language/core/common/locale_util.h" #include "components/spellcheck/common/spellcheck_common.h" #include "components/translate/core/browser/translate_download_manager.h" #include "components/translate/core/browser/translate_prefs.h" @@ -178,15 +180,14 @@ LanguageSettingsPrivateGetLanguageListFunction::Run() { app_locale, translate_prefs->IsTranslateAllowedByPolicy(), &languages); // Get the list of available locales (display languages) and convert to a set. - const std::vector<std::string>& locales = l10n_util::GetAvailableLocales(); - const std::unordered_set<std::string> locale_set(locales.begin(), - locales.end()); + const base::flat_set<std::string> locale_set( + l10n_util::GetAvailableLocales()); // Get the list of spell check languages and convert to a set. std::vector<std::string> spellcheck_languages = spellcheck::SpellCheckLanguages(); - const std::unordered_set<std::string> spellcheck_language_set( - spellcheck_languages.begin(), spellcheck_languages.end()); + const base::flat_set<std::string> spellcheck_language_set( + std::move(spellcheck_languages)); // Build the language list. std::unique_ptr<base::ListValue> language_list(new base::ListValue); @@ -198,15 +199,20 @@ LanguageSettingsPrivateGetLanguageListFunction::Run() { language.native_display_name = entry.native_display_name; // Set optional fields only if they differ from the default. - if (locale_set.count(entry.code) > 0) { - language.supports_ui.reset(new bool(true)); - } - if (spellcheck_language_set.count(entry.code) > 0) { + if (base::ContainsKey(spellcheck_language_set, entry.code)) { language.supports_spellcheck.reset(new bool(true)); } if (entry.supports_translate) { language.supports_translate.reset(new bool(true)); } + if (base::FeatureList::IsEnabled(translate::kRegionalLocalesAsDisplayUI)) { + std::string temp_locale = entry.code; + if (language::ConvertToActualUILocale(&temp_locale)) { + language.supports_ui.reset(new bool(true)); + } + } else if (base::ContainsKey(locale_set, entry.code)) { + language.supports_ui.reset(new bool(true)); + } language_list->Append(language.ToValue()); } diff --git a/chromium/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc b/chromium/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc index 12e2e46e9c2..8e1ad435a91 100644 --- a/chromium/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc +++ b/chromium/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc @@ -27,8 +27,6 @@ #include "chrome/common/web_application_info.h" #include "components/favicon/core/favicon_service.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/utility_process_host.h" -#include "content/public/browser/utility_process_host_client.h" #include "content/public/browser/web_contents.h" #include "content/public/common/service_manager_connection.h" #include "extensions/browser/api/management/management_api.h" diff --git a/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc b/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc index c4cae1b7845..e144c34a723 100644 --- a/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc @@ -216,10 +216,8 @@ class ExtensionManagementApiEscalationTest : function->SetRenderFrameHost(browser()->tab_strip_model()-> GetActiveWebContents()->GetMainFrame()); bool response = util::RunFunction( - function.get(), - base::StringPrintf("[\"%s\", %s]", kId, enabled_string), - browser(), - util::NONE); + function.get(), base::StringPrintf("[\"%s\", %s]", kId, enabled_string), + browser(), api_test_utils::NONE); if (expected_error.empty()) { EXPECT_EQ(true, response); } else { diff --git a/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc b/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc index 15518413297..4ffb76c9097 100644 --- a/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc @@ -81,7 +81,7 @@ bool ManagementApiUnitTest::RunFunction( const base::ListValue& args) { return extension_function_test_utils::RunFunction( function.get(), base::WrapUnique(args.DeepCopy()), browser(), - extension_function_test_utils::NONE); + api_test_utils::NONE); } void ManagementApiUnitTest::SetUp() { diff --git a/chromium/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc b/chromium/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc index b2e8d86bea0..b2acf5e8de8 100644 --- a/chromium/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc +++ b/chromium/chrome/browser/extensions/api/media_galleries/media_galleries_apitest.cc @@ -27,7 +27,7 @@ #include "chrome/browser/ui/extensions/app_launch_params.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/common/chrome_paths.h" -#include "components/nacl/common/features.h" +#include "components/nacl/common/buildflags.h" #include "components/storage_monitor/storage_info.h" #include "components/storage_monitor/storage_monitor.h" #include "content/public/browser/web_contents.h" diff --git a/chromium/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc b/chromium/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc index 46218419192..4b96ce3fc8f 100644 --- a/chromium/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc @@ -4,6 +4,8 @@ #include "chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h" +#include "base/bind.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/component_updater/cros_component_installer.h" namespace media_perception = extensions::api::media_perception_private; @@ -30,6 +32,13 @@ std::string GetComponentNameForComponentType( return ""; } +void OnLoadComponent( + MediaPerceptionAPIDelegate::LoadCrOSComponentCallback load_callback, + component_updater::CrOSComponentManager::Error error, + const base::FilePath& mount_point) { + std::move(load_callback).Run(mount_point); +} + } // namespace MediaPerceptionAPIDelegateChromeOS::MediaPerceptionAPIDelegateChromeOS() = @@ -43,7 +52,7 @@ void MediaPerceptionAPIDelegateChromeOS::LoadCrOSComponent( g_browser_process->platform_part()->cros_component_manager()->Load( GetComponentNameForComponentType(type), component_updater::CrOSComponentManager::MountPolicy::kMount, - std::move(load_callback)); + base::BindOnce(OnLoadComponent, std::move(load_callback))); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc b/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc index eeaa8191412..37f434a3de3 100644 --- a/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc +++ b/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc @@ -82,7 +82,13 @@ std::unique_ptr<base::DictionaryValue> ChromeMessagingDelegate::MaybeGetTabInfo( // Only the tab id is useful to platform apps for internal use. The // unnecessary bits will be stripped out in // MessagingBindings::DispatchOnConnect(). - return ExtensionTabUtil::CreateTabObject(web_contents)->ToValue(); + // Note: We don't bother scrubbing the tab object, because this is only + // reached as a result of a tab (or content script) messaging the extension. + // We need the extension to see the sender so that it can validate if it + // trusts it or not. + return ExtensionTabUtil::CreateTabObject( + web_contents, ExtensionTabUtil::kDontScrubTab, nullptr) + ->ToValue(); } return nullptr; } diff --git a/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.cc b/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.cc index 17963d18b30..7171d6263c3 100644 --- a/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.cc +++ b/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.cc @@ -36,11 +36,6 @@ IncognitoConnectabilityInfoBarDelegate:: } } -infobars::InfoBarDelegate::Type -IncognitoConnectabilityInfoBarDelegate::GetInfoBarType() const { - return PAGE_ACTION_TYPE; -} - infobars::InfoBarDelegate::InfoBarIdentifier IncognitoConnectabilityInfoBarDelegate::GetIdentifier() const { return INCOGNITO_CONNECTABILITY_INFOBAR_DELEGATE; diff --git a/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h b/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h index 8b067019516..002dfe9c733 100644 --- a/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h +++ b/chromium/chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h @@ -36,7 +36,6 @@ class IncognitoConnectabilityInfoBarDelegate : public ConfirmInfoBarDelegate { ~IncognitoConnectabilityInfoBarDelegate() override; // ConfirmInfoBarDelegate: - Type GetInfoBarType() const override; infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override; base::string16 GetMessageText() const override; base::string16 GetButtonLabel(InfoBarButton button) const override; diff --git a/chromium/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc b/chromium/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc index a0cf87625fd..53effb4a1dd 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc +++ b/chromium/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc @@ -24,7 +24,6 @@ #include "base/strings/stringprintf.h" #include "base/test/test_timeouts.h" #include "base/threading/platform_thread.h" -#include "base/threading/sequenced_worker_pool.h" #include "base/time/time.h" #include "build/build_config.h" #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h" diff --git a/chromium/chrome/browser/extensions/api/messaging/native_process_launcher.cc b/chromium/chrome/browser/extensions/api/messaging/native_process_launcher.cc index ec99eaed500..0d36259d062 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_process_launcher.cc +++ b/chromium/chrome/browser/extensions/api/messaging/native_process_launcher.cc @@ -16,7 +16,6 @@ #include "base/memory/ref_counted.h" #include "base/strings/stringprintf.h" #include "base/task_scheduler/post_task.h" -#include "base/threading/sequenced_worker_pool.h" #include "build/build_config.h" #include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h" #include "chrome/common/chrome_paths.h" @@ -228,8 +227,8 @@ void NativeProcessLauncherImpl::Core::PostErrorResult( content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::BindOnce(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread, - this, callback, error, Passed(base::Process()), - Passed(base::File()), Passed(base::File()))); + this, callback, error, base::Process(), base::File(), + base::File())); } void NativeProcessLauncherImpl::Core::PostResult( @@ -240,8 +239,8 @@ void NativeProcessLauncherImpl::Core::PostResult( content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::BindOnce(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread, - this, callback, RESULT_SUCCESS, Passed(&process), - Passed(&read_file), Passed(&write_file))); + this, callback, RESULT_SUCCESS, std::move(process), + std::move(read_file), std::move(write_file))); } NativeProcessLauncherImpl::NativeProcessLauncherImpl( diff --git a/chromium/chrome/browser/extensions/api/networking_config_chromeos_apitest_chromeos.cc b/chromium/chrome/browser/extensions/api/networking_config_chromeos_apitest_chromeos.cc index e9994eab59b..ae13ce020fb 100644 --- a/chromium/chrome/browser/extensions/api/networking_config_chromeos_apitest_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/networking_config_chromeos_apitest_chromeos.cc @@ -25,8 +25,8 @@ #include "extensions/test/result_catcher.h" #include "net/base/net_errors.h" #include "third_party/cros_system_api/dbus/service_constants.h" -#include "ui/message_center/notification.h" -#include "ui/message_center/notification_delegate.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notification_delegate.h" using chromeos::DBusThreadManager; using chromeos::NetworkPortalDetector; @@ -82,8 +82,7 @@ class NetworkingConfigTest content::RunAllPendingInMessageLoop(); network_portal_detector_ = new NetworkPortalDetectorImpl( - g_browser_process->system_request_context(), - true /* create_notification_controller */); + test_loader_factory(), true /* create_notification_controller */); chromeos::network_portal_detector::InitializeForTesting( network_portal_detector_); network_portal_detector_->Enable(false /* start_detection */); diff --git a/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc b/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc index a03527e9a0d..9ab7402b18d 100644 --- a/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc @@ -8,7 +8,7 @@ #include "base/run_loop.h" #include "base/task_scheduler/post_task.h" #include "chrome/browser/extensions/api/networking_private/networking_private_credentials_getter.h" -#include "chrome/services/wifi_util_win/public/interfaces/wifi_credentials_getter.mojom.h" +#include "chrome/services/wifi_util_win/public/mojom/wifi_credentials_getter.mojom.h" #include "chrome/test/base/in_process_browser_test.h" #include "content/public/browser/browser_thread.h" diff --git a/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_win.cc b/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_win.cc index 467a374a407..8a1bab0998d 100644 --- a/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_win.cc +++ b/chromium/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_win.cc @@ -11,8 +11,8 @@ #include "base/macros.h" #include "base/strings/string_piece.h" #include "chrome/browser/extensions/api/networking_private/networking_private_crypto.h" -#include "chrome/services/wifi_util_win/public/interfaces/constants.mojom.h" -#include "chrome/services/wifi_util_win/public/interfaces/wifi_credentials_getter.mojom.h" +#include "chrome/services/wifi_util_win/public/mojom/constants.mojom.h" +#include "chrome/services/wifi_util_win/public/mojom/wifi_credentials_getter.mojom.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/service_manager_connection.h" #include "services/service_manager/public/cpp/connector.h" diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper.cc b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper.cc index 5df72d7fce9..53b6a30f029 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper.cc +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper.cc @@ -11,7 +11,7 @@ #include "chrome/browser/notifications/notification_display_service.h" #include "chrome/browser/notifications/notification_display_service_factory.h" #include "chrome/browser/notifications/notification_handler.h" -#include "ui/message_center/notification.h" +#include "ui/message_center/public/cpp/notification.h" #include "url/gurl.h" namespace extensions { diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc index 0a29eb6e9e4..da8e0798932 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc @@ -12,13 +12,15 @@ #include "chrome/browser/extensions/api/notifications/extension_notification_display_helper.h" #include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h" #include "chrome/browser/notifications/notification_common.h" +#include "chrome/browser/notifications/notifier_state_tracker.h" +#include "chrome/browser/notifications/notifier_state_tracker_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/notifications.h" #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window_registry.h" #include "extensions/browser/app_window/native_app_window.h" #include "extensions/common/constants.h" -#include "ui/message_center/notifier_id.h" +#include "ui/message_center/public/cpp/notifier_id.h" #include "url/gurl.h" namespace extensions { @@ -107,6 +109,14 @@ void ExtensionNotificationHandler::OnClick( std::move(completed_closure).Run(); } +void ExtensionNotificationHandler::DisableNotifications(Profile* profile, + const GURL& origin) { + message_center::NotifierId notifier_id( + message_center::NotifierId::APPLICATION, origin.host()); + NotifierStateTrackerFactory::GetForProfile(profile)->SetNotifierEnabled( + notifier_id, false /* enabled */); +} + void ExtensionNotificationHandler::SendEvent( Profile* profile, const std::string& extension_id, diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.h b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.h index 062dab0ac8a..60464d79f35 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.h +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.h @@ -36,6 +36,7 @@ class ExtensionNotificationHandler : public NotificationHandler { const base::Optional<int>& action_index, const base::Optional<base::string16>& reply, base::OnceClosure completed_closure) override; + void DisableNotifications(Profile* profile, const GURL& origin) override; protected: // Overriden in unit tests. diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler_unittest.cc b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler_unittest.cc index 03dcc746162..607fd500580 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler_unittest.cc +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler_unittest.cc @@ -72,7 +72,7 @@ TEST_F(ExtensionNotificationHandlerTest, CloseHandler) { handler.SetTestExpectations(kChromeExtensionId, "notifications.onClosed", 2); handler.OnClose(profile.get(), GURL(kChromeExtensionOrigin), kChromeNotificationId, false /* by_user */, - base::BindOnce(&base::DoNothing)); + base::DoNothing()); } TEST_F(ExtensionNotificationHandlerTest, ClickHandler) { @@ -84,7 +84,7 @@ TEST_F(ExtensionNotificationHandlerTest, ClickHandler) { handler.SetTestExpectations(kChromeExtensionId, "notifications.onClicked", 1); handler.OnClick(profile.get(), GURL(kChromeExtensionOrigin), kChromeNotificationId, base::nullopt /* action_index */, - base::nullopt /* reply */, base::BindOnce(&base::DoNothing)); + base::nullopt /* reply */, base::DoNothing()); } TEST_F(ExtensionNotificationHandlerTest, ClickHandlerButton) { @@ -97,7 +97,7 @@ TEST_F(ExtensionNotificationHandlerTest, ClickHandlerButton) { "notifications.onButtonClicked", 2); handler.OnClick(profile.get(), GURL(kChromeExtensionOrigin), kChromeNotificationId, 1 /* action_index */, - base::nullopt /* reply */, base::BindOnce(&base::DoNothing)); + base::nullopt /* reply */, base::DoNothing()); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc b/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc index fb63bc5e496..fa154f233e9 100644 --- a/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc +++ b/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc @@ -48,10 +48,11 @@ #include "ui/gfx/image/image_skia_operations.h" #include "ui/gfx/image/image_skia_rep.h" #include "ui/gfx/skia_util.h" -#include "ui/message_center/notification.h" -#include "ui/message_center/notification_delegate.h" -#include "ui/message_center/notifier_id.h" +#include "ui/message_center/public/cpp/features.h" #include "ui/message_center/public/cpp/message_center_constants.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notification_delegate.h" +#include "ui/message_center/public/cpp/notifier_id.h" #include "url/gurl.h" using message_center::NotifierId; @@ -352,15 +353,19 @@ bool NotificationsApiFunction::CreateNotification( if (has_list_items) { using api::notifications::NotificationItem; for (const NotificationItem& api_item : *options->items) { - optional_fields.items.push_back(message_center::NotificationItem( - base::UTF8ToUTF16(api_item.title), - base::UTF8ToUTF16(api_item.message))); + optional_fields.items.push_back({base::UTF8ToUTF16(api_item.title), + base::UTF8ToUTF16(api_item.message)}); } } if (options->is_clickable.get()) optional_fields.clickable = *options->is_clickable; + optional_fields.settings_button_handler = + base::FeatureList::IsEnabled(message_center::kNewStyleNotifications) + ? message_center::SettingsButtonHandler::INLINE + : message_center::SettingsButtonHandler::NONE; + // TODO(crbug.com/772004): Remove the manual limitation in favor of an IDL // annotation once supported. if (id.size() > kNotificationIdLengthLimit) { @@ -510,9 +515,8 @@ bool NotificationsApiFunction::UpdateNotification( std::vector<message_center::NotificationItem> items; using api::notifications::NotificationItem; for (const NotificationItem& api_item : *options->items) { - items.push_back(message_center::NotificationItem( - base::UTF8ToUTF16(api_item.title), - base::UTF8ToUTF16(api_item.message))); + items.push_back({base::UTF8ToUTF16(api_item.title), + base::UTF8ToUTF16(api_item.message)}); } notification->set_items(items); } diff --git a/chromium/chrome/browser/extensions/api/notifications/notifications_api.h b/chromium/chrome/browser/extensions/api/notifications/notifications_api.h index 74278ac150f..b52400a65a6 100644 --- a/chromium/chrome/browser/extensions/api/notifications/notifications_api.h +++ b/chromium/chrome/browser/extensions/api/notifications/notifications_api.h @@ -11,7 +11,7 @@ #include "chrome/browser/extensions/chrome_extension_function.h" #include "chrome/common/extensions/api/notifications.h" #include "extensions/browser/extension_function.h" -#include "ui/message_center/notification_types.h" +#include "ui/message_center/public/cpp/notification_types.h" namespace message_center { class Notification; diff --git a/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc b/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc index 6b34b68c345..d999be59789 100644 --- a/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc +++ b/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc @@ -38,8 +38,8 @@ #include "extensions/common/features/feature.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" -#include "ui/message_center/notification.h" -#include "ui/message_center/notifier_id.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notifier_id.h" #if defined(OS_MACOSX) #include "base/mac/mac_util.h" @@ -311,7 +311,8 @@ IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestGetPermissionLevel) { notification_function->set_has_callback(true); std::unique_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult( - notification_function.get(), "[]", browser(), utils::NONE)); + notification_function.get(), "[]", browser(), + extensions::api_test_utils::NONE)); EXPECT_EQ(base::Value::Type::STRING, result->type()); std::string permission_level; @@ -334,7 +335,8 @@ IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestGetPermissionLevel) { GetNotifierStateTracker()->SetNotifierEnabled(notifier_id, false); std::unique_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult( - notification_function.get(), "[]", browser(), utils::NONE)); + notification_function.get(), "[]", browser(), + extensions::api_test_utils::NONE)); EXPECT_EQ(base::Value::Type::STRING, result->type()); std::string permission_level; diff --git a/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.cc b/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.cc index bc265038114..0a6441b4d03 100644 --- a/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.cc +++ b/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.cc @@ -72,9 +72,9 @@ PageCaptureSaveAsMHTMLFunction::PageCaptureSaveAsMHTMLFunction() { PageCaptureSaveAsMHTMLFunction::~PageCaptureSaveAsMHTMLFunction() { if (mhtml_file_.get()) { - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::BindOnce(&ClearFileReferenceOnIOThread, - base::Passed(&mhtml_file_))); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&ClearFileReferenceOnIOThread, std::move(mhtml_file_))); } } diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc index eebc8e3692e..f1b79559ce1 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc @@ -192,8 +192,50 @@ PasswordsPrivateExportPasswordsFunction::Run() { PasswordsPrivateDelegate* delegate = PasswordsPrivateDelegateFactory::GetForBrowserContext(browser_context(), true /* create */); - delegate->ExportPasswords(GetAssociatedWebContents()); + delegate->ExportPasswords( + base::BindOnce( + &PasswordsPrivateExportPasswordsFunction::ExportRequestCompleted, + this), + GetAssociatedWebContents()); + return RespondLater(); +} + +void PasswordsPrivateExportPasswordsFunction::ExportRequestCompleted( + const std::string& error) { + if (error.empty()) + Respond(NoArguments()); + else + Error(error); +} + +//////////////////////////////////////////////////////////////////////////////// +// PasswordsPrivateCancelExportPasswordsFunction + +PasswordsPrivateCancelExportPasswordsFunction:: + ~PasswordsPrivateCancelExportPasswordsFunction() {} + +ExtensionFunction::ResponseAction +PasswordsPrivateCancelExportPasswordsFunction::Run() { + PasswordsPrivateDelegate* delegate = + PasswordsPrivateDelegateFactory::GetForBrowserContext(browser_context(), + true /* create */); + delegate->CancelExportPasswords(); return RespondNow(NoArguments()); } +//////////////////////////////////////////////////////////////////////////////// +// PasswordsPrivateRequestExportProgressStatusFunction + +PasswordsPrivateRequestExportProgressStatusFunction:: + ~PasswordsPrivateRequestExportProgressStatusFunction() {} + +ExtensionFunction::ResponseAction +PasswordsPrivateRequestExportProgressStatusFunction::Run() { + PasswordsPrivateDelegate* delegate = + PasswordsPrivateDelegateFactory::GetForBrowserContext(browser_context(), + true /* create */); + return RespondNow(OneArgument(std::make_unique<base::Value>( + ToString(delegate->GetExportProgressStatus())))); +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.h index 59f7e3caf2a..f541487c4f4 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_api.h @@ -155,9 +155,45 @@ class PasswordsPrivateExportPasswordsFunction ResponseAction Run() override; private: + void ExportRequestCompleted(const std::string& error); + DISALLOW_COPY_AND_ASSIGN(PasswordsPrivateExportPasswordsFunction); }; +class PasswordsPrivateCancelExportPasswordsFunction + : public UIThreadExtensionFunction { + public: + PasswordsPrivateCancelExportPasswordsFunction() {} + DECLARE_EXTENSION_FUNCTION("passwordsPrivate.cancelExportPasswords", + PASSWORDSPRIVATE_CANCELEXPORTPASSWORDS); + + protected: + ~PasswordsPrivateCancelExportPasswordsFunction() override; + + // ExtensionFunction overrides. + ResponseAction Run() override; + + private: + DISALLOW_COPY_AND_ASSIGN(PasswordsPrivateCancelExportPasswordsFunction); +}; + +class PasswordsPrivateRequestExportProgressStatusFunction + : public UIThreadExtensionFunction { + public: + PasswordsPrivateRequestExportProgressStatusFunction() {} + DECLARE_EXTENSION_FUNCTION("passwordsPrivate.requestExportProgressStatus", + PASSWORDSPRIVATE_REQUESTEXPORTPROGRESSSTATUS); + + protected: + ~PasswordsPrivateRequestExportProgressStatusFunction() override; + + // ExtensionFunction overrides. + ResponseAction Run() override; + + private: + DISALLOW_COPY_AND_ASSIGN(PasswordsPrivateRequestExportProgressStatusFunction); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORDS_PRIVATE_API_H_ diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc index 09c69a450f0..6a1ac5b9d6b 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc @@ -150,15 +150,30 @@ class TestDelegate : public PasswordsPrivateDelegate { importPasswordsTriggered = true; } - void ExportPasswords(content::WebContents* web_contents) override { + void ExportPasswords(base::OnceCallback<void(const std::string&)> callback, + content::WebContents* web_contents) override { // The testing of password exporting itself should be handled via // |PasswordManagerPorter|. exportPasswordsTriggered = true; + std::move(callback).Run(std::string()); + } + + void CancelExportPasswords() override { + cancelExportPasswordsTriggered = true; + } + + api::passwords_private::ExportProgressStatus GetExportProgressStatus() + override { + // The testing of password exporting itself should be handled via + // |PasswordManagerPorter|. + return api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_IN_PROGRESS; } // Flags for detecting whether import/export operations have been invoked. bool importPasswordsTriggered = false; bool exportPasswordsTriggered = false; + bool cancelExportPasswordsTriggered = false; private: // The current list of entries/exceptions. Cached here so that when new @@ -220,6 +235,10 @@ class PasswordsPrivateApiTest : public ExtensionApiTest { return s_test_delegate_->exportPasswordsTriggered; } + bool cancelExportPasswordsWasTriggered() { + return s_test_delegate_->cancelExportPasswordsTriggered; + } + private: static TestDelegate* s_test_delegate_; @@ -281,4 +300,17 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, ExportPasswords) { } } +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, CancelExportPasswords) { + EXPECT_FALSE(cancelExportPasswordsWasTriggered()); + EXPECT_TRUE(RunPasswordsSubtest("cancelExportPasswords")) << message_; + + if (!ExtensionApiTest::ExtensionSubtestsAreSkipped()) { + EXPECT_TRUE(cancelExportPasswordsWasTriggered()); + } +} + +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestExportProgressStatus) { + EXPECT_TRUE(RunPasswordsSubtest("requestExportProgressStatus")) << message_; +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h index 4ac97e242d3..3b921ff9bc8 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h @@ -17,6 +17,7 @@ #include "chrome/browser/ui/passwords/password_ui_view.h" #include "chrome/common/extensions/api/passwords_private.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/password_manager/core/browser/ui/export_progress_status.h" #include "extensions/browser/extension_function.h" namespace content { @@ -76,8 +77,18 @@ class PasswordsPrivateDelegate : public KeyedService { virtual void ImportPasswords(content::WebContents* web_contents) = 0; // Trigger the password export procedure, allowing the user to save a file - // containing their passwords. - virtual void ExportPasswords(content::WebContents* web_contents) = 0; + // containing their passwords. |callback| will be called with an error + // message if the request is rejected, because another export is in progress. + virtual void ExportPasswords( + base::OnceCallback<void(const std::string&)> callback, + content::WebContents* web_contents) = 0; + + // Cancel any ongoing export. + virtual void CancelExportPasswords() = 0; + + // Get the most recent progress status. + virtual api::passwords_private::ExportProgressStatus + GetExportProgressStatus() = 0; }; } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc index 30280a6a7d5..8b975a1f16c 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc @@ -31,6 +31,43 @@ #include "chrome/browser/password_manager/password_manager_util_mac.h" #endif +namespace { + +// The error message returned to the UI when Chrome refuses to start multiple +// exports. +const char kExportInProgress[] = "in-progress"; +// The error message returned to the UI when the user fails to reauthenticate. +const char kReauthenticationFailed[] = "reauth-failed"; + +// Map password_manager::ExportProgressStatus to +// extensions::api::passwords_private::ExportProgressStatus. +extensions::api::passwords_private::ExportProgressStatus ConvertStatus( + password_manager::ExportProgressStatus status) { + switch (status) { + case password_manager::ExportProgressStatus::NOT_STARTED: + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_NOT_STARTED; + case password_manager::ExportProgressStatus::IN_PROGRESS: + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_IN_PROGRESS; + case password_manager::ExportProgressStatus::SUCCEEDED: + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_SUCCEEDED; + case password_manager::ExportProgressStatus::FAILED_CANCELLED: + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_FAILED_CANCELLED; + case password_manager::ExportProgressStatus::FAILED_WRITE_FAILED: + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_FAILED_WRITE_FAILED; + } + + NOTREACHED(); + return extensions::api::passwords_private::ExportProgressStatus:: + EXPORT_PROGRESS_STATUS_NONE; +} + +} // namespace + namespace extensions { PasswordsPrivateDelegateImpl::PasswordsPrivateDelegateImpl(Profile* profile) @@ -38,7 +75,10 @@ PasswordsPrivateDelegateImpl::PasswordsPrivateDelegateImpl(Profile* profile) password_manager_presenter_( std::make_unique<PasswordManagerPresenter>(this)), password_manager_porter_(std::make_unique<PasswordManagerPorter>( - password_manager_presenter_.get())), + password_manager_presenter_.get(), + base::BindRepeating( + &PasswordsPrivateDelegateImpl::OnPasswordsExportProgress, + base::Unretained(this)))), password_access_authenticator_( base::BindRepeating(&PasswordsPrivateDelegateImpl::OsReauthCall, base::Unretained(this))), @@ -132,7 +172,8 @@ void PasswordsPrivateDelegateImpl::RequestShowPasswordInternal( // TODO(stevenjb): Pass this directly to RequestShowPassword(); see // crbug.com/495290. web_contents_ = web_contents; - if (!password_access_authenticator_.EnsureUserIsAuthenticated()) { + if (!password_access_authenticator_.EnsureUserIsAuthenticated( + password_manager::ReauthPurpose::VIEW_PASSWORD)) { return; } @@ -140,11 +181,13 @@ void PasswordsPrivateDelegateImpl::RequestShowPasswordInternal( password_manager_presenter_->RequestShowPassword(index); } -bool PasswordsPrivateDelegateImpl::OsReauthCall() { +bool PasswordsPrivateDelegateImpl::OsReauthCall( + password_manager::ReauthPurpose purpose) { #if defined(OS_WIN) - return password_manager_util_win::AuthenticateUser(GetNativeWindow()); + return password_manager_util_win::AuthenticateUser(GetNativeWindow(), + purpose); #elif defined(OS_MACOSX) - return password_manager_util_mac::AuthenticateUser(); + return password_manager_util_mac::AuthenticateUser(purpose); #else return true; #endif @@ -240,18 +283,31 @@ void PasswordsPrivateDelegateImpl::ImportPasswords( } void PasswordsPrivateDelegateImpl::ExportPasswords( + base::OnceCallback<void(const std::string&)> callback, content::WebContents* web_contents) { // Save |web_contents| so that it can be used later when GetNativeWindow() is // called. Note: This is safe because the |web_contents| is used before // exiting this method. TODO(crbug.com/495290): Pass the native window // directly to the reauth-handling code. web_contents_ = web_contents; - if (!password_access_authenticator_.ForceUserReauthentication()) { + if (!password_access_authenticator_.ForceUserReauthentication( + password_manager::ReauthPurpose::EXPORT)) { + std::move(callback).Run(kReauthenticationFailed); return; } password_manager_porter_->set_web_contents(web_contents); - password_manager_porter_->Store(); + bool accepted = password_manager_porter_->Store(); + std::move(callback).Run(accepted ? std::string() : kExportInProgress); +} + +void PasswordsPrivateDelegateImpl::CancelExportPasswords() { + password_manager_porter_->CancelStore(); +} + +api::passwords_private::ExportProgressStatus +PasswordsPrivateDelegateImpl::GetExportProgressStatus() { + return ConvertStatus(password_manager_porter_->GetExportProgressStatus()); } #if !defined(OS_ANDROID) @@ -261,13 +317,23 @@ gfx::NativeWindow PasswordsPrivateDelegateImpl::GetNativeWindow() const { } #endif +void PasswordsPrivateDelegateImpl::OnPasswordsExportProgress( + password_manager::ExportProgressStatus status, + const std::string& folder_name) { + PasswordsPrivateEventRouter* router = + PasswordsPrivateEventRouterFactory::GetForProfile(profile_); + if (router) { + router->OnPasswordsExportProgress(ConvertStatus(status), folder_name); + } +} + void PasswordsPrivateDelegateImpl::Shutdown() { password_manager_presenter_.reset(); password_manager_porter_.reset(); } void PasswordsPrivateDelegateImpl::SetOsReauthCallForTesting( - base::RepeatingCallback<bool()> os_reauth_call) { + PasswordAccessAuthenticator::ReauthCallback os_reauth_call) { password_access_authenticator_.SetOsReauthCallForTesting( std::move(os_reauth_call)); } diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h index a7c7e4a6a3f..c9d02bcb6d0 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h @@ -16,12 +16,14 @@ #include "base/observer_list.h" #include "build/build_config.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h" +#include "chrome/browser/password_manager/reauth_purpose.h" #include "chrome/browser/ui/passwords/password_access_authenticator.h" #include "chrome/browser/ui/passwords/password_manager_porter.h" #include "chrome/browser/ui/passwords/password_manager_presenter.h" #include "chrome/browser/ui/passwords/password_ui_view.h" #include "chrome/common/extensions/api/passwords_private.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/password_manager/core/browser/ui/export_progress_status.h" #include "extensions/browser/extension_function.h" class Profile; @@ -51,7 +53,11 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, void RequestShowPassword(size_t index, content::WebContents* web_contents) override; void ImportPasswords(content::WebContents* web_contents) override; - void ExportPasswords(content::WebContents* web_contents) override; + void ExportPasswords(base::OnceCallback<void(const std::string&)> accepted, + content::WebContents* web_contents) override; + void CancelExportPasswords() override; + api::passwords_private::ExportProgressStatus GetExportProgressStatus() + override; // PasswordUIView implementation. Profile* GetProfile() override; @@ -68,12 +74,17 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, gfx::NativeWindow GetNativeWindow() const override; #endif + // Callback for when the password list has been written to the destination. + void OnPasswordsExportProgress(password_manager::ExportProgressStatus status, + const std::string& folder_name); + // KeyedService overrides: void Shutdown() override; // Use this in tests to mock the OS-level reauthentication. void SetOsReauthCallForTesting( - base::RepeatingCallback<bool()> os_reauth_call); + base::RepeatingCallback<bool(password_manager::ReauthPurpose)> + os_reauth_call); private: // Called after the lists are fetched. Once both lists have been set, the @@ -93,7 +104,7 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, // Triggers an OS-dependent UI to present OS account login challenge and // returns true if the user passed that challenge. - bool OsReauthCall(); + bool OsReauthCall(password_manager::ReauthPurpose purpose); // Not owned by this class. Profile* profile_; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc index 18816b55a21..4b8102b3239 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc @@ -13,6 +13,7 @@ #include "base/macros.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/mock_callback.h" #include "base/values.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h" @@ -27,6 +28,8 @@ #include "testing/gtest/include/gtest/gtest.h" using PasswordFormList = std::vector<std::unique_ptr<autofill::PasswordForm>>; +using ::testing::Ne; +using ::testing::StrictMock; namespace extensions { @@ -99,7 +102,9 @@ void PasswordEventObserver::OnBroadcastEvent(const extensions::Event& event) { enum class ReauthResult { PASS, FAIL }; -bool FakeOsReauthCall(bool* reauth_called, ReauthResult result) { +bool FakeOsReauthCall(bool* reauth_called, + ReauthResult result, + password_manager::ReauthPurpose purpose) { *reauth_called = true; return result == ReauthResult::PASS; } @@ -255,6 +260,8 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestFailedReauthOnView) { TEST_F(PasswordsPrivateDelegateImplTest, TestReauthOnExport) { SetUpPasswordStore({CreateSampleForm()}); + StrictMock<base::MockCallback<base::OnceCallback<void(const std::string&)>>> + mock_accepted; PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -265,17 +272,35 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestReauthOnExport) { delegate.SetOsReauthCallForTesting(base::BindRepeating( &FakeOsReauthCall, &reauth_called, ReauthResult::PASS)); - delegate.ExportPasswords(nullptr); + EXPECT_CALL(mock_accepted, Run(std::string())).Times(2); + + delegate.ExportPasswords(mock_accepted.Get(), nullptr); EXPECT_TRUE(reauth_called); // Export should ignore previous reauthentication results. reauth_called = false; - delegate.ExportPasswords(nullptr); + delegate.ExportPasswords(mock_accepted.Get(), nullptr); EXPECT_TRUE(reauth_called); +} + +TEST_F(PasswordsPrivateDelegateImplTest, TestReauthFailedOnExport) { + SetUpPasswordStore({CreateSampleForm()}); + StrictMock<base::MockCallback<base::OnceCallback<void(const std::string&)>>> + mock_accepted; - // TODO(crbug.com/341477): Once the export flow has defined messages to UI, - // such as progress indication, intercept them with PasswordEventObserver and - // check that exporting is aborted if the authentication failed. + PasswordsPrivateDelegateImpl delegate(&profile_); + // Spin the loop to allow PasswordStore tasks posted on the creation of + // |delegate| to be completed. + base::RunLoop().RunUntilIdle(); + + EXPECT_CALL(mock_accepted, Run(std::string("reauth-failed"))); + + bool reauth_called = false; + delegate.SetOsReauthCallForTesting(base::BindRepeating( + &FakeOsReauthCall, &reauth_called, ReauthResult::FAIL)); + + delegate.ExportPasswords(mock_accepted.Get(), nullptr); + EXPECT_TRUE(reauth_called); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.cc index ca05c357e70..3851d77d807 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.cc @@ -42,10 +42,10 @@ void PasswordsPrivateEventRouter::SendSavedPasswordListToListeners() { // If there is nothing to send, return early. return; - std::unique_ptr<Event> extension_event( - new Event(events::PASSWORDS_PRIVATE_ON_SAVED_PASSWORDS_LIST_CHANGED, - api::passwords_private::OnSavedPasswordsListChanged::kEventName, - cached_saved_password_parameters_->CreateDeepCopy())); + auto extension_event = std::make_unique<Event>( + events::PASSWORDS_PRIVATE_ON_SAVED_PASSWORDS_LIST_CHANGED, + api::passwords_private::OnSavedPasswordsListChanged::kEventName, + cached_saved_password_parameters_->CreateDeepCopy()); event_router_->BroadcastEvent(std::move(extension_event)); } @@ -62,10 +62,10 @@ void PasswordsPrivateEventRouter::SendPasswordExceptionListToListeners() { // If there is nothing to send, return early. return; - std::unique_ptr<Event> extension_event(new Event( + auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_PASSWORD_EXCEPTIONS_LIST_CHANGED, api::passwords_private::OnPasswordExceptionsListChanged::kEventName, - cached_password_exception_parameters_->CreateDeepCopy())); + cached_password_exception_parameters_->CreateDeepCopy()); event_router_->BroadcastEvent(std::move(extension_event)); } @@ -76,13 +76,30 @@ void PasswordsPrivateEventRouter::OnPlaintextPasswordFetched( params.index = index; params.plaintext_password = plaintext_password; - std::unique_ptr<base::ListValue> event_value(new base::ListValue); + auto event_value = std::make_unique<base::ListValue>(); event_value->Append(params.ToValue()); - std::unique_ptr<Event> extension_event(new Event( + auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_PLAINTEXT_PASSWORD_RETRIEVED, api::passwords_private::OnPlaintextPasswordRetrieved::kEventName, - std::move(event_value))); + std::move(event_value)); + event_router_->BroadcastEvent(std::move(extension_event)); +} + +void PasswordsPrivateEventRouter::OnPasswordsExportProgress( + api::passwords_private::ExportProgressStatus status, + const std::string& folder_name) { + api::passwords_private::PasswordExportProgress params; + params.status = status; + params.folder_name = std::make_unique<std::string>(std::move(folder_name)); + + auto event_value = std::make_unique<base::ListValue>(); + event_value->Append(params.ToValue()); + + auto extension_event = std::make_unique<Event>( + events::PASSWORDS_PRIVATE_ON_PASSWORDS_FILE_EXPORT_PROGRESS, + api::passwords_private::OnPasswordsFileExportProgress::kEventName, + std::move(event_value)); event_router_->BroadcastEvent(std::move(extension_event)); } diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h index b1d529327d2..b4bcfa6284c 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h @@ -5,6 +5,10 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORDS_PRIVATE_EVENT_ROUTER_H_ #define CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORDS_PRIVATE_EVENT_ROUTER_H_ +#include <memory> +#include <string> +#include <vector> + #include "base/macros.h" #include "chrome/common/extensions/api/passwords_private.h" #include "components/keyed_service/core/keyed_service.h" @@ -41,6 +45,14 @@ class PasswordsPrivateEventRouter : public KeyedService { void OnPlaintextPasswordFetched(size_t index, const std::string& plaintext_password); + // Notifies listeners after the passwords have been written to the export + // destination. + // |folder_name| In case of failure to export, this will describe destination + // we tried to write on. + void OnPasswordsExportProgress( + api::passwords_private::ExportProgressStatus status, + const std::string& folder_name); + protected: explicit PasswordsPrivateEventRouter(content::BrowserContext* context); diff --git a/chromium/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc b/chromium/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc index 28b90107c8b..a365158dbde 100644 --- a/chromium/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc @@ -59,8 +59,7 @@ class PermissionsAPIUnitTest : public ExtensionServiceTestWithInstall { new PermissionsContainsFunction()); function->set_extension(extension.get()); bool run_result = extension_function_test_utils::RunFunction( - function.get(), args_string, browser(), - extension_function_test_utils::NONE); + function.get(), args_string, browser(), api_test_utils::NONE); EXPECT_TRUE(run_result) << function->GetError(); bool has_permission; diff --git a/chromium/chrome/browser/extensions/api/preference/preference_api_prefs_unittest.cc b/chromium/chrome/browser/extensions/api/preference/preference_api_prefs_unittest.cc index 2964d6272fc..d66dd3a6f5b 100644 --- a/chromium/chrome/browser/extensions/api/preference/preference_api_prefs_unittest.cc +++ b/chromium/chrome/browser/extensions/api/preference/preference_api_prefs_unittest.cc @@ -212,8 +212,8 @@ class ControlledPrefsInstallIncognitoPersistent new base::Value("val1")); InstallExtensionControlledPrefIncognito(extension1(), kPref1, new base::Value("val2")); - std::unique_ptr<PrefService> incog_prefs( - prefs_.CreateIncognitoPrefService()); + std::unique_ptr<PrefService> incog_prefs = + prefs_.CreateIncognitoPrefService(); std::string actual = incog_prefs->GetString(kPref1); EXPECT_EQ("val2", actual); } @@ -223,8 +223,8 @@ class ControlledPrefsInstallIncognitoPersistent std::string actual = prefs()->pref_service()->GetString(kPref1); EXPECT_EQ("val1", actual); // Incognito pref service shall see incognito values. - std::unique_ptr<PrefService> incog_prefs( - prefs_.CreateIncognitoPrefService()); + std::unique_ptr<PrefService> incog_prefs = + prefs_.CreateIncognitoPrefService(); actual = incog_prefs->GetString(kPref1); EXPECT_EQ("val2", actual); } @@ -243,8 +243,8 @@ class ControlledPrefsInstallIncognitoSessionOnly new base::Value("val1")); InstallExtensionControlledPrefIncognitoSessionOnly(extension1(), kPref1, new base::Value("val2")); - std::unique_ptr<PrefService> incog_prefs( - prefs_.CreateIncognitoPrefService()); + std::unique_ptr<PrefService> incog_prefs = + prefs_.CreateIncognitoPrefService(); std::string actual = incog_prefs->GetString(kPref1); EXPECT_EQ("val2", actual); } @@ -255,8 +255,8 @@ class ControlledPrefsInstallIncognitoSessionOnly // Incognito pref service shall see session-only incognito values only // during first run. Once the pref service was reloaded, all values shall be // discarded. - std::unique_ptr<PrefService> incog_prefs( - prefs_.CreateIncognitoPrefService()); + std::unique_ptr<PrefService> incog_prefs = + prefs_.CreateIncognitoPrefService(); actual = incog_prefs->GetString(kPref1); if (iteration_ == 0) { EXPECT_EQ("val2", actual); @@ -314,8 +314,8 @@ class ControlledPrefsNotifyWhenNeeded : public ExtensionControlledPrefsTest { registrar.Add(kPref1, observer.GetCallback()); MockPrefChangeCallback incognito_observer(prefs()->pref_service()); - std::unique_ptr<PrefService> incog_prefs( - prefs_.CreateIncognitoPrefService()); + std::unique_ptr<PrefService> incog_prefs = + prefs_.CreateIncognitoPrefService(); PrefChangeRegistrar incognito_registrar; incognito_registrar.Init(incog_prefs.get()); incognito_registrar.Add(kPref1, incognito_observer.GetCallback()); diff --git a/chromium/chrome/browser/extensions/api/processes/processes_api.cc b/chromium/chrome/browser/extensions/api/processes/processes_api.cc index 63c005ec295..4ec343e20b9 100644 --- a/chromium/chrome/browser/extensions/api/processes/processes_api.cc +++ b/chromium/chrome/browser/extensions/api/processes/processes_api.cc @@ -372,11 +372,12 @@ void ProcessesEventRouter::UpdateRefreshTypesFlagsBasedOnListeners() { refresh_types |= GetRefreshTypesFlagOnlyEssentialData(); } + const int64_t on_updated_types = GetRefreshTypesForProcessOptionalData(); if (HasEventListeners(api::processes::OnUpdated::kEventName)) - refresh_types |= GetRefreshTypesForProcessOptionalData(); + refresh_types |= on_updated_types; if (HasEventListeners(api::processes::OnUpdatedWithMemory::kEventName)) - refresh_types |= task_manager::REFRESH_TYPE_MEMORY; + refresh_types |= (on_updated_types | task_manager::REFRESH_TYPE_MEMORY); SetRefreshTypesFlags(refresh_types); } diff --git a/chromium/chrome/browser/extensions/api/processes/processes_apitest.cc b/chromium/chrome/browser/extensions/api/processes/processes_apitest.cc index d75a4065760..42668f2615d 100644 --- a/chromium/chrome/browser/extensions/api/processes/processes_apitest.cc +++ b/chromium/chrome/browser/extensions/api/processes/processes_apitest.cc @@ -9,6 +9,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/common/extensions/api/processes.h" #include "extensions/common/switches.h" #include "extensions/test/extension_test_message_listener.h" @@ -69,6 +70,48 @@ IN_PROC_BROWSER_TEST_F(ProcessesApiTest, ProcessesApiListeners) { EXPECT_EQ(0, GetListenersCount()); } +IN_PROC_BROWSER_TEST_F(ProcessesApiTest, OnUpdatedWithMemoryRefreshTypes) { + EXPECT_EQ(0, GetListenersCount()); + + // Load an extension that listen to the onUpdatedWithMemory. + ExtensionTestMessageListener listener("ready", false /* will_reply */); + const extensions::Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("processes") + .AppendASCII("onupdated_with_memory")); + ASSERT_TRUE(extension); + ASSERT_TRUE(listener.WaitUntilSatisfied()); + + // The memory refresh type must be enabled now. + const task_manager::TaskManagerInterface* task_manager = + task_manager::TaskManagerInterface::GetTaskManager(); + EXPECT_EQ(1, GetListenersCount()); + extensions::EventRouter* event_router = + extensions::EventRouter::Get(profile()); + EXPECT_TRUE(event_router->HasEventListener( + extensions::api::processes::OnUpdatedWithMemory::kEventName)); + EXPECT_FALSE(event_router->HasEventListener( + extensions::api::processes::OnUpdated::kEventName)); + EXPECT_TRUE(task_manager->IsResourceRefreshEnabled( + task_manager::REFRESH_TYPE_MEMORY)); + + // Despite the fact that there are no onUpdated listeners, refresh types for + // CPU, Network, SQLite, V8 memory, and webcache stats should be enabled. + constexpr task_manager::RefreshType kOnUpdatedRefreshTypes[] = { + task_manager::REFRESH_TYPE_CPU, + task_manager::REFRESH_TYPE_NETWORK_USAGE, + task_manager::REFRESH_TYPE_SQLITE_MEMORY, + task_manager::REFRESH_TYPE_V8_MEMORY, + task_manager::REFRESH_TYPE_WEBCACHE_STATS, + }; + + for (const auto& type : kOnUpdatedRefreshTypes) + EXPECT_TRUE(task_manager->IsResourceRefreshEnabled(type)); + + // Unload the extensions and make sure the listeners count is updated. + UnloadExtension(extension->id()); + EXPECT_EQ(0, GetListenersCount()); +} + IN_PROC_BROWSER_TEST_F(ProcessesApiTest, CannotTerminateBrowserProcess) { ASSERT_TRUE(RunExtensionTest("processes/terminate-browser-process")) << message_; diff --git a/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc b/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc index 780aafeeb1e..8efe3706631 100644 --- a/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc +++ b/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc @@ -26,7 +26,7 @@ #include "components/proxy_config/proxy_config_dictionary.h" #include "extensions/common/error_utils.h" #include "net/base/data_url.h" -#include "net/proxy/proxy_config.h" +#include "net/proxy_resolution/proxy_config.h" namespace extensions { @@ -378,16 +378,16 @@ std::unique_ptr<base::DictionaryValue> CreateProxyRulesDict( rules.ParseFromString(proxy_servers); switch (rules.type) { - case net::ProxyConfig::ProxyRules::TYPE_NO_RULES: + case net::ProxyConfig::ProxyRules::Type::EMPTY: return NULL; - case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY: + case net::ProxyConfig::ProxyRules::Type::PROXY_LIST: if (!rules.single_proxies.IsEmpty()) { extension_proxy_rules->Set( keys::field_name[keys::SCHEME_ALL], CreateProxyServerDict(rules.single_proxies.Get())); } break; - case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: + case net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME: if (!rules.proxies_for_http.IsEmpty()) { extension_proxy_rules->Set( keys::field_name[keys::SCHEME_HTTP], diff --git a/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.h b/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.h index f572165276e..43428988aa6 100644 --- a/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.h +++ b/chromium/chrome/browser/extensions/api/proxy/proxy_api_helpers.h @@ -11,7 +11,7 @@ #include <string> #include "components/proxy_config/proxy_prefs.h" -#include "net/proxy/proxy_config.h" +#include "net/proxy_resolution/proxy_config.h" class ProxyConfigDictionary; diff --git a/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc b/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc index 2999bdd5c46..a1f7840198a 100644 --- a/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc +++ b/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc @@ -7,7 +7,6 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/test/browser_test_utils.h" #include "extensions/browser/api/runtime/runtime_api.h" @@ -17,6 +16,7 @@ #include "extensions/browser/extension_registry.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "url/url_constants.h" diff --git a/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc b/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc index aba5cc99f59..872c24eba79 100644 --- a/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc +++ b/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc @@ -6,6 +6,7 @@ #include <stddef.h> +#include <algorithm> #include <memory> #include <utility> #include <vector> @@ -380,8 +381,8 @@ ExtensionFunction::ResponseAction SessionsGetDevicesFunction::Run() { ExtensionFunction::ResponseValue SessionsRestoreFunction::GetRestoredTabResult( content::WebContents* contents) { - std::unique_ptr<tabs::Tab> tab( - ExtensionTabUtil::CreateTabObject(contents, extension())); + std::unique_ptr<tabs::Tab> tab(ExtensionTabUtil::CreateTabObject( + contents, ExtensionTabUtil::kScrubTab, extension())); std::unique_ptr<api::sessions::Session> restored_session( CreateSessionModelHelper(base::Time::Now().ToTimeT(), std::move(tab), std::unique_ptr<windows::Window>())); @@ -390,14 +391,15 @@ ExtensionFunction::ResponseValue SessionsRestoreFunction::GetRestoredTabResult( ExtensionFunction::ResponseValue SessionsRestoreFunction::GetRestoredWindowResult(int window_id) { - WindowController* controller = NULL; + Browser* browser = nullptr; std::string error; - if (!windows_util::GetWindowFromWindowID(this, window_id, 0, &controller, - &error)) { + if (!windows_util::GetBrowserFromWindowID(this, window_id, 0, &browser, + &error)) { return Error(error); } std::unique_ptr<base::DictionaryValue> window_value( - controller->CreateWindowValueWithTabs(extension())); + ExtensionTabUtil::CreateWindowValueForExtension( + *browser, extension(), ExtensionTabUtil::kPopulateTabs)); std::unique_ptr<windows::Window> window( windows::Window::FromValue(*window_value)); return ArgumentList(Restore::Results::Create(*CreateSessionModelHelper( diff --git a/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc b/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc index 6f68a4cd4d0..9d457c2dcae 100644 --- a/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc +++ b/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc @@ -30,8 +30,6 @@ #include "components/browser_sync/profile_sync_service_mock.h" #include "components/sync/device_info/local_device_info_provider_mock.h" #include "components/sync/driver/sync_api_component_factory_mock.h" -#include "components/sync/model/attachments/attachment_id.h" -#include "components/sync/model/attachments/attachment_service_proxy_for_test.h" #include "components/sync/model/fake_sync_change_processor.h" #include "components/sync/model/sync_error_factory_mock.h" #include "components/sync_sessions/sessions_sync_manager.h" @@ -266,16 +264,14 @@ void ExtensionSessionsTest::CreateSessionModels() { sync_pb::EntitySpecifics entity; entity.mutable_session()->CopyFrom(meta); initial_data.push_back(syncer::SyncData::CreateRemoteData( - 1, entity, base::Time(), syncer::AttachmentIdList(), - syncer::AttachmentServiceProxyForTest::Create(), + 1, entity, base::Time(), sync_sessions::SessionsSyncManager::TagHashFromSpecifics( entity.session()))); for (size_t i = 0; i < tabs.size(); i++) { sync_pb::EntitySpecifics entity; entity.mutable_session()->CopyFrom(tabs[i]); initial_data.push_back(syncer::SyncData::CreateRemoteData( - i + 2, entity, base::Time(), syncer::AttachmentIdList(), - syncer::AttachmentServiceProxyForTest::Create(), + i + 2, entity, base::Time(), sync_sessions::SessionsSyncManager::TagHashFromSpecifics( entity.session()))); } @@ -329,7 +325,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, std::unique_ptr<base::DictionaryValue> restored_window_session( utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( CreateFunction<SessionsRestoreFunction>(true).get(), "[\"tag3.3\"]", - browser_, utils::INCLUDE_INCOGNITO))); + browser_, api_test_utils::INCLUDE_INCOGNITO))); ASSERT_TRUE(restored_window_session); std::unique_ptr<base::ListValue> result( diff --git a/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc index 12188832837..ea8c4d19412 100644 --- a/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc @@ -284,6 +284,8 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() { settings_api::PrefType::PREF_TYPE_NUMBER; (*s_whitelist)[ash::prefs::kAccessibilityScreenMagnifierEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_whitelist)[ash::prefs::kAccessibilityScreenMagnifierScale] = + settings_api::PrefType::PREF_TYPE_NUMBER; (*s_whitelist)[ash::prefs::kAccessibilitySelectToSpeakEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_whitelist)[ash::prefs::kAccessibilityStickyKeysEnabled] = @@ -364,6 +366,10 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() { settings_api::PrefType::PREF_TYPE_NUMBER; (*s_whitelist)[ash::prefs::kNightLightCustomEndTime] = settings_api::PrefType::PREF_TYPE_NUMBER; + (*s_whitelist)[ash::prefs::kDockedMagnifierEnabled] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_whitelist)[ash::prefs::kDockedMagnifierScale] = + settings_api::PrefType::PREF_TYPE_NUMBER; // Input method settings. (*s_whitelist)[::prefs::kLanguagePreloadEngines] = @@ -633,20 +639,24 @@ settings_private::SetPrefResult PrefsUtil::SetPref(const std::string& pref_name, switch (pref->GetType()) { case base::Value::Type::BOOLEAN: - case base::Value::Type::DOUBLE: case base::Value::Type::LIST: case base::Value::Type::DICTIONARY: pref_service->Set(pref_name, *value); break; - case base::Value::Type::INTEGER: { - // In JS all numbers are doubles. + case base::Value::Type::DOUBLE: + case base::Value::Type::INTEGER: + // Explicitly set the double value or the integer value. + // Otherwise if the number is a whole number like 2.0, it will + // automatically be of type INTEGER causing type mismatches in + // PrefService::SetUserPrefValue for doubles, and vice versa. double double_value; if (!value->GetAsDouble(&double_value)) return settings_private::SetPrefResult::PREF_TYPE_MISMATCH; - - pref_service->SetInteger(pref_name, static_cast<int>(double_value)); + if (pref->GetType() == base::Value::Type::DOUBLE) + pref_service->SetDouble(pref_name, double_value); + else + pref_service->SetInteger(pref_name, static_cast<int>(double_value)); break; - } case base::Value::Type::STRING: { std::string string_value; if (!value->GetAsString(&string_value)) diff --git a/chromium/chrome/browser/extensions/api/socket/udp_socket_unittest.cc b/chromium/chrome/browser/extensions/api/socket/udp_socket_unittest.cc index 27fd02882f8..68b294cade7 100644 --- a/chromium/chrome/browser/extensions/api/socket/udp_socket_unittest.cc +++ b/chromium/chrome/browser/extensions/api/socket/udp_socket_unittest.cc @@ -15,17 +15,37 @@ #include "base/single_thread_task_runner.h" #include "base/test/test_timeouts.h" #include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/extensions/extension_service_test_base.h" #include "chrome/test/base/browser_with_test_window_test.h" +#include "content/public/browser/storage_partition.h" #include "extensions/browser/api/socket/udp_socket.h" #include "net/base/io_buffer.h" #include "net/base/ip_address.h" +#include "net/base/test_completion_callback.h" +#include "services/network/network_context.h" +#include "services/network/public/mojom/udp_socket.mojom.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { -// UDPSocketUnitTest exists solely to make it easier to pass a specific -// gtest_filter argument during development. -class UDPSocketUnitTest : public BrowserWithTestWindowTest { +class UDPSocketUnitTest : public extensions::ExtensionServiceTestBase { + protected: + // extensions::ExtensionServiceTestBase: + void SetUp() override { InitializeEmptyExtensionService(); } + + std::unique_ptr<UDPSocket> CreateSocket() { + network::mojom::NetworkContext* network_context = + content::BrowserContext::GetDefaultStoragePartition(profile()) + ->GetNetworkContext(); + network::mojom::UDPSocketPtrInfo socket; + network::mojom::UDPSocketReceiverPtr receiver_ptr; + network::mojom::UDPSocketReceiverRequest receiver_request = + mojo::MakeRequest(&receiver_ptr); + network_context->CreateUDPSocket(mojo::MakeRequest(&socket), + std::move(receiver_ptr)); + return std::make_unique<UDPSocket>( + std::move(socket), std::move(receiver_request), "abcdefghijklmnopqrst"); + } }; static void OnConnected(int result) { @@ -40,8 +60,8 @@ static void OnCompleted(int bytes_read, // Do nothing; don't care. } -static const char test_message[] = "$$TESTMESSAGETESTMESSAGETESTMESSAGETEST$$"; -static const int test_message_length = arraysize(test_message); +static const char kTestMessage[] = "$$TESTMESSAGETESTMESSAGETESTMESSAGETEST$$"; +static const int kTestMessageLength = arraysize(kTestMessage); net::AddressList CreateAddressList(const char* address_string, int port) { net::IPAddress ip; @@ -50,50 +70,69 @@ net::AddressList CreateAddressList(const char* address_string, int port) { } static void OnSendCompleted(int result) { - EXPECT_EQ(test_message_length, result); + EXPECT_EQ(kTestMessageLength, result); } -TEST(UDPSocketUnitTest, TestUDPSocketRecvFrom) { - base::MessageLoopForIO io_loop; // For RecvFrom to do its threaded work. - UDPSocket socket("abcdefghijklmnopqrst"); +TEST_F(UDPSocketUnitTest, TestUDPSocketRecvFrom) { + std::unique_ptr<UDPSocket> socket = CreateSocket(); // Confirm that we can call two RecvFroms in quick succession without // triggering crbug.com/146606. - socket.Connect(CreateAddressList("127.0.0.1", 40000), - base::Bind(&OnConnected)); - socket.RecvFrom(4096, base::Bind(&OnCompleted)); - socket.RecvFrom(4096, base::Bind(&OnCompleted)); + socket->Connect(CreateAddressList("127.0.0.1", 40000), + base::BindRepeating(&OnConnected)); + socket->RecvFrom(4096, base::BindRepeating(&OnCompleted)); + socket->RecvFrom(4096, base::BindRepeating(&OnCompleted)); } -TEST(UDPSocketUnitTest, TestUDPMulticastJoinGroup) { +TEST_F(UDPSocketUnitTest, TestUDPMulticastJoinGroup) { const char kGroup[] = "237.132.100.17"; - UDPSocket src("abcdefghijklmnopqrst"); - UDPSocket dest("abcdefghijklmnopqrst"); + std::unique_ptr<UDPSocket> src = CreateSocket(); + std::unique_ptr<UDPSocket> dest = CreateSocket(); - EXPECT_EQ(0, dest.Bind("0.0.0.0", 13333)); - EXPECT_EQ(0, dest.JoinGroup(kGroup)); - std::vector<std::string> groups = dest.GetJoinedGroups(); + { + net::TestCompletionCallback callback; + dest->Bind("0.0.0.0", 13333, callback.callback()); + EXPECT_EQ(net::OK, callback.WaitForResult()); + } + { + net::TestCompletionCallback callback; + dest->JoinGroup(kGroup, callback.callback()); + EXPECT_EQ(net::OK, callback.WaitForResult()); + } + std::vector<std::string> groups = dest->GetJoinedGroups(); EXPECT_EQ(static_cast<size_t>(1), groups.size()); EXPECT_EQ(kGroup, *groups.begin()); - EXPECT_NE(0, dest.LeaveGroup("237.132.100.13")); - EXPECT_EQ(0, dest.LeaveGroup(kGroup)); - groups = dest.GetJoinedGroups(); + { + net::TestCompletionCallback callback; + dest->LeaveGroup("237.132.100.13", callback.callback()); + EXPECT_NE(net::OK, callback.WaitForResult()); + } + { + net::TestCompletionCallback callback; + dest->LeaveGroup(kGroup, callback.callback()); + EXPECT_EQ(net::OK, callback.WaitForResult()); + } + groups = dest->GetJoinedGroups(); EXPECT_EQ(static_cast<size_t>(0), groups.size()); } -TEST(UDPSocketUnitTest, TestUDPMulticastTimeToLive) { +TEST_F(UDPSocketUnitTest, TestUDPMulticastTimeToLive) { const char kGroup[] = "237.132.100.17"; - UDPSocket socket("abcdefghijklmnopqrst"); - EXPECT_NE(0, socket.SetMulticastTimeToLive(-1)); // Negative TTL shall fail. - EXPECT_EQ(0, socket.SetMulticastTimeToLive(3)); - socket.Connect(CreateAddressList(kGroup, 13333), base::Bind(&OnConnected)); + std::unique_ptr<UDPSocket> socket = CreateSocket(); + + EXPECT_NE(0, socket->SetMulticastTimeToLive(-1)); // Negative TTL shall fail. + EXPECT_EQ(0, socket->SetMulticastTimeToLive(3)); + socket->Connect(CreateAddressList(kGroup, 13333), + base::BindRepeating(&OnConnected)); } -TEST(UDPSocketUnitTest, TestUDPMulticastLoopbackMode) { +TEST_F(UDPSocketUnitTest, TestUDPMulticastLoopbackMode) { const char kGroup[] = "237.132.100.17"; - UDPSocket socket("abcdefghijklmnopqrst"); - EXPECT_EQ(0, socket.SetMulticastLoopbackMode(false)); - socket.Connect(CreateAddressList(kGroup, 13333), base::Bind(&OnConnected)); + std::unique_ptr<UDPSocket> socket = CreateSocket(); + + EXPECT_EQ(0, socket->SetMulticastLoopbackMode(false)); + socket->Connect(CreateAddressList(kGroup, 13333), + base::BindRepeating(&OnConnected)); } // Send a test multicast packet every second. @@ -102,8 +141,8 @@ static void SendMulticastPacket(const base::Closure& quit_run_loop, UDPSocket* src, int result) { if (result == 0) { - scoped_refptr<net::IOBuffer> data = new net::WrappedIOBuffer(test_message); - src->Write(data, test_message_length, base::Bind(&OnSendCompleted)); + scoped_refptr<net::IOBuffer> data = new net::WrappedIOBuffer(kTestMessage); + src->Write(data, kTestMessageLength, base::BindRepeating(&OnSendCompleted)); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&SendMulticastPacket, quit_run_loop, src, result), @@ -118,37 +157,48 @@ static void OnMulticastReadCompleted(const base::Closure& quit_run_loop, bool* packet_received, int count, scoped_refptr<net::IOBuffer> io_buffer, - bool socket_destroying) { - EXPECT_EQ(test_message_length, count); - EXPECT_EQ(0, strncmp(io_buffer->data(), test_message, test_message_length)); + bool socket_destroying, + const std::string& ip, + uint16_t port) { + EXPECT_EQ(kTestMessageLength, count); + EXPECT_EQ(0, strncmp(io_buffer->data(), kTestMessage, kTestMessageLength)); *packet_received = true; quit_run_loop.Run(); } -TEST(UDPSocketUnitTest, TestUDPMulticastRecv) { +TEST_F(UDPSocketUnitTest, TestUDPMulticastRecv) { const int kPort = 9999; const char kGroup[] = "237.132.100.17"; bool packet_received = false; - base::MessageLoopForIO io_loop; // For Read to do its threaded work. - UDPSocket dest("abcdefghijklmnopqrst"); - UDPSocket src("abcdefghijklmnopqrst"); - - base::RunLoop run_loop; + std::unique_ptr<UDPSocket> src = CreateSocket(); + std::unique_ptr<UDPSocket> dest = CreateSocket(); // Receiver - EXPECT_EQ(0, dest.Bind("0.0.0.0", kPort)); - EXPECT_EQ(0, dest.JoinGroup(kGroup)); - dest.Read(1024, base::Bind(&OnMulticastReadCompleted, run_loop.QuitClosure(), - &packet_received)); + { + net::TestCompletionCallback callback; + dest->Bind("0.0.0.0", kPort, callback.callback()); + EXPECT_EQ(net::OK, callback.WaitForResult()); + } + { + net::TestCompletionCallback callback; + dest->JoinGroup(kGroup, callback.callback()); + EXPECT_EQ(net::OK, callback.WaitForResult()); + } + base::RunLoop run_loop; + // |dest| is used with Bind(), so use RecvFrom() instead of Read(). + dest->RecvFrom(1024, + base::BindRepeating(&OnMulticastReadCompleted, + run_loop.QuitClosure(), &packet_received)); // Sender - EXPECT_EQ(0, src.SetMulticastTimeToLive(0)); - src.Connect(CreateAddressList(kGroup, kPort), - base::Bind(&SendMulticastPacket, run_loop.QuitClosure(), &src)); + EXPECT_EQ(0, src->SetMulticastTimeToLive(0)); + src->Connect(CreateAddressList(kGroup, kPort), + base::BindRepeating(&SendMulticastPacket, run_loop.QuitClosure(), + src.get())); // If not received within the test action timeout, quit the message loop. - io_loop.task_runner()->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(), - TestTimeouts::action_timeout()); + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout()); run_loop.Run(); diff --git a/chromium/chrome/browser/extensions/api/storage/managed_value_store_cache.cc b/chromium/chrome/browser/extensions/api/storage/managed_value_store_cache.cc index 8997b290ed0..2e38a78801c 100644 --- a/chromium/chrome/browser/extensions/api/storage/managed_value_store_cache.cc +++ b/chromium/chrome/browser/extensions/api/storage/managed_value_store_cache.cc @@ -173,9 +173,8 @@ void ManagedValueStoreCache::ExtensionTracker::LoadSchemas( } GetExtensionFileTaskRunner()->PostTask( - FROM_HERE, - base::BindOnce(&ExtensionTracker::LoadSchemasOnFileTaskRunner, - base::Passed(&added), weak_factory_.GetWeakPtr())); + FROM_HERE, base::BindOnce(&ExtensionTracker::LoadSchemasOnFileTaskRunner, + std::move(added), weak_factory_.GetWeakPtr())); } bool ManagedValueStoreCache::ExtensionTracker::UsesManagedStorage( @@ -327,7 +326,7 @@ void ManagedValueStoreCache::OnPolicyUpdated(const policy::PolicyNamespace& ns, GetBackendTaskRunner()->PostTask( FROM_HERE, base::BindOnce(&ManagedValueStoreCache::UpdatePolicyOnBackend, base::Unretained(this), ns.component_id, - base::Passed(current.DeepCopy()))); + current.DeepCopy())); } // static diff --git a/chromium/chrome/browser/extensions/api/streams_private/streams_private_apitest.cc b/chromium/chrome/browser/extensions/api/streams_private/streams_private_apitest.cc index b62b194dcff..7cc6951d468 100644 --- a/chromium/chrome/browser/extensions/api/streams_private/streams_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/streams_private/streams_private_apitest.cc @@ -16,9 +16,10 @@ #include "chrome/common/extensions/api/streams_private.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/ui_test_utils.h" +#include "components/download/public/common/download_item.h" #include "components/prefs/pref_service.h" -#include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" +#include "content/public/browser/download_request_utils.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/test/download_test_observer.h" @@ -35,10 +36,10 @@ using content::BrowserContext; using content::BrowserThread; -using content::DownloadItem; using content::DownloadManager; -using content::DownloadUrlParameters; using content::WebContents; +using download::DownloadItem; +using download::DownloadUrlParameters; using extensions::Event; using extensions::ExtensionSystem; using extensions::ResultCatcher; @@ -399,7 +400,7 @@ IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, MAYBE_DirectDownload) { browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents); std::unique_ptr<DownloadUrlParameters> params( - DownloadUrlParameters::CreateForWebContentsMainFrame( + content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame( web_contents, url, TRAFFIC_ANNOTATION_FOR_TESTS)); params->set_file_path(target_path); diff --git a/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h b/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h index cb4ad6f0b90..3e792d1bf2c 100644 --- a/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h +++ b/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h @@ -15,7 +15,7 @@ #include "chrome/browser/sync_file_system/sync_status_code.h" #include "chrome/common/extensions/api/sync_file_system.h" #include "storage/browser/fileapi/file_system_url.h" -#include "third_party/WebKit/common/quota/quota_types.mojom.h" +#include "third_party/WebKit/public/mojom/quota/quota_types.mojom.h" namespace storage { class FileSystemContext; diff --git a/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_browsertest.cc b/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_browsertest.cc index 4a19a726f23..2ff9bc7e96f 100644 --- a/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/sync_file_system/sync_file_system_browsertest.cc @@ -10,7 +10,6 @@ #include "base/run_loop.h" #include "base/sequenced_task_runner.h" #include "base/task_scheduler/post_task.h" -#include "base/threading/sequenced_worker_pool.h" #include "base/threading/thread_task_runner_handle.h" #include "base/values.h" #include "chrome/browser/apps/app_browsertest_util.h" diff --git a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc index 44d733eacea..520f0c136d0 100644 --- a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc +++ b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc @@ -59,6 +59,7 @@ class TabCaptureApiTest : public ExtensionApiTest { class TabCaptureApiPixelTest : public TabCaptureApiTest { public: void SetUp() override { + // TODO(crbug/754872): Update this to match WCVCD content_browsertests. if (!IsTooIntensiveForThisPlatform()) EnablePixelOutput(); TabCaptureApiTest::SetUp(); @@ -178,8 +179,10 @@ IN_PROC_BROWSER_TEST_F(TabCaptureApiPixelTest, EndToEndWithoutRemoting) { return; } AddExtensionToCommandLineWhitelist(); + // TODO(crbug/758057): Determine why color accuracy went down in this test + // with the new VIZ-based tab capturer. ASSERT_TRUE(RunExtensionSubtest( - "tab_capture", "end_to_end.html?method=local&colorDeviation=10")) + "tab_capture", "end_to_end.html?method=local&colorDeviation=50")) << message_; } diff --git a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc index e1984cfcaaa..38e56a88a20 100644 --- a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc +++ b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc @@ -42,15 +42,9 @@ constexpr size_t kTrimEvents = 24; // 1 sec at 24fps, or 0.4 sec at 60 fps. constexpr size_t kMinDataPoints = 100; // ~5 sec at 24fps. enum TestFlags { - // TODO(miu): Remove kUseGpu (since the GPU is required), kForceGpuComposited - // (because there's no longer a such thing as Chrome w/o a compositor), and - // maybe kDisableVsync. http://crbug.com/567848 kUseGpu = 1 << 0, // Only execute test if --enable-gpu was given // on the command line. This is required for // tests that run on GPU. - kForceGpuComposited = 1 << 1, // Force the test to use the compositor. - kDisableVsync = 1 << 2, // Do not limit framerate to vertical refresh. - // when on GPU, nor to 60hz when not on GPU. kTestThroughWebRTC = 1 << 3, // Send video through a webrtc loopback. kSmallWindow = 1 << 4, // Window size: 1 = 800x600, 0 = 2000x1000 }; @@ -71,12 +65,8 @@ class TabCapturePerformanceTest std::string GetSuffixForTestFlags() { std::string suffix; - if (HasFlag(kForceGpuComposited)) - suffix += "_comp"; if (HasFlag(kUseGpu)) - suffix += "_gpu"; - if (HasFlag(kDisableVsync)) - suffix += "_novsync"; + suffix += "_comp_gpu"; if (HasFlag(kTestThroughWebRTC)) suffix += "_webrtc"; if (HasFlag(kSmallWindow)) @@ -86,6 +76,8 @@ class TabCapturePerformanceTest void SetUp() override { EnablePixelOutput(); + if (!HasFlag(kUseGpu)) + UseSoftwareCompositing(); ExtensionApiTest::SetUp(); } @@ -105,12 +97,10 @@ class TabCapturePerformanceTest if (!HasFlag(kUseGpu)) command_line->AppendSwitch(switches::kDisableGpu); - if (HasFlag(kDisableVsync)) - command_line->AppendSwitch(switches::kDisableGpuVsync); - command_line->AppendSwitchASCII( extensions::switches::kWhitelistedExtensionID, kExtensionId); + ExtensionApiTest::SetUpCommandLine(command_line); } @@ -258,9 +248,6 @@ class TabCapturePerformanceTest ASSERT_TRUE(tracing::BeginTracing("gpu,gpu.capture")); std::string page = "performance.html"; page += HasFlag(kTestThroughWebRTC) ? "?WebRTC=1" : "?WebRTC=0"; - // Ideally we'd like to run a higher capture rate when vsync is disabled, - // but libjingle currently doesn't allow that. - // page += HasFlag(kDisableVsync) ? "&fps=300" : "&fps=30"; page += "&fps=60"; ASSERT_TRUE(RunExtensionSubtest("tab_capture", page)) << message_; ASSERT_TRUE(tracing::EndTracing(&json_events)); @@ -303,15 +290,9 @@ IN_PROC_BROWSER_TEST_P(TabCapturePerformanceTest, Performance) { // Note: First argument is optional and intentionally left blank. // (it's a prefix for the generated test cases) -INSTANTIATE_TEST_CASE_P( - , - TabCapturePerformanceTest, - testing::Values( - 0, - kUseGpu | kForceGpuComposited, - kDisableVsync, - kDisableVsync | kUseGpu | kForceGpuComposited, - kTestThroughWebRTC, - kTestThroughWebRTC | kUseGpu | kForceGpuComposited, - kTestThroughWebRTC | kDisableVsync, - kTestThroughWebRTC | kDisableVsync | kUseGpu | kForceGpuComposited)); +INSTANTIATE_TEST_CASE_P(, + TabCapturePerformanceTest, + testing::Values(0, + kUseGpu, + kTestThroughWebRTC, + kTestThroughWebRTC | kUseGpu)); diff --git a/chromium/chrome/browser/extensions/api/tabs/app_window_controller.cc b/chromium/chrome/browser/extensions/api/tabs/app_window_controller.cc index 3d0bd3e606b..458fc5bdf09 100644 --- a/chromium/chrome/browser/extensions/api/tabs/app_window_controller.cc +++ b/chromium/chrome/browser/extensions/api/tabs/app_window_controller.cc @@ -7,14 +7,11 @@ #include <memory> #include <utility> -#include "base/strings/utf_string_conversions.h" -#include "base/values.h" #include "chrome/browser/extensions/api/tabs/app_base_window.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/window_controller.h" #include "chrome/browser/extensions/window_controller_list.h" -#include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/common/url_constants.h" #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/native_app_window.h" @@ -46,62 +43,6 @@ std::string AppWindowController::GetWindowTypeText() const { return tabs_constants::kWindowTypeValueApp; } -std::unique_ptr<base::DictionaryValue> -AppWindowController::CreateWindowValueWithTabs( - const Extension* extension) const { - std::unique_ptr<base::DictionaryValue> result = CreateWindowValue(); - - std::unique_ptr<base::DictionaryValue> tab_value = - CreateTabObject(extension, 0)->ToValue(); - if (!tab_value) - return result; - - auto tab_list = std::make_unique<base::ListValue>(); - tab_list->Append(std::move(tab_value)); - result->Set(tabs_constants::kTabsKey, std::move(tab_list)); - - return result; -} - -std::unique_ptr<api::tabs::Tab> AppWindowController::CreateTabObject( - const extensions::Extension* extension, - int tab_index) const { - if (tab_index > 0) - return nullptr; - - content::WebContents* web_contents = app_window_->web_contents(); - if (!web_contents) - return nullptr; - - std::unique_ptr<api::tabs::Tab> tab_object(new api::tabs::Tab); - tab_object->id.reset(new int(SessionTabHelper::IdForTab(web_contents))); - tab_object->index = 0; - tab_object->window_id = - SessionTabHelper::IdForWindowContainingTab(web_contents); - tab_object->url.reset(new std::string(web_contents->GetURL().spec())); - tab_object->status.reset(new std::string( - ExtensionTabUtil::GetTabStatusText(web_contents->IsLoading()))); - tab_object->active = app_window_->GetBaseWindow()->IsActive(); - tab_object->selected = true; - tab_object->highlighted = true; - tab_object->pinned = false; - tab_object->title.reset( - new std::string(base::UTF16ToUTF8(web_contents->GetTitle()))); - tab_object->incognito = app_window_->GetBaseWindow()->IsActive(); - gfx::Rect bounds = app_window_->GetBaseWindow()->GetBounds(); - tab_object->width.reset(new int(bounds.width())); - tab_object->height.reset(new int(bounds.height())); - - const Extension* ext = app_window_->GetExtension(); - if (ext) { - std::string icon_str(chrome::kChromeUIFaviconURL); - icon_str.append(app_window_->GetExtension()->url().spec()); - tab_object->fav_icon_url.reset(new std::string(icon_str)); - } - - return tab_object; -} - bool AppWindowController::CanClose(Reason* reason) const { return true; } @@ -121,8 +62,9 @@ Browser* AppWindowController::GetBrowser() const { return nullptr; } -bool AppWindowController::IsVisibleToExtension( - const Extension* extension) const { +bool AppWindowController::IsVisibleToTabsAPIForExtension( + const Extension* extension, + bool allow_dev_tools_windows) const { DCHECK(extension); return extension->id() == app_window_->extension_id(); } diff --git a/chromium/chrome/browser/extensions/api/tabs/app_window_controller.h b/chromium/chrome/browser/extensions/api/tabs/app_window_controller.h index 8b8511a7a65..a5fd5850302 100644 --- a/chromium/chrome/browser/extensions/api/tabs/app_window_controller.h +++ b/chromium/chrome/browser/extensions/api/tabs/app_window_controller.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_TABS_APP_WINDOW_CONTROLLER_H_ #define CHROME_BROWSER_EXTENSIONS_API_TABS_APP_WINDOW_CONTROLLER_H_ +#include <memory> #include <string> #include "base/macros.h" @@ -28,17 +29,13 @@ class AppWindowController : public WindowController { // extensions::WindowController: int GetWindowId() const override; std::string GetWindowTypeText() const override; - std::unique_ptr<base::DictionaryValue> CreateWindowValueWithTabs( - const Extension* extension) const override; - std::unique_ptr<api::tabs::Tab> CreateTabObject( - const extensions::Extension* extension, - int tab_index) const override; - bool CanClose(Reason* reason) const override; void SetFullscreenMode(bool is_fullscreen, const GURL& extension_url) const override; Browser* GetBrowser() const override; - bool IsVisibleToExtension(const Extension* extension) const override; + bool IsVisibleToTabsAPIForExtension( + const Extension* extension, + bool allow_dev_tools_windows) const override; private: AppWindow* app_window_; // Owns us. diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc index e802ba0057a..1376d685bf8 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc @@ -95,11 +95,17 @@ #include "ui/base/ui_base_types.h" #if defined(OS_CHROMEOS) +#include "ash/public/cpp/config.h" #include "ash/public/cpp/window_pin_type.h" #include "ash/public/cpp/window_properties.h" #include "ash/public/interfaces/window_pin_type.mojom.h" +#include "chrome/browser/chromeos/ash_config.h" +#include "chrome/browser/ui/ash/chrome_screenshot_grabber.h" #include "chrome/browser/ui/browser_command_controller.h" +#include "content/public/browser/devtools_agent_host.h" #include "ui/aura/window.h" +#include "ui/base/clipboard/clipboard.h" +#include "ui/base/clipboard/clipboard_types.h" #endif using content::BrowserThread; @@ -256,19 +262,34 @@ bool IsValidStateForWindowsCreateFunction( } #if defined(OS_CHROMEOS) -void SetWindowTrustedPinned(ui::BaseWindow* base_window, bool trusted_pinned) { - aura::Window* window = base_window->GetNativeWindow(); +bool ExtensionHasLockedFullscreenPermission(const Extension* extension) { + return extension->permissions_data()->HasAPIPermission( + APIPermission::kLockWindowFullscreenPrivate); +} + +void SetLockedFullscreenState(Browser* browser, bool locked) { + aura::Window* window = browser->window()->GetNativeWindow(); // TRUSTED_PINNED is used here because that one locks the window fullscreen // without allowing the user to exit (as opposed to regular PINNED). window->SetProperty(ash::kWindowPinTypeKey, - trusted_pinned ? ash::mojom::WindowPinType::TRUSTED_PINNED - : ash::mojom::WindowPinType::NONE); -} + locked ? ash::mojom::WindowPinType::TRUSTED_PINNED + : ash::mojom::WindowPinType::NONE); -bool ExtensionHasLockedFullscreenPermission(const Extension* extension) { - return extension->permissions_data()->HasAPIPermission( - APIPermission::kLockWindowFullscreenPrivate); + // Update the set of available browser commands. + browser->command_controller()->LockedFullscreenStateChanged(); + + // Disallow screenshots in locked fullscreen mode. + // TODO(isandrk, 816900): ChromeScreenshotGrabber isn't implemented in Mash + // yet, remove this conditional when it becomes available. + if (chromeos::GetAshConfig() != ash::Config::MASH) + ChromeScreenshotGrabber::Get()->set_screenshots_allowed(!locked); + + // Reset the clipboard and kill dev tools when entering or exiting locked + // fullscreen (security concerns). + ui::Clipboard::GetForCurrentThread()->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE); + content::DevToolsAgentHost::DetachAllClients(); } + #endif // defined(OS_CHROMEOS) } // namespace @@ -304,18 +325,20 @@ ExtensionFunction::ResponseAction WindowsGetFunction::Run() { EXTENSION_FUNCTION_VALIDATE(params.get()); ApiParameterExtractor<windows::Get::Params> extractor(params.get()); - WindowController* controller = nullptr; + Browser* browser = nullptr; std::string error; - if (!windows_util::GetWindowFromWindowID(this, params->window_id, - extractor.type_filters(), - &controller, &error)) { + if (!windows_util::GetBrowserFromWindowID(this, params->window_id, + extractor.type_filters(), &browser, + &error)) { return RespondNow(Error(error)); } + ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior = + extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs + : ExtensionTabUtil::kDontPopulateTabs; std::unique_ptr<base::DictionaryValue> windows = - extractor.populate_tabs() - ? controller->CreateWindowValueWithTabs(extension()) - : controller->CreateWindowValue(); + ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(), + populate_tab_behavior); return RespondNow(OneArgument(std::move(windows))); } @@ -325,18 +348,20 @@ ExtensionFunction::ResponseAction WindowsGetCurrentFunction::Run() { EXTENSION_FUNCTION_VALIDATE(params.get()); ApiParameterExtractor<windows::GetCurrent::Params> extractor(params.get()); - WindowController* controller = nullptr; + Browser* browser = nullptr; std::string error; - if (!windows_util::GetWindowFromWindowID( + if (!windows_util::GetBrowserFromWindowID( this, extension_misc::kCurrentWindowId, extractor.type_filters(), - &controller, &error)) { + &browser, &error)) { return RespondNow(Error(error)); } + ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior = + extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs + : ExtensionTabUtil::kDontPopulateTabs; std::unique_ptr<base::DictionaryValue> windows = - extractor.populate_tabs() - ? controller->CreateWindowValueWithTabs(extension()) - : controller->CreateWindowValue(); + ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(), + populate_tab_behavior); return RespondNow(OneArgument(std::move(windows))); } @@ -349,22 +374,28 @@ ExtensionFunction::ResponseAction WindowsGetLastFocusedFunction::Run() { params.get()); // The WindowControllerList should contain a list of application, // browser and devtools windows. - WindowController* controller = nullptr; - for (auto* iter : WindowControllerList::GetInstance()->windows()) { - if (windows_util::CanOperateOnWindow(this, iter, + Browser* browser = nullptr; + for (auto* controller : WindowControllerList::GetInstance()->windows()) { + if (controller->GetBrowser() && + windows_util::CanOperateOnWindow(this, controller, extractor.type_filters())) { - controller = iter; + // TODO(devlin): Doesn't this mean that we'll use the last window in the + // list if there is no active window? That seems wrong. + // See https://crbug.com/809822. + browser = controller->GetBrowser(); if (controller->window()->IsActive()) break; // Use focused window. } } - if (!controller) + if (!browser) return RespondNow(Error(keys::kNoLastFocusedWindowError)); + ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior = + extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs + : ExtensionTabUtil::kDontPopulateTabs; std::unique_ptr<base::DictionaryValue> windows = - extractor.populate_tabs() - ? controller->CreateWindowValueWithTabs(extension()) - : controller->CreateWindowValue(); + ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(), + populate_tab_behavior); return RespondNow(OneArgument(std::move(windows))); } @@ -375,18 +406,17 @@ ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() { ApiParameterExtractor<windows::GetAll::Params> extractor(params.get()); std::unique_ptr<base::ListValue> window_list(new base::ListValue()); - const WindowControllerList::ControllerList& windows = - WindowControllerList::GetInstance()->windows(); - for (WindowControllerList::ControllerList::const_iterator iter = - windows.begin(); - iter != windows.end(); ++iter) { - if (!windows_util::CanOperateOnWindow(this, *iter, - extractor.type_filters())) + ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior = + extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs + : ExtensionTabUtil::kDontPopulateTabs; + for (auto* controller : WindowControllerList::GetInstance()->windows()) { + if (!controller->GetBrowser() || + !windows_util::CanOperateOnWindow(this, controller, + extractor.type_filters())) { continue; - if (extractor.populate_tabs()) - window_list->Append((*iter)->CreateWindowValueWithTabs(extension())); - else - window_list->Append((*iter)->CreateWindowValue()); + } + window_list->Append(ExtensionTabUtil::CreateWindowValueForExtension( + *controller->GetBrowser(), extension(), populate_tab_behavior)); } return RespondNow(OneArgument(std::move(window_list))); @@ -639,8 +669,7 @@ ExtensionFunction::ResponseAction WindowsCreateFunction::Run() { // (otherwise the tabstrip is empty). if (create_data && create_data->state == windows::WINDOW_STATE_LOCKED_FULLSCREEN) { - SetWindowTrustedPinned(new_window->window(), true); - new_window->command_controller()->LockedFullscreenStateChanged(); + SetLockedFullscreenState(new_window, true); } #endif @@ -649,8 +678,6 @@ ExtensionFunction::ResponseAction WindowsCreateFunction::Run() { else new_window->window()->ShowInactive(); - WindowController* controller = new_window->extension_window_controller(); - std::unique_ptr<base::Value> result; if (new_window->profile()->IsOffTheRecord() && !browser_context()->IsOffTheRecord() && !include_incognito()) { @@ -658,7 +685,8 @@ ExtensionFunction::ResponseAction WindowsCreateFunction::Run() { // profile and CanCrossIncognito isn't allowed. result = std::make_unique<base::Value>(); } else { - result = controller->CreateWindowValueWithTabs(extension()); + result = ExtensionTabUtil::CreateWindowValueForExtension( + *new_window, extension(), ExtensionTabUtil::kPopulateTabs); } return RespondNow(OneArgument(std::move(result))); @@ -669,11 +697,11 @@ ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() { windows::Update::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); - WindowController* controller; + Browser* browser = nullptr; std::string error; - if (!windows_util::GetWindowFromWindowID( + if (!windows_util::GetBrowserFromWindowID( this, params->window_id, WindowController::GetAllWindowFilter(), - &controller, &error)) { + &browser, &error)) { return RespondNow(Error(error)); } @@ -683,7 +711,7 @@ ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() { #if defined(OS_CHROMEOS) const bool is_window_trusted_pinned = - ash::IsWindowTrustedPinned(controller->window()); + ash::IsWindowTrustedPinned(browser->window()); // Don't allow locked fullscreen operations on a window without the proper // permission (also don't allow any operations on a locked window if the // extension doesn't have the permission). @@ -698,15 +726,11 @@ ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() { if (is_window_trusted_pinned && params->update_info.state != windows::WINDOW_STATE_LOCKED_FULLSCREEN && params->update_info.state != windows::WINDOW_STATE_NONE) { - SetWindowTrustedPinned(controller->window(), false); - controller->GetBrowser()->command_controller()-> - LockedFullscreenStateChanged(); + SetLockedFullscreenState(browser, false); } else if (!is_window_trusted_pinned && params->update_info.state == windows::WINDOW_STATE_LOCKED_FULLSCREEN) { - SetWindowTrustedPinned(controller->window(), true); - controller->GetBrowser()->command_controller()-> - LockedFullscreenStateChanged(); + SetLockedFullscreenState(browser, true); } #endif @@ -714,34 +738,38 @@ ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() { ConvertToWindowShowState(params->update_info.state); if (show_state != ui::SHOW_STATE_FULLSCREEN && - show_state != ui::SHOW_STATE_DEFAULT) - controller->SetFullscreenMode(false, extension()->url()); + show_state != ui::SHOW_STATE_DEFAULT) { + browser->extension_window_controller()->SetFullscreenMode( + false, extension()->url()); + } switch (show_state) { case ui::SHOW_STATE_MINIMIZED: - controller->window()->Minimize(); + browser->window()->Minimize(); break; case ui::SHOW_STATE_MAXIMIZED: - controller->window()->Maximize(); + browser->window()->Maximize(); break; case ui::SHOW_STATE_FULLSCREEN: - if (controller->window()->IsMinimized() || - controller->window()->IsMaximized()) - controller->window()->Restore(); - controller->SetFullscreenMode(true, extension()->url()); + if (browser->window()->IsMinimized() || + browser->window()->IsMaximized()) { + browser->window()->Restore(); + } + browser->extension_window_controller()->SetFullscreenMode( + true, extension()->url()); break; case ui::SHOW_STATE_NORMAL: - controller->window()->Restore(); + browser->window()->Restore(); break; default: break; } gfx::Rect bounds; - if (controller->window()->IsMinimized()) - bounds = controller->window()->GetRestoredBounds(); + if (browser->window()->IsMinimized()) + bounds = browser->window()->GetRestoredBounds(); else - bounds = controller->window()->GetBounds(); + bounds = browser->window()->GetBounds(); bool set_bounds = false; // Any part of the bounds can optionally be set by the caller. @@ -773,27 +801,28 @@ ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() { } // TODO(varkha): Updating bounds during a drag can cause problems and a more // general solution is needed. See http://crbug.com/251813 . - controller->window()->SetBounds(bounds); + browser->window()->SetBounds(bounds); } if (params->update_info.focused) { if (*params->update_info.focused) { if (show_state == ui::SHOW_STATE_MINIMIZED) return RespondNow(Error(keys::kInvalidWindowStateError)); - controller->window()->Activate(); + browser->window()->Activate(); } else { if (show_state == ui::SHOW_STATE_MAXIMIZED || show_state == ui::SHOW_STATE_FULLSCREEN) { return RespondNow(Error(keys::kInvalidWindowStateError)); } - controller->window()->Deactivate(); + browser->window()->Deactivate(); } } if (params->update_info.draw_attention) - controller->window()->FlashFrame(*params->update_info.draw_attention); + browser->window()->FlashFrame(*params->update_info.draw_attention); - return RespondNow(OneArgument(controller->CreateWindowValue())); + return RespondNow(OneArgument(ExtensionTabUtil::CreateWindowValueForExtension( + *browser, extension(), ExtensionTabUtil::kDontPopulateTabs))); } ExtensionFunction::ResponseAction WindowsRemoveFunction::Run() { @@ -801,22 +830,23 @@ ExtensionFunction::ResponseAction WindowsRemoveFunction::Run() { windows::Remove::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); - WindowController* controller = nullptr; + Browser* browser = nullptr; std::string error; - if (!windows_util::GetWindowFromWindowID(this, params->window_id, - WindowController::kNoWindowFilter, - &controller, &error)) { + if (!windows_util::GetBrowserFromWindowID(this, params->window_id, + WindowController::kNoWindowFilter, + &browser, &error)) { return RespondNow(Error(error)); } #if defined(OS_CHROMEOS) - if (ash::IsWindowTrustedPinned(controller->window()) && + if (ash::IsWindowTrustedPinned(browser->window()) && !ExtensionHasLockedFullscreenPermission(extension())) { return RespondNow( Error(keys::kMissingLockWindowFullscreenPrivatePermission)); } #endif + WindowController* controller = browser->extension_window_controller(); WindowController::Reason reason; if (!controller->CanClose(&reason)) { return RespondNow(Error(reason == WindowController::REASON_NOT_EDITABLE @@ -850,7 +880,8 @@ ExtensionFunction::ResponseAction TabsGetSelectedFunction::Run() { return RespondNow(Error(keys::kNoSelectedTabError)); return RespondNow(ArgumentList( tabs::Get::Results::Create(*ExtensionTabUtil::CreateTabObject( - contents, tab_strip, tab_strip->active_index(), extension())))); + contents, ExtensionTabUtil::kScrubTab, extension(), tab_strip, + tab_strip->active_index())))); } ExtensionFunction::ResponseAction TabsGetAllInWindowFunction::Run() { @@ -928,8 +959,8 @@ ExtensionFunction::ResponseAction TabsQueryFunction::Run() { if (!include_incognito() && profile != browser->profile()) continue; - if (!browser->extension_window_controller()->IsVisibleToExtension( - extension())) { + if (!browser->extension_window_controller()->IsVisibleToTabsAPIForExtension( + extension(), false /*allow_dev_tools_windows*/)) { continue; } @@ -1033,8 +1064,9 @@ ExtensionFunction::ResponseAction TabsQueryFunction::Run() { if (loading_status_set && loading != web_contents->IsLoading()) continue; - result->Append(ExtensionTabUtil::CreateTabObject(web_contents, tab_strip, - i, extension()) + result->Append(ExtensionTabUtil::CreateTabObject( + web_contents, ExtensionTabUtil::kScrubTab, extension(), + tab_strip, i) ->ToValue()); } } @@ -1101,7 +1133,8 @@ ExtensionFunction::ResponseAction TabsDuplicateFunction::Run() { return RespondNow(ArgumentList( tabs::Get::Results::Create(*ExtensionTabUtil::CreateTabObject( - new_contents, new_tab_strip, new_tab_index, extension())))); + new_contents, ExtensionTabUtil::kScrubTab, extension(), new_tab_strip, + new_tab_index)))); } ExtensionFunction::ResponseAction TabsGetFunction::Run() { @@ -1118,9 +1151,9 @@ ExtensionFunction::ResponseAction TabsGetFunction::Run() { return RespondNow(Error(error)); } - return RespondNow(ArgumentList( - tabs::Get::Results::Create(*ExtensionTabUtil::CreateTabObject( - contents, tab_strip, tab_index, extension())))); + return RespondNow(ArgumentList(tabs::Get::Results::Create( + *ExtensionTabUtil::CreateTabObject(contents, ExtensionTabUtil::kScrubTab, + extension(), tab_strip, tab_index)))); } ExtensionFunction::ResponseAction TabsGetCurrentFunction::Run() { @@ -1131,8 +1164,8 @@ ExtensionFunction::ResponseAction TabsGetCurrentFunction::Run() { WebContents* caller_contents = GetSenderWebContents(); std::unique_ptr<base::ListValue> results; if (caller_contents && ExtensionTabUtil::GetTabId(caller_contents) >= 0) { - results = tabs::Get::Results::Create( - *ExtensionTabUtil::CreateTabObject(caller_contents, extension())); + results = tabs::Get::Results::Create(*ExtensionTabUtil::CreateTabObject( + caller_contents, ExtensionTabUtil::kScrubTab, extension())); } return RespondNow(results ? ArgumentList(std::move(results)) : NoArguments()); } @@ -1179,9 +1212,8 @@ ExtensionFunction::ResponseAction TabsHighlightFunction::Run() { selection.set_active(active_index); browser->tab_strip_model()->SetSelectionFromModel(std::move(selection)); - return RespondNow(OneArgument( - browser->extension_window_controller()->CreateWindowValueWithTabs( - extension()))); + return RespondNow(OneArgument(ExtensionTabUtil::CreateWindowValueForExtension( + *browser, extension(), ExtensionTabUtil::kPopulateTabs))); } bool TabsHighlightFunction::HighlightTab(TabStripModel* tabstrip, @@ -1215,7 +1247,7 @@ bool TabsUpdateFunction::RunAsync() { int tab_id = -1; WebContents* contents = NULL; if (!params->tab_id.get()) { - Browser* browser = GetCurrentBrowser(); + Browser* browser = ChromeExtensionFunctionDetails(this).GetCurrentBrowser(); if (!browser) { error_ = keys::kNoCurrentWindowError; return false; @@ -1389,7 +1421,7 @@ bool TabsUpdateFunction::UpdateURL(const std::string &url_string, ScriptExecutor::SINGLE_FRAME, ExtensionApiFrameIdMap::kTopFrameId, ScriptExecutor::DONT_MATCH_ABOUT_BLANK, UserScript::DOCUMENT_IDLE, ScriptExecutor::MAIN_WORLD, ScriptExecutor::DEFAULT_PROCESS, GURL(), - GURL(), user_gesture(), ScriptExecutor::NO_RESULT, + GURL(), user_gesture(), base::nullopt, ScriptExecutor::NO_RESULT, base::Bind(&TabsUpdateFunction::OnExecuteCodeFinished, this)); *is_async = true; @@ -1423,8 +1455,8 @@ void TabsUpdateFunction::PopulateResult() { if (!has_callback()) return; - results_ = tabs::Get::Results::Create( - *ExtensionTabUtil::CreateTabObject(web_contents_, extension())); + results_ = tabs::Get::Results::Create(*ExtensionTabUtil::CreateTabObject( + web_contents_, ExtensionTabUtil::kScrubTab, extension())); } void TabsUpdateFunction::OnExecuteCodeFinished( @@ -1552,10 +1584,10 @@ bool TabsMoveFunction::MoveTab(int tab_id, *new_index, web_contents, TabStripModel::ADD_NONE); if (has_callback()) { - tab_values->Append( - ExtensionTabUtil::CreateTabObject(web_contents, target_tab_strip, - *new_index, extension()) - ->ToValue()); + tab_values->Append(ExtensionTabUtil::CreateTabObject( + web_contents, ExtensionTabUtil::kScrubTab, + extension(), target_tab_strip, *new_index) + ->ToValue()); } return true; @@ -1574,7 +1606,8 @@ bool TabsMoveFunction::MoveTab(int tab_id, if (has_callback()) { tab_values->Append(ExtensionTabUtil::CreateTabObject( - contents, source_tab_strip, *new_index, extension()) + contents, ExtensionTabUtil::kScrubTab, extension(), + source_tab_strip, *new_index) ->ToValue()); } @@ -1682,10 +1715,9 @@ bool TabsCaptureVisibleTabFunction::HasPermission() { return true; } -bool TabsCaptureVisibleTabFunction::IsScreenshotEnabled() { +bool TabsCaptureVisibleTabFunction::IsScreenshotEnabled() const { PrefService* service = chrome_details_.GetProfile()->GetPrefs(); if (service->GetBoolean(prefs::kDisableScreenshots)) { - error_ = keys::kScreenshotsDisabled; return false; } return true; @@ -1730,10 +1762,14 @@ bool TabsCaptureVisibleTabFunction::RunAsync() { WebContents* contents = GetWebContentsForID(context_id); - return CaptureAsync( + const CaptureResult capture_result = CaptureAsync( contents, image_details.get(), - base::Bind(&TabsCaptureVisibleTabFunction::CopyFromSurfaceComplete, - this)); + base::BindOnce(&TabsCaptureVisibleTabFunction::CopyFromSurfaceComplete, + this)); + if (capture_result == OK) + return true; + SetErrorMessage(capture_result); + return false; } void TabsCaptureVisibleTabFunction::OnCaptureSuccess(const SkBitmap& bitmap) { @@ -1747,11 +1783,16 @@ void TabsCaptureVisibleTabFunction::OnCaptureSuccess(const SkBitmap& bitmap) { SendResponse(true); } -void TabsCaptureVisibleTabFunction::OnCaptureFailure(FailureReason reason) { +void TabsCaptureVisibleTabFunction::OnCaptureFailure(CaptureResult result) { + SetErrorMessage(result); + SendResponse(false); +} + +void TabsCaptureVisibleTabFunction::SetErrorMessage(CaptureResult result) { const char* reason_description = "internal error"; - switch (reason) { - case FAILURE_REASON_UNKNOWN: - reason_description = "unknown error"; + switch (result) { + case FAILURE_REASON_READBACK_FAILED: + reason_description = "image readback failed"; break; case FAILURE_REASON_ENCODING_FAILED: reason_description = "encoding failed"; @@ -1759,10 +1800,16 @@ void TabsCaptureVisibleTabFunction::OnCaptureFailure(FailureReason reason) { case FAILURE_REASON_VIEW_INVISIBLE: reason_description = "view is invisible"; break; + case FAILURE_REASON_SCREEN_SHOTS_DISABLED: + error_ = keys::kScreenshotsDisabled; + return; + case OK: + NOTREACHED() + << "SetErrorMessage should not be called with a successful result"; + return; } error_ = ErrorUtils::FormatErrorMessage("Failed to capture tab: *", reason_description); - SendResponse(false); } void TabsCaptureVisibleTabFunction::RegisterProfilePrefs( @@ -1790,7 +1837,7 @@ bool TabsDetectLanguageFunction::RunAsync() { if (!browser || !contents) return false; } else { - browser = GetCurrentBrowser(); + browser = ChromeExtensionFunctionDetails(this).GetCurrentBrowser(); if (!browser) return false; contents = browser->tab_strip_model()->GetActiveWebContents(); @@ -2020,7 +2067,7 @@ content::WebContents* ZoomAPIFunction::GetWebContents(int tab_id) { nullptr /* ignore TabStripModel* output */, &web_contents, nullptr /* ignore int tab_index output */, &error_); } else { - Browser* browser = GetCurrentBrowser(); + Browser* browser = ChromeExtensionFunctionDetails(this).GetCurrentBrowser(); if (!browser) error_ = keys::kNoCurrentWindowError; else if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, NULL)) @@ -2177,8 +2224,9 @@ ExtensionFunction::ResponseAction TabsDiscardFunction::Run() { // Create the Tab object and return it in case of success. if (contents) { - return RespondNow(ArgumentList(tabs::Discard::Results::Create( - *ExtensionTabUtil::CreateTabObject(contents)))); + return RespondNow(ArgumentList( + tabs::Discard::Results::Create(*ExtensionTabUtil::CreateTabObject( + contents, ExtensionTabUtil::kScrubTab, extension())))); } // Return appropriate error message otherwise. diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_api.h b/chromium/chrome/browser/extensions/api/tabs/tabs_api.h index c8e4071ec5a..7fe5521cd41 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api.h +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api.h @@ -215,10 +215,13 @@ class TabsCaptureVisibleTabFunction content::WebContents* GetWebContentsForID(int window_id); // extensions::WebContentsCaptureClient: - bool IsScreenshotEnabled() override; + bool IsScreenshotEnabled() const override; bool ClientAllowsTransparency() override; void OnCaptureSuccess(const SkBitmap& bitmap) override; - void OnCaptureFailure(FailureReason reason) override; + void OnCaptureFailure(CaptureResult result) override; + + private: + void SetErrorMessage(CaptureResult result); DECLARE_EXTENSION_FUNCTION("tabs.captureVisibleTab", TABS_CAPTUREVISIBLETAB) }; diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc index d06d5513d00..220421eef7b 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc @@ -15,12 +15,12 @@ #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/test_browser_window.h" #include "content/public/browser/navigation_entry.h" -#include "content/public/common/browser_side_navigation_policy.h" #include "content/public/test/browser_side_navigation_test_utils.h" #include "content/public/test/web_contents_tester.h" #include "extensions/browser/api_test_utils.h" #include "extensions/common/constants.h" #include "extensions/common/extension_builder.h" +#include "ui/display/test/test_screen.h" namespace extensions { @@ -34,8 +34,7 @@ std::unique_ptr<base::ListValue> RunTabsQueryFunction( function->set_extension(extension); std::unique_ptr<base::Value> value( extension_function_test_utils::RunFunctionAndReturnSingleResult( - function.get(), query_info, browser, - extension_function_test_utils::NONE)); + function.get(), query_info, browser, api_test_utils::NONE)); return base::ListValue::From(std::move(value)); } @@ -57,28 +56,28 @@ class TabsApiUnitTest : public ExtensionServiceTestBase { std::unique_ptr<TestBrowserWindow> browser_window_; std::unique_ptr<Browser> browser_; + display::test::TestScreen test_screen_; + DISALLOW_COPY_AND_ASSIGN(TabsApiUnitTest); }; void TabsApiUnitTest::SetUp() { ExtensionServiceTestBase::SetUp(); InitializeEmptyExtensionService(); - - if (content::IsBrowserSideNavigationEnabled()) - content::BrowserSideNavigationSetUp(); + content::BrowserSideNavigationSetUp(); browser_window_.reset(new TestBrowserWindow()); Browser::CreateParams params(profile(), true); params.type = Browser::TYPE_TABBED; params.window = browser_window_.get(); browser_.reset(new Browser(params)); + display::Screen::SetScreenInstance(&test_screen_); } void TabsApiUnitTest::TearDown() { browser_.reset(); browser_window_.reset(); - if (content::IsBrowserSideNavigationEnabled()) - content::BrowserSideNavigationTearDown(); + content::BrowserSideNavigationTearDown(); ExtensionServiceTestBase::TearDown(); } @@ -293,7 +292,7 @@ TEST_F(TabsApiUnitTest, ExecuteScriptNoTabIsNonFatalError) { std::string error = extension_function_test_utils::RunFunctionAndReturnError( function.get(), kArgs, browser(), // browser() doesn't have any tabs. - extension_function_test_utils::NONE); + api_test_utils::NONE); EXPECT_EQ(tabs_constants::kNoTabInBrowserWindowError, error); } diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc index 1ab063b1ea9..af63faf2661 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc @@ -45,7 +45,8 @@ bool WillDispatchTabUpdatedEvent( Event* event, const base::DictionaryValue* listener_filter) { std::unique_ptr<api::tabs::Tab> tab_object = - ExtensionTabUtil::CreateTabObject(contents, extension); + ExtensionTabUtil::CreateTabObject(contents, ExtensionTabUtil::kScrubTab, + extension); std::unique_ptr<base::DictionaryValue> tab_value = tab_object->ToValue(); @@ -190,7 +191,9 @@ static bool WillDispatchTabCreatedEvent( const base::DictionaryValue* listener_filter) { event->event_args->Clear(); std::unique_ptr<base::DictionaryValue> tab_value = - ExtensionTabUtil::CreateTabObject(contents, extension)->ToValue(); + ExtensionTabUtil::CreateTabObject(contents, ExtensionTabUtil::kScrubTab, + extension) + ->ToValue(); tab_value->SetBoolean(tabs_constants::kSelectedKey, active); tab_value->SetBoolean(tabs_constants::kActiveKey, active); event->event_args->Append(std::move(tab_value)); diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc index 01abcd19bea..82fa8cfe3d3 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc @@ -225,12 +225,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, MAYBE_GetWindow) { // With "include_incognito". function = new WindowsGetFunction(); function->set_extension(extension.get()); - result.reset(utils::ToDictionary( - utils::RunFunctionAndReturnSingleResult( - function.get(), - base::StringPrintf("[%u]", incognito_window_id), - browser(), - utils::INCLUDE_INCOGNITO))); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + function.get(), base::StringPrintf("[%u]", incognito_window_id), + browser(), api_test_utils::INCLUDE_INCOGNITO))); EXPECT_TRUE(api_test_utils::GetBoolean(result.get(), "incognito")); // DevTools window. @@ -244,7 +241,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, MAYBE_GetWindow) { base::StringPrintf("[%u, {\"windowTypes\": [\"devtools\"]}]", ExtensionTabUtil::GetWindowId( DevToolsWindowTesting::Get(devtools)->browser())), - browser(), utils::INCLUDE_INCOGNITO))); + browser(), api_test_utils::INCLUDE_INCOGNITO))); EXPECT_EQ("devtools", api_test_utils::GetString(result.get(), "type")); DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); @@ -301,12 +298,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); } - // Application windows should not be accessible, unless allWindowTypes is set - // to true. + // Application windows should not be accessible to extensions (app windows are + // only accessible to the owning item). AppWindow* app_window = CreateTestAppWindow("{}"); - // Undocked DevTools window should not be accessible, unless allWindowTypes is - // set to true. + // Undocked DevTools window should not be accessible, unless included in the + // type filter mask. DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); @@ -370,11 +367,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); } - // Application windows should be accessible. + // Application windows should not be accessible to extensions (app windows are + // only accessible to the owning item). AppWindow* app_window = CreateTestAppWindow("{}"); - window_ids.insert(app_window->session_id().id()); - // Undocked DevTools window should be accessible too. + // Undocked DevTools window should be accessible too, since they have been + // explicitly requested as part of the type filter mask. DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); window_ids.insert(ExtensionTabUtil::GetWindowId( @@ -401,8 +399,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { base::ListValue* tabs = nullptr; EXPECT_FALSE(result_window->GetList(keys::kTabsKey, &tabs)); } - // The returned ids should contain all the current app, browser and - // devtools instance ids. + // The returned ids should contain all the browser and devtools instance ids. EXPECT_EQ(window_ids, result_ids); result_ids.clear(); @@ -425,8 +422,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { base::ListValue* tabs = nullptr; EXPECT_TRUE(result_window->GetList(keys::kTabsKey, &tabs)); } - // The returned ids should contain all the current app, browser and - // devtools instance ids. + // The returned ids should contain all the browser and devtools instance ids. EXPECT_EQ(window_ids, result_ids); DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); @@ -468,7 +464,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, std::string error = extension_function_test_utils::RunFunctionAndReturnError( update_tab_function.get(), kArgsWithNonIncognitoUrl, incognito, // incognito doesn't have any tabs. - extension_function_test_utils::NONE); + api_test_utils::NONE); EXPECT_EQ(ErrorUtils::FormatErrorMessage( tabs_constants::kURLsNotAllowedInIncognitoError, "chrome://extensions/configureCommands"), @@ -496,7 +492,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DefaultToIncognitoWhenItIsForced) { std::unique_ptr<base::DictionaryValue> result( utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( function.get(), kArgsWithoutExplicitIncognitoParam, browser(), - utils::INCLUDE_INCOGNITO))); + api_test_utils::INCLUDE_INCOGNITO))); // Make sure it is a new(different) window. EXPECT_NE(ExtensionTabUtil::GetWindowId(browser()), @@ -511,12 +507,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DefaultToIncognitoWhenItIsForced) { function->SetRenderFrameHost( browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame()); function->set_extension(extension.get()); - result.reset(utils::ToDictionary( - utils::RunFunctionAndReturnSingleResult( - function.get(), - kArgsWithoutExplicitIncognitoParam, - incognito_browser, - utils::INCLUDE_INCOGNITO))); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + function.get(), kArgsWithoutExplicitIncognitoParam, incognito_browser, + api_test_utils::INCLUDE_INCOGNITO))); // Make sure it is a new(different) window. EXPECT_NE(ExtensionTabUtil::GetWindowId(incognito_browser), GetWindowId(result.get())); @@ -536,7 +529,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, function->set_extension(extension.get()); std::unique_ptr<base::DictionaryValue> result( utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( - function.get(), kEmptyArgs, browser(), utils::INCLUDE_INCOGNITO))); + function.get(), kEmptyArgs, browser(), + api_test_utils::INCLUDE_INCOGNITO))); // Make sure it is a new(different) window. EXPECT_NE(ExtensionTabUtil::GetWindowId(browser()), @@ -549,11 +543,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, // Run without an explicit "incognito" param. function = new WindowsCreateFunction(); function->set_extension(extension.get()); - result.reset(utils::ToDictionary( - utils::RunFunctionAndReturnSingleResult(function.get(), - kEmptyArgs, - incognito_browser, - utils::INCLUDE_INCOGNITO))); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + function.get(), kEmptyArgs, incognito_browser, + api_test_utils::INCLUDE_INCOGNITO))); // Make sure it is a new(different) window. EXPECT_NE(ExtensionTabUtil::GetWindowId(incognito_browser), GetWindowId(result.get())); @@ -762,55 +754,6 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, InvalidUpdateWindowState) { keys::kInvalidWindowStateError)); } -IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateAppWindowSizeConstraint) { - AppWindow* app_window = CreateTestAppWindow( - "{\"outerBounds\": " - "{\"width\": 300, \"height\": 300," - " \"minWidth\": 200, \"minHeight\": 200," - " \"maxWidth\": 400, \"maxHeight\": 400}}"); - - scoped_refptr<WindowsGetFunction> get_function = new WindowsGetFunction(); - scoped_refptr<Extension> extension(ExtensionBuilder("Test").Build().get()); - get_function->set_extension(extension.get()); - std::unique_ptr<base::DictionaryValue> result( - utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( - get_function.get(), - base::StringPrintf("[%u, {\"windowTypes\": [\"app\"]}]", - app_window->session_id().id()), - browser()))); - - EXPECT_EQ(300, api_test_utils::GetInteger(result.get(), "width")); - EXPECT_EQ(300, api_test_utils::GetInteger(result.get(), "height")); - - // Verify the min width/height of the application window are - // respected. - scoped_refptr<WindowsUpdateFunction> update_min_function = - new WindowsUpdateFunction(); - result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( - update_min_function.get(), - base::StringPrintf("[%u, {\"width\": 100, \"height\": 100}]", - app_window->session_id().id()), - browser()))); - - EXPECT_EQ(200, api_test_utils::GetInteger(result.get(), "width")); - EXPECT_EQ(200, api_test_utils::GetInteger(result.get(), "height")); - - // Verify the max width/height of the application window are - // respected. - scoped_refptr<WindowsUpdateFunction> update_max_function = - new WindowsUpdateFunction(); - result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( - update_max_function.get(), - base::StringPrintf("[%u, {\"width\": 500, \"height\": 500}]", - app_window->session_id().id()), - browser()))); - - EXPECT_EQ(400, api_test_utils::GetInteger(result.get(), "width")); - EXPECT_EQ(400, api_test_utils::GetInteger(result.get(), "height")); - - CloseAppWindow(app_window); -} - IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateDevToolsWindow) { DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); @@ -866,6 +809,8 @@ class ExtensionWindowLastFocusedTest : public ExtensionTabsTest { base::Value* RunFunction(UIThreadExtensionFunction* function, const std::string& params); + const Extension* extension() { return extension_.get(); } + private: // A helper class to wait for an views::Widget to become activated. class WidgetActivatedWaiter : public views::WidgetObserver { @@ -1083,13 +1028,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionWindowLastFocusedTest, scoped_refptr<WindowsGetLastFocusedFunction> get_current_app_function = new WindowsGetLastFocusedFunction(); - std::unique_ptr<base::DictionaryValue> result(utils::ToDictionary( - RunFunction(get_current_app_function.get(), - "[{\"populate\": true, \"windowTypes\": [ \"app\" ]}]"))); - int app_window_id = app_window->session_id().id(); - EXPECT_EQ(app_window_id, api_test_utils::GetInteger(result.get(), "id")); - EXPECT_EQ(-1, GetTabId(result.get())); - EXPECT_EQ("app", api_test_utils::GetString(result.get(), "type")); + get_current_app_function->set_extension(extension()); + EXPECT_EQ( + tabs_constants::kNoLastFocusedWindowError, + extension_function_test_utils::RunFunctionAndReturnError( + get_current_app_function.get(), + "[{\"populate\": true, \"windowTypes\": [ \"app\" ]}]", browser())); } chrome::CloseWindow(normal_browser); @@ -1113,7 +1057,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, AcceptState) { std::unique_ptr<base::DictionaryValue> result( utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"state\": \"minimized\"}]", browser(), - utils::INCLUDE_INCOGNITO))); + api_test_utils::INCLUDE_INCOGNITO))); int window_id = GetWindowId(result.get()); std::string error; Browser* new_window = ExtensionTabUtil::GetBrowserFromWindowID( @@ -1129,7 +1073,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, AcceptState) { function->set_extension(extension.get()); result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"state\": \"fullscreen\"}]", browser(), - utils::INCLUDE_INCOGNITO))); + api_test_utils::INCLUDE_INCOGNITO))); window_id = GetWindowId(result.get()); new_window = ExtensionTabUtil::GetBrowserFromWindowID( ChromeExtensionFunctionDetails(function.get()), window_id, &error); @@ -1406,7 +1350,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Creates Tab object to ensure the property is correct for the extension. std::unique_ptr<api::tabs::Tab> tab_object_a = - ExtensionTabUtil::CreateTabObject(web_contents_a, tab_strip_model, 0); + ExtensionTabUtil::CreateTabObject(web_contents_a, + ExtensionTabUtil::kDontScrubTab, + nullptr, tab_strip_model, 0); EXPECT_FALSE(tab_object_a->discarded); // Discards one tab. @@ -1414,8 +1360,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { web_contents_a = tab_strip_model->GetWebContentsAt(1); // Make sure the property is changed accordingly after discarding the tab. - tab_object_a = - ExtensionTabUtil::CreateTabObject(web_contents_a, tab_strip_model, 0); + tab_object_a = ExtensionTabUtil::CreateTabObject( + web_contents_a, ExtensionTabUtil::kDontScrubTab, nullptr, tab_strip_model, + 0); EXPECT_TRUE(tab_object_a->discarded); // Get non-discarded tabs after discarding one tab. @@ -1520,6 +1467,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardWithId) { tab_id = ExtensionTabUtil::GetTabId(web_contents); EXPECT_EQ(tab_id, api_test_utils::GetInteger(result.get(), "id")); EXPECT_TRUE(api_test_utils::GetBoolean(result.get(), "discarded")); + // The result should be scrubbed. + EXPECT_FALSE(result->FindKey("url")); // Tests chrome.tabs.discard(tabId) with an already discarded tab. It has to // return the error stating that the tab couldn't be discarded. @@ -1592,6 +1541,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardWithoutId) { EXPECT_EQ(ExtensionTabUtil::GetTabId(web_contents), api_test_utils::GetInteger(result.get(), "id")); EXPECT_TRUE(api_test_utils::GetBoolean(result.get(), "discarded")); + // The result should be scrubbed. + EXPECT_FALSE(result->FindKey("url")); } // Tests chrome.tabs.discard() without disabling protection time. @@ -1633,7 +1584,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { // Creates Tab object to ensure the property is correct for the extension. TabStripModel* tab_strip_model = browser()->tab_strip_model(); std::unique_ptr<api::tabs::Tab> tab_object_a = - ExtensionTabUtil::CreateTabObject(web_contents_a, tab_strip_model, 0); + ExtensionTabUtil::CreateTabObject(web_contents_a, + ExtensionTabUtil::kDontScrubTab, + nullptr, tab_strip_model, 0); EXPECT_TRUE(tab_object_a->auto_discardable); // Set up query and update functions with the extension. @@ -1675,8 +1628,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { api_test_utils::GetBoolean(update_result.get(), "autoDiscardable")); // Make sure the property is changed accordingly after updating the tab. - tab_object_a = - ExtensionTabUtil::CreateTabObject(web_contents_a, tab_strip_model, 0); + tab_object_a = ExtensionTabUtil::CreateTabObject( + web_contents_a, ExtensionTabUtil::kDontScrubTab, nullptr, tab_strip_model, + 0); EXPECT_FALSE(tab_object_a->auto_discardable); // Get auto-discardable tabs after changing the status of web contents A. @@ -1786,7 +1740,7 @@ bool ExtensionTabsZoomTest::RunSetZoom(int tab_id, double zoom_factor) { return utils::RunFunction( set_zoom_function.get(), base::StringPrintf("[%u, %lf]", tab_id, zoom_factor), browser(), - extension_function_test_utils::NONE); + api_test_utils::NONE); } testing::AssertionResult ExtensionTabsZoomTest::RunGetZoom( @@ -1825,10 +1779,8 @@ bool ExtensionTabsZoomTest::RunSetZoomSettings(int tab_id, args = base::StringPrintf("[%u, {\"mode\": \"%s\"}]", tab_id, mode); } - return utils::RunFunction(set_zoom_settings_function.get(), - args, - browser(), - extension_function_test_utils::NONE); + return utils::RunFunction(set_zoom_settings_function.get(), args, browser(), + api_test_utils::NONE); } testing::AssertionResult ExtensionTabsZoomTest::RunGetZoomSettings( diff --git a/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc index 0ef3e6f98d1..7e4412108a2 100644 --- a/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc +++ b/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc @@ -14,6 +14,7 @@ #include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/api/tabs/windows_util.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/window_controller.h" #include "chrome/browser/extensions/window_controller_list.h" @@ -42,14 +43,19 @@ bool ControllerVisibleToListener(WindowController* window_controller, // If there is no filter the visibility is based on the extension. const base::ListValue* filter_value = nullptr; - if (!listener_filter || - !listener_filter->GetList(keys::kWindowTypesKey, &filter_value)) - return window_controller->IsVisibleToExtension(extension); - - // Otherwise it's based on the type filter. - WindowController::TypeFilter filter = - WindowController::GetFilterFromWindowTypesValues(filter_value); - return window_controller->MatchesFilter(filter); + if (listener_filter) + listener_filter->GetList(keys::kWindowTypesKey, &filter_value); + + // TODO(https://crbug.com/807313): Remove this. + bool allow_dev_tools_windows = !!filter_value; + if (!window_controller->IsVisibleToTabsAPIForExtension( + extension, allow_dev_tools_windows)) { + return false; + } + + return !filter_value || + window_controller->MatchesFilter( + WindowController::GetFilterFromWindowTypesValues(filter_value)); } bool WillDispatchWindowEvent(WindowController* window_controller, @@ -59,6 +65,13 @@ bool WillDispatchWindowEvent(WindowController* window_controller, const base::DictionaryValue* listener_filter) { bool has_filter = listener_filter && listener_filter->HasKey(keys::kWindowTypesKey); + // TODO(https://crbug.com/807313): Remove this. + bool allow_dev_tools_windows = has_filter; + if (!window_controller->IsVisibleToTabsAPIForExtension( + extension, allow_dev_tools_windows)) { + return false; + } + // Cleanup previous values. event->filter_info = EventFilteringInfo(); // Only set the window type if the listener has set a filter. @@ -66,8 +79,7 @@ bool WillDispatchWindowEvent(WindowController* window_controller, if (has_filter) { event->filter_info.window_type = window_controller->GetWindowTypeText(); } else { - event->filter_info.window_exposed_by_default = - window_controller->IsVisibleToExtension(extension); + event->filter_info.window_exposed_by_default = true; } return true; } @@ -194,9 +206,14 @@ void WindowsEventRouter::OnWindowControllerAdded( return; if (!profile_->IsSameProfile(window_controller->profile())) return; + // Ignore any windows without an associated browser (e.g., AppWindows). + if (!window_controller->GetBrowser()) + return; std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append(window_controller->CreateWindowValue()); + args->Append(ExtensionTabUtil::CreateWindowValueForExtension( + *window_controller->GetBrowser(), nullptr, + ExtensionTabUtil::kDontPopulateTabs)); DispatchEvent(events::WINDOWS_ON_CREATED, windows::OnCreated::kEventName, window_controller, std::move(args)); } @@ -207,6 +224,9 @@ void WindowsEventRouter::OnWindowControllerRemoved( return; if (!profile_->IsSameProfile(window_controller->profile())) return; + // Ignore any windows without an associated browser (e.g., AppWindows). + if (!window_controller->GetBrowser()) + return; int window_id = window_controller->GetWindowId(); std::unique_ptr<base::ListValue> args(new base::ListValue()); diff --git a/chromium/chrome/browser/extensions/api/tabs/windows_util.cc b/chromium/chrome/browser/extensions/api/tabs/windows_util.cc index e7179e62225..3d75225bf78 100644 --- a/chromium/chrome/browser/extensions/api/tabs/windows_util.cc +++ b/chromium/chrome/browser/extensions/api/tabs/windows_util.cc @@ -23,38 +23,48 @@ namespace windows_util { -bool GetWindowFromWindowID(UIThreadExtensionFunction* function, - int window_id, - extensions::WindowController::TypeFilter filter, - extensions::WindowController** controller, - std::string* error) { +bool GetBrowserFromWindowID(UIThreadExtensionFunction* function, + int window_id, + extensions::WindowController::TypeFilter filter, + Browser** browser, + std::string* error) { + DCHECK(browser); DCHECK(error); + + *browser = nullptr; if (window_id == extension_misc::kCurrentWindowId) { - extensions::WindowController* extension_window_controller = - function->dispatcher()->GetExtensionWindowController(); // If there is a window controller associated with this extension, use that. - if (extension_window_controller) { - *controller = extension_window_controller; - } else { + extensions::WindowController* window_controller = + function->dispatcher()->GetExtensionWindowController(); + if (!window_controller) { // Otherwise get the focused or most recently added window. - *controller = extensions::WindowControllerList::GetInstance() - ->CurrentWindowForFunctionWithFilter(function, filter); + window_controller = + extensions::WindowControllerList::GetInstance() + ->CurrentWindowForFunctionWithFilter(function, filter); } - if (!(*controller)) { + + if (window_controller) + *browser = window_controller->GetBrowser(); + + if (!(*browser)) { *error = extensions::tabs_constants::kNoCurrentWindowError; return false; } } else { - *controller = + extensions::WindowController* window_controller = extensions::WindowControllerList::GetInstance() ->FindWindowForFunctionByIdWithFilter(function, window_id, filter); - if (!(*controller)) { + if (window_controller) + *browser = window_controller->GetBrowser(); + + if (!(*browser)) { *error = extensions::ErrorUtils::FormatErrorMessage( extensions::tabs_constants::kWindowNotFoundError, base::IntToString(window_id)); return false; } } + DCHECK(*browser); return true; } @@ -64,9 +74,13 @@ bool CanOperateOnWindow(const UIThreadExtensionFunction* function, if (filter && !controller->MatchesFilter(filter)) return false; - if (!filter && function->extension() && - !controller->IsVisibleToExtension(function->extension())) + // TODO(https://crbug.com/807313): Remove this. + bool allow_dev_tools_windows = !!filter; + if (function->extension() && + !controller->IsVisibleToTabsAPIForExtension(function->extension(), + allow_dev_tools_windows)) { return false; + } if (function->browser_context() == controller->profile()) return true; diff --git a/chromium/chrome/browser/extensions/api/tabs/windows_util.h b/chromium/chrome/browser/extensions/api/tabs/windows_util.h index d5526e10c30..f080d068c72 100644 --- a/chromium/chrome/browser/extensions/api/tabs/windows_util.h +++ b/chromium/chrome/browser/extensions/api/tabs/windows_util.h @@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_TABS_WINDOWS_UTIL_H__ #define CHROME_BROWSER_EXTENSIONS_API_TABS_WINDOWS_UTIL_H__ +#include <string> + #include "chrome/browser/extensions/window_controller_list.h" class UIThreadExtensionFunction; @@ -15,13 +17,13 @@ class WindowController; namespace windows_util { -// Populates |controller| for given |window_id|. If the window is not found, +// Populates |browser| for given |window_id|. If the window is not found, // returns false and sets |error|. -bool GetWindowFromWindowID(UIThreadExtensionFunction* function, - int window_id, - extensions::WindowController::TypeFilter filter, - extensions::WindowController** controller, - std::string* error); +bool GetBrowserFromWindowID(UIThreadExtensionFunction* function, + int window_id, + extensions::WindowController::TypeFilter filter, + Browser** browser, + std::string* error); // Returns true if |function| (and the profile and extension that it was // invoked from) can operate on the window wrapped by |window_controller|. diff --git a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc index ee44ebfbdde..6a0a795e8bb 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc @@ -6,6 +6,7 @@ #include <memory> #include <utility> +#include <vector> #include "base/bind.h" #include "base/command_line.h" @@ -25,6 +26,7 @@ #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window_registry.h" #include "extensions/browser/event_router.h" +#include "extensions/browser/extensions_browser_client.h" namespace terminal_private = extensions::api::terminal_private; namespace OnTerminalResize = @@ -43,6 +45,9 @@ const char kCroshCommand[] = "/usr/bin/crosh"; // We make stubbed crosh just echo back input. const char kStubbedCroshCommand[] = "cat"; +const char kVmShellName[] = "vmshell"; +const char kVmShellCommand[] = "/usr/bin/vsh"; + std::string GetCroshPath() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kCroshCommand)) @@ -54,13 +59,24 @@ std::string GetCroshPath() { return std::string(kStubbedCroshCommand); } +// Get the program to run based on the openTerminalProcess JS request. std::string GetProcessCommandForName(const std::string& name) { if (name == kCroshName) return GetCroshPath(); + else if (name == kVmShellName) + return kVmShellCommand; else return std::string(); } +// Whether the program accepts arbitrary command line arguments. +bool CommandSupportsArguments(const std::string& name) { + if (name == kVmShellName) + return true; + + return false; +} + void NotifyProcessOutput(content::BrowserContext* browser_context, const std::string& extension_id, int tab_id, @@ -119,10 +135,24 @@ TerminalPrivateOpenTerminalProcessFunction::Run() { OpenTerminalProcess::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - command_ = GetProcessCommandForName(params->process_name); - if (command_.empty()) + const std::string command = GetProcessCommandForName(params->process_name); + if (command.empty()) return RespondNow(Error("Invalid process name.")); + const std::string user_id_hash = + ExtensionsBrowserClient::Get()->GetUserIdHashFromContext( + browser_context()); + + std::vector<std::string> arguments; + arguments.push_back(command); + if (params->args) { + for (const std::string& arg : *params->args) + arguments.push_back(arg); + } + + if (arguments.size() > 1 && !CommandSupportsArguments(params->process_name)) + return RespondNow(Error("Specified command does not support arguments.")); + content::WebContents* caller_contents = GetSenderWebContents(); if (!caller_contents) return RespondNow(Error("No web contents.")); @@ -151,19 +181,23 @@ TerminalPrivateOpenTerminalProcessFunction::Run() { tab_id), base::Bind( &TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread, - this))); + this), + arguments, + user_id_hash)); return RespondLater(); } void TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner( const ProcessOutputCallback& output_callback, - const OpenProcessCallback& callback) { - DCHECK(!command_.empty()); - + const OpenProcessCallback& callback, + const std::vector<std::string>& arguments, + const std::string& user_id_hash) { chromeos::ProcessProxyRegistry* registry = chromeos::ProcessProxyRegistry::Get(); + const base::CommandLine cmdline{arguments}; - int terminal_id = registry->OpenProcess(command_.c_str(), output_callback); + int terminal_id = + registry->OpenProcess(cmdline, user_id_hash, output_callback); content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, base::BindOnce(callback, terminal_id)); diff --git a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h index c6455784059..cd7018fb2e4 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_TERMINAL_TERMINAL_PRIVATE_API_H_ #include <string> +#include <vector> #include "extensions/browser/extension_function.h" @@ -33,10 +34,10 @@ class TerminalPrivateOpenTerminalProcessFunction using OpenProcessCallback = base::Callback<void(int terminal_id)>; void OpenOnRegistryTaskRunner(const ProcessOutputCallback& output_callback, - const OpenProcessCallback& callback); + const OpenProcessCallback& callback, + const std::vector<std::string>& arguments, + const std::string& user_id_hash); void RespondOnUIThread(int terminal_id); - - std::string command_; }; // Send input to the terminal process specified by the terminal ID, which is set diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/DEPS b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/DEPS index fee9b1012b1..381dab83266 100644 --- a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/DEPS +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/DEPS @@ -2,4 +2,5 @@ include_rules = [ # TODO(mash): Remove. http://crbug.com/678705 "+ash/shell.h", "+media/audio" + "+services/audio/public/cpp" ] diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc index 02d77771c79..7e58654da56 100644 --- a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc @@ -21,10 +21,13 @@ #include "chrome/browser/ui/chrome_pages.h" #include "chrome/common/url_constants.h" #include "components/user_manager/user_manager.h" +#include "content/public/common/service_manager_connection.h" #include "extensions/browser/event_router.h" #include "extensions/common/api/virtual_keyboard.h" #include "extensions/common/api/virtual_keyboard_private.h" #include "media/audio/audio_system.h" +#include "services/audio/public/cpp/audio_system_factory.h" +#include "services/service_manager/public/cpp/connector.h" #include "ui/aura/window_tree_host.h" #include "ui/keyboard/keyboard_controller.h" #include "ui/keyboard/keyboard_switches.h" @@ -74,7 +77,10 @@ void ChromeVirtualKeyboardDelegate::GetKeyboardConfig( OnKeyboardSettingsCallback on_settings_callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (!audio_system_) - audio_system_ = media::AudioSystem::CreateInstance(); + audio_system_ = audio::CreateAudioSystem( + content::ServiceManagerConnection::GetForProcess() + ->GetConnector() + ->Clone()); audio_system_->HasInputDevices( base::BindOnce(&ChromeVirtualKeyboardDelegate::OnHasInputDevices, weak_this_, std::move(on_settings_callback))); @@ -152,24 +158,31 @@ bool ChromeVirtualKeyboardDelegate::ShowLanguageSettings() { return true; } -bool ChromeVirtualKeyboardDelegate::SetVirtualKeyboardMode(int mode_enum) { +bool ChromeVirtualKeyboardDelegate::SetVirtualKeyboardMode( + int mode_enum, + OnSetModeCallback on_set_mode_callback) { keyboard::KeyboardController* controller = keyboard::KeyboardController::GetInstance(); if (!controller) return false; - switch (mode_enum) { + controller->SetContainerType(ConvertKeyboardModeToContainerType(mode_enum), + std::move(on_set_mode_callback)); + return true; +} + +keyboard::ContainerType +ChromeVirtualKeyboardDelegate::ConvertKeyboardModeToContainerType( + int mode) const { + switch (mode) { case keyboard_api::KEYBOARD_MODE_FULL_WIDTH: - controller->SetContainerType(keyboard::ContainerType::FULL_WIDTH); - break; + return keyboard::ContainerType::FULL_WIDTH; case keyboard_api::KEYBOARD_MODE_FLOATING: - controller->SetContainerType(keyboard::ContainerType::FLOATING); - break; - default: - NOTREACHED(); - break; + return keyboard::ContainerType::FLOATING; } - return true; + + NOTREACHED(); + return keyboard::ContainerType::FULL_WIDTH; } bool ChromeVirtualKeyboardDelegate::SetDraggableArea( diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h index 1ec4b52b068..d0af47f0095 100644 --- a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h @@ -13,6 +13,7 @@ #include "content/public/browser/browser_context.h" #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" #include "extensions/common/api/virtual_keyboard.h" +#include "ui/keyboard/container_type.h" namespace media { class AudioSystem; @@ -44,7 +45,8 @@ class ChromeVirtualKeyboardDelegate : public VirtualKeyboardDelegate { int modifiers) override; bool ShowLanguageSettings() override; bool IsLanguageSettingsEnabled() override; - bool SetVirtualKeyboardMode(int mode_enum) override; + bool SetVirtualKeyboardMode(int mode_enum, + OnSetModeCallback on_set_mode_callback) override; bool SetDraggableArea( const api::virtual_keyboard_private::Bounds& rect) override; bool SetRequestedKeyboardState(int state_enum) override; @@ -57,6 +59,7 @@ class ChromeVirtualKeyboardDelegate : public VirtualKeyboardDelegate { bool has_audio_input_devices); void DispatchConfigChangeEvent( std::unique_ptr<base::DictionaryValue> settings); + keyboard::ContainerType ConvertKeyboardModeToContainerType(int mode) const; content::BrowserContext* browser_context_; std::unique_ptr<media::AudioSystem> audio_system_; diff --git a/chromium/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc b/chromium/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc index 43798f375e0..cd090aab65c 100644 --- a/chromium/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc +++ b/chromium/chrome/browser/extensions/api/vpn_provider/vpn_provider_apitest.cc @@ -197,8 +197,8 @@ class VpnProviderApiTest : public ExtensionApiTest, void TriggerInternalRemove() { NetworkHandler::Get()->network_configuration_handler()->RemoveConfiguration( GetSingleServicePath(), - NetworkConfigurationObserver::SOURCE_USER_ACTION, - base::Bind(base::DoNothing), base::Bind(DoNothingFailureCallback)); + NetworkConfigurationObserver::SOURCE_USER_ACTION, base::DoNothing(), + base::Bind(DoNothingFailureCallback)); } // NetworkConfigurationObserver: diff --git a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc index 28dbc774e12..747efc84c3b 100644 --- a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc +++ b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc @@ -37,7 +37,6 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/web_contents.h" -#include "content/public/common/browser_side_navigation_policy.h" #include "content/public/common/context_menu_params.h" #include "content/public/common/resource_type.h" #include "content/public/common/url_constants.h" @@ -276,8 +275,6 @@ IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, ServerRedirectSingleProcess) { } IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, ForwardBack) { - if (content::IsBrowserSideNavigationEnabled()) - return; // TODO(jam): investigate ASSERT_TRUE(RunExtensionTest("webnavigation/forwardBack")) << message_; } @@ -384,9 +381,9 @@ IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, RequestOpenTab) { ui_test_utils::NavigateToURL(browser(), url); // There's a link on a.html. Middle-click on it to open it in a new tab. - blink::WebMouseEvent mouse_event(blink::WebInputEvent::kMouseDown, - blink::WebInputEvent::kNoModifiers, - blink::WebInputEvent::kTimeStampForTesting); + blink::WebMouseEvent mouse_event( + blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.button = blink::WebMouseEvent::Button::kMiddle; mouse_event.SetPositionInWidget(7, 7); mouse_event.click_count = 1; @@ -416,9 +413,9 @@ IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, TargetBlank) { // There's a link with target=_blank on a.html. Click on it to open it in a // new tab. - blink::WebMouseEvent mouse_event(blink::WebInputEvent::kMouseDown, - blink::WebInputEvent::kNoModifiers, - blink::WebInputEvent::kTimeStampForTesting); + blink::WebMouseEvent mouse_event( + blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.button = blink::WebMouseEvent::Button::kLeft; mouse_event.SetPositionInWidget(7, 7); mouse_event.click_count = 1; @@ -446,9 +443,9 @@ IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, TargetBlankIncognito) { // There's a link with target=_blank on a.html. Click on it to open it in a // new tab. - blink::WebMouseEvent mouse_event(blink::WebInputEvent::kMouseDown, - blink::WebInputEvent::kNoModifiers, - blink::WebInputEvent::kTimeStampForTesting); + blink::WebMouseEvent mouse_event( + blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.button = blink::WebMouseEvent::Button::kLeft; mouse_event.SetPositionInWidget(7, 7); mouse_event.click_count = 1; diff --git a/chromium/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chromium/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc index dd86f9b6972..b43a05f9a61 100644 --- a/chromium/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc @@ -139,6 +139,19 @@ void GetPartOfMessageArguments(IPC::Message* message, ASSERT_TRUE(list.GetDictionary(0, out)); } +base::Value FormBinaryValue(base::StringPiece str) { + base::Value list(base::Value::Type::LIST); + list.GetList().emplace_back(base::Value( + base::Value::BlobStorage(str.data(), str.data() + str.size()))); + return list; +} + +base::Value FormStringValue(base::StringPiece str) { + base::Value list(base::Value::Type::LIST); + list.GetList().emplace_back(base::Value(str)); + return list; +} + } // namespace // A mock event router that responds to events with a pre-arranged queue of @@ -566,7 +579,7 @@ void ExtensionWebRequestTest::FireURLRequestWithData( &(bytes_2[0]), bytes_2.size())); request->set_upload(std::make_unique<net::ElementsUploadDataStream>( std::move(element_readers), 0)); - ipc_sender_.PushTask(base::Bind(&base::DoNothing)); + ipc_sender_.PushTask(base::DoNothing()); request->Start(); } @@ -592,17 +605,26 @@ TEST_F(ExtensionWebRequestTest, AccessRequestBodyData) { const size_t kPlainBlock2Length = sizeof(kPlainBlock2) - 1; std::vector<char> plain_2(kPlainBlock2, kPlainBlock2 + kPlainBlock2Length); #define kBoundary "THIS_IS_A_BOUNDARY" - const char kFormBlock1[] = "--" kBoundary "\r\n" + const char kFormBlock1[] = + "--" kBoundary + "\r\n" "Content-Disposition: form-data; name=\"A\"\r\n" "\r\n" "test text\r\n" - "--" kBoundary "\r\n" + "--" kBoundary + "\r\n" "Content-Disposition: form-data; name=\"B\"; filename=\"\"\r\n" "Content-Type: application/octet-stream\r\n" - "\r\n"; + "\r\n" + "--" kBoundary + "\r\n" + "Content-Disposition: form-data; name=\"B_content\"\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "\uffff\uffff\uffff\uffff\r\n" + "--" kBoundary "\r\n"; std::vector<char> form_1(kFormBlock1, kFormBlock1 + sizeof(kFormBlock1) - 1); - const char kFormBlock2[] = "\r\n" - "--" kBoundary "\r\n" + const char kFormBlock2[] = "Content-Disposition: form-data; name=\"C\"\r\n" "\r\n" "test password\r\n" @@ -623,10 +645,21 @@ TEST_F(ExtensionWebRequestTest, AccessRequestBodyData) { &kRawPath }; // Contents of formData. - const char kFormData[] = - "{\"A\":[\"test text\"],\"B\":[\"\"],\"C\":[\"test password\"]}"; - std::unique_ptr<const base::Value> form_data = - base::JSONReader::Read(kFormData); + struct KeyValuePairs { + const char* key; + base::Value value; + }; + KeyValuePairs kFormDataPairs[] = { + {"A", FormStringValue("test text")}, + {"B", FormStringValue("")}, + {"B_content", FormBinaryValue("\uffff\uffff\uffff\uffff")}, + {"C", FormStringValue("test password")}}; + std::unique_ptr<base::Value> form_data = + std::make_unique<base::Value>(base::Value::Type::DICTIONARY); + for (auto& pair : kFormDataPairs) { + form_data->SetKey(pair.key, std::move(pair.value)); + } + ASSERT_TRUE(form_data.get() != NULL); ASSERT_TRUE(form_data->type() == base::Value::Type::DICTIONARY); // Contents of raw. @@ -782,7 +815,7 @@ TEST_F(ExtensionWebRequestTest, MinimalAccessRequestBodyData) { // Only one request is sent, but more than one event will be triggered. for (size_t i = 1; i < arraysize(kExpected); ++i) - ipc_sender_.PushTask(base::Bind(&base::DoNothing)); + ipc_sender_.PushTask(base::DoNothing()); const std::vector<char> part_of_body(1); FireURLRequestWithData("POST", nullptr, part_of_body, part_of_body); @@ -855,7 +888,7 @@ TEST_F(ExtensionWebRequestTest, ProperFilteringInPublicSession) { // Only one request is sent, but more than one event will be triggered. for (size_t i = 1; i < arraysize(kExpected); ++i) - ipc_sender_.PushTask(base::Bind(&base::DoNothing)); + ipc_sender_.PushTask(base::DoNothing()); const std::vector<char> part_of_body(1); FireURLRequestWithData("POST", nullptr, part_of_body, part_of_body); @@ -920,7 +953,7 @@ TEST_F(ExtensionWebRequestTest, NoAccessRequestBodyData) { context_->CreateRequest(request_url, net::DEFAULT_PRIORITY, &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS)); request->set_method(kMethods[i]); - ipc_sender_.PushTask(base::Bind(&base::DoNothing)); + ipc_sender_.PushTask(base::DoNothing()); request->Start(); } @@ -1195,7 +1228,7 @@ TEST_P(ExtensionWebRequestHeaderModificationTest, TestModifications) { } // Don't do anything for the onSendHeaders message. - ipc_sender_.PushTask(base::Bind(&base::DoNothing)); + ipc_sender_.PushTask(base::DoNothing()); // Note that we mess up the headers slightly: // request->Start() will first add additional headers (e.g. the User-Agent) diff --git a/chromium/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chromium/chrome/browser/extensions/api/web_request/web_request_apitest.cc index e0b875c7d40..bd3b124bbf5 100644 --- a/chromium/chrome/browser/extensions/api/web_request/web_request_apitest.cc +++ b/chromium/chrome/browser/extensions/api/web_request/web_request_apitest.cc @@ -21,7 +21,6 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_with_management_policy_apitest.h" #include "chrome/browser/extensions/tab_helper.h" -#include "chrome/browser/extensions/test_extension_dir.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" @@ -48,6 +47,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/common/page_type.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/url_loader_interceptor.h" #include "extensions/browser/api/web_request/web_request_api.h" #include "extensions/browser/blocked_action_type.h" #include "extensions/browser/extension_system.h" @@ -55,6 +55,7 @@ #include "extensions/common/features/feature.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" #include "google_apis/gaia/gaia_switches.h" #include "net/dns/mock_host_resolver.h" #include "net/http/http_util.h" @@ -69,6 +70,7 @@ #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_filter.h" #include "net/url_request/url_request_interceptor.h" +#include "services/network/public/cpp/features.h" #include "third_party/WebKit/public/platform/WebInputEvent.h" #if defined(OS_CHROMEOS) @@ -283,22 +285,33 @@ class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest { host_resolver()->AddRule("*", "127.0.0.1"); int port = embedded_test_server()->port(); - base::RunLoop run_loop; - content::BrowserThread::PostTaskAndReply( - content::BrowserThread::IO, FROM_HERE, - base::BindOnce(&SetUpDevToolsFrontendInterceptorOnIO, port, - test_root_dir_), - run_loop.QuitClosure()); - run_loop.Run(); + + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { + url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>( + base::BindRepeating(&DevToolsFrontendInWebRequestApiTest::OnIntercept, + base::Unretained(this), port)); + } else { + base::RunLoop run_loop; + content::BrowserThread::PostTaskAndReply( + content::BrowserThread::IO, FROM_HERE, + base::BindOnce(&SetUpDevToolsFrontendInterceptorOnIO, port, + test_root_dir_), + run_loop.QuitClosure()); + run_loop.Run(); + } } void TearDownOnMainThread() override { - base::RunLoop run_loop; - content::BrowserThread::PostTaskAndReply( - content::BrowserThread::IO, FROM_HERE, - base::BindOnce(&TearDownDevToolsFrontendInterceptorOnIO), - run_loop.QuitClosure()); - run_loop.Run(); + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { + url_loader_interceptor_.reset(); + } else { + base::RunLoop run_loop; + content::BrowserThread::PostTaskAndReply( + content::BrowserThread::IO, FROM_HERE, + base::BindOnce(&TearDownDevToolsFrontendInterceptorOnIO), + run_loop.QuitClosure()); + run_loop.Run(); + } ExtensionApiTest::TearDownOnMainThread(); } @@ -317,7 +330,49 @@ class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest { } private: + bool OnIntercept(int test_server_port, + content::URLLoaderInterceptor::RequestParams* params) { + // See comments in DevToolsFrontendInterceptor above. The devtools remote + // frontend URLs are hardcoded into Chrome and are requested by some of the + // tests here to exercise their behavior with respect to WebRequest. + // + // We treat any URL request not targeting the test server as targeting the + // remote frontend, and we intercept them to fulfill from test data rather + // than hitting the network. + if (params->url_request.url.EffectiveIntPort() == test_server_port) + return false; + + std::string status_line; + std::string contents; + GetFileContents( + test_root_dir_.AppendASCII(params->url_request.url.path().substr(1)), + &status_line, &contents); + content::URLLoaderInterceptor::WriteResponse(status_line, contents, + params->client.get()); + return true; + } + + static void GetFileContents(const base::FilePath& path, + std::string* status_line, + std::string* contents) { + base::ScopedAllowBlockingForTesting allow_io; + if (!base::ReadFileToString(path, contents)) { + *status_line = "HTTP/1.0 404 Not Found\n\n"; + return; + } + + std::string content_type; + if (path.Extension() == FILE_PATH_LITERAL(".html")) + content_type = "Content-type: text/html\n"; + else if (path.Extension() == FILE_PATH_LITERAL(".js")) + content_type = "Content-type: application/javascript\n"; + + *status_line = + base::StringPrintf("HTTP/1.0 200 OK\n%s\n", content_type.c_str()); + } + base::FilePath test_root_dir_; + std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_; }; IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestApi) { @@ -467,9 +522,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestNewTab) { // There's a link on a.html with target=_blank. Click on it to open it in a // new tab. - blink::WebMouseEvent mouse_event(blink::WebInputEvent::kMouseDown, - blink::WebInputEvent::kNoModifiers, - blink::WebInputEvent::kTimeStampForTesting); + blink::WebMouseEvent mouse_event( + blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, + blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.button = blink::WebMouseEvent::Button::kLeft; mouse_event.SetPositionInWidget(7, 7); mouse_event.click_count = 1; @@ -1297,7 +1352,21 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MinimumAccessInitiator) { // Ensure that devtools frontend requests are hidden from the webRequest API. IN_PROC_BROWSER_TEST_F(DevToolsFrontendInWebRequestApiTest, HiddenRequests) { - ASSERT_TRUE(RunExtensionSubtest("webrequest", "test_devtools.html")) + // Test expectations differ with the Network Service because of the way + // request interception is done for the test. In the legacy networking path a + // URLRequestMockHTTPJob is used, which does not generate + // |onBeforeHeadersSent| events. With the Network Service enabled, requests + // issued to HTTP URLs by these tests look like real HTTP requests and + // therefore do generate |onBeforeHeadersSent| events. + // + // These tests adjust their expectations accordingly based on whether or not + // the Network Service is enabled. + const char* network_service_arg = + base::FeatureList::IsEnabled(network::features::kNetworkService) + ? "NetworkServiceEnabled" + : "NetworkServiceDisabled"; + ASSERT_TRUE(RunExtensionSubtestWithArg("webrequest", "test_devtools.html", + network_service_arg)) << message_; } diff --git a/chromium/chrome/browser/extensions/api/web_request/web_request_permissions_unittest.cc b/chromium/chrome/browser/extensions/api/web_request/web_request_permissions_unittest.cc index 8bcd3345fe3..b2723a15ed6 100644 --- a/chromium/chrome/browser/extensions/api/web_request/web_request_permissions_unittest.cc +++ b/chromium/chrome/browser/extensions/api/web_request/web_request_permissions_unittest.cc @@ -193,6 +193,7 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, TestHideRequestForURL) { TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, TestCanExtensionAccessURL_HostPermissions) { + // Request with empty initiator. std::unique_ptr<net::URLRequest> request( context.CreateRequest(GURL("http://example.com"), net::DEFAULT_PRIORITY, NULL, TRAFFIC_ANNOTATION_FOR_TESTS)); @@ -211,15 +212,23 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, request->url(), -1, // No tab id. false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION, + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, request->initiator())); EXPECT_EQ(PermissionsData::ACCESS_ALLOWED, WebRequestPermissions::CanExtensionAccessURL( extension_info_map_.get(), com_extension_->id(), request->url(), -1, // No tab id. false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION, + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, request->initiator())); + EXPECT_EQ( + PermissionsData::ACCESS_ALLOWED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), com_extension_->id(), request->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL_AND_INITIATOR, + request->initiator())); EXPECT_EQ(PermissionsData::ACCESS_DENIED, WebRequestPermissions::CanExtensionAccessURL( extension_info_map_.get(), com_extension_->id(), request->url(), @@ -227,6 +236,54 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, false, // crosses_incognito WebRequestPermissions::REQUIRE_ALL_URLS, request->initiator())); + std::unique_ptr<net::URLRequest> request_with_initiator( + context.CreateRequest(GURL("http://example.com"), net::DEFAULT_PRIORITY, + nullptr, TRAFFIC_ANNOTATION_FOR_TESTS)); + request_with_initiator->set_initiator( + url::Origin::Create(GURL("http://www.example.org"))); + + EXPECT_EQ(PermissionsData::ACCESS_ALLOWED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), permissionless_extension_->id(), + request_with_initiator->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::DO_NOT_CHECK_HOST, + request_with_initiator->initiator())); + EXPECT_EQ(PermissionsData::ACCESS_DENIED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), permissionless_extension_->id(), + request_with_initiator->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, + request_with_initiator->initiator())); + EXPECT_EQ(PermissionsData::ACCESS_ALLOWED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), com_extension_->id(), + request_with_initiator->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, + request_with_initiator->initiator())); + EXPECT_EQ( + PermissionsData::ACCESS_DENIED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), com_extension_->id(), + request_with_initiator->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL_AND_INITIATOR, + request_with_initiator->initiator())); + EXPECT_EQ(PermissionsData::ACCESS_DENIED, + WebRequestPermissions::CanExtensionAccessURL( + extension_info_map_.get(), com_extension_->id(), + request_with_initiator->url(), + -1, // No tab id. + false, // crosses_incognito + WebRequestPermissions::REQUIRE_ALL_URLS, + request_with_initiator->initiator())); + // Public Sessions tests. #if defined(OS_CHROMEOS) std::unique_ptr<net::URLRequest> org_request(context.CreateRequest( @@ -239,7 +296,7 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, org_request->url(), -1, // No tab id. false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION, + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, org_request->initiator())); chromeos::ScopedTestPublicSessionLoginState login_state; @@ -252,7 +309,7 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, org_request->url(), -1, // No tab id. false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION, + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, org_request->initiator())); EXPECT_EQ( @@ -275,7 +332,7 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, chrome_request->url(), -1, // No tab id. false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION, + WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, chrome_request->initiator())); #endif } diff --git a/chromium/chrome/browser/extensions/api/webrtc_audio_private/DEPS b/chromium/chrome/browser/extensions/api/webrtc_audio_private/DEPS index 67c01d06986..287341aed80 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_audio_private/DEPS +++ b/chromium/chrome/browser/extensions/api/webrtc_audio_private/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+media/audio" + "services/audio/public/cpp" ] diff --git a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.cc b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.cc index fa1e2bdda32..22301de62d9 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.cc +++ b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.cc @@ -18,11 +18,14 @@ #include "content/public/browser/media_device_id.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/service_manager_connection.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/error_utils.h" #include "extensions/common/permissions/permissions_data.h" #include "media/audio/audio_system.h" +#include "services/audio/public/cpp/audio_system_factory.h" +#include "services/service_manager/public/cpp/connector.h" #include "url/gurl.h" #include "url/origin.h" @@ -106,7 +109,7 @@ WebrtcAudioPrivateFunction::~WebrtcAudioPrivateFunction() {} std::string WebrtcAudioPrivateFunction::CalculateHMAC( const std::string& raw_id) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CURRENTLY_ON(BrowserThread::UI); // We don't hash the default device description, and we always return // "default" for the default device. There is code in SetActiveSink @@ -130,9 +133,13 @@ std::string WebrtcAudioPrivateFunction::device_id_salt() const { } media::AudioSystem* WebrtcAudioPrivateFunction::GetAudioSystem() { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (!audio_system_) - audio_system_ = media::AudioSystem::CreateInstance(); + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!audio_system_) { + audio_system_ = audio::CreateAudioSystem( + content::ServiceManagerConnection::GetForProcess() + ->GetConnector() + ->Clone()); + } return audio_system_.get(); } @@ -176,27 +183,17 @@ WebrtcAudioPrivateFunction::GetRenderProcessHostFromRequest( bool WebrtcAudioPrivateGetSinksFunction::RunAsync() { DCHECK_CURRENTLY_ON(BrowserThread::UI); InitDeviceIDSalt(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::BindOnce(&WebrtcAudioPrivateGetSinksFunction:: - GetOutputDeviceDescriptionsOnIOThread, - this)); - return true; -} - -void WebrtcAudioPrivateGetSinksFunction:: - GetOutputDeviceDescriptionsOnIOThread() { - DCHECK_CURRENTLY_ON(BrowserThread::IO); GetAudioSystem()->GetDeviceDescriptions( - false, base::BindOnce(&WebrtcAudioPrivateGetSinksFunction:: - ReceiveOutputDeviceDescriptionsOnIOThread, - this)); + false, + base::BindOnce( + &WebrtcAudioPrivateGetSinksFunction::ReceiveOutputDeviceDescriptions, + this)); + return true; } -void WebrtcAudioPrivateGetSinksFunction:: - ReceiveOutputDeviceDescriptionsOnIOThread( - media::AudioDeviceDescriptions sink_devices) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); +void WebrtcAudioPrivateGetSinksFunction::ReceiveOutputDeviceDescriptions( + media::AudioDeviceDescriptions sink_devices) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); auto results = std::make_unique<SinkInfoVector>(); for (const media::AudioDeviceDescription& description : sink_devices) { wap::SinkInfo info; @@ -205,15 +202,6 @@ void WebrtcAudioPrivateGetSinksFunction:: // TODO(joi): Add other parameters. results->push_back(std::move(info)); } - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::BindOnce(&WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread, this, - base::Passed(&results))); -} - -void WebrtcAudioPrivateGetSinksFunction::DoneOnUIThread( - std::unique_ptr<SinkInfoVector> results) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); results_ = wap::GetSinks::Results::Create(*results); SendResponse(true); } @@ -230,28 +218,17 @@ bool WebrtcAudioPrivateGetAssociatedSinkFunction::RunAsync() { EXTENSION_FUNCTION_VALIDATE(params_.get()); InitDeviceIDSalt(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::BindOnce(&WebrtcAudioPrivateGetAssociatedSinkFunction:: - GetInputDeviceDescriptionsOnIOThread, - this)); - - return true; -} - -void WebrtcAudioPrivateGetAssociatedSinkFunction:: - GetInputDeviceDescriptionsOnIOThread() { - DCHECK_CURRENTLY_ON(BrowserThread::IO); GetAudioSystem()->GetDeviceDescriptions( true, base::BindOnce(&WebrtcAudioPrivateGetAssociatedSinkFunction:: - ReceiveInputDeviceDescriptionsOnIOThread, + ReceiveInputDeviceDescriptions, this)); + return true; } void WebrtcAudioPrivateGetAssociatedSinkFunction:: - ReceiveInputDeviceDescriptionsOnIOThread( + ReceiveInputDeviceDescriptions( media::AudioDeviceDescriptions source_devices) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CURRENTLY_ON(BrowserThread::UI); url::Origin security_origin = url::Origin::Create(GURL(params_->security_origin)); std::string source_id_in_origin(params_->source_id_in_origin); @@ -269,29 +246,25 @@ void WebrtcAudioPrivateGetAssociatedSinkFunction:: } } if (raw_source_id.empty()) { - CalculateHMACOnIOThread(std::string()); + CalculateHMACAndReply(base::nullopt); return; } GetAudioSystem()->GetAssociatedOutputDeviceID( raw_source_id, base::BindOnce( - &WebrtcAudioPrivateGetAssociatedSinkFunction::CalculateHMACOnIOThread, + &WebrtcAudioPrivateGetAssociatedSinkFunction::CalculateHMACAndReply, this)); } -void WebrtcAudioPrivateGetAssociatedSinkFunction::CalculateHMACOnIOThread( +void WebrtcAudioPrivateGetAssociatedSinkFunction::CalculateHMACAndReply( const base::Optional<std::string>& raw_sink_id) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!raw_sink_id || !raw_sink_id->empty()); // If no |raw_sink_id| is provided, the default device is used. - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::BindOnce( - &WebrtcAudioPrivateGetAssociatedSinkFunction::ReceiveHMACOnUIThread, - this, CalculateHMAC(raw_sink_id.value_or(std::string())))); + Reply(CalculateHMAC(raw_sink_id.value_or(std::string()))); } -void WebrtcAudioPrivateGetAssociatedSinkFunction::ReceiveHMACOnUIThread( +void WebrtcAudioPrivateGetAssociatedSinkFunction::Reply( const std::string& associated_sink_id) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (associated_sink_id == media::AudioDeviceDescription::kDefaultDeviceId) { diff --git a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h index 9f022383542..7e85f87df1b 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h +++ b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h @@ -59,7 +59,6 @@ class WebrtcAudioPrivateFunction : public ChromeAsyncExtensionFunction { protected: // Calculates a single HMAC, using the extension ID as the security origin. - // Call only on IO thread. std::string CalculateHMAC(const std::string& raw_id); // Initializes |device_id_salt_|. Must be called on the UI thread, @@ -98,16 +97,10 @@ class WebrtcAudioPrivateGetSinksFunction : public WebrtcAudioPrivateFunction { bool RunAsync() override; - // Requests output device descriptions. - void GetOutputDeviceDescriptionsOnIOThread(); - - // Receives output device descriptions, calculates HMACs for them and replies - // to UI thread with DoneOnUIThread(). - void ReceiveOutputDeviceDescriptionsOnIOThread( + // Receives output device descriptions, calculates HMACs for them and sends + // the response. + void ReceiveOutputDeviceDescriptions( media::AudioDeviceDescriptions sink_devices); - - // Sends the response. - void DoneOnUIThread(std::unique_ptr<SinkInfoVector> results); }; class WebrtcAudioPrivateGetAssociatedSinkFunction @@ -125,23 +118,17 @@ class WebrtcAudioPrivateGetAssociatedSinkFunction // UI thread: Entry point, posts GetInputDeviceDescriptions() to IO thread. bool RunAsync() override; - // Enumerates input devices. - void GetInputDeviceDescriptionsOnIOThread(); - // Receives the input device descriptions, looks up the raw source device ID // basing on |params|, and requests the associated raw sink ID for it. - void ReceiveInputDeviceDescriptionsOnIOThread( + void ReceiveInputDeviceDescriptions( media::AudioDeviceDescriptions source_devices); - // IO thread: Receives the raw sink ID, calculates HMAC and replies to IO - // thread with ReceiveHMACOnUIThread(). - void CalculateHMACOnIOThread(const base::Optional<std::string>& raw_sink_id); + // Receives the raw sink ID, calculates HMAC and calls Reply(). + void CalculateHMACAndReply(const base::Optional<std::string>& raw_sink_id); // Receives the associated sink ID as HMAC and sends the response. - void ReceiveHMACOnUIThread(const std::string& hmac); + void Reply(const std::string& hmac); - // Initialized on UI thread in RunAsync(), read-only access on IO thread - no - // locking needed. std::unique_ptr<api::webrtc_audio_private::GetAssociatedSink::Params> params_; }; diff --git a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc index b09a14d684f..5b8a78852b1 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc @@ -25,12 +25,13 @@ #include "chrome/browser/media/webrtc/webrtc_log_uploader.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.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/media_device_id.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/service_manager_connection.h" #include "content/public/test/browser_test_utils.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" @@ -38,6 +39,8 @@ #include "media/audio/audio_system.h" #include "media/base/media_switches.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "services/audio/public/cpp/audio_system_factory.h" +#include "services/service_manager/public/cpp/connector.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_WIN) @@ -63,8 +66,10 @@ namespace { void GetAudioDeviceDescriptions(bool for_input, AudioDeviceDescriptions* device_descriptions) { base::RunLoop run_loop; - std::unique_ptr<media::AudioSystem> audio_system = - media::AudioSystem::CreateInstance(); + std::unique_ptr<media::AudioSystem> audio_system = audio::CreateAudioSystem( + content::ServiceManagerConnection::GetForProcess() + ->GetConnector() + ->Clone()); audio_system->GetDeviceDescriptions( for_input, base::BindOnce( @@ -73,7 +78,7 @@ void GetAudioDeviceDescriptions(bool for_input, *result = std::move(received); finished_callback.Run(); }, - base::Passed(run_loop.QuitClosure()), device_descriptions)); + run_loop.QuitClosure(), device_descriptions)); run_loop.Run(); } diff --git a/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc b/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc index 233fb3e34d2..af8a700f354 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc +++ b/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc @@ -238,7 +238,7 @@ bool WebrtcLoggingPrivateSetMetaDataFunction::RunAsync() { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::BindOnce(&WebRtcLoggingHandlerHost::SetMetaData, webrtc_logging_handler_host, - base::Passed(&meta_data), callback)); + std::move(meta_data), callback)); return true; } diff --git a/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_browsertest.cc b/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_browsertest.cc index c1a9c2fd7ae..07b4d99c5b0 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_browsertest.cc @@ -7,8 +7,8 @@ #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "chrome/browser/apps/app_browsertest_util.h" -#include "chrome/browser/media/webrtc/webrtc_log_list.h" #include "chrome/common/chrome_switches.h" +#include "components/webrtc_logging/browser/log_list.h" class WebrtcLoggingPrivateApiBrowserTest : public extensions::PlatformAppBrowserTest { @@ -17,7 +17,7 @@ class WebrtcLoggingPrivateApiBrowserTest ~WebrtcLoggingPrivateApiBrowserTest() override = default; base::FilePath webrtc_logs_path() { - return WebRtcLogList::GetWebRtcLogDirectoryForBrowserContextPath( + return webrtc_logging::LogList::GetWebRtcLogDirectoryForBrowserContextPath( profile()->GetPath()); } diff --git a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc index 1b82d5446c9..71f464d8fb9 100644 --- a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +++ b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc @@ -221,7 +221,8 @@ WebstorePrivateBeginInstallWithManifest3Function::Run() { if (!icon_url.is_empty()) { loader_factory = content::BrowserContext::GetDefaultStoragePartition(browser_context()) - ->GetURLLoaderFactoryForBrowserProcess(); + ->GetURLLoaderFactoryForBrowserProcess() + .get(); } scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( diff --git a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc index 85228f0e23a..620a8b9b3a3 100644 --- a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_apitest.cc @@ -22,9 +22,9 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_test_util.h" -#include "chrome/common/features.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/notification_observer.h" diff --git a/chromium/chrome/browser/media/BUILD.gn b/chromium/chrome/browser/media/BUILD.gn index 434a2623f7d..00bd50db06a 100644 --- a/chromium/chrome/browser/media/BUILD.gn +++ b/chromium/chrome/browser/media/BUILD.gn @@ -12,7 +12,7 @@ mojom("mojo_bindings") { public_deps = [ "//mojo/common:common_custom_types", - "//url/mojo:url_mojom_gurl", + "//url/mojom:url_mojom_gurl", ] } diff --git a/chromium/chrome/browser/media/media_engagement_score_details.mojom b/chromium/chrome/browser/media/media_engagement_score_details.mojom index 25a7e9bd076..6ee85659821 100644 --- a/chromium/chrome/browser/media/media_engagement_score_details.mojom +++ b/chromium/chrome/browser/media/media_engagement_score_details.mojom @@ -4,7 +4,7 @@ module media.mojom; -import "url/mojo/url.mojom"; +import "url/mojom/url.mojom"; struct MediaEngagementScoreDetails { url.mojom.Url origin; @@ -23,6 +23,9 @@ struct MediaEngagementScoreDetails { // Data used for experiments. int32 audible_playbacks; int32 significant_playbacks; + + // How many time the score changed `is_high` status. + int32 high_score_changes; }; struct MediaEngagementConfig { diff --git a/chromium/chrome/browser/media/router/BUILD.gn b/chromium/chrome/browser/media/router/BUILD.gn index 8f58165a4e8..ec64441b2c0 100644 --- a/chromium/chrome/browser/media/router/BUILD.gn +++ b/chromium/chrome/browser/media/router/BUILD.gn @@ -64,6 +64,7 @@ static_library("router") { "discovery", "//extensions/browser", "//mojo/public/cpp/bindings", + "//ui/base:ui_features", ] sources += [ "event_page_request_manager.cc", @@ -80,10 +81,22 @@ static_library("router") { "mojo/media_router_mojo_impl.h", "mojo/media_router_mojo_metrics.cc", "mojo/media_router_mojo_metrics.h", + "mojo/media_sink_service_status.cc", + "mojo/media_sink_service_status.h", "presentation/independent_otr_profile_manager.cc", "presentation/independent_otr_profile_manager.h", "presentation/presentation_navigation_policy.cc", "presentation/presentation_navigation_policy.h", + "providers/cast/cast_app_availability_tracker.cc", + "providers/cast/cast_app_availability_tracker.h", + "providers/cast/cast_app_discovery_service.cc", + "providers/cast/cast_app_discovery_service.h", + "providers/cast/cast_media_route_provider.cc", + "providers/cast/cast_media_route_provider.h", + "providers/cast/cast_media_route_provider_metrics.cc", + "providers/cast/cast_media_route_provider_metrics.h", + "providers/cast/chrome_cast_message_handler.cc", + "providers/cast/chrome_cast_message_handler.h", "providers/cast/dual_media_sink_service.cc", "providers/cast/dual_media_sink_service.h", "providers/extension/extension_media_route_provider_proxy.cc", @@ -130,6 +143,8 @@ static_library("test_support") { "test/mock_dns_sd_registry.h", "test/mock_mojo_media_router.cc", "test/mock_mojo_media_router.h", + "test/noop_dual_media_sink_service.cc", + "test/noop_dual_media_sink_service.h", ] } } diff --git a/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h index 8e5199b05e1..ec743c3d1d7 100644 --- a/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h +++ b/chromium/chrome/browser/net/chrome_mojo_proxy_resolver_factory.h @@ -9,9 +9,9 @@ #include <string> #include "base/macros.h" -#include "services/proxy_resolver/public/interfaces/proxy_resolver.mojom.h" +#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" #include "services/service_manager/public/cpp/connector.h" -#include "services/service_manager/public/interfaces/connector.mojom.h" +#include "services/service_manager/public/mojom/connector.mojom.h" // ProxyResolverFactory that acts as a proxy to the proxy resolver service. // Starts the service as needed, and maintains no active mojo pipes to it, diff --git a/chromium/chrome/browser/printing/background_printing_manager.cc b/chromium/chrome/browser/printing/background_printing_manager.cc index d1ca0dcc18e..a204adf02be 100644 --- a/chromium/chrome/browser/printing/background_printing_manager.cc +++ b/chromium/chrome/browser/printing/background_printing_manager.cc @@ -72,7 +72,7 @@ void BackgroundPrintingManager::OwnPrintPreviewDialog( CHECK(!HasPrintPreviewDialog(preview_dialog)); printing_contents_map_[preview_dialog] = - base::MakeUnique<Observer>(this, preview_dialog); + std::make_unique<Observer>(this, preview_dialog); // Watch for print jobs finishing. Everything else is watched for by the // Observer. TODO(avi, cait): finish the job of removing this last diff --git a/chromium/chrome/browser/printing/cloud_print/cloud_print_proxy_service_unittest.cc b/chromium/chrome/browser/printing/cloud_print/cloud_print_proxy_service_unittest.cc index 6de22a51161..3edbd44c2ab 100644 --- a/chromium/chrome/browser/printing/cloud_print/cloud_print_proxy_service_unittest.cc +++ b/chromium/chrome/browser/printing/cloud_print/cloud_print_proxy_service_unittest.cc @@ -242,7 +242,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyDisabled) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>( + std::make_unique<base::Value>( MockServiceProcessControl::EnabledUserId())); service.Initialize(); @@ -260,7 +260,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyEnabled) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); service.Initialize(); service.RefreshStatusFromService(); @@ -279,9 +279,9 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithPolicySetProxyDisabled) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); service.Initialize(); @@ -298,9 +298,9 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithPolicySetProxyEnabled) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); service.Initialize(); @@ -318,7 +318,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyDisabledThenSetPolicy) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>( + std::make_unique<base::Value>( MockServiceProcessControl::EnabledUserId())); service.Initialize(); @@ -326,7 +326,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyDisabledThenSetPolicy) { EXPECT_EQ(std::string(), prefs->GetString(prefs::kCloudPrintEmail)); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); EXPECT_EQ(std::string(), prefs->GetString(prefs::kCloudPrintEmail)); } @@ -341,7 +341,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyEnabledThenSetPolicy) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); service.Initialize(); service.RefreshStatusFromService(); @@ -350,7 +350,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyEnabledThenSetPolicy) { prefs->GetString(prefs::kCloudPrintEmail)); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); EXPECT_EQ(std::string(), prefs->GetString(prefs::kCloudPrintEmail)); EXPECT_TRUE(service.GetMockCloudPrintProxy().has_been_disabled()); @@ -367,9 +367,9 @@ TEST_F(CloudPrintProxyPolicyTest, sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); service.Initialize(); @@ -389,9 +389,9 @@ TEST_F(CloudPrintProxyPolicyTest, sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); service.Initialize(); @@ -411,7 +411,7 @@ TEST_F(CloudPrintProxyPolicyTest, StartWithNoPolicyProxyDisabledThenEnable) { sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>( + std::make_unique<base::Value>( MockServiceProcessControl::EnabledUserId())); service.Initialize(); @@ -435,9 +435,9 @@ TEST_F(CloudPrintProxyPolicyTest, sync_preferences::TestingPrefServiceSyncable* prefs = profile_.GetTestingPrefService(); prefs->SetUserPref(prefs::kCloudPrintEmail, - base::MakeUnique<base::Value>(std::string())); + std::make_unique<base::Value>(std::string())); prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled, - base::MakeUnique<base::Value>(false)); + std::make_unique<base::Value>(false)); service.Initialize(); diff --git a/chromium/chrome/browser/printing/cloud_print/gcd_api_flow_unittest.cc b/chromium/chrome/browser/printing/cloud_print/gcd_api_flow_unittest.cc index 161e082abe0..2f5c919f387 100644 --- a/chromium/chrome/browser/printing/cloud_print/gcd_api_flow_unittest.cc +++ b/chromium/chrome/browser/printing/cloud_print/gcd_api_flow_unittest.cc @@ -59,12 +59,12 @@ class GCDApiFlowTest : public testing::Test { request_context_.get()); token_service_.AddAccount(account_id_); - std::unique_ptr<MockDelegate> delegate = base::MakeUnique<MockDelegate>(); + std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>(); mock_delegate_ = delegate.get(); EXPECT_CALL(*mock_delegate_, GetURL()) .WillRepeatedly(Return( GURL("https://www.google.com/cloudprint/confirm?token=SomeToken"))); - gcd_flow_ = base::MakeUnique<GCDApiFlowImpl>(request_context_.get(), + gcd_flow_ = std::make_unique<GCDApiFlowImpl>(request_context_.get(), &token_service_, account_id_); gcd_flow_->Start(std::move(delegate)); } diff --git a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.cc b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.cc index 46c8ebfcbcc..5c92422bdad 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.cc +++ b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.cc @@ -14,21 +14,24 @@ PrivetDeviceListerImpl::PrivetDeviceListerImpl( local_discovery::ServiceDiscoveryClient* service_discovery_client, PrivetDeviceLister::Delegate* delegate) : delegate_(delegate), - device_lister_(this, service_discovery_client, kPrivetDefaultDeviceType) { -} + device_lister_(local_discovery::ServiceDiscoveryDeviceLister::Create( + this, + service_discovery_client, + kPrivetDefaultDeviceType)) {} PrivetDeviceListerImpl::~PrivetDeviceListerImpl() { } void PrivetDeviceListerImpl::Start() { - device_lister_.Start(); + device_lister_->Start(); } void PrivetDeviceListerImpl::DiscoverNewDevices() { - device_lister_.DiscoverNewDevices(); + device_lister_->DiscoverNewDevices(); } void PrivetDeviceListerImpl::OnDeviceChanged( + const std::string& service_type, bool added, const local_discovery::ServiceDescription& service_description) { if (!delegate_) @@ -38,12 +41,14 @@ void PrivetDeviceListerImpl::OnDeviceChanged( DeviceDescription(service_description)); } -void PrivetDeviceListerImpl::OnDeviceRemoved(const std::string& service_name) { +void PrivetDeviceListerImpl::OnDeviceRemoved(const std::string& service_type, + const std::string& service_name) { if (delegate_) delegate_->DeviceRemoved(service_name); } -void PrivetDeviceListerImpl::OnDeviceCacheFlushed() { +void PrivetDeviceListerImpl::OnDeviceCacheFlushed( + const std::string& service_type) { if (delegate_) delegate_->DeviceCacheFlushed(); } diff --git a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.h b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.h index 5eeff67fec1..5d91da407cb 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.h +++ b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_impl.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRIVET_DEVICE_LISTER_IMPL_H_ #define CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRIVET_DEVICE_LISTER_IMPL_H_ +#include <memory> #include <string> #include "chrome/browser/local_discovery/service_discovery_device_lister.h" @@ -32,14 +33,16 @@ class PrivetDeviceListerImpl protected: // ServiceDiscoveryDeviceLister: void OnDeviceChanged( + const std::string& service_type, bool added, const local_discovery::ServiceDescription& service_description) override; - void OnDeviceRemoved(const std::string& service_name) override; - void OnDeviceCacheFlushed() override; + void OnDeviceRemoved(const std::string& service_type, + const std::string& service_name) override; + void OnDeviceCacheFlushed(const std::string& service_type) override; private: PrivetDeviceLister::Delegate* const delegate_; - local_discovery::ServiceDiscoveryDeviceLister device_lister_; + std::unique_ptr<local_discovery::ServiceDiscoveryDeviceLister> device_lister_; }; } // namespace cloud_print diff --git a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_unittest.cc b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_unittest.cc index 846e6586bc3..6d79bcf3b9a 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_device_lister_unittest.cc +++ b/chromium/chrome/browser/printing/cloud_print/privet_device_lister_unittest.cc @@ -120,7 +120,7 @@ class MockServiceDiscoveryClient : public ServiceDiscoveryClient { std::unique_ptr<ServiceWatcher> CreateServiceWatcher( const std::string& service_type, const ServiceWatcher::UpdatedCallback& callback) override { - return base::MakeUnique<MockServiceWatcher>(service_type, callback, + return std::make_unique<MockServiceWatcher>(service_type, callback, mock_delegate_); } @@ -129,7 +129,7 @@ class MockServiceDiscoveryClient : public ServiceDiscoveryClient { std::unique_ptr<ServiceResolver> CreateServiceResolver( const std::string& service_name, ServiceResolver::ResolveCompleteCallback callback) override { - return base::MakeUnique<MockServiceResolver>( + return std::make_unique<MockServiceResolver>( service_name, std::move(callback), mock_delegate_); } diff --git a/chromium/chrome/browser/printing/cloud_print/privet_http_impl.cc b/chromium/chrome/browser/printing/cloud_print/privet_http_impl.cc index 156fe3ab55a..ec57fcf4268 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_http_impl.cc +++ b/chromium/chrome/browser/printing/cloud_print/privet_http_impl.cc @@ -510,8 +510,8 @@ void PrivetLocalPrintOperationImpl::StartConvertToPWG() { data_.get(), PwgRasterConverter::GetConversionSettings(capabilities_, page_size_), PwgRasterConverter::GetBitmapSettings(capabilities_, ticket_), - base::Bind(&PrivetLocalPrintOperationImpl::OnPWGRasterConverted, - weak_factory_.GetWeakPtr())); + base::BindOnce(&PrivetLocalPrintOperationImpl::OnPWGRasterConverted, + weak_factory_.GetWeakPtr())); } void PrivetLocalPrintOperationImpl::OnSubmitdocResponse( diff --git a/chromium/chrome/browser/printing/cloud_print/privet_notifications.cc b/chromium/chrome/browser/printing/cloud_print/privet_notifications.cc index 771b1910378..a9bca9731e7 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_notifications.cc +++ b/chromium/chrome/browser/printing/cloud_print/privet_notifications.cc @@ -43,8 +43,8 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/page_transition_types.h" #include "ui/base/resource/resource_bundle.h" -#include "ui/message_center/notification.h" -#include "ui/message_center/notifier_id.h" +#include "ui/message_center/public/cpp/notification.h" +#include "ui/message_center/public/cpp/notifier_id.h" #if BUILDFLAG(ENABLE_MDNS) #include "chrome/browser/printing/cloud_print/privet_traffic_detector.h" diff --git a/chromium/chrome/browser/printing/cloud_print/privet_notifications.h b/chromium/chrome/browser/printing/cloud_print/privet_notifications.h index c322f0ee71f..80477dea1e4 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_notifications.h +++ b/chromium/chrome/browser/printing/cloud_print/privet_notifications.h @@ -14,7 +14,7 @@ #include "components/keyed_service/core/keyed_service.h" #include "components/prefs/pref_member.h" #include "net/net_features.h" -#include "ui/message_center/notification_delegate.h" +#include "ui/message_center/public/cpp/notification_delegate.h" class Profile; diff --git a/chromium/chrome/browser/printing/cloud_print/privet_notifications_unittest.cc b/chromium/chrome/browser/printing/cloud_print/privet_notifications_unittest.cc index dad0f0ade18..bfb271c0691 100644 --- a/chromium/chrome/browser/printing/cloud_print/privet_notifications_unittest.cc +++ b/chromium/chrome/browser/printing/cloud_print/privet_notifications_unittest.cc @@ -9,6 +9,7 @@ #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/notifications/notification_display_service_tester.h" #include "chrome/browser/notifications/notification_test_util.h" #include "chrome/browser/printing/cloud_print/privet_http_asynchronous_factory.h" #include "chrome/browser/printing/cloud_print/privet_http_impl.h" @@ -79,7 +80,7 @@ class MockPrivetHttpFactory : public PrivetHTTPAsynchronousFactory { std::unique_ptr<PrivetHTTPResolution> CreatePrivetHTTP( const std::string& name) override { - return base::MakeUnique<MockResolution>(name, request_context_.get()); + return std::make_unique<MockResolution>(name, request_context_.get()); } private: @@ -255,13 +256,15 @@ class PrivetNotificationsNotificationTest : public testing::Test { void SetUp() override { testing::Test::SetUp(); - profile_manager_ = base::MakeUnique<TestingProfileManager>( + profile_manager_ = std::make_unique<TestingProfileManager>( TestingBrowserProcess::GetGlobal()); ASSERT_TRUE(profile_manager_->SetUp()); profile_ = profile_manager_->CreateTestingProfile("test-user"); + display_service_ = + std::make_unique<NotificationDisplayServiceTester>(profile_); TestingBrowserProcess::GetGlobal()->SetNotificationUIManager( - base::MakeUnique<StubNotificationUIManager>()); + std::make_unique<StubNotificationUIManager>()); } void TearDown() override { @@ -269,21 +272,17 @@ class PrivetNotificationsNotificationTest : public testing::Test { testing::Test::TearDown(); } - protected: - StubNotificationUIManager* ui_manager() const { - return static_cast<StubNotificationUIManager*>( - TestingBrowserProcess::GetGlobal()->notification_ui_manager()); - } - Profile* profile() { return profile_; } - private: // The thread bundle must be first so it is destroyed last. content::TestBrowserThreadBundle thread_bundle_; + std::unique_ptr<NotificationDisplayServiceTester> display_service_; + std::unique_ptr<TestingProfileManager> profile_manager_; Profile* profile_; + private: DISALLOW_COPY_AND_ASSIGN(PrivetNotificationsNotificationTest); }; @@ -293,14 +292,20 @@ TEST_F(PrivetNotificationsNotificationTest, AddToCloudPrint) { // The notification is added asynchronously. base::RunLoop().RunUntilIdle(); - ASSERT_EQ(1U, ui_manager()->GetNotificationCount()); - const auto& notification = ui_manager()->GetNotificationAt(0); - notification.ButtonClick(0 /* add */); + auto notifications = display_service_->GetDisplayedNotificationsForType( + NotificationHandler::Type::TRANSIENT); + ASSERT_EQ(1U, notifications.size()); + display_service_->SimulateClick(NotificationHandler::Type::TRANSIENT, + notifications[0].id(), 0 /* add */, + base::nullopt); EXPECT_EQ("chrome://devices/", service.open_tab_url().spec()); EXPECT_EQ(1U, service.open_tab_count()); EXPECT_EQ(0U, service.disable_notifications_count()); - EXPECT_EQ(0U, ui_manager()->GetNotificationCount()); + EXPECT_EQ(0U, display_service_ + ->GetDisplayedNotificationsForType( + NotificationHandler::Type::TRANSIENT) + .size()); } TEST_F(PrivetNotificationsNotificationTest, DontShowAgain) { @@ -309,14 +314,20 @@ TEST_F(PrivetNotificationsNotificationTest, DontShowAgain) { // The notification is added asynchronously. base::RunLoop().RunUntilIdle(); - ASSERT_EQ(1U, ui_manager()->GetNotificationCount()); - const auto& notification = ui_manager()->GetNotificationAt(0); - notification.ButtonClick(1 /* don't show again */); + auto notifications = display_service_->GetDisplayedNotificationsForType( + NotificationHandler::Type::TRANSIENT); + ASSERT_EQ(1U, notifications.size()); + display_service_->SimulateClick(NotificationHandler::Type::TRANSIENT, + notifications[0].id(), + 1 /* don't show again */, base::nullopt); EXPECT_EQ("", service.open_tab_url().spec()); EXPECT_EQ(0U, service.open_tab_count()); EXPECT_EQ(1U, service.disable_notifications_count()); - EXPECT_EQ(0U, ui_manager()->GetNotificationCount()); + EXPECT_EQ(0U, display_service_ + ->GetDisplayedNotificationsForType( + NotificationHandler::Type::TRANSIENT) + .size()); } } // namespace diff --git a/chromium/chrome/browser/printing/pdf_to_emf_converter.cc b/chromium/chrome/browser/printing/pdf_to_emf_converter.cc index 358341995d0..4715ec3bed8 100644 --- a/chromium/chrome/browser/printing/pdf_to_emf_converter.cc +++ b/chromium/chrome/browser/printing/pdf_to_emf_converter.cc @@ -26,8 +26,8 @@ #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" -#include "chrome/services/printing/public/interfaces/constants.mojom.h" -#include "chrome/services/printing/public/interfaces/pdf_to_emf_converter.mojom.h" +#include "chrome/services/printing/public/mojom/constants.mojom.h" +#include "chrome/services/printing/public/mojom/pdf_to_emf_converter.mojom.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_data.h" #include "content/public/common/service_manager_connection.h" diff --git a/chromium/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc b/chromium/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc index c0ab2f79d47..db7992cda30 100644 --- a/chromium/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc +++ b/chromium/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc @@ -119,7 +119,8 @@ IN_PROC_BROWSER_TEST_F(PDFToEMFConverterBrowserTest, TestSuccess) { // A4 page format. PdfRenderSettings pdf_settings(gfx::Rect(0, 0, 1700, 2200), gfx::Point(0, 0), - /*dpi=*/200, /*autorotate=*/false, + /*dpi=*/gfx::Size(200, 200), + /*autorotate=*/false, PdfRenderSettings::Mode::NORMAL); constexpr int kNumberOfPages = 3; diff --git a/chromium/chrome/browser/printing/print_browsertest.cc b/chromium/chrome/browser/printing/print_browsertest.cc index 036681e9063..09f7e7b9c15 100644 --- a/chromium/chrome/browser/printing/print_browsertest.cc +++ b/chromium/chrome/browser/printing/print_browsertest.cc @@ -2,23 +2,47 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <memory> + #include "base/auto_reset.h" +#include "base/files/file_path.h" +#include "base/path_service.h" #include "base/run_loop.h" +#include "base/threading/thread_restrictions.h" +#include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/printing/print_view_manager_common.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" +#include "chrome/common/chrome_paths.h" #include "chrome/common/pref_names.h" +#include "chrome/common/webui_url_constants.cc" #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/printing/browser/print_composite_client.h" +#include "components/printing/browser/print_manager_utils.h" +#include "components/printing/common/print_messages.h" +#include "content/public/browser/browser_message_filter.h" +#include "content/public/browser/render_frame_host.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 "extensions/common/extension.h" +#include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#if defined(OS_CHROMEOS) +#include "ui/aura/env.h" +#endif + namespace printing { namespace { +static constexpr int kDefaultDocumentCookie = 1234; + class PrintPreviewObserver : PrintPreviewUI::TestingDelegate { public: PrintPreviewObserver() { PrintPreviewUI::SetDelegateForTesting(this); } @@ -53,6 +77,69 @@ class PrintPreviewObserver : PrintPreviewUI::TestingDelegate { base::RunLoop* run_loop_ = nullptr; }; +class TestPrintFrameContentMsgFilter : public content::BrowserMessageFilter { + public: + TestPrintFrameContentMsgFilter(int document_cookie, + base::RepeatingClosure msg_callback) + : content::BrowserMessageFilter(PrintMsgStart), + document_cookie_(document_cookie), + task_runner_(base::SequencedTaskRunnerHandle::Get()), + msg_callback_(msg_callback) {} + + bool OnMessageReceived(const IPC::Message& message) override { + // Only expect PrintHostMsg_DidPrintFrameContent message. + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(TestPrintFrameContentMsgFilter, message) + IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintFrameContent, CheckMessage) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + EXPECT_TRUE(handled); + task_runner_->PostTask(FROM_HERE, msg_callback_); + return true; + } + + private: + ~TestPrintFrameContentMsgFilter() override {} + + void CheckMessage(int document_cookie, + const PrintHostMsg_DidPrintContent_Params& param) { + EXPECT_EQ(document_cookie, document_cookie_); + EXPECT_TRUE(param.metafile_data_handle.IsValid()); + EXPECT_GT(param.data_size, 0u); + } + + const int document_cookie_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + base::RepeatingClosure msg_callback_; +}; + +class KillPrintFrameContentMsgFilter : public content::BrowserMessageFilter { + public: + explicit KillPrintFrameContentMsgFilter(content::RenderProcessHost* rph) + : content::BrowserMessageFilter(PrintMsgStart), rph_(rph) {} + + bool OnMessageReceived(const IPC::Message& message) override { + // Only handle PrintHostMsg_DidPrintFrameContent message. + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(KillPrintFrameContentMsgFilter, message) + IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintFrameContent, KillRenderProcess) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; + } + + private: + ~KillPrintFrameContentMsgFilter() override {} + + void KillRenderProcess(int document_cookie, + const PrintHostMsg_DidPrintContent_Params& param) { + base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives; + rph_->Shutdown(0, true); + } + + content::RenderProcessHost* rph_; +}; + } // namespace class PrintBrowserTest : public InProcessBrowserTest { @@ -60,6 +147,19 @@ class PrintBrowserTest : public InProcessBrowserTest { PrintBrowserTest() {} ~PrintBrowserTest() override {} + void SetUp() override { + num_expected_messages_ = 1; // By default, only wait on one message. + num_received_messages_ = 0; + run_loop_.reset(); + InProcessBrowserTest::SetUp(); + } + + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + content::SetupCrossSiteRedirector(embedded_test_server()); + ASSERT_TRUE(embedded_test_server()->Start()); + } + void PrintAndWaitUntilPreviewIsReady(bool print_only_selection) { PrintPreviewObserver print_preview_observer; @@ -69,6 +169,123 @@ class PrintBrowserTest : public InProcessBrowserTest { print_preview_observer.WaitUntilPreviewIsReady(); } + + // The following are helper functions for having a wait loop in the test and + // exit when all expected messages are received. + void SetNumExpectedMessages(unsigned int num) { + num_expected_messages_ = num; + } + + void WaitUntilMessagesReceived() { + run_loop_ = std::make_unique<base::RunLoop>(); + run_loop_->Run(); + } + + void CheckForQuit() { + if (++num_received_messages_ == num_expected_messages_) { + run_loop_->QuitWhenIdle(); + } + } + + void AddFilterForFrame(content::RenderFrameHost* frame_host) { + auto filter = base::MakeRefCounted<TestPrintFrameContentMsgFilter>( + kDefaultDocumentCookie, + base::BindRepeating(&PrintBrowserTest::CheckForQuit, + base::Unretained(this))); + frame_host->GetProcess()->AddFilter(filter.get()); + } + + PrintMsg_PrintFrame_Params GetDefaultPrintParams() { + PrintMsg_PrintFrame_Params params; + gfx::Rect area(800, 600); + params.printable_area = area; + params.document_cookie = kDefaultDocumentCookie; + return params; + } + + private: + unsigned int num_expected_messages_; + unsigned int num_received_messages_; + std::unique_ptr<base::RunLoop> run_loop_; +}; + +class SitePerProcessPrintBrowserTest : public PrintBrowserTest { + public: + SitePerProcessPrintBrowserTest() {} + ~SitePerProcessPrintBrowserTest() override {} + + // content::BrowserTestBase + void SetUpCommandLine(base::CommandLine* command_line) override { + content::IsolateAllSitesForTesting(command_line); + } +}; + +class IsolateOriginsPrintBrowserTest : public PrintBrowserTest { + public: + static constexpr char kIsolatedSite[] = "b.com"; + + IsolateOriginsPrintBrowserTest() {} + ~IsolateOriginsPrintBrowserTest() override {} + + // content::BrowserTestBase + void SetUpCommandLine(base::CommandLine* command_line) override { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::string origin_list = + embedded_test_server()->GetURL(kIsolatedSite, "/").spec(); + command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list); + } + + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + } +}; + +constexpr char IsolateOriginsPrintBrowserTest::kIsolatedSite[]; + +class PrintExtensionBrowserTest : public ExtensionBrowserTest { + public: + PrintExtensionBrowserTest() {} + ~PrintExtensionBrowserTest() override {} + + void PrintAndWaitUntilPreviewIsReady(bool print_only_selection) { + PrintPreviewObserver print_preview_observer; + + printing::StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), + /*print_preview_disabled=*/false, + print_only_selection); + + print_preview_observer.WaitUntilPreviewIsReady(); + } + + void LoadExtensionAndNavigateToOptionPage() { + const extensions::Extension* extension = nullptr; + { + base::ScopedAllowBlockingForTesting allow_blocking; + base::FilePath test_data_dir; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir); + extension = LoadExtension( + test_data_dir.AppendASCII("printing").AppendASCII("test_extension")); + ASSERT_TRUE(extension); + } + + GURL url(chrome::kChromeUIExtensionsURL); + std::string query = + base::StringPrintf("options=%s", extension->id().c_str()); + GURL::Replacements replacements; + replacements.SetQueryStr(query); + url = url.ReplaceComponents(replacements); + ui_test_utils::NavigateToURL(browser(), url); + } +}; + +class SitePerProcessPrintExtensionBrowserTest + : public PrintExtensionBrowserTest { + public: + // content::BrowserTestBase + void SetUpCommandLine(base::CommandLine* command_line) override { + content::IsolateAllSitesForTesting(command_line); + } }; // Printing only a selection containing iframes is partially supported. @@ -76,11 +293,275 @@ class PrintBrowserTest : public InProcessBrowserTest { // preview is rendered (i.e. no timeout in the test). // This test shouldn't crash. See https://crbug.com/732780. IN_PROC_BROWSER_TEST_F(PrintBrowserTest, SelectionContainsIframe) { - ASSERT_TRUE(embedded_test_server()->Start()); + ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/selection_iframe.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/true); } +// Printing frame content for the main frame of a generic webpage. +// This test passes when the printed result is sent back and checked in +// TestPrintFrameContentMsgFilter::CheckMessage(). +IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintFrameContent) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test1.html")); + ui_test_utils::NavigateToURL(browser(), url); + + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + content::RenderFrameHost* rfh = original_contents->GetMainFrame(); + AddFilterForFrame(rfh); + + rfh->Send(new PrintMsg_PrintFrameContent(rfh->GetRoutingID(), + GetDefaultPrintParams())); + + // The printed result will be received and checked in + // TestPrintFrameContentMsgFilter. + WaitUntilMessagesReceived(); +} + +// Printing frame content for a cross-site iframe. +// This test passes when the iframe responds to the print message. +// The response is checked in TestPrintFrameContentMsgFilter::CheckMessage(). +IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeContent) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url( + embedded_test_server()->GetURL("/printing/content_with_iframe.html")); + ui_test_utils::NavigateToURL(browser(), url); + + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_EQ(2u, original_contents->GetAllFrames().size()); + content::RenderFrameHost* test_frame = original_contents->GetAllFrames()[1]; + ASSERT_TRUE(test_frame); + + AddFilterForFrame(test_frame); + + test_frame->Send(new PrintMsg_PrintFrameContent(test_frame->GetRoutingID(), + GetDefaultPrintParams())); + + // The printed result will be received and checked in + // TestPrintFrameContentMsgFilter. + WaitUntilMessagesReceived(); +} + +// Printing frame content with a cross-site iframe which also has a cross-site +// iframe. The site reference chain is a.com --> b.com --> c.com. +// This test passes when both cross-site frames are printed and their +// responses which are checked in +// TestPrintFrameContentMsgFilter::CheckMessage(). +IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeChain) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL( + "/printing/content_with_iframe_chain.html")); + ui_test_utils::NavigateToURL(browser(), url); + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_EQ(3u, original_contents->GetAllFrames().size()); + // Create composite client so subframe print message can be forwarded. + PrintCompositeClient::CreateForWebContents(original_contents); + + content::RenderFrameHost* main_frame = original_contents->GetMainFrame(); + content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0); + ASSERT_TRUE(child_frame); + ASSERT_NE(child_frame, main_frame); + bool oopif_enabled = child_frame->GetProcess() != main_frame->GetProcess(); + + content::RenderFrameHost* grandchild_frame = + content::ChildFrameAt(child_frame, 0); + ASSERT_TRUE(grandchild_frame); + ASSERT_NE(grandchild_frame, child_frame); + if (oopif_enabled) { + ASSERT_NE(grandchild_frame->GetProcess(), child_frame->GetProcess()); + ASSERT_NE(grandchild_frame->GetProcess(), main_frame->GetProcess()); + } + + AddFilterForFrame(main_frame); + if (oopif_enabled) { + AddFilterForFrame(child_frame); + AddFilterForFrame(grandchild_frame); + } + + main_frame->Send(new PrintMsg_PrintFrameContent(main_frame->GetRoutingID(), + GetDefaultPrintParams())); + + // The printed result will be received and checked in + // TestPrintFrameContentMsgFilter. + SetNumExpectedMessages(oopif_enabled ? 3 : 1); + WaitUntilMessagesReceived(); +} + +// Printing frame content with a cross-site iframe who also has a cross site +// iframe, but this iframe resides in the same site as the main frame. +// The site reference loop is a.com --> b.com --> a.com. +// This test passes when both cross-site frames are printed and send back +// responses which are checked in +// TestPrintFrameContentMsgFilter::CheckMessage(). +IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeABA) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL( + "a.com", "/printing/content_with_iframe_loop.html")); + ui_test_utils::NavigateToURL(browser(), url); + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_EQ(3u, original_contents->GetAllFrames().size()); + // Create composite client so subframe print message can be forwarded. + PrintCompositeClient::CreateForWebContents(original_contents); + + content::RenderFrameHost* main_frame = original_contents->GetMainFrame(); + content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0); + ASSERT_TRUE(child_frame); + ASSERT_NE(child_frame, main_frame); + bool oopif_enabled = main_frame->GetProcess() != child_frame->GetProcess(); + + content::RenderFrameHost* grandchild_frame = + content::ChildFrameAt(child_frame, 0); + ASSERT_TRUE(grandchild_frame); + ASSERT_NE(grandchild_frame, child_frame); + // |grandchild_frame| is in the same site as |frame|, so whether OOPIF is + // enabled, they will be in the same process. + ASSERT_EQ(grandchild_frame->GetProcess(), main_frame->GetProcess()); + + AddFilterForFrame(main_frame); + if (oopif_enabled) + AddFilterForFrame(child_frame); + + main_frame->Send(new PrintMsg_PrintFrameContent(main_frame->GetRoutingID(), + GetDefaultPrintParams())); + + // The printed result will be received and checked in + // TestPrintFrameContentMsgFilter. + SetNumExpectedMessages(oopif_enabled ? 3 : 1); + WaitUntilMessagesReceived(); +} + +// Printing a web page with a dead subframe for site per process should succeed. +// This test passes whenever the print preview is rendered. This should not be +// a timed out test which indicates the print preview hung. +IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, + SubframeUnavailableBeforePrint) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url( + embedded_test_server()->GetURL("/printing/content_with_iframe.html")); + ui_test_utils::NavigateToURL(browser(), url); + + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_EQ(2u, original_contents->GetAllFrames().size()); + content::RenderFrameHost* test_frame = original_contents->GetAllFrames()[1]; + ASSERT_TRUE(test_frame); + ASSERT_TRUE(test_frame->IsRenderFrameLive()); + // Wait for the renderer to be down. + content::RenderProcessHostWatcher render_process_watcher( + test_frame->GetProcess(), + content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + // Shutdown the subframe. + ASSERT_TRUE(test_frame->GetProcess()->Shutdown(0, false)); + render_process_watcher.Wait(); + ASSERT_FALSE(test_frame->IsRenderFrameLive()); + + PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); +} + +// If a subframe dies during printing, the page printing should still succeed. +// This test passes whenever the print preview is rendered. This should not be +// a timed out test which indicates the print preview hung. +IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, + SubframeUnavailableDuringPrint) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url( + embedded_test_server()->GetURL("/printing/content_with_iframe.html")); + ui_test_utils::NavigateToURL(browser(), url); + + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_EQ(2u, original_contents->GetAllFrames().size()); + content::RenderFrameHost* subframe = original_contents->GetAllFrames()[1]; + ASSERT_TRUE(subframe); + auto* subframe_rph = subframe->GetProcess(); + + auto filter = + base::MakeRefCounted<KillPrintFrameContentMsgFilter>(subframe_rph); + subframe_rph->AddFilter(filter.get()); + + PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); +} + +// Printing preview a web page with an iframe from an isolated origin. +// This test passes whenever the print preview is rendered. This should not be +// a timed out test which indicates the print preview hung or crash. +IN_PROC_BROWSER_TEST_F(IsolateOriginsPrintBrowserTest, PrintIsolatedSubframe) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL( + "/printing/content_with_same_site_iframe.html")); + GURL isolated_url( + embedded_test_server()->GetURL(kIsolatedSite, "/printing/test1.html")); + ui_test_utils::NavigateToURL(browser(), url); + + content::WebContents* original_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + EXPECT_TRUE(NavigateIframeToURL(original_contents, "iframe", isolated_url)); + + ASSERT_EQ(2u, original_contents->GetAllFrames().size()); + auto* main_frame = original_contents->GetMainFrame(); + auto* subframe = original_contents->GetAllFrames()[1]; + ASSERT_NE(main_frame->GetProcess(), subframe->GetProcess()); + + PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); +} + +// Printing preview a webpage. +// Test that we won't use oopif printing by default, unless the +// test is run with site-per-process flag enabled. +IN_PROC_BROWSER_TEST_F(PrintBrowserTest, RegularPrinting) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test1.html")); + ui_test_utils::NavigateToURL(browser(), url); + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSitePerProcess)) { + EXPECT_TRUE(IsOopifEnabled()); + } else { + EXPECT_FALSE(IsOopifEnabled()); + } +} + +// Printing preview a webpage with isolate-origins enabled. +// Test that we will use oopif printing for this case. +IN_PROC_BROWSER_TEST_F(IsolateOriginsPrintBrowserTest, OopifPrinting) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test1.html")); + ui_test_utils::NavigateToURL(browser(), url); + + EXPECT_TRUE(IsOopifEnabled()); +} + +// Printing an extension option page. +// The test should not crash or timeout. +IN_PROC_BROWSER_TEST_F(PrintExtensionBrowserTest, PrintOptionPage) { +#if defined(OS_CHROMEOS) + // Mus can not support this test now https://crbug.com/823782. + if (aura::Env::GetInstance()->mode() == aura::Env::Mode::MUS) + return; +#endif + + LoadExtensionAndNavigateToOptionPage(); + PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); +} + +// Printing an extension option page with site per process is enabled. +// The test should not crash or timeout. +IN_PROC_BROWSER_TEST_F(SitePerProcessPrintExtensionBrowserTest, + PrintOptionPage) { +#if defined(OS_CHROMEOS) + // Mus can not support this test now https://crbug.com/823782. + if (aura::Env::GetInstance()->mode() == aura::Env::Mode::MUS) + return; +#endif + + LoadExtensionAndNavigateToOptionPage(); + PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); +} + } // namespace printing diff --git a/chromium/chrome/browser/printing/print_job.cc b/chromium/chrome/browser/printing/print_job.cc index 7bb027c7f57..9c91c3d2530 100644 --- a/chromium/chrome/browser/printing/print_job.cc +++ b/chromium/chrome/browser/printing/print_job.cc @@ -15,7 +15,6 @@ #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/task_scheduler/post_task.h" -#include "base/threading/sequenced_worker_pool.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" @@ -35,9 +34,9 @@ using base::TimeDelta; namespace printing { // Helper function to ensure |owner| is valid until at least |callback| returns. -void HoldRefCallback(const scoped_refptr<PrintJobWorkerOwner>& owner, - const base::Closure& callback) { - callback.Run(); +void HoldRefCallback(scoped_refptr<PrintJobWorkerOwner> owner, + base::OnceClosure callback) { + std::move(callback).Run(); } PrintJob::PrintJob() @@ -86,6 +85,23 @@ void PrintJob::Initialize(PrintJobWorkerOwner* job, content::Source<PrintJob>(this)); } +#if defined(OS_WIN) +void PrintJob::ResetPageMapping() { + std::vector<int> pages = PageRange::GetPages(settings_.ranges()); + if (pages.empty()) + return; + + pdf_page_mapping_ = std::vector<int>(document_->page_count(), -1); + for (int page_number : pages) { + // Make sure the page is in range. + if (page_number >= 0 && + page_number < static_cast<int>(pdf_page_mapping_.size())) { + pdf_page_mapping_[page_number] = page_number; + } + } +} +#endif + void PrintJob::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { @@ -125,11 +141,11 @@ void PrintJob::StartPrinting() { } // Real work is done in PrintJobWorker::StartPrinting(). - worker_->PostTask(FROM_HERE, - base::Bind(&HoldRefCallback, base::WrapRefCounted(this), - base::Bind(&PrintJobWorker::StartPrinting, - base::Unretained(worker_.get()), - base::RetainedRef(document_)))); + worker_->PostTask( + FROM_HERE, base::BindOnce(&HoldRefCallback, base::WrapRefCounted(this), + base::BindOnce(&PrintJobWorker::StartPrinting, + base::Unretained(worker_.get()), + base::RetainedRef(document_)))); // Set the flag right now. is_job_pending_ = true; @@ -261,14 +277,15 @@ void PrintJob::StartPdfToEmfConversion( bool print_text_with_gdi) { DCHECK(!pdf_conversion_state_); pdf_conversion_state_ = - base::MakeUnique<PdfConversionState>(page_size, content_area); - const int kPrinterDpi = settings().dpi(); - PdfRenderSettings settings( - content_area, gfx::Point(0, 0), kPrinterDpi, /*autorotate=*/true, + std::make_unique<PdfConversionState>(page_size, content_area); + PdfRenderSettings render_settings( + content_area, gfx::Point(0, 0), settings().dpi_size(), + /*autorotate=*/true, print_text_with_gdi ? PdfRenderSettings::Mode::GDI_TEXT : PdfRenderSettings::Mode::NORMAL); pdf_conversion_state_->Start( - bytes, settings, base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); + bytes, render_settings, + base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); } void PrintJob::OnPdfConversionStarted(int page_count) { @@ -281,7 +298,7 @@ void PrintJob::OnPdfConversionStarted(int page_count) { } pdf_conversion_state_->set_page_count(page_count); pdf_conversion_state_->GetMorePages( - base::Bind(&PrintJob::OnPdfPageConverted, this)); + base::BindRepeating(&PrintJob::OnPdfPageConverted, this)); } void PrintJob::OnPdfPageConverted(int page_number, @@ -297,10 +314,14 @@ void PrintJob::OnPdfPageConverted(int page_number, return; } - // Update the rendered document. It will send notifications to the listener. - document_->SetPage(pdf_page_mapping_[page_number], std::move(metafile), - scale_factor, pdf_conversion_state_->page_size(), - pdf_conversion_state_->content_area()); + // Add the page to the document if it is one of the pages requested by the + // user. If it is not, ignore it. + if (pdf_page_mapping_[page_number] != -1) { + // Update the rendered document. It will send notifications to the listener. + document_->SetPage(pdf_page_mapping_[page_number], std::move(metafile), + scale_factor, pdf_conversion_state_->page_size(), + pdf_conversion_state_->content_area()); + } pdf_conversion_state_->GetMorePages( base::Bind(&PrintJob::OnPdfPageConverted, this)); @@ -311,14 +332,14 @@ void PrintJob::StartPdfToTextConversion( const gfx::Size& page_size) { DCHECK(!pdf_conversion_state_); pdf_conversion_state_ = - base::MakeUnique<PdfConversionState>(gfx::Size(), gfx::Rect()); - const int kPrinterDpi = settings().dpi(); + std::make_unique<PdfConversionState>(gfx::Size(), gfx::Rect()); gfx::Rect page_area = gfx::Rect(0, 0, page_size.width(), page_size.height()); - PdfRenderSettings settings(page_area, gfx::Point(0, 0), kPrinterDpi, - /*autorotate=*/true, - PdfRenderSettings::Mode::TEXTONLY); + PdfRenderSettings render_settings( + page_area, gfx::Point(0, 0), settings().dpi_size(), + /*autorotate=*/true, PdfRenderSettings::Mode::TEXTONLY); pdf_conversion_state_->Start( - bytes, settings, base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); + bytes, render_settings, + base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); } void PrintJob::StartPdfToPostScriptConversion( @@ -327,15 +348,16 @@ void PrintJob::StartPdfToPostScriptConversion( const gfx::Point& physical_offsets, bool ps_level2) { DCHECK(!pdf_conversion_state_); - pdf_conversion_state_ = base::MakeUnique<PdfConversionState>( + pdf_conversion_state_ = std::make_unique<PdfConversionState>( gfx::Size(), gfx::Rect()); - const int kPrinterDpi = settings().dpi(); - PdfRenderSettings settings( - content_area, physical_offsets, kPrinterDpi, /*autorotate=*/true, + PdfRenderSettings render_settings( + content_area, physical_offsets, settings().dpi_size(), + /*autorotate=*/true, ps_level2 ? PdfRenderSettings::Mode::POSTSCRIPT_LEVEL2 : PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3); pdf_conversion_state_->Start( - bytes, settings, base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); + bytes, render_settings, + base::BindOnce(&PrintJob::OnPdfConversionStarted, this)); } #endif // defined(OS_WIN) @@ -351,11 +373,12 @@ void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { if (worker_) { DCHECK(!is_job_pending_); // Sync the document with the worker. - worker_->PostTask(FROM_HERE, - base::Bind(&HoldRefCallback, base::WrapRefCounted(this), - base::Bind(&PrintJobWorker::OnDocumentChanged, - base::Unretained(worker_.get()), - base::RetainedRef(document_)))); + worker_->PostTask( + FROM_HERE, + base::BindOnce(&HoldRefCallback, base::WrapRefCounted(this), + base::BindOnce(&PrintJobWorker::OnDocumentChanged, + base::Unretained(worker_.get()), + base::RetainedRef(document_)))); } } diff --git a/chromium/chrome/browser/printing/print_job.h b/chromium/chrome/browser/printing/print_job.h index b594c3dec07..b12753257a0 100644 --- a/chromium/chrome/browser/printing/print_job.h +++ b/chromium/chrome/browser/printing/print_job.h @@ -31,8 +31,8 @@ class PrintedPage; #endif class PrinterQuery; -void HoldRefCallback(const scoped_refptr<PrintJobWorkerOwner>& owner, - const base::Closure& callback); +void HoldRefCallback(scoped_refptr<PrintJobWorkerOwner> owner, + base::OnceClosure callback); // Manages the print work for a specific document. Talks to the printer through // PrintingContext through PrintJobWorker. Hides access to PrintingContext in a @@ -53,6 +53,15 @@ class PrintJob : public PrintJobWorkerOwner, const base::string16& name, int page_count); +#if defined(OS_WIN) + // Overwrites the PDF page mapping to fill in values of -1 for all indices + // that are not selected. This is needed when the user opens the system + // dialog from the link in Print Preview on Windows and then sets a selection + // of pages, because all PDF pages will be converted, but only the user's + // selected pages should be sent to the printer. See https://crbug.com/823876. + void ResetPageMapping(); +#endif + // content::NotificationObserver implementation. void Observe(int type, const content::NotificationSource& source, diff --git a/chromium/chrome/browser/printing/print_job_manager.cc b/chromium/chrome/browser/printing/print_job_manager.cc index 35a516b69f9..7d4d9c6e79a 100644 --- a/chromium/chrome/browser/printing/print_job_manager.cc +++ b/chromium/chrome/browser/printing/print_job_manager.cc @@ -61,7 +61,8 @@ void PrintQueriesQueue::Shutdown() { // corresponding PrintJob, so any pending preview requests are not covered // by PrintJobManager::StopJobs and should be stopped explicitly. for (auto& query : queries_to_stop) { - query->PostTask(FROM_HERE, base::Bind(&PrinterQuery::StopWorker, query)); + query->PostTask(FROM_HERE, + base::BindOnce(&PrinterQuery::StopWorker, query)); } } diff --git a/chromium/chrome/browser/printing/print_job_unittest.cc b/chromium/chrome/browser/printing/print_job_unittest.cc index 481d3631694..fc49e35accc 100644 --- a/chromium/chrome/browser/printing/print_job_unittest.cc +++ b/chromium/chrome/browser/printing/print_job_unittest.cc @@ -45,7 +45,7 @@ class TestOwner : public PrintJobWorkerOwner { PrintJobWorkerOwner* new_owner) override { // We're screwing up here since we're calling worker from the main thread. // That's fine for testing. It is actually simulating PrinterQuery behavior. - auto worker = base::MakeUnique<TestPrintJobWorker>(new_owner); + auto worker = std::make_unique<TestPrintJobWorker>(new_owner); EXPECT_TRUE(worker->Start()); worker->printing_context()->UseDefaultSettings(); settings_ = worker->printing_context()->settings(); diff --git a/chromium/chrome/browser/printing/print_job_worker.cc b/chromium/chrome/browser/printing/print_job_worker.cc index 4f093cb61d6..5bee7d3a553 100644 --- a/chromium/chrome/browser/printing/print_job_worker.cc +++ b/chromium/chrome/browser/printing/print_job_worker.cc @@ -6,6 +6,7 @@ #include <memory> #include <string> +#include <utility> #include "base/bind.h" #include "base/bind_helpers.h" @@ -102,11 +103,12 @@ void NotificationCallback(PrintJobWorkerOwner* print_job, content::Details<JobEventDetails>(details)); } -void PostOnOwnerThread(const scoped_refptr<PrintJobWorkerOwner>& owner, - const PrintingContext::PrintSettingsCallback& callback, +void PostOnOwnerThread(scoped_refptr<PrintJobWorkerOwner> owner, + PrintingContext::PrintSettingsCallback callback, PrintingContext::Result result) { - owner->PostTask(FROM_HERE, base::Bind(&HoldRefCallback, owner, - base::Bind(callback, result))); + owner->PostTask(FROM_HERE, + base::BindOnce(&HoldRefCallback, owner, + base::BindOnce(std::move(callback), result))); } #if defined(OS_WIN) @@ -178,16 +180,17 @@ void PrintJobWorker::GetSettings(bool ask_user_for_settings, if (ask_user_for_settings) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::BindOnce(&HoldRefCallback, base::WrapRefCounted(owner_), - base::Bind(&PrintJobWorker::GetSettingsWithUI, - base::Unretained(this), document_page_count, - has_selection, is_scripted))); + base::BindOnce( + &HoldRefCallback, base::WrapRefCounted(owner_), + base::BindOnce(&PrintJobWorker::GetSettingsWithUI, + base::Unretained(this), document_page_count, + has_selection, is_scripted))); } else { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&HoldRefCallback, base::WrapRefCounted(owner_), - base::Bind(&PrintJobWorker::UseDefaultSettings, - base::Unretained(this)))); + base::BindOnce(&PrintJobWorker::UseDefaultSettings, + base::Unretained(this)))); } } @@ -199,9 +202,23 @@ void PrintJobWorker::SetSettings( BrowserThread::UI, FROM_HERE, base::BindOnce( &HoldRefCallback, base::WrapRefCounted(owner_), - base::Bind(&PrintJobWorker::UpdatePrintSettings, - base::Unretained(this), base::Passed(&new_settings)))); + base::BindOnce(&PrintJobWorker::UpdatePrintSettings, + base::Unretained(this), std::move(new_settings)))); +} + +#if defined(OS_CHROMEOS) +void PrintJobWorker::SetSettingsFromPOD( + std::unique_ptr<printing::PrintSettings> new_settings) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce( + &HoldRefCallback, base::WrapRefCounted(owner_), + base::BindOnce(&PrintJobWorker::UpdatePrintSettingsFromPOD, + base::Unretained(this), std::move(new_settings)))); } +#endif void PrintJobWorker::UpdatePrintSettings( std::unique_ptr<base::DictionaryValue> new_settings) { @@ -211,6 +228,16 @@ void PrintJobWorker::UpdatePrintSettings( GetSettingsDone(result); } +#if defined(OS_CHROMEOS) +void PrintJobWorker::UpdatePrintSettingsFromPOD( + std::unique_ptr<printing::PrintSettings> new_settings) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + PrintingContext::Result result = + printing_context_->UpdatePrintSettingsFromPOD(std::move(new_settings)); + GetSettingsDone(result); +} +#endif + void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { // Most PrintingContext functions may start a message loop and process // message recursively, so disable recursive task processing. @@ -223,9 +250,9 @@ void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { // PrintJob will create the new PrintedDocument. owner_->PostTask(FROM_HERE, - base::Bind(&PrintJobWorkerOwner::GetSettingsDone, - base::WrapRefCounted(owner_), - printing_context_->settings(), result)); + base::BindOnce(&PrintJobWorkerOwner::GetSettingsDone, + base::WrapRefCounted(owner_), + printing_context_->settings(), result)); } void PrintJobWorker::GetSettingsWithUI( @@ -262,9 +289,9 @@ void PrintJobWorker::GetSettingsWithUI( // weak_factory_ creates pointers valid only on owner_ thread. printing_context_->AskUserForSettings( document_page_count, has_selection, is_scripted, - base::Bind(&PostOnOwnerThread, base::WrapRefCounted(owner_), - base::Bind(&PrintJobWorker::GetSettingsDone, - weak_factory_.GetWeakPtr()))); + base::BindOnce(&PostOnOwnerThread, base::WrapRefCounted(owner_), + base::BindOnce(&PrintJobWorker::GetSettingsDone, + weak_factory_.GetWeakPtr()))); } void PrintJobWorker::UseDefaultSettings() { @@ -295,8 +322,7 @@ void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { return; } - // Try to print already cached data. It may already have been generated for - // the print preview. + // This will start a loop to wait for the page data. OnNewPage(); // Don't touch this anymore since the instance could be destroyed. It happens // if all the pages are printed a one sweep and the client doesn't have a @@ -391,9 +417,9 @@ bool PrintJobWorker::IsRunning() const { } bool PrintJobWorker::PostTask(const base::Location& from_here, - const base::Closure& task) { + base::OnceClosure task) { if (task_runner_.get()) - return task_runner_->PostTask(from_here, task); + return task_runner_->PostTask(from_here, std::move(task)); return false; } @@ -422,10 +448,10 @@ void PrintJobWorker::OnDocumentDone() { return; } - owner_->PostTask(FROM_HERE, - base::Bind(&NotificationCallback, base::RetainedRef(owner_), - JobEventDetails::DOC_DONE, job_id, - base::RetainedRef(document_))); + owner_->PostTask(FROM_HERE, base::BindOnce(&NotificationCallback, + base::RetainedRef(owner_), + JobEventDetails::DOC_DONE, job_id, + base::RetainedRef(document_))); // Makes sure the variables are reinitialized. document_ = NULL; @@ -473,8 +499,8 @@ void PrintJobWorker::OnFailure() { scoped_refptr<PrintJobWorkerOwner> handle(owner_); owner_->PostTask( - FROM_HERE, base::BindRepeating( - &NotificationCallback, base::RetainedRef(owner_), + FROM_HERE, + base::BindOnce(&NotificationCallback, base::RetainedRef(owner_), JobEventDetails::FAILED, 0, base::RetainedRef(document_))); Cancel(); diff --git a/chromium/chrome/browser/printing/print_job_worker.h b/chromium/chrome/browser/printing/print_job_worker.h index 11e2c85813e..4ad10b420a8 100644 --- a/chromium/chrome/browser/printing/print_job_worker.h +++ b/chromium/chrome/browser/printing/print_job_worker.h @@ -54,9 +54,15 @@ class PrintJobWorker { bool is_scripted, bool is_modifiable); - // Set the new print settings. + // Set the new print settings from a dictionary value. void SetSettings(std::unique_ptr<base::DictionaryValue> new_settings); +#if defined(OS_CHROMEOS) + // Set the new print settings from a POD type. + void SetSettingsFromPOD( + std::unique_ptr<printing::PrintSettings> new_settings); +#endif + // Starts the printing loop. Every pages are printed as soon as the data is // available. Makes sure the new_document is the right one. void StartPrinting(PrintedDocument* new_document); @@ -76,7 +82,7 @@ class PrintJobWorker { bool IsRunning() const; // Posts the given task to be run. - bool PostTask(const base::Location& from_here, const base::Closure& task); + bool PostTask(const base::Location& from_here, base::OnceClosure task); // Signals the thread to exit in the near future. void StopSoon(); @@ -129,6 +135,12 @@ class PrintJobWorker { // Called on the UI thread to update the print settings. void UpdatePrintSettings(std::unique_ptr<base::DictionaryValue> new_settings); +#if defined(OS_CHROMEOS) + // Called on the UI thread to update the print settings. + void UpdatePrintSettingsFromPOD( + std::unique_ptr<printing::PrintSettings> new_settings); +#endif + // Reports settings back to owner_. void GetSettingsDone(PrintingContext::Result result); diff --git a/chromium/chrome/browser/printing/print_job_worker_owner.cc b/chromium/chrome/browser/printing/print_job_worker_owner.cc index 60b8473f0fb..decac596362 100644 --- a/chromium/chrome/browser/printing/print_job_worker_owner.cc +++ b/chromium/chrome/browser/printing/print_job_worker_owner.cc @@ -21,8 +21,8 @@ bool PrintJobWorkerOwner::RunsTasksInCurrentSequence() const { } bool PrintJobWorkerOwner::PostTask(const base::Location& from_here, - const base::Closure& task) { - return task_runner_->PostTask(from_here, task); + base::OnceClosure task) { + return task_runner_->PostTask(from_here, std::move(task)); } } // namespace printing diff --git a/chromium/chrome/browser/printing/print_job_worker_owner.h b/chromium/chrome/browser/printing/print_job_worker_owner.h index d94ea6e1447..8625ac71be3 100644 --- a/chromium/chrome/browser/printing/print_job_worker_owner.h +++ b/chromium/chrome/browser/printing/print_job_worker_owner.h @@ -47,7 +47,7 @@ class PrintJobWorkerOwner bool RunsTasksInCurrentSequence() const; // Posts the given task to be run. - bool PostTask(const base::Location& from_here, const base::Closure& task); + bool PostTask(const base::Location& from_here, base::OnceClosure task); protected: friend class base::RefCountedThreadSafe<PrintJobWorkerOwner>; diff --git a/chromium/chrome/browser/printing/print_preview_data_service.cc b/chromium/chrome/browser/printing/print_preview_data_service.cc index 0b40be8c036..b26a6cf8ebc 100644 --- a/chromium/chrome/browser/printing/print_preview_data_service.cc +++ b/chromium/chrome/browser/printing/print_preview_data_service.cc @@ -106,7 +106,7 @@ void PrintPreviewDataService::SetDataEntry( int index, scoped_refptr<base::RefCountedBytes> data_bytes) { if (!base::ContainsKey(data_store_map_, preview_ui_id)) - data_store_map_[preview_ui_id] = base::MakeUnique<PrintPreviewDataStore>(); + data_store_map_[preview_ui_id] = std::make_unique<PrintPreviewDataStore>(); data_store_map_[preview_ui_id]->SetPreviewDataForIndex(index, std::move(data_bytes)); } diff --git a/chromium/chrome/browser/printing/print_preview_dialog_controller.cc b/chromium/chrome/browser/printing/print_preview_dialog_controller.cc index 449ec7465b0..0abdaa583c3 100644 --- a/chromium/chrome/browser/printing/print_preview_dialog_controller.cc +++ b/chromium/chrome/browser/printing/print_preview_dialog_controller.cc @@ -377,7 +377,7 @@ WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog( content::HostZoomMap::Get(preview_dialog->GetSiteInstance()) ->SetZoomLevelForHostAndScheme(print_url.scheme(), print_url.host(), 0); PrintViewManager::CreateForWebContents(preview_dialog); - CreateCompositeClientIfNeeded(preview_dialog, true /* for_preview */); + CreateCompositeClientIfNeeded(preview_dialog); extensions::ChromeExtensionWebContentsObserver::CreateForWebContents( preview_dialog); diff --git a/chromium/chrome/browser/printing/print_preview_dialog_controller_browsertest.cc b/chromium/chrome/browser/printing/print_preview_dialog_controller_browsertest.cc index 1de6aeb87da..9c5cf7bf89c 100644 --- a/chromium/chrome/browser/printing/print_preview_dialog_controller_browsertest.cc +++ b/chromium/chrome/browser/printing/print_preview_dialog_controller_browsertest.cc @@ -94,7 +94,7 @@ class PrintPreviewDialogClonedObserver : public WebContentsObserver { void DidCloneToNewWebContents(WebContents* old_web_contents, WebContents* new_web_contents) override { request_preview_dialog_observer_ = - base::MakeUnique<RequestPrintPreviewObserver>(new_web_contents); + std::make_unique<RequestPrintPreviewObserver>(new_web_contents); } std::unique_ptr<RequestPrintPreviewObserver> request_preview_dialog_observer_; @@ -192,7 +192,7 @@ class PrintPreviewDialogControllerBrowserTest : public InProcessBrowserTest { // RequestPrintPreviewObserver to get messages first for the purposes of // this test. cloned_tab_observer_ = - base::MakeUnique<PrintPreviewDialogClonedObserver>(first_tab); + std::make_unique<PrintPreviewDialogClonedObserver>(first_tab); chrome::DuplicateTab(browser()); initiator_ = browser()->tab_strip_model()->GetActiveWebContents(); diff --git a/chromium/chrome/browser/printing/print_preview_message_handler.cc b/chromium/chrome/browser/printing/print_preview_message_handler.cc index baacab9508e..d088fc05909 100644 --- a/chromium/chrome/browser/printing/print_preview_message_handler.cc +++ b/chromium/chrome/browser/printing/print_preview_message_handler.cc @@ -24,8 +24,11 @@ #include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_manager_utils.h" #include "components/printing/common/print_messages.h" +#include "components/printing/service/public/cpp/pdf_service_mojo_types.h" #include "components/printing/service/public/cpp/pdf_service_mojo_utils.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/web_contents.h" #include "content/public/browser/web_ui.h" #include "printing/page_size_margins.h" @@ -118,16 +121,16 @@ void PrintPreviewMessageHandler::OnDidGetPreviewPageCount( if (!print_preview_ui) return; - if (params.clear_preview_data) - print_preview_ui->ClearAllPreviewData(); - + print_preview_ui->ClearAllPreviewData(); print_preview_ui->OnDidGetPreviewPageCount(params); } void PrintPreviewMessageHandler::OnDidPreviewPage( + content::RenderFrameHost* render_frame_host, const PrintHostMsg_DidPreviewPage_Params& params) { int page_number = params.page_number; - if (page_number < FIRST_PAGE_INDEX || !params.data_size) + const PrintHostMsg_DidPrintContent_Params& content = params.content; + if (page_number < FIRST_PAGE_INDEX || !content.data_size) return; PrintPreviewUI* print_preview_ui = GetPrintPreviewUI(); @@ -139,19 +142,22 @@ void PrintPreviewMessageHandler::OnDidPreviewPage( DCHECK(client); // Use utility process to convert skia metafile to pdf. - client->DoComposite( - params.metafile_data_handle, params.data_size, + client->DoCompositePageToPdf( + params.document_cookie, render_frame_host, params.page_number, + content.metafile_data_handle, content.data_size, + content.subframe_content_info, base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfPageDone, weak_ptr_factory_.GetWeakPtr(), params.page_number, params.preview_request_id)); } else { NotifyUIPreviewPageReady( page_number, params.preview_request_id, - GetDataFromHandle(params.metafile_data_handle, params.data_size)); + GetDataFromHandle(content.metafile_data_handle, content.data_size)); } } void PrintPreviewMessageHandler::OnMetafileReadyForPrinting( + content::RenderFrameHost* render_frame_host, const PrintHostMsg_DidPreviewDocument_Params& params) { // Always try to stop the worker. StopWorker(params.document_cookie); @@ -165,19 +171,21 @@ void PrintPreviewMessageHandler::OnMetafileReadyForPrinting( if (!print_preview_ui) return; + const PrintHostMsg_DidPrintContent_Params& content = params.content; if (IsOopifEnabled() && print_preview_ui->source_is_modifiable()) { auto* client = PrintCompositeClient::FromWebContents(web_contents()); DCHECK(client); - client->DoComposite( - params.metafile_data_handle, params.data_size, + client->DoCompositeDocumentToPdf( + params.document_cookie, render_frame_host, content.metafile_data_handle, + content.data_size, content.subframe_content_info, base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfDocumentDone, weak_ptr_factory_.GetWeakPtr(), params.expected_pages_count, params.preview_request_id)); } else { NotifyUIPreviewDocumentReady( params.expected_pages_count, params.preview_request_id, - GetDataFromHandle(params.metafile_data_handle, params.data_size)); + GetDataFromHandle(content.metafile_data_handle, content.data_size)); } } @@ -294,6 +302,9 @@ bool PrintPreviewMessageHandler::OnMessageReceived( render_frame_host) IPC_MESSAGE_HANDLER(PrintHostMsg_RequestPrintPreview, OnRequestPrintPreview) + IPC_MESSAGE_HANDLER(PrintHostMsg_DidPreviewPage, OnDidPreviewPage) + IPC_MESSAGE_HANDLER(PrintHostMsg_MetafileReadyForPrinting, + OnMetafileReadyForPrinting) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() if (handled) @@ -303,9 +314,6 @@ bool PrintPreviewMessageHandler::OnMessageReceived( IPC_BEGIN_MESSAGE_MAP(PrintPreviewMessageHandler, message) IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPreviewPageCount, OnDidGetPreviewPageCount) - IPC_MESSAGE_HANDLER(PrintHostMsg_DidPreviewPage, OnDidPreviewPage) - IPC_MESSAGE_HANDLER(PrintHostMsg_MetafileReadyForPrinting, - OnMetafileReadyForPrinting) IPC_MESSAGE_HANDLER(PrintHostMsg_PrintPreviewFailed, OnPrintPreviewFailed) IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDefaultPageLayout, diff --git a/chromium/chrome/browser/printing/print_preview_message_handler.h b/chromium/chrome/browser/printing/print_preview_message_handler.h index 71ca9d5bb1e..1c30f2c1a44 100644 --- a/chromium/chrome/browser/printing/print_preview_message_handler.h +++ b/chromium/chrome/browser/printing/print_preview_message_handler.h @@ -64,8 +64,10 @@ class PrintPreviewMessageHandler bool has_custom_page_size_style); void OnDidGetPreviewPageCount( const PrintHostMsg_DidGetPreviewPageCount_Params& params); - void OnDidPreviewPage(const PrintHostMsg_DidPreviewPage_Params& params); + void OnDidPreviewPage(content::RenderFrameHost* render_frame_host, + const PrintHostMsg_DidPreviewPage_Params& params); void OnMetafileReadyForPrinting( + content::RenderFrameHost* render_frame_host, const PrintHostMsg_DidPreviewDocument_Params& params); void OnPrintPreviewFailed(int document_cookie); void OnPrintPreviewCancelled(int document_cookie); diff --git a/chromium/chrome/browser/printing/print_preview_pdf_generated_browsertest.cc b/chromium/chrome/browser/printing/print_preview_pdf_generated_browsertest.cc index 3a47bb1a1a1..afd223480e8 100644 --- a/chromium/chrome/browser/printing/print_preview_pdf_generated_browsertest.cc +++ b/chromium/chrome/browser/printing/print_preview_pdf_generated_browsertest.cc @@ -214,7 +214,7 @@ class PrintPreviewObserver : public WebContentsObserver { // Saves the print preview settings to be sent to the print preview dialog. void SetPrintPreviewSettings(const PrintPreviewSettings& settings) { - settings_ = base::MakeUnique<PrintPreviewSettings>(settings); + settings_ = std::make_unique<PrintPreviewSettings>(settings); } // Returns the setting that could not be set in the preview dialog. @@ -281,7 +281,7 @@ class PrintPreviewObserver : public WebContentsObserver { ASSERT_TRUE(ui->web_ui()); ui->web_ui()->AddMessageHandler( - base::MakeUnique<UIDoneLoadingMessageHandler>(this)); + std::make_unique<UIDoneLoadingMessageHandler>(this)); ui->SendEnableManipulateSettingsForTest(); } @@ -369,8 +369,8 @@ class PrintPreviewPdfGeneratedBrowserTest : public InProcessBrowserTest { total_height_in_pixels += height_in_pixels; gfx::Rect rect(width_in_pixels, height_in_pixels); - PdfRenderSettings settings(rect, gfx::Point(0, 0), kDpi, true, - PdfRenderSettings::Mode::NORMAL); + PdfRenderSettings settings(rect, gfx::Point(0, 0), gfx::Size(kDpi, kDpi), + true, PdfRenderSettings::Mode::NORMAL); int int_max = std::numeric_limits<int>::max(); if (settings.area.width() > int_max / kColorChannels || @@ -386,7 +386,7 @@ class PrintPreviewPdfGeneratedBrowserTest : public InProcessBrowserTest { ASSERT_TRUE(chrome_pdf::RenderPDFPageToBitmap( pdf_data.data(), pdf_data.size(), i, page_bitmap_data.data(), settings.area.size().width(), settings.area.size().height(), - settings.dpi, settings.autorotate)); + settings.dpi.width(), settings.dpi.height(), settings.autorotate)); FillPng(&page_bitmap_data, width_in_pixels, max_width_in_pixels, settings.area.size().height()); bitmap_data.insert(bitmap_data.end(), @@ -456,7 +456,7 @@ class PrintPreviewPdfGeneratedBrowserTest : public InProcessBrowserTest { browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(tab); - print_preview_observer_ = base::MakeUnique<PrintPreviewObserver>( + print_preview_observer_ = std::make_unique<PrintPreviewObserver>( browser(), tab, pdf_file_save_path_); chrome::DuplicateTab(browser()); diff --git a/chromium/chrome/browser/printing/print_view_manager.cc b/chromium/chrome/browser/printing/print_view_manager.cc index 9ce7794365e..8e115370d1d 100644 --- a/chromium/chrome/browser/printing/print_view_manager.cc +++ b/chromium/chrome/browser/printing/print_view_manager.cc @@ -96,7 +96,7 @@ bool PrintViewManager::PrintForSystemDialogNow( SetPrintingRFH(print_preview_rfh_); int32_t id = print_preview_rfh_->GetRoutingID(); return PrintNowInternal(print_preview_rfh_, - base::MakeUnique<PrintMsg_PrintForSystemDialog>(id)); + std::make_unique<PrintMsg_PrintForSystemDialog>(id)); } bool PrintViewManager::BasicPrint(content::RenderFrameHost* rfh) { @@ -122,7 +122,7 @@ bool PrintViewManager::PrintPreviewNow(content::RenderFrameHost* rfh, if (print_preview_state_ != NOT_PREVIEWING) return false; - auto message = base::MakeUnique<PrintMsg_InitiatePrintPreview>( + auto message = std::make_unique<PrintMsg_InitiatePrintPreview>( rfh->GetRoutingID(), has_selection); if (!PrintNowInternal(rfh, std::move(message))) return false; diff --git a/chromium/chrome/browser/printing/print_view_manager_base.cc b/chromium/chrome/browser/printing/print_view_manager_base.cc index 9c3ba082e01..ba01494ae09 100644 --- a/chromium/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium/chrome/browser/printing/print_view_manager_base.cc @@ -24,6 +24,7 @@ #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/printing/print_job.h" #include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/print_view_manager_common.h" #include "chrome/browser/printing/printer_query.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/simple_message_box.h" @@ -34,6 +35,7 @@ #include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_manager_utils.h" #include "components/printing/common/print_messages.h" +#include "components/printing/service/public/cpp/pdf_service_mojo_types.h" #include "components/printing/service/public/cpp/pdf_service_mojo_utils.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" @@ -134,7 +136,7 @@ bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) { SetPrintingRFH(rfh); int32_t id = rfh->GetRoutingID(); - return PrintNowInternal(rfh, base::MakeUnique<PrintMsg_PrintPages>(id)); + return PrintNowInternal(rfh, std::make_unique<PrintMsg_PrintPages>(id)); } #endif @@ -197,7 +199,7 @@ void PrintViewManagerBase::PrintDocument( document->SetConvertingPdf(); #else std::unique_ptr<PdfMetafileSkia> metafile = - std::make_unique<PdfMetafileSkia>(SkiaDocumentType::PDF); + std::make_unique<PdfMetafileSkia>(); CHECK(metafile->InitFromData(print_data->front(), print_data->size())); // Update the rendered document. It will send notifications to the listener. @@ -261,6 +263,10 @@ void PrintViewManagerBase::StartLocalPrintJob( return; } +#if defined(OS_WIN) + print_job_->ResetPageMapping(); +#endif + const printing::PrintSettings& settings = printer_query->settings(); gfx::Size page_size = settings.page_setup_device_units().physical_size(); gfx::Rect content_area = @@ -336,29 +342,31 @@ void PrintViewManagerBase::OnComposePdfDone( } void PrintViewManagerBase::OnDidPrintDocument( + content::RenderFrameHost* render_frame_host, const PrintHostMsg_DidPrintDocument_Params& params) { PrintedDocument* document = GetDocument(params.document_cookie); if (!document) return; - if (!base::SharedMemory::IsHandleValid(params.metafile_data_handle)) { + const PrintHostMsg_DidPrintContent_Params& content = params.content; + if (!base::SharedMemory::IsHandleValid(content.metafile_data_handle)) { NOTREACHED() << "invalid memory handle"; web_contents()->Stop(); return; } auto* client = PrintCompositeClient::FromWebContents(web_contents()); - if (IsOopifEnabled() && !client->for_preview() && - !document->settings().is_modifiable()) { - client->DoComposite(params.metafile_data_handle, params.data_size, - base::BindOnce(&PrintViewManagerBase::OnComposePdfDone, - weak_ptr_factory_.GetWeakPtr(), params)); + if (IsOopifEnabled() && !PrintingPdfContent(render_frame_host)) { + client->DoCompositeDocumentToPdf( + params.document_cookie, render_frame_host, content.metafile_data_handle, + content.data_size, content.subframe_content_info, + base::BindOnce(&PrintViewManagerBase::OnComposePdfDone, + weak_ptr_factory_.GetWeakPtr(), params)); return; } - - std::unique_ptr<base::SharedMemory> shared_buf = - std::make_unique<base::SharedMemory>(params.metafile_data_handle, true); - if (!shared_buf->Map(params.data_size)) { + auto shared_buf = + std::make_unique<base::SharedMemory>(content.metafile_data_handle, true); + if (!shared_buf->Map(content.data_size)) { NOTREACHED() << "couldn't map"; web_contents()->Stop(); return; @@ -366,7 +374,7 @@ void PrintViewManagerBase::OnDidPrintDocument( scoped_refptr<base::RefCountedBytes> bytes = base::MakeRefCounted<base::RefCountedBytes>( reinterpret_cast<const unsigned char*>(shared_buf->memory()), - params.data_size); + content.data_size); PrintDocument(document, bytes, params.page_size, params.content_area, params.physical_offsets); } @@ -438,8 +446,16 @@ bool PrintViewManagerBase::OnMessageReceived( const IPC::Message& message, content::RenderFrameHost* render_frame_host) { bool handled = true; - IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message) + IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(PrintViewManagerBase, message, + render_frame_host) IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintDocument, OnDidPrintDocument) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + if (handled) + return true; + + handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message) IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError, OnShowInvalidPrinterSettingsError) IPC_MESSAGE_UNHANDLED(handled = false) @@ -566,8 +582,6 @@ bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner* job) { return false; } - // Ask the renderer to generate the print preview, create the print preview - // view and switch to it, initialize the printer and show the print dialog. DCHECK(!print_job_.get()); DCHECK(job); if (!job) @@ -627,7 +641,7 @@ void PrintViewManagerBase::ReleasePrintJob() { return; if (rfh) { - auto msg = base::MakeUnique<PrintMsg_PrintingDone>(rfh->GetRoutingID(), + auto msg = std::make_unique<PrintMsg_PrintingDone>(rfh->GetRoutingID(), printing_succeeded_); rfh->Send(msg.release()); } diff --git a/chromium/chrome/browser/printing/print_view_manager_base.h b/chromium/chrome/browser/printing/print_view_manager_base.h index 7cf2d0d81fd..6daa186202a 100644 --- a/chromium/chrome/browser/printing/print_view_manager_base.h +++ b/chromium/chrome/browser/printing/print_view_manager_base.h @@ -106,7 +106,8 @@ class PrintViewManagerBase : public content::NotificationObserver, void OnDidGetPrintedPagesCount(int cookie, int number_pages) override; void OnPrintingFailed(int cookie) override; void OnShowInvalidPrinterSettingsError(); - void OnDidPrintDocument(const PrintHostMsg_DidPrintDocument_Params& params); + void OnDidPrintDocument(content::RenderFrameHost* render_frame_host, + const PrintHostMsg_DidPrintDocument_Params& params); // IPC message handlers for service. void OnComposePdfDone(const PrintHostMsg_DidPrintDocument_Params& params, diff --git a/chromium/chrome/browser/printing/print_view_manager_common.cc b/chromium/chrome/browser/printing/print_view_manager_common.cc index 1832389b903..eb2cb0b2e8c 100644 --- a/chromium/chrome/browser/printing/print_view_manager_common.cc +++ b/chromium/chrome/browser/printing/print_view_manager_common.cc @@ -4,9 +4,12 @@ #include "chrome/browser/printing/print_view_manager_common.h" +#include "chrome/common/webui_url_constants.h" #include "content/public/browser/render_frame_host.h" +#include "extensions/common/constants.h" #include "extensions/features/features.h" #include "printing/features/features.h" +#include "url/gurl.h" #if BUILDFLAG(ENABLE_EXTENSIONS) #include "components/guest_view/browser/guest_view_manager.h" @@ -122,4 +125,12 @@ content::RenderFrameHost* GetFrameToPrint(content::WebContents* contents) { : contents->GetMainFrame(); } +bool PrintingPdfContent(content::RenderFrameHost* rfh) { + GURL url = rfh->GetLastCommittedURL(); + // Whether it is inside print preview or pdf plugin extension. + return url.GetOrigin() == chrome::kChromeUIPrintURL || + (url.SchemeIs(extensions::kExtensionScheme) && + url.host_piece() == extension_misc::kPdfExtensionId); +} + } // namespace printing diff --git a/chromium/chrome/browser/printing/print_view_manager_common.h b/chromium/chrome/browser/printing/print_view_manager_common.h index da97bc7503d..dff9e61b1cc 100644 --- a/chromium/chrome/browser/printing/print_view_manager_common.h +++ b/chromium/chrome/browser/printing/print_view_manager_common.h @@ -28,6 +28,13 @@ void StartBasicPrint(content::WebContents* contents); // frame (this makes print selection work for multiple frames). content::RenderFrameHost* GetFrameToPrint(content::WebContents* contents); +// Whether the content sent to |rfh| is in PDF format. +// When print preview dialog is printed, the content returned is always +// in PDF format because print preview already stores the PDF file for +// the previewed web page; When a full page PDF plugin is printed, the document +// in it is in PDF format so will return in PDF also. +bool PrintingPdfContent(content::RenderFrameHost* rfh); + } // namespace printing #endif // CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_COMMON_H_ diff --git a/chromium/chrome/browser/printing/printer_manager_dialog_linux.cc b/chromium/chrome/browser/printing/printer_manager_dialog_linux.cc index 8e62d44ce6f..0c46c5fdcd2 100644 --- a/chromium/chrome/browser/printing/printer_manager_dialog_linux.cc +++ b/chromium/chrome/browser/printing/printer_manager_dialog_linux.cc @@ -17,13 +17,21 @@ namespace { -// KDE printer config command ("system-config-printer-kde") causes the -// OptionWidget to crash (https://bugs.kde.org/show_bug.cgi?id=271957). -// Therefore, use GNOME printer config command for KDE. -const char* const kSystemConfigPrinterCommand[] = {"system-config-printer", - nullptr}; +// Older KDE shipped with system-config-printer-kde, which was buggy. Thus do +// not bother with system-config-printer-kde and just always use +// system-config-printer. +// https://bugs.kde.org/show_bug.cgi?id=271957. +constexpr const char* kSystemConfigPrinterCommand[] = {"system-config-printer", + nullptr}; -const char* const kGnomeControlCenterPrintersCommand[] = { +// Newer KDE has an improved print manager. +constexpr const char* kKde4KcmPrinterCommand[] = { + "kcmshell4", "kcm_printer_manager", nullptr}; +constexpr const char* kKde5KcmPrinterCommand[] = { + "kcmshell5", "kcm_printer_manager", nullptr}; + +// Older GNOME printer manager. Used as a fallback. +constexpr const char* kGnomeControlCenterPrintersCommand[] = { "gnome-control-center", "printers", nullptr}; // Returns true if the dialog was opened successfully. @@ -54,10 +62,16 @@ void DetectAndOpenPrinterConfigDialog() { opened = OpenPrinterConfigDialog(kSystemConfigPrinterCommand) || OpenPrinterConfigDialog(kGnomeControlCenterPrintersCommand); break; - case base::nix::DESKTOP_ENVIRONMENT_CINNAMON: - case base::nix::DESKTOP_ENVIRONMENT_KDE3: case base::nix::DESKTOP_ENVIRONMENT_KDE4: + opened = OpenPrinterConfigDialog(kKde4KcmPrinterCommand) || + OpenPrinterConfigDialog(kSystemConfigPrinterCommand); + break; case base::nix::DESKTOP_ENVIRONMENT_KDE5: + opened = OpenPrinterConfigDialog(kKde5KcmPrinterCommand) || + OpenPrinterConfigDialog(kSystemConfigPrinterCommand); + break; + case base::nix::DESKTOP_ENVIRONMENT_CINNAMON: + case base::nix::DESKTOP_ENVIRONMENT_KDE3: case base::nix::DESKTOP_ENVIRONMENT_PANTHEON: case base::nix::DESKTOP_ENVIRONMENT_UNITY: case base::nix::DESKTOP_ENVIRONMENT_XFCE: diff --git a/chromium/chrome/browser/printing/printer_query.cc b/chromium/chrome/browser/printing/printer_query.cc index a03a60c0835..4bf550558ce 100644 --- a/chromium/chrome/browser/printing/printer_query.cc +++ b/chromium/chrome/browser/printing/printer_query.cc @@ -83,9 +83,10 @@ void PrinterQuery::GetSettings(GetSettingsAskParam ask_user_for_settings, ask_user_for_settings == GetSettingsAskParam::ASK_USER; worker_->PostTask( FROM_HERE, - base::Bind(&PrintJobWorker::GetSettings, base::Unretained(worker_.get()), - is_print_dialog_box_shown_, expected_page_count, has_selection, - margin_type, is_scripted, is_modifiable)); + base::BindOnce(&PrintJobWorker::GetSettings, + base::Unretained(worker_.get()), + is_print_dialog_box_shown_, expected_page_count, + has_selection, margin_type, is_scripted, is_modifiable)); } void PrinterQuery::SetSettings( @@ -93,12 +94,24 @@ void PrinterQuery::SetSettings( base::OnceClosure callback) { StartWorker(std::move(callback)); - worker_->PostTask(FROM_HERE, - base::Bind(&PrintJobWorker::SetSettings, - base::Unretained(worker_.get()), - base::Passed(&new_settings))); + worker_->PostTask(FROM_HERE, base::BindOnce(&PrintJobWorker::SetSettings, + base::Unretained(worker_.get()), + std::move(new_settings))); } +#if defined(OS_CHROMEOS) +void PrinterQuery::SetSettingsFromPOD( + std::unique_ptr<printing::PrintSettings> new_settings, + base::OnceClosure callback) { + StartWorker(std::move(callback)); + + worker_->PostTask( + FROM_HERE, + base::BindOnce(&PrintJobWorker::SetSettingsFromPOD, + base::Unretained(worker_.get()), std::move(new_settings))); +} +#endif + void PrinterQuery::StartWorker(base::OnceClosure callback) { DCHECK(!callback_); DCHECK(worker_); diff --git a/chromium/chrome/browser/printing/printer_query.h b/chromium/chrome/browser/printing/printer_query.h index 769d653cd53..e0251a48fb2 100644 --- a/chromium/chrome/browser/printing/printer_query.h +++ b/chromium/chrome/browser/printing/printer_query.h @@ -55,6 +55,12 @@ class PrinterQuery : public PrintJobWorkerOwner { void SetSettings(std::unique_ptr<base::DictionaryValue> new_settings, base::OnceClosure callback); +#if defined(OS_CHROMEOS) + // Updates the current settings with |new_settings|. + void SetSettingsFromPOD(std::unique_ptr<printing::PrintSettings> new_settings, + base::OnceClosure callback); +#endif + // Stops the worker thread since the client is done with this object. void StopWorker(); diff --git a/chromium/chrome/browser/printing/printing_init.cc b/chromium/chrome/browser/printing/printing_init.cc index 3560d1c7939..37562cc1213 100644 --- a/chromium/chrome/browser/printing/printing_init.cc +++ b/chromium/chrome/browser/printing/printing_init.cc @@ -24,7 +24,7 @@ void InitializePrinting(content::WebContents* web_contents) { #else printing::PrintViewManagerBasic::CreateForWebContents(web_contents); #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) - CreateCompositeClientIfNeeded(web_contents, false /* for_preview */); + CreateCompositeClientIfNeeded(web_contents); } } // namespace printing diff --git a/chromium/chrome/browser/printing/printing_message_filter.cc b/chromium/chrome/browser/printing/printing_message_filter.cc index 8e535f71587..096407abc20 100644 --- a/chromium/chrome/browser/printing/printing_message_filter.cc +++ b/chromium/chrome/browser/printing/printing_message_filter.cc @@ -269,7 +269,7 @@ void PrintingMessageFilter::OnScriptedPrintReply( } PrintHostMsg_ScriptedPrint::WriteReplyParams(reply_msg, params); Send(reply_msg); - if (params.params.dpi && params.params.document_cookie) { + if (!params.params.dpi.IsEmpty() && params.params.document_cookie) { #if defined(OS_ANDROID) int file_descriptor; const base::string16& device_name = printer_query->settings().device_name(); diff --git a/chromium/chrome/browser/printing/pwg_raster_converter.cc b/chromium/chrome/browser/printing/pwg_raster_converter.cc index 14f13aff11a..c9517f12e65 100644 --- a/chromium/chrome/browser/printing/pwg_raster_converter.cc +++ b/chromium/chrome/browser/printing/pwg_raster_converter.cc @@ -21,9 +21,8 @@ #include "base/task_scheduler/post_task.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" -#include "chrome/common/chrome_utility_printing_messages.h" -#include "chrome/services/printing/public/interfaces/constants.mojom.h" -#include "chrome/services/printing/public/interfaces/pdf_to_pwg_raster_converter.mojom.h" +#include "chrome/services/printing/public/mojom/constants.mojom.h" +#include "chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom.h" #include "components/cloud_devices/common/cloud_device_description.h" #include "components/cloud_devices/common/printer_description.h" #include "content/public/browser/browser_thread.h" @@ -208,63 +207,64 @@ class PwgRasterConverterImpl : public PwgRasterConverter { ResultCallback callback) override; private: - // TODO (rbpotter): Once CancelableOnceCallback is added, remove this and - // change callback_ to a CancelableOnceCallback. - void RunCallback(bool success, const base::FilePath& temp_file); - scoped_refptr<PwgRasterConverterHelper> utility_client_; - ResultCallback callback_; - base::WeakPtrFactory<PwgRasterConverterImpl> weak_ptr_factory_; + + // Cancelable version of ResultCallback. + base::CancelableOnceCallback<void(bool, const base::FilePath&)> callback_; DISALLOW_COPY_AND_ASSIGN(PwgRasterConverterImpl); }; -PwgRasterConverterImpl::PwgRasterConverterImpl() : weak_ptr_factory_(this) {} +PwgRasterConverterImpl::PwgRasterConverterImpl() = default; -PwgRasterConverterImpl::~PwgRasterConverterImpl() {} +PwgRasterConverterImpl::~PwgRasterConverterImpl() = default; void PwgRasterConverterImpl::Start(base::RefCountedMemory* data, const PdfRenderSettings& conversion_settings, const PwgRasterSettings& bitmap_settings, ResultCallback callback) { - // Bind callback here and pass a wrapper to the utility client to avoid - // calling callback if PwgRasterConverterImpl is destroyed. - callback_ = std::move(callback); + callback_.Reset(std::move(callback)); utility_client_ = base::MakeRefCounted<PwgRasterConverterHelper>( conversion_settings, bitmap_settings); - utility_client_->Convert(data, - base::BindOnce(&PwgRasterConverterImpl::RunCallback, - weak_ptr_factory_.GetWeakPtr())); -} - -void PwgRasterConverterImpl::RunCallback(bool success, - const base::FilePath& temp_file) { - std::move(callback_).Run(success, temp_file); + utility_client_->Convert(data, callback_.callback()); } } // namespace // static std::unique_ptr<PwgRasterConverter> PwgRasterConverter::CreateDefault() { - return base::MakeUnique<PwgRasterConverterImpl>(); + return std::make_unique<PwgRasterConverterImpl>(); } // static PdfRenderSettings PwgRasterConverter::GetConversionSettings( const cloud_devices::CloudDeviceDescription& printer_capabilities, const gfx::Size& page_size) { - int dpi = kDefaultPdfDpi; + gfx::Size dpi = gfx::Size(kDefaultPdfDpi, kDefaultPdfDpi); cloud_devices::printer::DpiCapability dpis; if (dpis.LoadFrom(printer_capabilities)) - dpi = std::max(dpis.GetDefault().horizontal, dpis.GetDefault().vertical); - - const double scale = static_cast<double>(dpi) / kPointsPerInch; + dpi = gfx::Size(dpis.GetDefault().horizontal, dpis.GetDefault().vertical); + + bool page_is_landscape = + static_cast<double>(page_size.width()) / dpi.width() > + static_cast<double>(page_size.height()) / dpi.height(); + + // Pdfium assumes that page width is given in dpi.width(), and height in + // dpi.height(). If we rotate the page, we need to also swap the DPIs. + gfx::Size final_page_size = page_size; + if (page_is_landscape) { + final_page_size = gfx::Size(page_size.height(), page_size.width()); + dpi = gfx::Size(dpi.height(), dpi.width()); + } + double scale_x = static_cast<double>(dpi.width()) / kPointsPerInch; + double scale_y = static_cast<double>(dpi.height()) / kPointsPerInch; // Make vertical rectangle to optimize streaming to printer. Fix orientation // by autorotate. - gfx::Rect area(std::min(page_size.width(), page_size.height()) * scale, - std::max(page_size.width(), page_size.height()) * scale); - return PdfRenderSettings(area, gfx::Point(0, 0), dpi, /*autorotate=*/true, + gfx::Rect area(final_page_size.width() * scale_x, + final_page_size.height() * scale_y); + return PdfRenderSettings(area, gfx::Point(0, 0), dpi, + /*autorotate=*/true, PdfRenderSettings::Mode::NORMAL); } diff --git a/chromium/chrome/browser/printing/pwg_raster_converter_browsertest.cc b/chromium/chrome/browser/printing/pwg_raster_converter_browsertest.cc index 5532b8e4002..570dd88c7a1 100644 --- a/chromium/chrome/browser/printing/pwg_raster_converter_browsertest.cc +++ b/chromium/chrome/browser/printing/pwg_raster_converter_browsertest.cc @@ -87,8 +87,8 @@ class PdfToPwgRasterBrowserTest : public InProcessBrowserTest { bool success = false; base::RunLoop run_loop; converter_->Start(pdf_data, conversion_settings, bitmap_settings, - base::Bind(&ResultCallbackImpl, &called, &success, - temp_file, run_loop.QuitClosure())); + base::BindOnce(&ResultCallbackImpl, &called, &success, + temp_file, run_loop.QuitClosure())); run_loop.Run(); ASSERT_TRUE(called); EXPECT_EQ(success, expect_success); @@ -116,7 +116,8 @@ IN_PROC_BROWSER_TEST_F(PdfToPwgRasterBrowserTest, TestSuccessColor) { GetPdfData("pdf_to_pwg_raster_test.pdf", &test_data_dir, &pdf_data); PdfRenderSettings pdf_settings(gfx::Rect(0, 0, 500, 500), gfx::Point(0, 0), - /*dpi=*/1000, /*autorotate=*/false, + /*dpi=*/gfx::Size(1000, 1000), + /*autorotate=*/false, PdfRenderSettings::Mode::NORMAL); PwgRasterSettings pwg_settings; pwg_settings.odd_page_transform = PwgRasterTransformType::TRANSFORM_NORMAL; @@ -142,7 +143,8 @@ IN_PROC_BROWSER_TEST_F(PdfToPwgRasterBrowserTest, TestSuccessMono) { GetPdfData("pdf_to_pwg_raster_test.pdf", &test_data_dir, &pdf_data); PdfRenderSettings pdf_settings(gfx::Rect(0, 0, 500, 500), gfx::Point(0, 0), - /*dpi=*/1000, /*autorotate=*/false, + /*dpi=*/gfx::Size(1000, 1000), + /*autorotate=*/false, PdfRenderSettings::Mode::NORMAL); PwgRasterSettings pwg_settings; pwg_settings.odd_page_transform = PwgRasterTransformType::TRANSFORM_NORMAL; diff --git a/chromium/chrome/browser/profiling_host/BUILD.gn b/chromium/chrome/browser/profiling_host/BUILD.gn index 9d7dbf3ddc1..7b90c77e756 100644 --- a/chromium/chrome/browser/profiling_host/BUILD.gn +++ b/chromium/chrome/browser/profiling_host/BUILD.gn @@ -16,7 +16,7 @@ if (!is_android) { deps = [ "//base", - "//base/allocator:features", + "//base/allocator:buildflags", "//chrome/test:test_support_ui", "//testing/gmock", "//testing/gtest", diff --git a/chromium/chrome/browser/renderer_host/pepper/DEPS b/chromium/chrome/browser/renderer_host/pepper/DEPS index 90188c90290..4aa9156f0d9 100644 --- a/chromium/chrome/browser/renderer_host/pepper/DEPS +++ b/chromium/chrome/browser/renderer_host/pepper/DEPS @@ -1,6 +1,6 @@ include_rules = [ # ppapi/host and ppapi/proxy are already added at a higher level DEPS file. "+ppapi/shared_impl", - "+services/device/public/interfaces", + "+services/device/public/mojom", ] diff --git a/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.cc b/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.cc index d23b6a7a55a..1c1844a9eb7 100644 --- a/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.cc +++ b/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.cc @@ -22,8 +22,8 @@ #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/resource_message_params.h" #include "ppapi/shared_impl/time_conversion.h" -#include "services/device/public/interfaces/constants.mojom.h" -#include "services/device/public/interfaces/wake_lock_provider.mojom.h" +#include "services/device/public/mojom/constants.mojom.h" +#include "services/device/public/mojom/wake_lock_provider.mojom.h" #include "services/service_manager/public/cpp/connector.h" #include "url/gurl.h" @@ -54,7 +54,7 @@ scoped_refptr<content_settings::CookieSettings> GetCookieSettings( return NULL; } -void BindConnectorRequest( +void PepperBindConnectorRequest( service_manager::mojom::ConnectorRequest connector_request) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(ServiceManagerConnection::GetForProcess()); @@ -194,9 +194,9 @@ device::mojom::WakeLock* PepperFlashBrowserHost::GetWakeLock() { // The existing connector is bound to the UI thread, the current thread is // IO thread. So bind the ConnectorRequest of IO thread to the connector // in UI thread. - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::BindOnce(&BindConnectorRequest, std::move(connector_request))); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::BindOnce(&PepperBindConnectorRequest, + std::move(connector_request))); device::mojom::WakeLockProviderPtr wake_lock_provider; connector->BindInterface(device::mojom::kServiceName, diff --git a/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h b/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h index f2c631ea59f..154120ce515 100644 --- a/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h +++ b/chromium/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h @@ -13,7 +13,7 @@ #include "base/timer/timer.h" #include "ppapi/host/host_message_context.h" #include "ppapi/host/resource_host.h" -#include "services/device/public/interfaces/wake_lock.mojom.h" +#include "services/device/public/mojom/wake_lock.mojom.h" namespace base { class Time; diff --git a/chromium/chrome/browser/ui/tabs/BUILD.gn b/chromium/chrome/browser/resource_coordinator/BUILD.gn index 25778d9f60c..25778d9f60c 100644 --- a/chromium/chrome/browser/ui/tabs/BUILD.gn +++ b/chromium/chrome/browser/resource_coordinator/BUILD.gn diff --git a/chromium/chrome/browser/resources/BUILD.gn b/chromium/chrome/browser/resources/BUILD.gn index f0559f28e62..c9f770b9a8e 100644 --- a/chromium/chrome/browser/resources/BUILD.gn +++ b/chromium/chrome/browser/resources/BUILD.gn @@ -166,15 +166,3 @@ if (enable_print_preview) { output_dir = "$root_gen_dir/chrome" } } - -if (enable_vr) { - grit("vr_shell_resources") { - source = "vr_shell_resources.grd" - defines = chrome_grit_defines - outputs = [ - "grit/vr_shell_resources.h", - "vr_shell_resources.pak", - ] - output_dir = "$root_gen_dir/chrome" - } -} diff --git a/chromium/chrome/browser/resources/PRESUBMIT.py b/chromium/chrome/browser/resources/PRESUBMIT.py index 9ebdb0ab8e6..e2a39c26688 100644 --- a/chromium/chrome/browser/resources/PRESUBMIT.py +++ b/chromium/chrome/browser/resources/PRESUBMIT.py @@ -8,8 +8,6 @@ See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for more details about the presubmit API built into depot_tools. """ -import re - ACTION_XML_PATH = '../../../tools/metrics/actions/actions.xml' diff --git a/chromium/chrome/browser/resources/about_voicesearch.html b/chromium/chrome/browser/resources/about_voicesearch.html deleted file mode 100644 index 93a43dc6485..00000000000 --- a/chromium/chrome/browser/resources/about_voicesearch.html +++ /dev/null @@ -1,38 +0,0 @@ -<!doctype html> -<html i18n-values="dir:textdirection;lang:language"> -<head> -<meta charset="utf-8"> -<link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> -<style> -.key { - font-weight: bold; -} - -.value { - margin-left: 15px; -} -</style> -</head> -<body> -<div id="loading-message" i18n-content="loadingMessage">LOADING_MESSAGE</div> -<div id="body-container" hidden> - <div id="header"> - <h1 i18n-content="voiceSearchLongTitle">ABOUT_VOICESEARCH</h1> - </div> - <div id="voice-search-info-template"> - <table cellpadding="2" cellspacing="0" border="0"> - <tr jsselect="voiceSearchInfo"> - <td><span dir="ltr" jscontent="key" class="key">KEY</span></td> - <td><span dir="ltr" jscontent="value" class="value">VALUE</span></td> - </tr> - </table> - </div> -</div> -<script src="chrome://resources/js/load_time_data.js"></script> -<script src="chrome://voicesearch/about_voicesearch.js"></script> -<script src="chrome://voicesearch/strings.js"></script> -<script src="chrome://resources/js/i18n_template.js"></script> -<script src="chrome://resources/js/jstemplate_compiled.js"></script> -<script src="chrome://resources/js/util.js"></script> -</body> -</html> diff --git a/chromium/chrome/browser/resources/about_voicesearch.js b/chromium/chrome/browser/resources/about_voicesearch.js deleted file mode 100644 index bcf8cbd5f7b..00000000000 --- a/chromium/chrome/browser/resources/about_voicesearch.js +++ /dev/null @@ -1,37 +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. - -/** - * Takes the |moduleListData| input argument which represents data about - * the currently available modules and populates the html jstemplate - * with that data. It expects an object structure like the above. - * @param {Object} moduleListData Information about available modules - */ -function renderTemplate(moduleListData) { - var input = new JsEvalContext(moduleListData); - var output = $('voice-search-info-template'); - jstProcess(input, output); -} - -/** - * Asks the C++ VoiceSearchUIDOMHandler to get details about voice search and - * return the data in returnVoiceSearchInfo() (below). - */ -function requestVoiceSearchInfo() { - chrome.send('requestVoiceSearchInfo'); -} - -/** - * Called by the WebUI to re-populate the page with data representing the - * current state of voice search. - * @param {Object} moduleListData Information about available modules. - */ -function returnVoiceSearchInfo(moduleListData) { - $('loading-message').hidden = true; - $('body-container').hidden = false; - renderTemplate(moduleListData); -} - -// Get data and have it displayed upon loading. -document.addEventListener('DOMContentLoaded', requestVoiceSearchInfo); diff --git a/chromium/chrome/browser/resources/app_list/OWNERS b/chromium/chrome/browser/resources/app_list/OWNERS deleted file mode 100644 index ed54f09c40c..00000000000 --- a/chromium/chrome/browser/resources/app_list/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -calamity@chromium.org -khmel@chromium.org diff --git a/chromium/chrome/browser/resources/app_list/start_page.css b/chromium/chrome/browser/resources/app_list/start_page.css deleted file mode 100644 index 3efaef1955a..00000000000 --- a/chromium/chrome/browser/resources/app_list/start_page.css +++ /dev/null @@ -1,32 +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. */ - -html, -body { - height: 100%; - margin: 0; - overflow: hidden; - padding: 0; - user-select: none; - width: 100%; -} - -#doodle { - display: none; - justify-content: center; -} - -#default_logo { - background-image: url(../../../../ui/webui/resources/images/google_logo.svg); - background-repeat: no-repeat; - height: 92px; - margin: auto; - width: 272px; -} - -#logo_container { - bottom: 0; - position: absolute; - width: 100%; -} diff --git a/chromium/chrome/browser/resources/app_list/start_page.html b/chromium/chrome/browser/resources/app_list/start_page.html deleted file mode 100644 index 5388e65d334..00000000000 --- a/chromium/chrome/browser/resources/app_list/start_page.html +++ /dev/null @@ -1,23 +0,0 @@ -<!doctype html> -<html i18n-values="dir:textdirection;lang:language"> -<head> - <meta charset="utf-8"> - <link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> - <link rel="stylesheet" href="chrome://app-list/start_page.css"> - <script src="chrome://resources/js/load_time_data.js"></script> - <script src="chrome://resources/js/cr.js"></script> - <script src="chrome://resources/js/cr/event_target.js"></script> - <script src="chrome://resources/js/cr/ui.js"></script> - <script src="chrome://resources/js/util.js"></script> - <script src="chrome://app-list/strings.js"></script> - <script src="chrome://app-list/start_page.js"></script> - <base id="base"> -</head> - -<body> - <div id="logo_container"> - <div id="default_logo"></div> - </div> - <script src="chrome://resources/js/i18n_template.js"></script> -</body> -</html> diff --git a/chromium/chrome/browser/resources/app_list/start_page.js b/chromium/chrome/browser/resources/app_list/start_page.js deleted file mode 100644 index 2a01e136998..00000000000 --- a/chromium/chrome/browser/resources/app_list/start_page.js +++ /dev/null @@ -1,120 +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. - -/** - * @fileoverview App launcher start page implementation. - */ - -/** - * The maximum height of the Google Doodle. Note this value should be consistent - * with kWebViewHeight in start_page_view.cc. - */ -var doodleMaxHeight = 224; - -cr.define('appList.startPage', function() { - 'use strict'; - - // The element containing the current Google Doodle. - var doodle = null; - - /** - * Initialize the page. - */ - function initialize() { - chrome.send('initialize'); - } - - /** - * Invoked when the app-list bubble is shown. - */ - function onAppListShown() { - chrome.send('appListShown', [this.doodle != null]); - } - - /** - * Sets the doodle's visibility, hiding or showing the default logo. - * - * @param {boolean} visible Whether the doodle should be made visible. - */ - function setDoodleVisible(visible) { - var doodle = $('doodle'); - var defaultLogo = $('default_logo'); - if (visible) { - doodle.style.display = 'flex'; - defaultLogo.style.display = 'none'; - } else { - if (doodle) - doodle.style.display = 'none'; - - defaultLogo.style.display = 'block'; - } - } - - /** - * Invoked when the app-list doodle is updated. - * - * @param {Object} data The data object representing the current doodle. - */ - function onAppListDoodleUpdated(data, base_url) { - if (this.doodle) { - this.doodle.parentNode.removeChild(this.doodle); - this.doodle = null; - } - - var doodleData = data.ddljson; - if (!doodleData || !doodleData.transparent_large_image) { - setDoodleVisible(false); - return; - } - - // Set the page's base URL so that links will resolve relative to the Google - // homepage. - $('base').href = base_url; - - this.doodle = document.createElement('div'); - this.doodle.id = 'doodle'; - this.doodle.style.display = 'none'; - - var doodleImage = document.createElement('img'); - doodleImage.id = 'doodle_image'; - if (doodleData.transparent_large_image.height > doodleMaxHeight) - doodleImage.setAttribute('height', doodleMaxHeight); - if (doodleData.alt_text) { - doodleImage.alt = doodleData.alt_text; - doodleImage.title = doodleData.alt_text; - } - - doodleImage.onload = function() { - setDoodleVisible(true); - }; - doodleImage.src = doodleData.transparent_large_image.url; - - if (doodleData.target_url) { - var doodleLink = document.createElement('a'); - doodleLink.id = 'doodle_link'; - doodleLink.href = doodleData.target_url; - doodleLink.target = '_blank'; - doodleLink.appendChild(doodleImage); - doodleLink.onclick = function() { - chrome.send('doodleClicked'); - return true; - }; - this.doodle.appendChild(doodleLink); - } else { - this.doodle.appendChild(doodleImage); - } - $('logo_container').appendChild(this.doodle); - } - - return { - initialize: initialize, - onAppListDoodleUpdated: onAppListDoodleUpdated, - onAppListShown: onAppListShown, - }; -}); - -document.addEventListener('contextmenu', function(e) { - e.preventDefault(); -}); -document.addEventListener('DOMContentLoaded', appList.startPage.initialize); diff --git a/chromium/chrome/browser/resources/bluetooth_internals/adapter_broker.js b/chromium/chrome/browser/resources/bluetooth_internals/adapter_broker.js index e357336fe7e..d345167a581 100644 --- a/chromium/chrome/browser/resources/bluetooth_internals/adapter_broker.js +++ b/chromium/chrome/browser/resources/bluetooth_internals/adapter_broker.js @@ -111,7 +111,7 @@ cr.define('adapter_broker', function() { /** * The implementation of AdapterClient in - * device/bluetooth/public/interfaces/adapter.mojom. Dispatches events + * device/bluetooth/public/mojom/adapter.mojom. Dispatches events * through AdapterBroker to notify client objects of changes to the Adapter * service. * @constructor diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/BUILD.gn b/chromium/chrome/browser/resources/chromeos/chromevox/BUILD.gn index 98fdb591fca..329a2f37113 100644 --- a/chromium/chrome/browser/resources/chromeos/chromevox/BUILD.gn +++ b/chromium/chrome/browser/resources/chromeos/chromevox/BUILD.gn @@ -126,6 +126,7 @@ chromevox_modules = [ "cvox2/background/automation_util.js", "cvox2/background/background.js", "cvox2/background/base_automation_handler.js", + "cvox2/background/braille_command_data.js", "cvox2/background/braille_command_handler.js", "cvox2/background/chromevox_state.js", "cvox2/background/command_handler.js", diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd index ed7b45e0e20..f47bdfdc16b 100644 --- a/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd +++ b/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd @@ -2783,6 +2783,27 @@ If you're done with the tutorial, use ChromeVox to navigate to the Close button <message desc="Shown to a user when they invoke the read current title command in a context without a title." name="IDS_CHROMEVOX_NO_TITLE"> No title </message> + <message desc="Spoken when a user issues a command when nothing is focused." name="IDS_CHROMEVOX_WARNING_NO_CURRENT_RANGE"> + No focus. Press Ctrl+T to open a new tab. + </message> + <message desc="A hint to the user that the current control is checkable." name="IDS_CHROMEVOX_HINT_CHECKABLE"> + Press Search+Space to toggle. + </message> + <message desc="A hint to the user that the current control is clickable." name="IDS_CHROMEVOX_HINT_CLICKABLE"> + Press Search+Space to activate. + </message> + <message desc="A hint to the user that the current control has a list of auto completions." name="IDS_CHROMEVOX_HINT_AUTOCOMPLETE_LIST"> + Press up or down arrow for auto completions. + </message> + <message desc="A hint to the user that the current control has inline auto completions." name="IDS_CHROMEVOX_HINT_AUTOCOMPLETE_INLINE"> + Type to auto complete. + </message> + <message desc="A hint to the user for interacting with the table control." name="IDS_CHROMEVOX_HINT_TABLE"> + Press Search+Ctrl+Alt with arrows to navigate by cell. + </message> + <message desc="A hint to the user for interacting with the menu control." name="IDS_CHROMEVOX_HINT_MENU"> + Press up or down arrow to navigate; enter to activate. + </message> </messages> </release> </grit> diff --git a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json b/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json index 6a625f766a7..e0f1b720490 100644 --- a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json +++ b/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json @@ -46,6 +46,7 @@ "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" ] }, diff --git a/chromium/chrome/browser/resources/chromeos/login/BUILD.gn b/chromium/chrome/browser/resources/chromeos/login/BUILD.gn deleted file mode 100644 index 8ad38cb8443..00000000000 --- a/chromium/chrome/browser/resources/chromeos/login/BUILD.gn +++ /dev/null @@ -1,30 +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. - -import("//third_party/closure_compiler/compile_js.gni") - -group("closure_compile") { - deps = [ - ":offline_ad_login", - ":oobe_change_picture", - ] -} - -js_binary("offline_ad_login") { - deps = [ - "//ui/webui/resources/js:load_time_data", - ] -} - -js_binary("oobe_change_picture") { - deps = [ - "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_list", - "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_pane", - "//ui/webui/resources/cr_elements/chromeos/cr_picture:cr_picture_types", - "//ui/webui/resources/js:assert", - "//ui/webui/resources/js:i18n_behavior", - "//ui/webui/resources/js:load_time_data", - "//ui/webui/resources/js:util", - ] -} diff --git a/chromium/chrome/browser/resources/chromeos/login/compiled_resources2.gyp b/chromium/chrome/browser/resources/chromeos/login/compiled_resources2.gyp index de8e38ce6b7..dbe606c4978 100644 --- a/chromium/chrome/browser/resources/chromeos/login/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/chromeos/login/compiled_resources2.gyp @@ -4,9 +4,14 @@ { 'targets': [ { + 'target_name': 'oobe_select', + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { 'target_name': 'offline_ad_login', 'dependencies': [ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + ':oobe_select', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, diff --git a/chromium/chrome/browser/resources/chromeos/quick_unlock/compiled_resources2.gyp b/chromium/chrome/browser/resources/chromeos/quick_unlock/compiled_resources2.gyp deleted file mode 100644 index a953cb9433b..00000000000 --- a/chromium/chrome/browser/resources/chromeos/quick_unlock/compiled_resources2.gyp +++ /dev/null @@ -1,25 +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. -{ - 'targets': [ - { - 'target_name': 'md_pin_keyboard', - 'dependencies': [ - '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-button/compiled_resources2.gyp:paper-button-extracted', - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-input/compiled_resources2.gyp:paper-input-extracted', - ], - 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], - }, - { - 'target_name': 'pin_keyboard', - 'dependencies': [ - '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-button/compiled_resources2.gyp:paper-button-extracted', - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-input/compiled_resources2.gyp:paper-input-extracted', - ], - 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], - }, - ], -} diff --git a/chromium/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn b/chromium/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn index 3a2962e1cab..d01029bf728 100644 --- a/chromium/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn +++ b/chromium/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn @@ -32,14 +32,18 @@ run_jsbundler("select_to_speak_copied_files") { "../chromevox/cvox2/background/tree_walker.js", "checked.png", "closure_shim.js", + "earcons/null_selection.ogg", + "node_utils.js", "options.css", "options.html", "paragraph_utils.js", + "rect_utils.js", "select_to_speak.js", "select_to_speak_gdocs_script.js", "select_to_speak_main.js", "select_to_speak_options.js", "unchecked.png", + "word_utils.js", ] rewrite_rules = [ rebase_path(".", root_build_dir) + ":", @@ -146,11 +150,13 @@ js2gtest("select_to_speak_extjs_tests") { test_type = "extension" sources = [ "select_to_speak_keystroke_selection_test.extjs", + "select_to_speak_mouse_selection_test.extjs", ] gen_include_files = [ "../chromevox/testing/callback_helper.js", "mock_tts.js", "select_to_speak_e2e_test_base.js", + "pipe.jpg", ] defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] } diff --git a/chromium/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp b/chromium/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp index ec7035886ae..b7a05836e6f 100644 --- a/chromium/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp @@ -8,24 +8,28 @@ 'dependencies': [ '../chromevox/cvox2/background/constants', '../chromevox/cvox2/background/automation_util', - 'externs', - 'paragraph_utils', - '<(EXTERNS_GYP):accessibility_private', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', - '<(EXTERNS_GYP):command_line_private', - '<(EXTERNS_GYP):metrics_private', + 'externs', + 'rect_utils', + 'paragraph_utils', + 'word_utils', + 'node_utils', + '<(EXTERNS_GYP):accessibility_private', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', + '<(EXTERNS_GYP):clipboard', + '<(EXTERNS_GYP):command_line_private', + '<(EXTERNS_GYP):metrics_private', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { 'target_name': 'select_to_speak_options', 'dependencies': [ - 'externs', - '<(EXTERNS_GYP):accessibility_private', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', - '<(EXTERNS_GYP):metrics_private', + 'externs', + '<(EXTERNS_GYP):accessibility_private', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', + '<(EXTERNS_GYP):metrics_private', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, @@ -34,42 +38,65 @@ 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { + 'target_name': 'node_utils', + 'dependencies': [ + 'externs', + 'rect_utils', + 'paragraph_utils', + '<(EXTERNS_GYP):automation', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'word_utils', + 'dependencies': [ + 'externs', + 'paragraph_utils', + '<(EXTERNS_GYP):automation', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { 'target_name': 'paragraph_utils', 'dependencies': [ - 'externs', - '<(EXTERNS_GYP):accessibility_private', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', + 'externs', + '<(EXTERNS_GYP):accessibility_private', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { + 'target_name': 'rect_utils', + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { 'target_name': '../chromevox/cvox2/background/automation_util', 'dependencies': [ - '../chromevox/cvox2/background/automation_predicate', - '../chromevox/cvox2/background/tree_walker', - '../chromevox/cvox2/background/constants', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', + '../chromevox/cvox2/background/automation_predicate', + '../chromevox/cvox2/background/tree_walker', + '../chromevox/cvox2/background/constants', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { 'target_name': '../chromevox/cvox2/background/tree_walker', 'dependencies': [ - '../chromevox/cvox2/background/automation_predicate', - '../chromevox/cvox2/background/constants', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', + '../chromevox/cvox2/background/automation_predicate', + '../chromevox/cvox2/background/constants', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { 'target_name': '../chromevox/cvox2/background/automation_predicate', 'dependencies': [ - '../chromevox/cvox2/background/constants', - '<(EXTERNS_GYP):automation', - '<(EXTERNS_GYP):chrome_extensions', + '../chromevox/cvox2/background/constants', + '<(EXTERNS_GYP):automation', + '<(EXTERNS_GYP):chrome_extensions', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json index 8feeb9309b5..d4c6c37d202 100644 --- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json +++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json @@ -20,6 +20,7 @@ "alarms", "app.window.alpha", "chrome://resources/", + "commandLinePrivate", "experimental", "storage", "unlimitedStorage", diff --git a/chromium/chrome/browser/resources/components.js b/chromium/chrome/browser/resources/components.js index f1247bbc0b3..8d29201a3d0 100644 --- a/chromium/chrome/browser/resources/components.js +++ b/chromium/chrome/browser/resources/components.js @@ -5,6 +5,13 @@ 'use strict'; /** + * An array of the latest component data including ID, name, status and + * version. This is populated in returnComponentsData() for the convenience of + * tests. + */ +var currentComponentsData = null; + +/** * Takes the |componentsData| input argument which represents data about the * currently installed components and populates the html jstemplate with * that data. It expects an object structure like the above. @@ -32,7 +39,8 @@ function requestComponentsData() { /** * Called by the WebUI to re-populate the page with data representing the - * current state of installed components. + * current state of installed components. The componentsData will also be + * stored in currentComponentsData to be available to JS for testing purposes. * @param {Object} componentsData Detailed info about installed components. The * template expects each component's format to match the following * structure to correctly populate the page: @@ -56,6 +64,10 @@ function returnComponentsData(componentsData) { bodyContainer.style.visibility = 'hidden'; body.className = ''; + // Initialize |currentComponentsData|, which can also be updated in + // onComponentEvent() later. + currentComponentsData = componentsData.components; + renderTemplate(componentsData); // Add handlers to dynamically created HTML elements. @@ -85,12 +97,24 @@ function returnComponentsData(componentsData) { * optional. */ function onComponentEvent(eventArgs) { - if (eventArgs['id']) { - var id = eventArgs['id']; - $('status-' + id).textContent = eventArgs['event']; - } + if (!eventArgs['id']) + return; + + var id = eventArgs['id']; + + var filteredComponents = currentComponentsData.filter(function(entry) { + return entry.id === id; + }); + var component = filteredComponents[0]; + + var status = eventArgs['event']; + $('status-' + id).textContent = status; + component['status'] = status; + if (eventArgs['version']) { - $('version-' + id).textContent = eventArgs['version']; + var version = eventArgs['version']; + $('version-' + id).textContent = version; + component['version'] = version; } } diff --git a/chromium/chrome/browser/resources/cryptotoken/enroller.js b/chromium/chrome/browser/resources/cryptotoken/enroller.js index 9b1349281cd..4bc7bb02d16 100644 --- a/chromium/chrome/browser/resources/cryptotoken/enroller.js +++ b/chromium/chrome/browser/resources/cryptotoken/enroller.js @@ -144,7 +144,7 @@ async function makeCertAndKey(original) { b.addASN1(Tag.SET, (b) => { b.addASN1(Tag.SEQUENCE, (b) => { b.addASN1ObjectIdentifier(commonName); - b.addASN1PrintableString('U2F'); + b.addASN1PrintableString('U2F Issuer'); }); }); }); @@ -160,7 +160,7 @@ async function makeCertAndKey(original) { b.addASN1(Tag.SET, (b) => { b.addASN1(Tag.SEQUENCE, (b) => { b.addASN1ObjectIdentifier(commonName); - b.addASN1PrintableString('U2F'); + b.addASN1PrintableString('U2F Device'); }); }); }); @@ -192,7 +192,21 @@ async function makeCertAndKey(original) { b.addASN1ObjectIdentifier(ecdsaWithSHA256); }); b.addASN1(Tag.BITSTRING, (b) => { // Signature - b.addBytesFromString('\x00'); // (not valid, obviously.) + // This signature is obviously not correct since it's constant and the + // rest of the certificate is not. However, since the issuer certificate + // doesn't exist, there's no way for anyone to check the signature on this + // certificate and thus this sufficies. However, at least fastmail.com + // expects to be able to parse out a valid ECDSA signature and so one is + // provided. + b.addBytes(new Uint8Array([ + 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xc1, 0xa3, 0xa6, 0x8e, 0x2f, + 0x16, 0xa7, 0x21, 0x46, 0x27, 0x05, 0x7f, 0x62, 0xbb, 0x72, 0x8c, + 0x9e, 0x03, 0xe7, 0xa1, 0xba, 0x62, 0xd0, 0x46, 0x52, 0x4e, 0x45, + 0x6d, 0x2c, 0x2f, 0x3f, 0x73, 0x02, 0x20, 0x0b, 0x5f, 0x78, 0xe5, + 0x11, 0xaa, 0x18, 0x12, 0x9f, 0x6f, 0x23, 0x6d, 0x92, 0x13, 0x22, + 0x7d, 0x92, 0xb4, 0xe6, 0x7e, 0xdf, 0x53, 0xe8, 0x16, 0xdf, 0xb0, + 0x5d, 0x9d, 0xc8, 0xb9, 0x0f, 0xde + ])); }); }); return {privateKey: keypair.privateKey, certDER: certBuilder.data}; @@ -207,11 +221,13 @@ const Registration = class { * @param {string} registrationData the registration response message, * base64-encoded. * @param {string} appId the application identifier. - * @param {string=} opt_clientData the client data, base64-encoded. This - * field is not really optional; it is an error if it is empty or missing. + * @param {string} challenge the server-generated challenge parameter. This + * is only used if opt_clientData is null and, in that case, is expected + * to be a webSafeBase64-encoded, 32-byte value. + * @param {string=} opt_clientData the client data, base64-encoded. * @throws {Error} */ - constructor(registrationData, appId, opt_clientData) { + constructor(registrationData, appId, challenge, opt_clientData) { var data = new ByteString(decodeWebSafeBase64ToArray(registrationData)); var magic = data.getBytes(1); if (magic[0] != 5) { @@ -231,12 +247,21 @@ const Registration = class { throw Error('extra trailing bytes'); } + var challengeHash; if (!opt_clientData) { - throw Error('missing client data'); + // U2F_V1 - deprecated + challengeHash = decodeWebSafeBase64ToArray(challenge); + if (challengeHash.length != 32) { + throw Error('bad challenge length for U2F_V1'); + } + } else { + // U2F_V2 + challengeHash = + sha256HashOfString(atob(webSafeBase64ToNormal(opt_clientData))); } + /** @private {string} */ - this.clientData_ = atob(webSafeBase64ToNormal(opt_clientData)); - JSON.parse(this.clientData_); // Just checking. + this.challengeHash_ = challengeHash; /** @private {string} */ this.appId_ = appId; @@ -262,7 +287,7 @@ const Registration = class { var tbs = new ByteBuilder(); tbs.addBytesFromString('\0'); tbs.addBytes(sha256HashOfString(this.appId_)); - tbs.addBytes(sha256HashOfString(this.clientData_)); + tbs.addBytes(this.challengeHash_); tbs.addBytes(this.keyHandle_); tbs.addBytes(this.publicKey_); return tbs.data; @@ -338,10 +363,11 @@ var ConveyancePreference = { */ function conveyancePreference(enrollChallenge) { if (enrollChallenge.hasOwnProperty('attestation') && - enrollChallenge['attestation'] == 'none') { - return ConveyancePreference.NONE; + (enrollChallenge['attestation'] == 'direct' || + enrollChallenge['attestation'] == 'indirect')) { + return ConveyancePreference.DIRECT; } - return ConveyancePreference.DIRECT; + return ConveyancePreference.NONE; } /** @@ -379,7 +405,8 @@ function handleU2fEnrollRequest(messageSender, request, sendResponse) { return registrationData; } - const reg = new Registration(registrationData, appId, opt_clientData); + const reg = new Registration( + registrationData, appId, enrollChallenge['challenge'], opt_clientData); const keypair = await makeCertAndKey(reg.certificate); const signature = await reg.sign(keypair.privateKey); return reg.withReplacement(keypair.certDER, signature); @@ -538,7 +565,7 @@ function isValidEnrollChallengeArray(enrollChallenges, appIdRequired) { } /** - * Finds the enroll challenge of the given version in the enroll challlenge + * Finds the enroll challenge of the given version in the enroll challenge * array. * @param {Array<EnrollChallenge>} enrollChallenges The enroll challenges to * search. diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js b/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js index d0335ec9ddf..4d219debe02 100644 --- a/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js +++ b/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js @@ -151,8 +151,8 @@ Gnubby.prototype.version = function(cb) { cb(-GnubbyDevice.OK, v1.buffer); return; } - if (rc == 0x6700) { - // Wrong length. Try with non-ISO 7816-4-conforming layout defined in + if (rc) { + // Error. Try with non-ISO 7816-4-conforming layout defined in // earlier U2F drafts. apdu = new Uint8Array( [0x00, Gnubby.U2F_VERSION, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); diff --git a/chromium/chrome/browser/resources/cryptotoken/manifest.json b/chromium/chrome/browser/resources/cryptotoken/manifest.json index fecac5426a2..2f86ab26a09 100644 --- a/chromium/chrome/browser/resources/cryptotoken/manifest.json +++ b/chromium/chrome/browser/resources/cryptotoken/manifest.json @@ -24,7 +24,7 @@ ], "externally_connectable": { "matches": [ - "<all_urls>" + "https://*/*" ], "ids": [ "fjajfjhkeibgmiggdfehjplbhmfkialk" diff --git a/chromium/chrome/browser/resources/download_internals/download_internals.html b/chromium/chrome/browser/resources/download_internals/download_internals.html index 49e67cdce47..acb85302d75 100644 --- a/chromium/chrome/browser/resources/download_internals/download_internals.html +++ b/chromium/chrome/browser/resources/download_internals/download_internals.html @@ -21,6 +21,12 @@ </head> <body> <h1>Download Internals</h1> + <h4>Start Download</h4> + <div id="download-service-request-info"> + <input id="download-url" type="url" + placeholder="http://www.example.com"> + <button id="start-download">Download</button> + </div> <h4>Service State</h4> <div> State: <span id="service-state" class="status"></span> diff --git a/chromium/chrome/browser/resources/download_internals/download_internals.js b/chromium/chrome/browser/resources/download_internals/download_internals.js index 62f1f1f0ab8..d46a2106ca7 100644 --- a/chromium/chrome/browser/resources/download_internals/download_internals.js +++ b/chromium/chrome/browser/resources/download_internals/download_internals.js @@ -122,6 +122,10 @@ cr.define('downloadInternals', function() { cr.addWebUIListener('service-download-failed', onServiceDownloadFailed); cr.addWebUIListener('service-request-made', onServiceRequestMade); + $('start-download').onclick = function() { + browserProxy.startDownload($('download-url').value); + }; + // Kick off requests for the current system state. browserProxy.getServiceStatus().then(onServiceStatusChanged); browserProxy.getServiceDownloads().then(onServiceDownloadsAvailable); diff --git a/chromium/chrome/browser/resources/download_internals/download_internals_browser_proxy.js b/chromium/chrome/browser/resources/download_internals/download_internals_browser_proxy.js index 89642e117d5..a79453ac7f3 100644 --- a/chromium/chrome/browser/resources/download_internals/download_internals_browser_proxy.js +++ b/chromium/chrome/browser/resources/download_internals/download_internals_browser_proxy.js @@ -105,6 +105,12 @@ cr.define('downloadInternals', function() { * of downloads is fetched. */ getServiceDownloads() {} + + /** + * Starts a download with the Download Service. + * @param {string} url The download URL. + */ + startDownload(url) {} } /** @@ -120,6 +126,11 @@ cr.define('downloadInternals', function() { getServiceDownloads() { return cr.sendWithPromise('getServiceDownloads'); } + + /** @override */ + startDownload(url) { + return cr.sendWithPromise('startDownload', url); + } } cr.addSingletonGetter(DownloadInternalsBrowserProxyImpl); diff --git a/chromium/chrome/browser/resources/extensions/extensions.js b/chromium/chrome/browser/resources/extensions/extensions.js index 663f8382b3d..4f9d1b2beb6 100644 --- a/chromium/chrome/browser/resources/extensions/extensions.js +++ b/chromium/chrome/browser/resources/extensions/extensions.js @@ -19,10 +19,6 @@ // <include src="chromeos/kiosk_apps.js"> // </if> -// Used for observing function of the backend datasource for this page by -// tests. -var webuiResponded = false; - cr.define('extensions', function() { var ExtensionList = extensions.ExtensionList; @@ -185,7 +181,6 @@ cr.define('extensions', function() { // don't need to display the interstitial spinner. if (!this.hasLoaded_) this.setLoading_(true); - webuiResponded = true; /** @const */ var supervised = profileInfo.isSupervised; diff --git a/chromium/chrome/browser/resources/feedback/js/feedback.js b/chromium/chrome/browser/resources/feedback/js/feedback.js index 909a5e50a68..538d7c825e4 100644 --- a/chromium/chrome/browser/resources/feedback/js/feedback.js +++ b/chromium/chrome/browser/resources/feedback/js/feedback.js @@ -334,6 +334,13 @@ function initialize() { }); chrome.app.window.current().show(); + // Allow feedback to be sent even if the screenshot failed. + if (!screenshotCanvas) { + $('screenshot-checkbox').disabled = true; + $('screenshot-checkbox').checked = false; + return; + } + screenshotCanvas.toBlob(function(blob) { $('screenshot-image').src = URL.createObjectURL(blob); // Only set the alt text when the src url is available, otherwise we'd diff --git a/chromium/chrome/browser/resources/feedback/js/take_screenshot.js b/chromium/chrome/browser/resources/feedback/js/take_screenshot.js index 3f099724440..830fd726004 100644 --- a/chromium/chrome/browser/resources/feedback/js/take_screenshot.js +++ b/chromium/chrome/browser/resources/feedback/js/take_screenshot.js @@ -5,7 +5,7 @@ /** * Function to take the screenshot of the current screen. * @param {function(HTMLCanvasElement)} callback Callback for returning the - * canvas with the screenshot on it. + * canvas with the screenshot. Called with null if the screenshot failed. */ function takeScreenshot(callback) { var screenshotStream = null; @@ -47,5 +47,6 @@ function takeScreenshot(callback) { console.error( 'takeScreenshot failed: ' + err.name + '; ' + err.message + '; ' + err.constraintName); + callback(null); }); } diff --git a/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js b/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js index 670bf7f90af..ba62489f2e2 100644 --- a/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js +++ b/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js @@ -469,7 +469,7 @@ cr.define('cr.login', function() { this.authDomain = extractDomain(msg.url); this.dispatchEvent(new CustomEvent('authPageLoaded', { detail: { - url: url, + url: msg.url, isSAMLPage: this.isSamlPage_, domain: this.authDomain } diff --git a/chromium/chrome/browser/resources/identity_internals.html b/chromium/chrome/browser/resources/identity_internals.html index fcfcc431028..3be44786a73 100644 --- a/chromium/chrome/browser/resources/identity_internals.html +++ b/chromium/chrome/browser/resources/identity_internals.html @@ -1,8 +1,8 @@ <!doctype html> -<html i18n-values="dir:textdirection;lang:language"> +<html dir="$i18n{textdirection}" lang="$i18n{language}"> <head> <meta charset="utf-8"> - <title i18n-content="tokenCacheHeader"></title> + <title>$i18n{tokenCacheHeader}</title> <link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> <link rel="stylesheet" href="identity_internals.css"> <script src="chrome://resources/js/cr.js"></script> @@ -13,7 +13,7 @@ <script src="identity_internals.js"></script> </head> <body> - <h2 class="header" i18n-content="tokenCacheHeader"></h2> + <h2 class="header">$i18n{tokenCacheHeader}</h2> <div id="token-list"></div> <script src="chrome://resources/js/i18n_template.js"></script> </body> diff --git a/chromium/chrome/browser/resources/input_ime/ime_window_close.png b/chromium/chrome/browser/resources/input_ime/ime_window_close.png Binary files differindex 0010118ca99..6b3bd5d836f 100644 --- a/chromium/chrome/browser/resources/input_ime/ime_window_close.png +++ b/chromium/chrome/browser/resources/input_ime/ime_window_close.png diff --git a/chromium/chrome/browser/resources/input_ime/ime_window_close_click.png b/chromium/chrome/browser/resources/input_ime/ime_window_close_click.png Binary files differindex a954503b6d3..60a218cf395 100644 --- a/chromium/chrome/browser/resources/input_ime/ime_window_close_click.png +++ b/chromium/chrome/browser/resources/input_ime/ime_window_close_click.png diff --git a/chromium/chrome/browser/resources/input_ime/ime_window_close_hover.png b/chromium/chrome/browser/resources/input_ime/ime_window_close_hover.png Binary files differindex a954503b6d3..60a218cf395 100644 --- a/chromium/chrome/browser/resources/input_ime/ime_window_close_hover.png +++ b/chromium/chrome/browser/resources/input_ime/ime_window_close_hover.png diff --git a/chromium/chrome/browser/resources/inspect/inspect.css b/chromium/chrome/browser/resources/inspect/inspect.css index 5bd5a239edb..eefce85827b 100644 --- a/chromium/chrome/browser/resources/inspect/inspect.css +++ b/chromium/chrome/browser/resources/inspect/inspect.css @@ -178,6 +178,14 @@ img { margin-left: 6px; } +.browser-fallback-note { + display: flex; + flex-flow: row wrap; + margin-left: 4px; + margin-top: 5px; + min-height: 15px; +} + .used-for-port-forwarding { background-image: url(../../../../ui/webui/resources/images/info.svg); height: 15px; diff --git a/chromium/chrome/browser/resources/inspect/inspect.js b/chromium/chrome/browser/resources/inspect/inspect.js index 64288b83e80..1901d601dd6 100644 --- a/chromium/chrome/browser/resources/inspect/inspect.js +++ b/chromium/chrome/browser/resources/inspect/inspect.js @@ -7,12 +7,17 @@ var MIN_VERSION_TARGET_ID = 26; var MIN_VERSION_NEW_TAB = 29; var MIN_VERSION_TAB_ACTIVATE = 30; var WEBRTC_SERIAL = 'WEBRTC'; +var HOST_CHROME_VERSION; var queryParamsObject = {}; var browserInspector; var browserInspectorTitle; (function() { +var chromeMatch = navigator.userAgent.match(/(?:^|\W)Chrome\/(\S+)/); +if (chromeMatch && chromeMatch.length > 1) + HOST_CHROME_VERSION = chromeMatch[1].split('.').map(s => Number(s) || 0); + var queryParams = window.location.search; if (!queryParams) return; @@ -31,6 +36,21 @@ if ('trace' in queryParamsObject || 'tracing' in queryParamsObject) { } })(); +function isVersionNewerThanHost(version) { + if (!HOST_CHROME_VERSION) + return false; + version = version.split('.').map(s => Number(s) || 0); + for (var i = 0; i < HOST_CHROME_VERSION.length; i++) { + if (i > version.length) + return false; + if (HOST_CHROME_VERSION[i] > version[i]) + return false; + if (HOST_CHROME_VERSION[i] < version[i]) + return true; + } + return false; +} + function sendCommand(command, args) { chrome.send(command, Array.prototype.slice.call(arguments, 1)); } @@ -295,6 +315,8 @@ function populateRemoteTargets(devices) { var majorChromeVersion = browser.adbBrowserChromeVersion; var pageList; var browserSection = $(browser.id); + var browserNeedsFallback = + isVersionNewerThanHost(browser.adbBrowserVersion); if (browserSection) { pageList = browserSection.querySelector('.pages'); } else { @@ -320,6 +342,15 @@ function populateRemoteTargets(devices) { } browserSection.appendChild(browserHeader); + if (browserNeedsFallback) { + var browserFallbackNote = document.createElement('div'); + browserFallbackNote.className = 'browser-fallback-note'; + browserFallbackNote.textContent = + '\u26A0 Remote browser is newer than client browser. ' + + 'Try `inspect fallback` if inspection fails.'; + browserSection.appendChild(browserFallbackNote); + } + if (majorChromeVersion >= MIN_VERSION_NEW_TAB) { var newPage = document.createElement('div'); newPage.className = 'open'; @@ -401,6 +432,12 @@ function populateRemoteTargets(devices) { row, 'close', sendTargetCommand.bind(null, 'close', page), false); } + if (browserNeedsFallback) { + addActionLink( + row, 'inspect fallback', + sendTargetCommand.bind(null, 'inspect-fallback', page), + page.hasNoUniqueId || page.adbAttachedForeign); + } } } updateBrowserVisibility(browserSection); diff --git a/chromium/chrome/browser/resources/local_discovery/local_discovery.html b/chromium/chrome/browser/resources/local_discovery/local_discovery.html index 1ba6126155d..a99ecdcb5c5 100644 --- a/chromium/chrome/browser/resources/local_discovery/local_discovery.html +++ b/chromium/chrome/browser/resources/local_discovery/local_discovery.html @@ -26,6 +26,7 @@ <h1>$i18n{confirmRegistration}</h1> <div class="dialog-contents"> <div id="register-message"> + $i18nRaw{registerPrinterInformationMessage} </div> <div class="button-list"> @@ -38,10 +39,8 @@ </a> </div> <button class="register-cancel">$i18n{cancel}</button> - <button id="register-continue-button"> - $i18n{serviceRegister} - </button> - </div> + <button id="register-continue">$i18n{confirm}</button> + </div> </div> </div> diff --git a/chromium/chrome/browser/resources/local_discovery/local_discovery.js b/chromium/chrome/browser/resources/local_discovery/local_discovery.js index 6906a948908..0b9bc336cc9 100644 --- a/chromium/chrome/browser/resources/local_discovery/local_discovery.js +++ b/chromium/chrome/browser/resources/local_discovery/local_discovery.js @@ -118,6 +118,7 @@ cr.define('local_discovery', function() { deviceContainer: function() { return $('register-device-list'); }, + /** * Register the device. */ @@ -133,11 +134,8 @@ cr.define('local_discovery', function() { */ showRegister: function() { recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CLICKED); - $('register-message').textContent = loadTimeData.getStringF( - isPrinter(this.info.type) ? 'registerPrinterConfirmMessage' : - 'registerDeviceConfirmMessage', - this.info.display_name); - $('register-continue-button').onclick = this.register.bind(this); + $('register-continue').onclick = this.register.bind(this); + showRegisterOverlay(); }, /** @@ -521,7 +519,7 @@ cr.define('local_discovery', function() { isUserLoggedIn || isUserSupervisedOrOffTheRecord; $('register-overlay-login-promo').hidden = isUserLoggedIn || isUserSupervisedOrOffTheRecord; - $('register-continue-button').disabled = + $('register-continue').disabled = !isUserLoggedIn || isUserSupervisedOrOffTheRecord; $('my-devices-container').hidden = userSupervisedOrOffTheRecord; diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp.css b/chromium/chrome/browser/resources/local_ntp/local_ntp.css index 2b38a36fc16..343afa804b9 100644 --- a/chromium/chrome/browser/resources/local_ntp/local_ntp.css +++ b/chromium/chrome/browser/resources/local_ntp/local_ntp.css @@ -92,6 +92,8 @@ button { display: flex; flex-direction: column; height: 100%; + position: relative; + z-index: 1; } #logo, @@ -536,7 +538,6 @@ html[dir=rtl] #attribution, right: 0; top: 0; transition: opacity 130ms; - z-index: 1; } #one-google.hidden { diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js index c4c0bfe2da7..9bdd106296f 100644 --- a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js +++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js @@ -27,35 +27,6 @@ var LOG_TYPE = { /** - * The different sources where an NTP tile's title can originate from. - * Note: Keep in sync with components/ntp_tiles/tile_title_source.h - * @enum {number} - * @const - */ -var TileTitleSource = { - UNKNOWN: 0, - MANIFEST: 1, - META_TAG: 2, - TITLE: 3, - INFERRED: 4 -}; - - -/** - * The different sources that an NTP tile can have. - * Note: Keep in sync with components/ntp_tiles/tile_source.h - * @enum {number} - * @const - */ -var TileSource = { - TOP_SITES: 0, - SUGGESTIONS_SERVICE: 1, - POPULAR: 3, - WHITELIST: 4, -}; - - -/** * The different (visual) types that an NTP tile can have. * Note: Keep in sync with components/ntp_tiles/tile_visual_type.h * @enum {number} @@ -128,9 +99,11 @@ var logEvent = function(eventType) { /** * Log impression of an NTP tile. * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. - * @param {number} tileTitleSource The title's source from TileTitleSource. - * @param {number} tileSource The source from TileSource. - * @param {number} tileType The type from TileVisualType. + * @param {number} tileTitleSource The source of the tile's title as received + * from getMostVisitedItemData. + * @param {number} tileSource The tile's source as received from + * getMostVisitedItemData. + * @param {number} tileType The tile's visual type from TileVisualType. * @param {Date} dataGenerationTime Timestamp representing when the tile was * produced by a ranking algorithm. */ @@ -143,9 +116,11 @@ function logMostVisitedImpression( /** * Log click on an NTP tile. * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. - * @param {number} tileTitleSource The title's source from TileTitleSource. - * @param {number} tileSource The source from TileSource. - * @param {number} tileType The type from TileVisualType. + * @param {number} tileTitleSource The source of the tile's title as received + * from getMostVisitedItemData. + * @param {number} tileSource The tile's source as received from + * getMostVisitedItemData. + * @param {number} tileType The tile's visual type from TileVisualType. * @param {Date} dataGenerationTime Timestamp representing when the tile was * produced by a ranking algorithm. */ diff --git a/chromium/chrome/browser/resources/md_bookmarks/app.html b/chromium/chrome/browser/resources/md_bookmarks/app.html index 64393cde7f8..82c01245dd4 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/app.html +++ b/chromium/chrome/browser/resources/md_bookmarks/app.html @@ -30,6 +30,7 @@ display: flex; flex-direction: row; flex-grow: 1; + overflow: hidden; } #splitter { diff --git a/chromium/chrome/browser/resources/md_bookmarks/bookmarks.html b/chromium/chrome/browser/resources/md_bookmarks/bookmarks.html index 6b15d439bb8..a12bb061aab 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/bookmarks.html +++ b/chromium/chrome/browser/resources/md_bookmarks/bookmarks.html @@ -6,6 +6,11 @@ <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css"> <link rel="stylesheet" href="chrome://resources/css/md_colors.css"> <style> + html { + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; + } + html, body { background: var(--md-background-color); diff --git a/chromium/chrome/browser/resources/md_bookmarks/command_manager.html b/chromium/chrome/browser/resources/md_bookmarks/command_manager.html index 508b0501a43..13b59a66782 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/command_manager.html +++ b/chromium/chrome/browser/resources/md_bookmarks/command_manager.html @@ -2,6 +2,7 @@ <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> <link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/html/cr/ui/command.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html"> <link rel="import" href="chrome://bookmarks/dialog_focus_manager.html"> @@ -11,7 +12,7 @@ <dom-module id="bookmarks-command-manager"> <template> - <style include="shared-style"> + <style include="shared-style paper-button-style"> .label { flex: 1; } @@ -30,7 +31,7 @@ <template is="cr-lazy-render" id="dropdown"> <dialog is="cr-action-menu" on-mousedown="onMenuMousedown_"> <template is="dom-repeat" items="[[menuCommands_]]" as="command"> - <button class="dropdown-item" + <button slot="item" class="dropdown-item" command$="[[command]]" hidden$="[[!isCommandVisible_(command, menuIds_)]]" disabled$="[[!isCommandEnabled_(command, menuIds_)]]" @@ -56,10 +57,10 @@ <div slot="title">$i18n{openDialogTitle}</div> <div slot="body"></div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onOpenCancelTap_"> + <paper-button class="cancel-button" on-click="onOpenCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onOpenConfirmTap_"> + <paper-button class="action-button" on-click="onOpenConfirmTap_"> $i18n{openDialogConfirm} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_bookmarks/command_manager.js b/chromium/chrome/browser/resources/md_bookmarks/command_manager.js index 668a4497522..900da62a5f6 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/command_manager.js +++ b/chromium/chrome/browser/resources/md_bookmarks/command_manager.js @@ -58,21 +58,6 @@ cr.define('bookmarks', function() { }); this.updateFromStore(); - /** @private {function(!Event)} */ - this.boundOnOpenCommandMenu_ = this.onOpenCommandMenu_.bind(this); - document.addEventListener( - 'open-command-menu', this.boundOnOpenCommandMenu_); - - /** @private {function()} */ - this.boundOnCommandUndo_ = () => { - this.handle(Command.UNDO, new Set()); - }; - document.addEventListener('command-undo', this.boundOnCommandUndo_); - - /** @private {function(!Event)} */ - this.boundOnKeydown_ = this.onKeydown_.bind(this); - document.addEventListener('keydown', this.boundOnKeydown_); - /** @private {!Map<Command, cr.ui.KeyboardShortcutList>} */ this.shortcuts_ = new Map(); @@ -92,14 +77,40 @@ cr.define('bookmarks', function() { this.addShortcut_(Command.CUT, 'Ctrl|x', 'Meta|x'); this.addShortcut_(Command.COPY, 'Ctrl|c', 'Meta|c'); this.addShortcut_(Command.PASTE, 'Ctrl|v', 'Meta|v'); + + /** @private {!Map<string, Function>} */ + this.boundListeners_ = new Map(); + + const addDocumentListener = (eventName, handler) => { + assert(!this.boundListeners_.has(eventName)); + const boundListener = handler.bind(this); + this.boundListeners_.set(eventName, boundListener); + document.addEventListener(eventName, boundListener); + }; + addDocumentListener('open-command-menu', this.onOpenCommandMenu_); + addDocumentListener('keydown', this.onKeydown_); + + const addDocumentListenerForCommand = (eventName, command) => { + addDocumentListener(eventName, (e) => { + if (e.path[0].tagName == 'INPUT') + return; + + const items = this.getState().selection.items; + if (this.canExecute(command, items)) + this.handle(command, items); + }); + }; + addDocumentListenerForCommand('command-undo', Command.UNDO); + addDocumentListenerForCommand('cut', Command.CUT); + addDocumentListenerForCommand('copy', Command.COPY); + addDocumentListenerForCommand('paste', Command.PASTE); }, detached: function() { CommandManager.instance_ = null; - document.removeEventListener( - 'open-command-menu', this.boundOnOpenCommandMenu_); - document.removeEventListener('command-undo', this.boundOnCommandUndo_); - document.removeEventListener('keydown', this.boundOnKeydown_); + this.boundListeners_.forEach( + (handler, eventName) => + document.removeEventListener(eventName, handler)); }, /** diff --git a/chromium/chrome/browser/resources/md_bookmarks/edit_dialog.html b/chromium/chrome/browser/resources/md_bookmarks/edit_dialog.html index c9e6ce563d9..2be1feb461a 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/edit_dialog.html +++ b/chromium/chrome/browser/resources/md_bookmarks/edit_dialog.html @@ -2,6 +2,7 @@ <link rel="import" href="chrome://resources/html/assert.html"> <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> @@ -9,7 +10,7 @@ <dom-module id="bookmarks-edit-dialog"> <template> - <style include="cr-shared-style"></style> + <style include="cr-shared-style paper-button-style"></style> <dialog is="cr-dialog" id="dialog"> <div slot="title"> [[getDialogTitle_(isFolder_, isEdit_)]] @@ -30,11 +31,11 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelButtonTap_"> + <paper-button class="cancel-button" on-click="onCancelButtonTap_"> $i18n{cancel} </paper-button> <paper-button id="saveButton" class="action-button" - on-tap="onSaveButtonTap_"> + on-click="onSaveButtonTap_"> $i18n{saveEdit} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_bookmarks/folder_node.html b/chromium/chrome/browser/resources/md_bookmarks/folder_node.html index e8d13978fb4..c1a85652d3b 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/folder_node.html +++ b/chromium/chrome/browser/resources/md_bookmarks/folder_node.html @@ -76,7 +76,7 @@ <div id="container" class="v-centered" - on-tap="selectFolder_" + on-click="selectFolder_" on-dblclick="toggleFolder_" on-contextmenu="onContextMenu_" tabindex$="[[getTabIndex_(selectedFolder_, itemId)]]" @@ -85,7 +85,7 @@ <template is="dom-if" if="[[hasChildFolder_]]"> <button is="paper-icon-button-light" id="arrow" - on-tap="toggleFolder_" + on-click="toggleFolder_" on-mousedown="preventDefault_" tabindex="-1" aria-label$="[[getButtonAriaLabel_(isOpen, item_)]]"> @@ -98,7 +98,7 @@ open$="[[isSelectedFolder_]]" no-children$="[[!hasChildFolder_]]"> </div> - <div class="menu-label elided-text"" title="[[item_.title]]"> + <div class="menu-label elided-text" title="[[item_.title]]"> [[item_.title]] </div> </div> diff --git a/chromium/chrome/browser/resources/md_bookmarks/folder_node.js b/chromium/chrome/browser/resources/md_bookmarks/folder_node.js index 78008a9655f..4b913c3ea09 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/folder_node.js +++ b/chromium/chrome/browser/resources/md_bookmarks/folder_node.js @@ -141,7 +141,7 @@ Polymer({ changeKeyboardSelection_: function(xDirection, yDirection, currentFocus) { let newFocusFolderNode = null; const isChildFolderNodeFocused = - currentFocus.tagName == 'BOOKMARKS-FOLDER-NODE'; + currentFocus && currentFocus.tagName == 'BOOKMARKS-FOLDER-NODE'; if (xDirection == 1) { // The right arrow opens a folder if closed and goes to the first child diff --git a/chromium/chrome/browser/resources/md_bookmarks/list.html b/chromium/chrome/browser/resources/md_bookmarks/list.html index 1927916322b..ddbf28f533c 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/list.html +++ b/chromium/chrome/browser/resources/md_bookmarks/list.html @@ -21,7 +21,7 @@ } #list { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: #fff; margin: 0 auto; max-width: var(--card-max-width); diff --git a/chromium/chrome/browser/resources/md_bookmarks/list.js b/chromium/chrome/browser/resources/md_bookmarks/list.js index 3b6aa847a67..11b1580b221 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/list.js +++ b/chromium/chrome/browser/resources/md_bookmarks/list.js @@ -142,7 +142,13 @@ Polymer({ /** @private */ emptyListMessage_: function() { - const emptyListMessage = this.searchTerm_ ? 'noSearchResults' : 'emptyList'; + let emptyListMessage = 'noSearchResults'; + if (!this.searchTerm_) { + emptyListMessage = bookmarks.util.canReorderChildren( + this.getState(), this.getState().selectedFolder) ? + 'emptyList' : + 'emptyUnmodifiableList'; + } return loadTimeData.getString(emptyListMessage); }, diff --git a/chromium/chrome/browser/resources/md_bookmarks/toast_manager.html b/chromium/chrome/browser/resources/md_bookmarks/toast_manager.html index f94f6342f80..454d1f1ab70 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/toast_manager.html +++ b/chromium/chrome/browser/resources/md_bookmarks/toast_manager.html @@ -2,11 +2,12 @@ <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/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://bookmarks/shared_style.html"> <dom-module id="bookmarks-toast-manager"> <template> - <style include="shared-style"> + <style include="shared-style paper-button-style"> #content { color: #fff; display: flex; @@ -17,9 +18,7 @@ -webkit-margin-end: 0; -webkit-margin-start: 32px; color: var(--google-blue-300); - font-weight: 500; height: 32px; - min-width: 52px; padding: 8px; } @@ -34,7 +33,7 @@ </style> <cr-toast id="toast" duration="[[duration]]"> <div id="content" class="elided-text"></div> - <paper-button id="button" hidden$="[[!showUndo_]]" on-tap="onUndoTap_"> + <paper-button id="button" hidden$="[[!showUndo_]]" on-click="onUndoTap_"> $i18n{undo} </paper-button> </cr-toast> diff --git a/chromium/chrome/browser/resources/md_bookmarks/toolbar.html b/chromium/chrome/browser/resources/md_bookmarks/toolbar.html index 219c383cd75..4f8b2786f45 100644 --- a/chromium/chrome/browser/resources/md_bookmarks/toolbar.html +++ b/chromium/chrome/browser/resources/md_bookmarks/toolbar.html @@ -52,7 +52,7 @@ id="menuButton" class="more-actions more-vert-button" title="$i18n{organizeButtonTitle}" - on-tap="onMenuButtonOpenTap_" + on-click="onMenuButtonOpenTap_" aria-haspopup="menu"> <div></div> <div></div> diff --git a/chromium/chrome/browser/resources/md_downloads/1x/incognito_marker.png b/chromium/chrome/browser/resources/md_downloads/1x/incognito_marker.png Binary files differindex 2668850b677..4d7b4f0457a 100644 --- a/chromium/chrome/browser/resources/md_downloads/1x/incognito_marker.png +++ b/chromium/chrome/browser/resources/md_downloads/1x/incognito_marker.png diff --git a/chromium/chrome/browser/resources/md_downloads/1x/no_downloads.png b/chromium/chrome/browser/resources/md_downloads/1x/no_downloads.png Binary files differindex e445faebe3b..9cb8a7cab08 100644 --- a/chromium/chrome/browser/resources/md_downloads/1x/no_downloads.png +++ b/chromium/chrome/browser/resources/md_downloads/1x/no_downloads.png diff --git a/chromium/chrome/browser/resources/md_downloads/2x/incognito_marker.png b/chromium/chrome/browser/resources/md_downloads/2x/incognito_marker.png Binary files differindex 3ee7c32a700..af60569dd0d 100644 --- a/chromium/chrome/browser/resources/md_downloads/2x/incognito_marker.png +++ b/chromium/chrome/browser/resources/md_downloads/2x/incognito_marker.png diff --git a/chromium/chrome/browser/resources/md_downloads/2x/no_downloads.png b/chromium/chrome/browser/resources/md_downloads/2x/no_downloads.png Binary files differindex 90edf87a248..6181df50770 100644 --- a/chromium/chrome/browser/resources/md_downloads/2x/no_downloads.png +++ b/chromium/chrome/browser/resources/md_downloads/2x/no_downloads.png diff --git a/chromium/chrome/browser/resources/md_downloads/compiled_resources2.gyp b/chromium/chrome/browser/resources/md_downloads/compiled_resources2.gyp index b9e2ca905c3..4adfce2fe86 100644 --- a/chromium/chrome/browser/resources/md_downloads/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/md_downloads/compiled_resources2.gyp @@ -75,7 +75,6 @@ '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu', '<(DEPTH)/ui/webui/resources/cr_elements/cr_toolbar/compiled_resources2.gyp:cr_toolbar', '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-a11y-announcer/compiled_resources2.gyp:iron-a11y-announcer-extracted', - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-menu/compiled_resources2.gyp:paper-menu-extracted', 'browser_proxy', 'search_service', ], diff --git a/chromium/chrome/browser/resources/md_downloads/downloads.html b/chromium/chrome/browser/resources/md_downloads/downloads.html index bc126bb68c6..07eaf62e051 100644 --- a/chromium/chrome/browser/resources/md_downloads/downloads.html +++ b/chromium/chrome/browser/resources/md_downloads/downloads.html @@ -9,6 +9,9 @@ --downloads-card-margin: 24px; --downloads-card-width: 680px; background: #f1f1f1; + + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; } .loading { diff --git a/chromium/chrome/browser/resources/md_downloads/item.html b/chromium/chrome/browser/resources/md_downloads/item.html index a230d16a3d3..3696956b9a7 100644 --- a/chromium/chrome/browser/resources/md_downloads/item.html +++ b/chromium/chrome/browser/resources/md_downloads/item.html @@ -54,7 +54,7 @@ } #content.is-active { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; } #content:not(.is-active) { @@ -244,7 +244,7 @@ <div id="title-area"><!-- Can't have any line breaks. --><a is="action-link" id="file-link" href="[[data.url]]" - on-tap="onFileLinkTap_" + on-click="onFileLinkTap_" hidden="[[!completelyOnDisk_]]">[[data.file_name]]</a><!-- Before #name. --><span id="name" @@ -263,20 +263,20 @@ </template> <div id="safe" class="controls" hidden="[[isDangerous_]]"> - <a is="action-link" id="show" on-tap="onShowTap_" + <a is="action-link" id="show" on-click="onShowTap_" hidden="[[!completelyOnDisk_]]">$i18n{controlShowInFolder}</a> <template is="dom-if" if="[[data.retry]]"> - <paper-button id="retry" on-tap="onRetryTap_"> + <paper-button id="retry" on-click="onRetryTap_"> $i18n{controlRetry} </paper-button> </template> <template is="dom-if" if="[[pauseOrResumeText_]]"> - <paper-button id="pause-or-resume" on-tap="onPauseOrResumeTap_"> + <paper-button id="pause-or-resume" on-click="onPauseOrResumeTap_"> [[pauseOrResumeText_]] </paper-button> </template> <template is="dom-if" if="[[showCancel_]]"> - <paper-button id="cancel" on-tap="onCancelTap_"> + <paper-button id="cancel" on-click="onCancelTap_"> $i18n{controlCancel} </paper-button> </template> @@ -287,17 +287,17 @@ <div id="dangerous" class="controls"> <!-- Dangerous file types (e.g. .exe, .jar). --> <template is="dom-if" if="[[!isMalware_]]"> - <paper-button id="discard" on-tap="onDiscardDangerousTap_" + <paper-button id="discard" on-click="onDiscardDangerousTap_" class="discard">$i18n{dangerDiscard}</paper-button> - <paper-button id="save" on-tap="onSaveDangerousTap_" + <paper-button id="save" on-click="onSaveDangerousTap_" class="keep">$i18n{dangerSave}</paper-button> </template> <!-- Things that safe browsing has determined to be dangerous. --> <template is="dom-if" if="[[isMalware_]]"> - <paper-button id="danger-remove" on-tap="onDiscardDangerousTap_" + <paper-button id="danger-remove" on-click="onDiscardDangerousTap_" class="discard">$i18n{controlRemoveFromList}</paper-button> - <paper-button id="restore" on-tap="onSaveDangerousTap_" + <paper-button id="restore" on-click="onSaveDangerousTap_" class="keep">$i18n{dangerRestore}</paper-button> </template> </div> @@ -307,8 +307,9 @@ <div id="remove-wrapper" class="icon-wrapper"> <button is="paper-icon-button-light" id="remove" title="$i18n{controlRemoveFromList}" + aria-label="$i18n{controlRemoveFromList}" style$="[[computeRemoveStyle_(isDangerous_, showCancel_)]]" - on-tap="onRemoveTap_">✕</button> + on-click="onRemoveTap_">✕</button> </div> <div id="incognito" title="$i18n{inIncognito}" hidden="[[!data.otr]]"> diff --git a/chromium/chrome/browser/resources/md_downloads/manager.html b/chromium/chrome/browser/resources/md_downloads/manager.html index c8c68366be5..3662b4d2e1a 100644 --- a/chromium/chrome/browser/resources/md_downloads/manager.html +++ b/chromium/chrome/browser/resources/md_downloads/manager.html @@ -23,6 +23,7 @@ flex: 1 0; flex-direction: column; height: 100%; + overflow: hidden; z-index: 0; } @@ -39,7 +40,7 @@ } #drop-shadow { - @apply(--cr-container-shadow); + @apply --cr-container-shadow; } :host([has-shadow_]) #drop-shadow { diff --git a/chromium/chrome/browser/resources/md_downloads/toolbar.html b/chromium/chrome/browser/resources/md_downloads/toolbar.html index 24209d11177..a98f6b02ca5 100644 --- a/chromium/chrome/browser/resources/md_downloads/toolbar.html +++ b/chromium/chrome/browser/resources/md_downloads/toolbar.html @@ -41,15 +41,17 @@ spinner-active="{{spinnerActive}}" on-search-changed="onSearchChanged_"> <button is="paper-icon-button-light" id="moreActions" title="$i18n{moreActions}" class="dropdown-trigger" - on-tap="onMoreActionsTap_"> + on-click="onMoreActionsTap_"> <iron-icon icon="cr:more-vert"></iron-icon> </button> </cr-toolbar> <dialog is="cr-action-menu" id="moreActionsMenu"> - <button class="dropdown-item clear-all" on-tap="onClearAllTap_"> + <button slot="item" class="dropdown-item clear-all" + on-click="onClearAllTap_"> $i18n{clearAll} - </div> - <button class="dropdown-item" on-tap="onOpenDownloadsFolderTap_"> + </button> + <button slot="item" class="dropdown-item" + on-click="onOpenDownloadsFolderTap_"> $i18n{openDownloadsFolder} </button> </dialog> diff --git a/chromium/chrome/browser/resources/md_extensions/code_section.html b/chromium/chrome/browser/resources/md_extensions/code_section.html index 093e9a62042..5d575bae3f5 100644 --- a/chromium/chrome/browser/resources/md_extensions/code_section.html +++ b/chromium/chrome/browser/resources/md_extensions/code_section.html @@ -2,6 +2,7 @@ <link rel="import" href="chrome://resources/cr_elements/hidden_style_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/html/load_time_data.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> @@ -54,10 +55,16 @@ .more-code { color: var(--paper-grey-500); } + + #highlight-description { + height: 0; + overflow: hidden; + } </style> - <div id="scroll-container" hidden="[[!codeText_]]"> + <div id="scroll-container" hidden="[[!highlighted_]]"> <div id="main"> - <div id="line-numbers"> + <!-- Line numbers are not useful to a screenreader --> + <div id="line-numbers" aria-hidden="true"> <div class="more-code before" hidden="[[!truncatedBefore_]]"> ... </div> @@ -73,7 +80,14 @@ '$i18nPolymer{errorLinesNotShownSingular}', '$i18nPolymer{errorLinesNotShownPlural}')]] </div> - <span>[[codeText_]]</span> + <span><!-- Whitespace is preserved in this span. Ignore new lines. + --><span>[[before_]]</span><!-- + --><mark aria-label$="[[highlighted_]]" + aria-describedby="highlight-description"><!-- + --><span aria-hidden="true">[[highlighted_]]</span><!-- + --></mark><!-- + --><span>[[after_]]</span><!-- + --></span> <div class="more-code after" hidden="[[!truncatedAfter_]]"> [[getLinesNotShownLabel_( truncatedAfter_, @@ -83,7 +97,10 @@ </div> </div> </div> - <div id="no-code" hidden="[[codeText_]]">[[couldNotDisplayCode]]</div> + <div id="no-code" hidden="[[highlighted_]]">[[couldNotDisplayCode]]</div> + <div id="highlight-description" aria-hidden="true"> + [[highlightDescription_]] + </div> </template> <script src="code_section.js"></script> </dom-module> diff --git a/chromium/chrome/browser/resources/md_extensions/code_section.js b/chromium/chrome/browser/resources/md_extensions/code_section.js index 149d5d7d054..001dad3a045 100644 --- a/chromium/chrome/browser/resources/md_extensions/code_section.js +++ b/chromium/chrome/browser/resources/md_extensions/code_section.js @@ -21,6 +21,8 @@ cr.define('extensions', function() { const CodeSection = Polymer({ is: 'extensions-code-section', + behaviors: [I18nBehavior], + properties: { /** * The code this object is displaying. @@ -31,13 +33,17 @@ cr.define('extensions', function() { value: null, }, - /** - * The text of the entire source file. This value does not update on - * highlight changes; it only updates if the content of the source - * changes. - * @private - */ - codeText_: String, + /** @private Highlighted code. */ + highlighted_: String, + + /** @private Code before the highlighted section. */ + before_: String, + + /** @private Code after the highlighted section. */ + after_: String, + + /** @private Description for the highlighted section. */ + highlightDescription_: String, /** @private */ lineNumbers_: String, @@ -67,7 +73,10 @@ cr.define('extensions', function() { if (!this.code || (!this.code.beforeHighlight && !this.code.highlight && !this.code.afterHighlight)) { - this.codeText_ = ''; + this.highlighted_ = ''; + this.highlightDescription_ = ''; + this.before_ = ''; + this.after_ = ''; this.lineNumbers_ = ''; return; } @@ -91,15 +100,19 @@ cr.define('extensions', function() { if (visibleAfter.charAt(visibleAfter.length - 1) == '\n') visibleAfter += ' '; - this.codeText_ = visibleBefore + highlight + visibleAfter; + this.highlighted_ = highlight; + this.highlightDescription_ = this.getAccessibilityHighlightDescription_( + linesBefore.length, highlight.split('\n').length); + this.before_ = visibleBefore; + this.after_ = visibleAfter; this.truncatedBefore_ = linesBefore.length - visibleLineCountBefore; this.truncatedAfter_ = linesAfter.length - visibleLineCountAfter; + let visibleCode = visibleBefore + highlight + visibleAfter; + this.setLineNumbers_( this.truncatedBefore_ + 1, - this.truncatedBefore_ + this.codeText_.split('\n').length); - this.createHighlight_( - visibleBefore.length, visibleBefore.length + highlight.length); + this.truncatedBefore_ + visibleCode.split('\n').length); this.scrollToHighlight_(visibleLineCountBefore); }, @@ -130,23 +143,6 @@ cr.define('extensions', function() { }, /** - * Uses the native text-selection API to highlight desired code. - * @param {number} start - * @param {number} end - * @private - */ - createHighlight_: function(start, end) { - const range = document.createRange(); - const node = this.$.source.querySelector('span').firstChild; - range.setStart(node, start); - range.setEnd(node, end); - - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - }, - - /** * @param {number} linesBeforeHighlight * @private */ @@ -161,6 +157,22 @@ cr.define('extensions', function() { this.$['scroll-container'].scrollTo({top: targetTop}); }, + + /** + * @param {number} lineStart + * @param {number} numLines + * @return {string} + * @private + */ + getAccessibilityHighlightDescription_: function(lineStart, numLines) { + if (numLines > 1) { + return this.i18n( + 'accessibilityErrorMultiLine', lineStart.toString(), + (lineStart + numLines - 1).toString()); + } else { + return this.i18n('accessibilityErrorLine', lineStart.toString()); + } + }, }); return {CodeSection: CodeSection}; diff --git a/chromium/chrome/browser/resources/md_extensions/compiled_resources2.gyp b/chromium/chrome/browser/resources/md_extensions/compiled_resources2.gyp index 74850796458..c4c7924b659 100644 --- a/chromium/chrome/browser/resources/md_extensions/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/md_extensions/compiled_resources2.gyp @@ -7,6 +7,7 @@ 'target_name': 'code_section', 'dependencies': [ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', '<(EXTERNS_GYP):developer_private', ], @@ -50,7 +51,6 @@ { 'target_name': 'error_page', 'dependencies': [ - '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-menu/compiled_resources2.gyp:paper-menu-extracted', '<(DEPTH)/ui/webui/resources/cr_elements/compiled_resources2.gyp:cr_container_shadow_behavior', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', '<(DEPTH)/ui/webui/resources/js/cr/ui/compiled_resources2.gyp:focus_outline_manager', diff --git a/chromium/chrome/browser/resources/md_extensions/detail_view.html b/chromium/chrome/browser/resources/md_extensions/detail_view.html index a111266af18..f730ba9363d 100644 --- a/chromium/chrome/browser/resources/md_extensions/detail_view.html +++ b/chromium/chrome/browser/resources/md_extensions/detail_view.html @@ -55,7 +55,7 @@ } #main { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: white; margin: auto; min-height: 100%; @@ -80,7 +80,7 @@ #name { flex-grow: 1; - @apply(--cr-title-text); + @apply --cr-title-text; } #learn-more-link { @@ -93,7 +93,7 @@ } .section { - @apply(--cr-section); + @apply --cr-section; } .section.block { @@ -144,6 +144,10 @@ width: 78px; } + #reload-button { + color: var(--google-blue-500); + } + .warning div { display: flex; } @@ -155,9 +159,20 @@ .warning-icon { --iron-icon-fill-color: var(--paper-red-700); - --iron-icon-height: 19px; - --iron-icon-width: 19px; - margin-right: 16px; + -webkit-margin-end: 16px; + height: 19px; + width: 19px; + } + + #error-icon { + --iron-icon-fill-color: var(--google-red-700); + -webkit-margin-end: 4px; + height: 18px; + width: 18px; + } + + #runtime-warnings { + color: var(--google-red-700); } ul { @@ -187,7 +202,7 @@ } paper-spinner-lite { - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; } </style> <div id="container"> @@ -195,7 +210,7 @@ <div id="top-bar"> <button id="close-button" is="paper-icon-button-light" aria-label="$i18n{back}" class="icon-arrow-back no-overlap" - on-tap="onCloseButtonTap_"></button> + on-click="onCloseButtonTap_"></button> <img id="icon" src="[[data.iconUrl]]" alt$="[[appOrExtension( data.type, @@ -227,6 +242,19 @@ </div> </div> <div id="warnings" hidden$="[[!hasWarnings_(data.*)]]"> + <div id="runtime-warnings" aria-describedby="a11yAssociation" + hidden$="[[!data.runtimeWarnings.length]]" + class="section continuation control-line"> + <div> + <iron-icon id="error-icon" icon="error"></iron-icon> + <template is="dom-repeat" items="[[data.runtimeWarnings]]"> + [[item]] + </template> + </div> + <paper-button id="reload-button" on-click="onReloadTap_"> + $i18n{itemReload} + </paper-button> + </div> <div class="section continuation warning" id="suspicious-warning" hidden$="[[!data.disableReasons.suspiciousInstall]]"> <div> @@ -247,7 +275,7 @@ <span>$i18n{itemCorruptInstall}</span> </div> <paper-button id="repair-button" class="action-button" - on-tap="onRepairTap_"> + on-click="onRepairTap_"> $i18n{itemRepair} </paper-button> </div> @@ -299,7 +327,7 @@ <template is="dom-repeat" items="[[data.views]]"> <li> <a is="action-link" class="inspectable-view" - on-tap="onInspectTap_"> + on-click="onInspectTap_"> [[computeInspectLabel_(item)]] </a> </li> @@ -382,15 +410,15 @@ disabled="[[!isEnabled_(data.state)]]" hidden="[[!shouldShowOptionsLink_(data.*)]]" icon-class="icon-external" label="$i18n{itemOptions}" - on-tap="onExtensionOptionsTap_"> + on-click="onExtensionOptionsTap_"> </button> <button class="hr" hidden="[[!data.manifestHomePageUrl.length]]" is="cr-link-row" icon-class="icon-external" id="extensionWebsite" - label="$i18n{extensionWebsite}" on-tap="onExtensionWebSiteTap_"> + label="$i18n{extensionWebsite}" on-click="onExtensionWebSiteTap_"> </button> <button class="hr" hidden="[[!data.webStoreUrl.length]]" is="cr-link-row" icon-class="icon-external" id="viewInStore" - label="$i18n{viewInStore}" on-tap="onViewInStoreTap_"> + label="$i18n{viewInStore}" on-click="onViewInStoreTap_"> </button> <div class="section block"> <div class="section-title">$i18n{itemSource}</div> @@ -400,7 +428,7 @@ <div id="load-path" class="section-content" hidden$="[[!data.prettifiedPath]]"> <span>$i18n{itemExtensionPath}</span> - <a is="action-link" on-tap="onLoadPathTap_"> + <a is="action-link" on-click="onLoadPathTap_"> [[data.prettifiedPath]] </a> </div> @@ -408,7 +436,7 @@ <button class="hr" is="cr-link-row" hidden="[[isControlled_(data.controlledInfo)]]" icon-class="subpage-arrow" id="remove-extension" - label="$i18n{itemRemoveExtension}" on-tap="onRemoveTap_"> + label="$i18n{itemRemoveExtension}" on-click="onRemoveTap_"> </button> </div> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/detail_view.js b/chromium/chrome/browser/resources/md_extensions/detail_view.js index 6115bc07b77..364eb45cf4a 100644 --- a/chromium/chrome/browser/resources/md_extensions/detail_view.js +++ b/chromium/chrome/browser/resources/md_extensions/detail_view.js @@ -101,7 +101,8 @@ cr.define('extensions', function() { hasWarnings_: function() { return this.data.disableReasons.corruptInstall || this.data.disableReasons.suspiciousInstall || - this.data.disableReasons.updateRequired || !!this.data.blacklistText; + this.data.disableReasons.updateRequired || + !!this.data.blacklistText || this.data.runtimeWarnings.length > 0; }, /** @@ -179,6 +180,13 @@ cr.define('extensions', function() { }, /** @private */ + onReloadTap_: function() { + this.delegate.reloadItem(this.data.id).catch(loadError => { + this.fire('load-error', loadError); + }); + }, + + /** @private */ onRemoveTap_: function() { this.delegate.deleteItem(this.data.id); }, diff --git a/chromium/chrome/browser/resources/md_extensions/error_page.html b/chromium/chrome/browser/resources/md_extensions/error_page.html index 2cae6446d16..e872d27f2a3 100644 --- a/chromium/chrome/browser/resources/md_extensions/error_page.html +++ b/chromium/chrome/browser/resources/md_extensions/error_page.html @@ -31,7 +31,7 @@ iron-icon { --iron-icon-fill-color: var(--paper-grey-500); - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; flex-shrink: 0; } @@ -47,7 +47,7 @@ * detail_view.html and error_page.html. Refactor such that no duplication * happens.*/ #main { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: white; margin: auto; min-height: 100%; @@ -64,7 +64,7 @@ height: 40px; margin-bottom: 30px; padding: 8px 12px 0; - @apply(--cr-title-text); + @apply --cr-title-text; } #heading span { @@ -77,7 +77,7 @@ } .error-item { - @apply(--cr-section); + @apply --cr-section; padding-left: 0; } @@ -108,7 +108,7 @@ } .details-heading { - @apply(--cr-title-text); + @apply --cr-title-text; align-items: center; display: flex; height: var(--cr-section-min-height); @@ -177,10 +177,10 @@ <div id="heading"> <button id="close-button" is="paper-icon-button-light" aria-label="$i18n{back}" - class="icon-arrow-back no-overlap" on-tap="onCloseButtonTap_"> + class="icon-arrow-back no-overlap" on-click="onCloseButtonTap_"> </button> <span>$i18n{errorsPageHeading}</span> - <paper-button on-tap="onClearAllTap_" hidden="[[!entries_.length]]"> + <paper-button on-click="onClearAllTap_" hidden="[[!entries_.length]]"> $i18n{clearAll} </paper-button> </div> @@ -190,7 +190,7 @@ <div class="item-container"> <div class$="error-item [[computeErrorClass_(item, selectedEntry_)]]"> - <div actionable class=" start" on-tap="onErrorItemAction_" + <div actionable class=" start" on-click="onErrorItemAction_" on-keydown="onErrorItemAction_" tabindex="0" role="button"> <iron-icon icon$="[[computeErrorIcon_(item)]]" @@ -204,7 +204,7 @@ </div> <div class="separator"></div> <button is="paper-icon-button-light" class="icon-delete-gray" - on-tap="onDeleteErrorAction_" + on-click="onDeleteErrorAction_" aria-describedby$="[[item.id]]" aria-label="$i18n{clearEntry}" on-keydown="onDeleteErrorAction_"> @@ -226,7 +226,7 @@ </div> <ul class="stack-trace-container"> <template is="dom-repeat" items="[[item.stackTrace]]"> - <li on-tap="onStackFrameTap_" + <li on-click="onStackFrameTap_" hidden="[[!shouldDisplayFrame_(item.url)]]" class$="[[getStackFrameClass_(item, selectedStackFrame_)]]"> @@ -244,7 +244,7 @@ <paper-button class="devtool-button action-button" hidden$="[[!computeIsRuntimeError_(item)]]" disabled="[[!item.canInspect]]" - on-tap="onDevToolButtonTap_"> + on-click="onDevToolButtonTap_"> $i18n{openInDevtool} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/extensions.html b/chromium/chrome/browser/resources/md_extensions/extensions.html index 5b728b9ca9d..5b851ed3990 100644 --- a/chromium/chrome/browser/resources/md_extensions/extensions.html +++ b/chromium/chrome/browser/resources/md_extensions/extensions.html @@ -12,6 +12,9 @@ /* --md-background-color in disguise. Not using the var for increased * performance. */ background-color: #f1f1f1; + + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; } .loading { diff --git a/chromium/chrome/browser/resources/md_extensions/icons.html b/chromium/chrome/browser/resources/md_extensions/icons.html index 34714cc4e7b..f05904be4f3 100644 --- a/chromium/chrome/browser/resources/md_extensions/icons.html +++ b/chromium/chrome/browser/resources/md_extensions/icons.html @@ -57,4 +57,4 @@ </g> </defs> </svg> -</iron-icon-set> +</iron-iconset-svg> diff --git a/chromium/chrome/browser/resources/md_extensions/install_warnings_dialog.html b/chromium/chrome/browser/resources/md_extensions/install_warnings_dialog.html index d50ce39ff17..286b25e54d6 100644 --- a/chromium/chrome/browser/resources/md_extensions/install_warnings_dialog.html +++ b/chromium/chrome/browser/resources/md_extensions/install_warnings_dialog.html @@ -30,7 +30,7 @@ </ul> </div> <div slot="button-container"> - <paper-button class="action-button" on-tap="onOkTap_"> + <paper-button class="action-button" on-click="onOkTap_"> $i18n{ok} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/item.html b/chromium/chrome/browser/resources/md_extensions/item.html index f9f2d770906..f2fe3442f46 100644 --- a/chromium/chrome/browser/resources/md_extensions/item.html +++ b/chromium/chrome/browser/resources/md_extensions/item.html @@ -60,7 +60,7 @@ } #card { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background: white; border-radius: 2px; display: flex; @@ -90,7 +90,7 @@ } #name-and-version { - @apply(--cr-primary-text); + @apply --cr-primary-text; margin-bottom: 4px; } @@ -108,6 +108,13 @@ margin-bottom: 8px; } + #error-icon { + --iron-icon-fill-color: var(--google-red-700); + -webkit-margin-end: 4px; + height: 18px; + width: 18px; + } + #description, #version, #extension-id, @@ -177,7 +184,7 @@ paper-tooltip { --paper-tooltip: { - @apply(--cr-tooltip); + @apply --cr-tooltip; min-width: 0; }; } @@ -255,28 +262,29 @@ [[data.description]] </div> <template is="dom-if" if="[[hasWarnings_(data.*)]]"> - <div id="warnings" > - <div id="runtime-warnings" aria-describedby="a11yAssociation" + <div id="warnings"> + <iron-icon id="error-icon" icon="error"></iron-icon> + <span id="runtime-warnings" aria-describedby="a11yAssociation" hidden$="[[!data.runtimeWarnings.length]]"> <template is="dom-repeat" items="[[data.runtimeWarnings]]"> [[item]] </template> - </div> - <div id="suspicious-warning" aria-describedby="a11yAssociation" + </span> + <span id="suspicious-warning" aria-describedby="a11yAssociation" hidden$="[[!data.disableReasons.suspiciousInstall]]"> $i18n{itemSuspiciousInstall} <a target="_blank" id="learn-more-link" href="$i18n{suspiciousInstallHelpUrl}"> $i18n{learnMore} </a> - </div> - <div id="corrupted-warning" aria-describedby="a11yAssociation" + </span> + <span id="corrupted-warning" aria-describedby="a11yAssociation" hidden$="[[!data.disableReasons.corruptInstall]]"> $i18n{itemCorruptInstall} - </div> - <div id="blacklisted-warning"><!-- No whitespace + </span> + <span id="blacklisted-warning"><!-- No whitespace -->[[data.blacklistText]]<!-- so we can use :empty in css. - --></div> + --></span> </div> </template> <template is="dom-if" if="[[inDevMode]]"> @@ -292,12 +300,12 @@ </span> <a class="clippable-flex-text" is="action-link" title="[[computeFirstInspectTitle_(data.views)]]" - on-tap="onInspectTap_"> + on-click="onInspectTap_"> [[computeFirstInspectLabel_(data.views)]] </a> <a is="action-link" hidden$="[[computeExtraViewsHidden_(data.views)]]" - on-tap="onExtraInspectTap_"> + on-click="onExtraInspectTap_"> [[computeExtraInspectLabel_(data.views)]] </a> </div> @@ -308,17 +316,17 @@ </div> <div id="button-strip" class="layout horizontal center"> <div class="layout flex horizontal center"> - <paper-button id="details-button" on-tap="onDetailsTap_" + <paper-button id="details-button" on-click="onDetailsTap_" aria-describedby="a11yAssociation"> $i18n{itemDetails} </paper-button> - <paper-button id="remove-button" on-tap="onRemoveTap_" + <paper-button id="remove-button" on-click="onRemoveTap_" aria-describedby="a11yAssociation" hidden="[[isControlled_(data.controlledInfo)]]"> $i18n{itemRemove} </paper-button> <template is="dom-if" if="[[shouldShowErrorsButton_(data.*)]]"> - <paper-button id="errors-button" on-tap="onErrorsTap_" + <paper-button id="errors-button" on-click="onErrorsTap_" aria-describedby="a11yAssociation"> $i18n{itemErrors} </paper-button> @@ -327,22 +335,22 @@ <template is="dom-if" if="[[!computeDevReloadButtonHidden_(data.*)]]"> <button id="dev-reload-button" is="paper-icon-button-light" aria-label="$i18n{itemReload}" aria-describedby="a11yAssociation" - class="icon-refresh no-overlap" on-tap="onReloadTap_"> + class="icon-refresh no-overlap" on-click="onReloadTap_"> </button> </template> <template is="dom-if" if="[[data.disableReasons.corruptInstall]]"> <paper-button id="repair-button" class="action-button" - aria-describedby="a11yAssociation" on-tap="onRepairTap_"> + aria-describedby="a11yAssociation" on-click="onRepairTap_"> $i18n{itemRepair} </paper-button> </template> <template is="dom-if" if="[[isTerminated_(data.state)]]"> - <paper-button id="terminated-reload-button" on-tap="onReloadTap_" + <paper-button id="terminated-reload-button" on-click="onReloadTap_" aria-describedby="a11yAssociation" class="action-button"> $i18n{itemReload} </paper-button> </template> - <cr-toggle id="enable-toggle" class="action-button" + <cr-toggle id="enable-toggle" aria-label$="[[appOrExtension( data.type, '$i18nPolymer{appEnabled}', diff --git a/chromium/chrome/browser/resources/md_extensions/item.js b/chromium/chrome/browser/resources/md_extensions/item.js index 116f96d50ea..bdf69ab5bf5 100644 --- a/chromium/chrome/browser/resources/md_extensions/item.js +++ b/chromium/chrome/browser/resources/md_extensions/item.js @@ -109,7 +109,12 @@ cr.define('extensions', function() { /** @private string */ a11yAssociation_: function() { - return this.i18n('extensionA11yAssociation', this.data.name); + // Don't use I18nBehavior.i18n because of additional checks it performs. + // Polymer ensures that this string is not stamped into arbitrary HTML. + // |this.data.name| can contain any data including html tags. + // ex: "My <video> download extension!" + return loadTimeData.getStringF( + 'extensionA11yAssociation', this.data.name); }, /** @private */ diff --git a/chromium/chrome/browser/resources/md_extensions/item_list.html b/chromium/chrome/browser/resources/md_extensions/item_list.html index 2ac11babaa0..849590719b1 100644 --- a/chromium/chrome/browser/resources/md_extensions/item_list.html +++ b/chromium/chrome/browser/resources/md_extensions/item_list.html @@ -49,7 +49,7 @@ } #app-title { - @apply(--cr-section-text); + @apply --cr-section-text; margin-bottom: 12px; margin-top: 21px; } @@ -61,7 +61,7 @@ <div id="no-items" class="empty-list-message" hidden$="[[!shouldShowEmptyItemsMessage_( apps.length, extensions.length)]]"> - <span on-tap="onNoExtensionsTap_">$i18nRaw{noExtensionsOrApps}</span> + <span on-click="onNoExtensionsTap_">$i18nRaw{noExtensionsOrApps}</span> </div> <div id="no-search-results" class="empty-list-message" hidden$="[[!shouldShowEmptySearchMessage_( diff --git a/chromium/chrome/browser/resources/md_extensions/item_util.js b/chromium/chrome/browser/resources/md_extensions/item_util.js index aaefd16eb47..5f4aac7e18c 100644 --- a/chromium/chrome/browser/resources/md_extensions/item_util.js +++ b/chromium/chrome/browser/resources/md_extensions/item_util.js @@ -24,6 +24,7 @@ cr.define('extensions', function() { case chrome.developerPrivate.ExtensionState.ENABLED: case chrome.developerPrivate.ExtensionState.TERMINATED: return true; + case chrome.developerPrivate.ExtensionState.BLACKLISTED: case chrome.developerPrivate.ExtensionState.DISABLED: return false; } diff --git a/chromium/chrome/browser/resources/md_extensions/keyboard_shortcuts.html b/chromium/chrome/browser/resources/md_extensions/keyboard_shortcuts.html index cb443c889fc..5bdefc60481 100644 --- a/chromium/chrome/browser/resources/md_extensions/keyboard_shortcuts.html +++ b/chromium/chrome/browser/resources/md_extensions/keyboard_shortcuts.html @@ -74,15 +74,15 @@ .icon { -webkit-margin-end: 20px; - height: 16px; - width: 16px; + height: 20px; + width: 20px; } .card-controls { /* We line up the controls with the name, which is after the - 20px left padding + 16px icon + 20px margin on the icon. */ + 20px left padding + 20px icon + 20px margin on the icon. */ -webkit-margin-end: 20px; - -webkit-margin-start: 56px; + -webkit-margin-start: 60px; } </style> <div id="container"> diff --git a/chromium/chrome/browser/resources/md_extensions/kiosk_dialog.html b/chromium/chrome/browser/resources/md_extensions/kiosk_dialog.html index 2709a38694b..fa86c94ec05 100644 --- a/chromium/chrome/browser/resources/md_extensions/kiosk_dialog.html +++ b/chromium/chrome/browser/resources/md_extensions/kiosk_dialog.html @@ -95,13 +95,13 @@ </div> <div class="item-controls"> <paper-button hidden="[[!canEditAutoLaunch_]]" - on-tap="onAutoLaunchButtonTap_"> + on-click="onAutoLaunchButtonTap_"> [[getAutoLaunchButtonLabel_(item.autoLaunch, '$i18nPolymer{kioskDisableAutoLaunch}', '$i18nPolymer{kioskEnableAutoLaunch}')]] </paper-button> <button is="paper-icon-button-light" class="icon-delete-gray" - on-tap="onDeleteAppTap_"></button> + on-click="onDeleteAppTap_"></button> </div> </div> </template> @@ -114,7 +114,7 @@ '$i18nPolymer{kioskInvalidApp}', errorAppId_)]]" on-keydown="clearInputInvalid_"> </paper-input> - <paper-button id="add-button" on-tap="onAddAppTap_" + <paper-button id="add-button" on-click="onAddAppTap_" disabled="[[!addAppInput_]]"> $i18n{add} </paper-button> @@ -126,7 +126,7 @@ </paper-checkbox> </div> <div slot="button-container"> - <paper-button class="action-button" on-tap="onDoneTap_"> + <paper-button class="action-button" on-click="onDoneTap_"> $i18n{done} </paper-button> </div> @@ -136,10 +136,12 @@ <div slot="title">$i18n{kioskDisableBailoutWarningTitle}</div> <div slot="body">$i18n{kioskDisableBailoutWarningBody}</div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onBailoutDialogCancelTap_"> + <paper-button class="cancel-button" + on-click="onBailoutDialogCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onBailoutDialogConfirmTap_"> + <paper-button class="action-button" + on-click="onBailoutDialogConfirmTap_"> $i18n{confirm} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/load_error.html b/chromium/chrome/browser/resources/md_extensions/load_error.html index e5de72a0497..10080e154d5 100644 --- a/chromium/chrome/browser/resources/md_extensions/load_error.html +++ b/chromium/chrome/browser/resources/md_extensions/load_error.html @@ -40,11 +40,11 @@ </div> <div slot="button-container"> <paper-spinner-lite active="[[retrying_]]"></paper-spinner-lite> - <paper-button class="cancel-button" on-tap="close"> + <paper-button class="cancel-button" on-click="close"> $i18n{cancel} </paper-button> <paper-button class="action-button" disabled="[[retrying_]]" - on-tap="onRetryTap_"> + on-click="onRetryTap_"> $i18n{loadErrorRetry} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/options_dialog.html b/chromium/chrome/browser/resources/md_extensions/options_dialog.html index 3c959b13644..5c5abc51db6 100644 --- a/chromium/chrome/browser/resources/md_extensions/options_dialog.html +++ b/chromium/chrome/browser/resources/md_extensions/options_dialog.html @@ -23,12 +23,13 @@ ExtensionOptions { display: block; min-width: 300px; + overflow: hidden; } dialog { + --scroll-border: 0; width: fit-content; --cr-dialog-body: { - overflow: hidden; padding: 0; }; diff --git a/chromium/chrome/browser/resources/md_extensions/options_dialog.js b/chromium/chrome/browser/resources/md_extensions/options_dialog.js index adffadd9da2..d9ecf142ee3 100644 --- a/chromium/chrome/browser/resources/md_extensions/options_dialog.js +++ b/chromium/chrome/browser/resources/md_extensions/options_dialog.js @@ -5,6 +5,25 @@ cr.define('extensions', function() { 'use strict'; + /** + * @return {!Promise} A signal that the document is ready. Need to wait for + * this, otherwise the custom ExtensionOptions element might not have been + * registered yet. + */ + function whenDocumentReady() { + if (document.readyState == 'complete') + return Promise.resolve(); + + return new Promise(function(resolve) { + document.addEventListener('readystatechange', function f() { + if (document.readyState == 'complete') { + document.removeEventListener('readystatechange', f); + resolve(); + } + }); + }); + } + const OptionsDialog = Polymer({ is: 'extensions-options-dialog', @@ -25,21 +44,23 @@ cr.define('extensions', function() { /** @param {chrome.developerPrivate.ExtensionInfo} data */ show: function(data) { this.data_ = data; - if (!this.extensionOptions_) - this.extensionOptions_ = document.createElement('ExtensionOptions'); - this.extensionOptions_.extension = this.data_.id; - this.extensionOptions_.onclose = this.close.bind(this); + whenDocumentReady().then(() => { + if (!this.extensionOptions_) + this.extensionOptions_ = document.createElement('ExtensionOptions'); + this.extensionOptions_.extension = this.data_.id; + this.extensionOptions_.onclose = this.close.bind(this); - const onSizeChanged = e => { - this.extensionOptions_.style.height = e.height + 'px'; - this.extensionOptions_.style.width = e.width + 'px'; + const onSizeChanged = e => { + this.extensionOptions_.style.height = e.height + 'px'; + this.extensionOptions_.style.width = e.width + 'px'; - if (!this.$$('dialog').open) - this.$$('dialog').showModal(); - }; + if (!this.$$('dialog').open) + this.$$('dialog').showModal(); + }; - this.extensionOptions_.onpreferredsizechanged = onSizeChanged; - this.$.body.appendChild(this.extensionOptions_); + this.extensionOptions_.onpreferredsizechanged = onSizeChanged; + this.$.body.appendChild(this.extensionOptions_); + }); }, close: function() { diff --git a/chromium/chrome/browser/resources/md_extensions/pack_dialog.html b/chromium/chrome/browser/resources/md_extensions/pack_dialog.html index 1ee1ae2c232..e984b85658b 100644 --- a/chromium/chrome/browser/resources/md_extensions/pack_dialog.html +++ b/chromium/chrome/browser/resources/md_extensions/pack_dialog.html @@ -36,7 +36,7 @@ <paper-input id="root-dir" label="$i18n{packDialogExtensionRoot}" always-float-label value="{{packDirectory_}}"> </paper-input> - <paper-button id="root-dir-browse" on-tap="onRootBrowse_"> + <paper-button id="root-dir-browse" on-click="onRootBrowse_"> $i18n{packDialogBrowse} </paper-button> </div> @@ -44,16 +44,16 @@ <paper-input id="key-file" label="$i18n{packDialogKeyFile}" always-float-label value="{{keyFile_}}"> </paper-input> - <paper-button id="key-file-browse" on-tap="onKeyBrowse_"> + <paper-button id="key-file-browse" on-click="onKeyBrowse_"> $i18n{packDialogBrowse} </paper-button> </div> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onConfirmTap_" + <paper-button class="action-button" on-click="onConfirmTap_" disabled="[[!packDirectory_]]"> $i18n{packDialogConfirm} </paper-button> diff --git a/chromium/chrome/browser/resources/md_extensions/pack_dialog_alert.html b/chromium/chrome/browser/resources/md_extensions/pack_dialog_alert.html index 68bc231c581..ebf69a90b1b 100644 --- a/chromium/chrome/browser/resources/md_extensions/pack_dialog_alert.html +++ b/chromium/chrome/browser/resources/md_extensions/pack_dialog_alert.html @@ -22,10 +22,10 @@ <div class="body" slot="body">[[model.message]]</div> <div class="button-container" slot="button-container"> <paper-button class$="[[getCancelButtonClass_(confirmLabel_)]]" - on-tap="onCancelTap_" hidden="[[!cancelLabel_]]"> + on-click="onCancelTap_" hidden="[[!cancelLabel_]]"> [[cancelLabel_]] </paper-button> - <paper-button class="action-button" on-tap="onConfirmTap_" + <paper-button class="action-button" on-click="onConfirmTap_" hidden="[[!confirmLabel_]]"> [[confirmLabel_]] </paper-button> diff --git a/chromium/chrome/browser/resources/md_extensions/shortcut_input.html b/chromium/chrome/browser/resources/md_extensions/shortcut_input.html index ad5c9bb03b9..94b9d0d31f0 100644 --- a/chromium/chrome/browser/resources/md_extensions/shortcut_input.html +++ b/chromium/chrome/browser/resources/md_extensions/shortcut_input.html @@ -46,7 +46,7 @@ no-label-float> </paper-input> <button id="clear" is="paper-icon-button-light" - class="icon-clear no-overlap" on-tap="onClearTap_" + class="icon-clear no-overlap" on-click="onClearTap_" hidden$="[[computeClearHidden_(capturing_, shortcut)]]"> </button> </div> diff --git a/chromium/chrome/browser/resources/md_extensions/sidebar.html b/chromium/chrome/browser/resources/md_extensions/sidebar.html index a8857075a54..5e89ab45503 100644 --- a/chromium/chrome/browser/resources/md_extensions/sidebar.html +++ b/chromium/chrome/browser/resources/md_extensions/sidebar.html @@ -62,12 +62,12 @@ <iron-selector id="sectionMenu"> <!-- Values for "data-path" attribute must match the "Page" enum. --> <a class="section-item" id="sections-extensions" href="/" - on-tap="onLinkTap_" data-path="items-list"> + on-click="onLinkTap_" data-path="items-list"> $i18n{sidebarExtensions} <paper-ripple></paper-ripple> </a> <a class="section-item" id="sections-shortcuts" href="/shortcuts" - on-tap="onLinkTap_" data-path="keyboard-shortcuts"> + on-click="onLinkTap_" data-path="keyboard-shortcuts"> $i18n{keyboardShortcuts} <paper-ripple></paper-ripple> </a> @@ -75,7 +75,7 @@ <div hidden="[[isSupervised]]"> <div class="separator"></div> <a class="section-item" id="more-extensions" target="_blank" - href="$i18n{getMoreExtensionsUrl}" on-tap="onMoreExtensionsTap_"> + href="$i18n{getMoreExtensionsUrl}" on-click="onMoreExtensionsTap_"> <span>$i18n{openChromeWebStore}</span> <div class="cr-icon icon-external"></div> <paper-ripple></paper-ripple> diff --git a/chromium/chrome/browser/resources/md_extensions/toolbar.html b/chromium/chrome/browser/resources/md_extensions/toolbar.html index b2d3b2f9af8..8e554185091 100644 --- a/chromium/chrome/browser/resources/md_extensions/toolbar.html +++ b/chromium/chrome/browser/resources/md_extensions/toolbar.html @@ -1,5 +1,6 @@ <link rel="import" href="chrome://resources/html/polymer.html"> +<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html"> <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html"> <link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar.html"> <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> @@ -40,7 +41,7 @@ -webkit-margin-end: 20px; } - #devDrawer[expanded] #button-strip { + #devDrawer[expanded] #buttonStrip { top: 0; } @@ -55,7 +56,7 @@ height: var(--button-row-height); } - #button-strip { + #buttonStrip { -webkit-margin-end: auto; -webkit-margin-start: auto; background: var(--toolbar-color); @@ -69,7 +70,7 @@ width: 100%; } - #button-strip paper-button { + #buttonStrip paper-button { -webkit-margin-end: 16px; color: white; /* Increase contrast compared to default values. */ @@ -87,6 +88,15 @@ .more-actions span { -webkit-margin-end: 16px; } + + cr-toast > div { + color: #fff; + display: flex; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } </style> <cr-toolbar page-name="$i18n{toolbarTitle}" search-prompt="$i18n{search}" @@ -101,7 +111,7 @@ icon-class="cr20:domain" icon-aria-label="$i18n{controlledSettingPolicy}"> </cr-tooltip-icon> - <cr-toggle id="dev-mode" on-change="onDevModeToggleChange_" + <cr-toggle id="devMode" on-change="onDevModeToggleChange_" disabled="[[shouldDisableDevMode_( devModeControlledByPolicy, isSupervised)]]" checked="[[inDevMode]]" aria-labelledby="devModeLabel"> @@ -109,20 +119,23 @@ </div> </cr-toolbar> <div id="devDrawer" expanded$="[[expanded_]]"> - <div id="button-strip"> - <paper-button hidden$="[[!canLoadUnpacked]]" id="load-unpacked" - on-tap="onLoadUnpackedTap_"> + <div id="buttonStrip"> + <paper-button hidden$="[[!canLoadUnpacked]]" id="loadUnpacked" + on-click="onLoadUnpackedTap_"> $i18n{toolbarLoadUnpacked} </paper-button> - <paper-button id="pack-extensions" on-tap="onPackTap_"> + <paper-button id="packExtensions" on-click="onPackTap_"> $i18n{toolbarPack} </paper-button> - <paper-button id="update-now" on-tap="onUpdateNowTap_" + <paper-button id="updateNow" on-click="onUpdateNowTap_" title="$i18n{toolbarUpdateNowTooltip}"> $i18n{toolbarUpdateNow} </paper-button> + <cr-toast duration="3000"> + <div>[[toastLabel_]]</div> + </cr-toast> <if expr="chromeos"> - <paper-button id="kiosk-extensions" on-tap="onKioskTap_" + <paper-button id="kioskExtensions" on-click="onKioskTap_" hidden$="[[!kioskEnabled]]"> $i18n{manageKioskApp} </paper-button> diff --git a/chromium/chrome/browser/resources/md_extensions/toolbar.js b/chromium/chrome/browser/resources/md_extensions/toolbar.js index 3205f8f4f21..b5d3a5cfd37 100644 --- a/chromium/chrome/browser/resources/md_extensions/toolbar.js +++ b/chromium/chrome/browser/resources/md_extensions/toolbar.js @@ -53,6 +53,12 @@ cr.define('extensions', function() { /** @private */ expanded_: Boolean, + + /** + * Text to display in update toast + * @private + */ + toastLabel_: String, }, behaviors: [I18nBehavior], @@ -130,12 +136,24 @@ cr.define('extensions', function() { /** @private */ onUpdateNowTap_: function() { - this.delegate.updateAllExtensions().then(() => { - Polymer.IronA11yAnnouncer.requestAvailability(); - this.fire('iron-announce', { - text: this.i18n('toolbarUpdateDone'), - }); - }); + const updateButton = this.$.updateNow; + assert(!updateButton.disabled); + updateButton.disabled = true; + const toastElement = this.$$('cr-toast'); + this.toastLabel_ = this.i18n('toolbarUpdatingToast'); + toastElement.show(); + this.delegate.updateAllExtensions() + .then(() => { + Polymer.IronA11yAnnouncer.requestAvailability(); + const doneText = this.i18n('toolbarUpdateDone'); + this.fire('iron-announce', {text: doneText}); + this.toastLabel_ = doneText; + toastElement.show(); + updateButton.disabled = false; + }) + .catch(function() { + updateButton.disabled = false; + }); }, }); diff --git a/chromium/chrome/browser/resources/md_history/app.html b/chromium/chrome/browser/resources/md_history/app.html index 5afaa5d63a1..a6bfb168ffd 100644 --- a/chromium/chrome/browser/resources/md_history/app.html +++ b/chromium/chrome/browser/resources/md_history/app.html @@ -49,7 +49,7 @@ } #drop-shadow { - @apply(--cr-container-shadow); + @apply --cr-container-shadow; } :host([toolbar-shadow_]) #drop-shadow { diff --git a/chromium/chrome/browser/resources/md_history/app.js b/chromium/chrome/browser/resources/md_history/app.js index c187aca222c..56f975fb23f 100644 --- a/chromium/chrome/browser/resources/md_history/app.js +++ b/chromium/chrome/browser/resources/md_history/app.js @@ -168,6 +168,13 @@ Polymer({ .getSelectedItemCount(); }, + selectOrUnselectAll: function() { + const list = /** @type {HistoryListElement} */ (this.$.history); + const toolbar = /** @type {HistoryToolbarElement} */ (this.$.toolbar); + list.selectOrUnselectAll(); + toolbar.count = list.getSelectedItemCount(); + }, + /** * Listens for call to cancel selection and loops through all items to set * checkbox to be unselected. @@ -218,6 +225,9 @@ Polymer({ case 'delete-command': e.canExecute = this.$.toolbar.count > 0; break; + case 'select-all-command': + e.canExecute = !this.$.toolbar.searchField.isSearchFocused(); + break; } }, @@ -230,6 +240,8 @@ Polymer({ this.focusToolbarSearchField(); else if (e.command.id == 'delete-command') this.deleteSelected(); + else if (e.command.id == 'select-all-command') + this.selectOrUnselectAll(); }, /** diff --git a/chromium/chrome/browser/resources/md_history/history.html b/chromium/chrome/browser/resources/md_history/history.html index cc221baf1bd..bdce86e6070 100644 --- a/chromium/chrome/browser/resources/md_history/history.html +++ b/chromium/chrome/browser/resources/md_history/history.html @@ -8,6 +8,11 @@ <link rel="stylesheet" href="chrome://resources/css/md_colors.css"> <style> + html { + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; + } + html, body { height: 100%; @@ -79,6 +84,12 @@ </if> <command id="delete-command" shortcut="Delete Backspace"> <command id="slash-command" shortcut="/"> +<if expr="is_macosx"> + <command id="select-all-command" shortcut="Meta|a"> +</if> +<if expr="not is_macosx"> + <command id="select-all-command" shortcut="Ctrl|a"> +</if> <link rel="import" href="chrome://resources/html/util.html"> <link rel="import" href="chrome://resources/html/load_time_data.html"> diff --git a/chromium/chrome/browser/resources/md_history/history_item.html b/chromium/chrome/browser/resources/md_history/history_item.html index 958032b7386..221f09b7af3 100644 --- a/chromium/chrome/browser/resources/md_history/history_item.html +++ b/chromium/chrome/browser/resources/md_history/history_item.html @@ -169,7 +169,7 @@ } #background { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background: #fff; bottom: 0; left: 0; diff --git a/chromium/chrome/browser/resources/md_history/history_item.js b/chromium/chrome/browser/resources/md_history/history_item.js index fa0dd0ec630..3329aa189f7 100644 --- a/chromium/chrome/browser/resources/md_history/history_item.js +++ b/chromium/chrome/browser/resources/md_history/history_item.js @@ -278,13 +278,13 @@ cr.define('md_history', function() { item: this.item, }); - // Stops the 'tap' event from closing the menu when it opens. + // Stops the 'click' event from closing the menu when it opens. e.stopPropagation(); }, /** - * Record metrics when a result is clicked. This is deliberately tied to - * on-click rather than on-tap, as on-click triggers from middle clicks. + * Record metrics when a result is clicked. + * @private */ onLinkClick_: function() { const browserService = md_history.BrowserService.getInstance(); diff --git a/chromium/chrome/browser/resources/md_history/history_list.html b/chromium/chrome/browser/resources/md_history/history_list.html index b27ce8d74e2..ed46d9fcda9 100644 --- a/chromium/chrome/browser/resources/md_history/history_list.html +++ b/chromium/chrome/browser/resources/md_history/history_list.html @@ -1,6 +1,7 @@ <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/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_style_css.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-list/iron-list.html"> @@ -14,7 +15,7 @@ <dom-module id="history-list"> <template> - <style include="shared-style cr-shared-style"> + <style include="shared-style cr-shared-style paper-button-style"> :host { box-sizing: border-box; display: block; @@ -22,7 +23,7 @@ } iron-list { - @apply(--card-sizing); + @apply --card-sizing; margin-top: var(--first-card-padding-top); } @@ -62,10 +63,10 @@ <div slot="title">$i18n{removeSelected}</div> <div slot="body">$i18n{deleteWarning}</div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onDialogCancelTap_"> + <paper-button class="cancel-button" on-click="onDialogCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onDialogConfirmTap_"> + <paper-button class="action-button" on-click="onDialogConfirmTap_"> $i18n{deleteConfirm} </paper-button> </div> @@ -74,15 +75,15 @@ <template is="cr-lazy-render" id="sharedMenu"> <dialog is="cr-action-menu"> - <button id="menuMoreButton" class="dropdown-item" + <button id="menuMoreButton" slot="item" class="dropdown-item" hidden="[[!canSearchMoreFromSite_( searchedTerm, actionMenuModel_.item.domain)]]" - on-tap="onMoreFromSiteTap_"> + on-click="onMoreFromSiteTap_"> $i18n{moreFromSite} </button> - <button id="menuRemoveButton" class="dropdown-item" + <button id="menuRemoveButton" slot="item" class="dropdown-item" hidden="[[!canDeleteHistory_]]" - on-tap="onRemoveFromHistoryTap_"> + on-click="onRemoveFromHistoryTap_"> $i18n{removeFromHistory} </button> </dialog> diff --git a/chromium/chrome/browser/resources/md_history/history_list.js b/chromium/chrome/browser/resources/md_history/history_list.js index 8b94ed48774..9cd061e5c60 100644 --- a/chromium/chrome/browser/resources/md_history/history_list.js +++ b/chromium/chrome/browser/resources/md_history/history_list.js @@ -138,6 +138,25 @@ Polymer({ this.fire('query-history', false); }, + selectOrUnselectAll: function() { + if (this.historyData_.length == this.getSelectedItemCount()) + this.unselectAllItems(); + else + this.selectAllItems(); + }, + + /** + * Select each item in |historyData|. + */ + selectAllItems: function() { + if (this.historyData_.length == this.getSelectedItemCount()) + return; + + this.historyData_.forEach((item, index) => { + this.changeSelection_(index, true); + }); + }, + /** * Deselect each item in |selectedItems|. */ @@ -205,6 +224,10 @@ Polymer({ .then((items) => { this.removeItemsByIndex_(Array.from(this.selectedItems)); this.fire('unselect-all'); + if (this.historyData_.length == 0) { + // Try reloading if nothing is rendered. + this.fire('query-history', false); + } }); }, diff --git a/chromium/chrome/browser/resources/md_history/side_bar.html b/chromium/chrome/browser/resources/md_history/side_bar.html index b49c3e2fe5c..49b11840b7b 100644 --- a/chromium/chrome/browser/resources/md_history/side_bar.html +++ b/chromium/chrome/browser/resources/md_history/side_bar.html @@ -107,7 +107,7 @@ <div class="separator"></div> <a id="clear-browsing-data" href="chrome://settings/clearBrowserData" - on-tap="onClearBrowsingDataTap_" + on-click="onClearBrowsingDataTap_" disabled$="[[guestSession_]]" tabindex$="[[computeClearBrowsingDataTabIndex_(guestSession_)]]"> $i18n{clearBrowsingData} diff --git a/chromium/chrome/browser/resources/md_history/synced_device_card.html b/chromium/chrome/browser/resources/md_history/synced_device_card.html index 708f60bcf3c..a9cc4154ac8 100644 --- a/chromium/chrome/browser/resources/md_history/synced_device_card.html +++ b/chromium/chrome/browser/resources/md_history/synced_device_card.html @@ -16,7 +16,7 @@ <template> <style include="shared-style"> :host { - @apply(--card-sizing); + @apply --card-sizing; -webkit-tap-highlight-color: transparent; display: block; padding-bottom: var(--card-padding-between); @@ -57,7 +57,7 @@ } #history-item-container { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background: #fff; border-radius: 2px; } diff --git a/chromium/chrome/browser/resources/md_history/synced_device_card.js b/chromium/chrome/browser/resources/md_history/synced_device_card.js index 3206eb236eb..bbd109a64a9 100644 --- a/chromium/chrome/browser/resources/md_history/synced_device_card.js +++ b/chromium/chrome/browser/resources/md_history/synced_device_card.js @@ -68,8 +68,7 @@ Polymer({ }, /** - * Open a single synced tab. Listens to 'click' rather than 'tap' - * to determine what modifier keys were pressed. + * Open a single synced tab. * @param {DomRepeatClickEvent} e * @private */ diff --git a/chromium/chrome/browser/resources/md_history/synced_device_manager.html b/chromium/chrome/browser/resources/md_history/synced_device_manager.html index 55e529c83e5..7b10c59e6cb 100644 --- a/chromium/chrome/browser/resources/md_history/synced_device_manager.html +++ b/chromium/chrome/browser/resources/md_history/synced_device_manager.html @@ -4,13 +4,14 @@ <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.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_lazy_render/cr_lazy_render.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> <link rel="import" href="chrome://history/shared_style.html"> <link rel="import" href="chrome://history/synced_device_card.html"> <dom-module id="history-synced-device-manager"> <template> - <style include="shared-style cr-shared-style"> + <style include="shared-style cr-shared-style paper-button-style"> :host { display: block; overflow: auto; @@ -81,19 +82,19 @@ <div id="sign-in-promo">$i18n{signInPromo}</div> <div id="sign-in-promo-desc">$i18n{signInPromoDesc}</div> <paper-button id="sign-in-button" class="action-button" - on-tap="onSignInTap_"> + on-click="onSignInTap_"> $i18n{signInButton} </paper-button> </div> <template is="cr-lazy-render" id="menu"> <dialog is="cr-action-menu"> - <button id="menuOpenButton" class="dropdown-item" - on-tap="onOpenAllTap_"> + <button id="menuOpenButton" slot="item" class="dropdown-item" + on-click="onOpenAllTap_"> $i18n{openAll} </button> - <button id="menuDeleteButton" class="dropdown-item" - on-tap="onDeleteSessionTap_"> + <button id="menuDeleteButton" slot="item" class="dropdown-item" + on-click="onDeleteSessionTap_"> $i18n{deleteSession} </button> </dialog> diff --git a/chromium/chrome/browser/resources/md_user_manager/shared_styles.html b/chromium/chrome/browser/resources/md_user_manager/shared_styles.html index 7c9d1aa16ab..02eac8fb435 100644 --- a/chromium/chrome/browser/resources/md_user_manager/shared_styles.html +++ b/chromium/chrome/browser/resources/md_user_manager/shared_styles.html @@ -26,7 +26,7 @@ } paper-button.action { - @apply(--action-button); + @apply --action-button; } paper-button.action.primary { diff --git a/chromium/chrome/browser/resources/md_user_manager/user_manager.html b/chromium/chrome/browser/resources/md_user_manager/user_manager.html index 8dbe1d9def7..244e159517d 100644 --- a/chromium/chrome/browser/resources/md_user_manager/user_manager.html +++ b/chromium/chrome/browser/resources/md_user_manager/user_manager.html @@ -322,7 +322,7 @@ --paper-button-flat-keyboard-focus: { background: rgb(173, 50, 36); }; - @apply(--action-button); + @apply --action-button; } #user-manager-prompt-message { diff --git a/chromium/chrome/browser/resources/md_user_manager/user_manager_pages.html b/chromium/chrome/browser/resources/md_user_manager/user_manager_pages.html index 8a4acecdaf6..c2f8945b653 100644 --- a/chromium/chrome/browser/resources/md_user_manager/user_manager_pages.html +++ b/chromium/chrome/browser/resources/md_user_manager/user_manager_pages.html @@ -7,6 +7,7 @@ <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-out-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animated-pages.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/web-animations.html"> <dom-module id="user-manager-pages"> <template> diff --git a/chromium/chrome/browser/resources/media/media_engagement.html b/chromium/chrome/browser/resources/media/media_engagement.html index fd374ce52d3..af4c2d8e1e2 100644 --- a/chromium/chrome/browser/resources/media/media_engagement.html +++ b/chromium/chrome/browser/resources/media/media_engagement.html @@ -18,6 +18,10 @@ font-size: 14px; } + button { + margin-bottom: 20px; + } + table { border-collapse: collapse; margin-bottom: 20px; @@ -50,7 +54,6 @@ .origin-cell { background-color: rgba(230, 230, 230, 0.5); - min-width: 500px; } .visits-count-cell, @@ -60,6 +63,7 @@ .significant-playbacks-count-cell { background-color: rgba(230, 230, 230, 0.5); text-align: right; + white-space: nowrap; } .base-score-input { @@ -96,6 +100,7 @@ </head> <body> <h1>Media Engagement</h1> + <button id="copy-all-to-clipboard">Copy all to clipboard</button> <table> <thead> <tr id="config-table-header"> @@ -110,6 +115,12 @@ <tbody id="config-table-body"> </tbody> </table> + <p> + <label> + <input id="show-no-playbacks" type="checkbox"> + Show sessions with no playbacks + </label> + </p> <table> <thead> <tr id="engagement-table-header"> @@ -117,10 +128,10 @@ Origin </th> <th sort-key="visits" sort-reverse> - Visits + Sessions </th> <th sort-key="mediaPlaybacks" sort-reverse> - Playbacks + Sessions with playback </th> <th sort-key="audiblePlaybacks" sort-reverse> Audible Playbacks* @@ -134,6 +145,9 @@ <th sort-key="isHigh" sort-reverse> Is High </th> + <th sort-key="highScoreChanges" sort-reverse> + Is High Changes + </th> <th sort-key="totalScore" class="sort-column" sort-reverse> Score </th> @@ -156,6 +170,7 @@ <td class="significant-playbacks-count-cell"></td> <td class="last-playback-time-cell"></td> <td class="is-high-cell"></td> + <td class="is-high-changes-cell"></td> <td class="total-score-cell"></td> <td class="engagement-bar-cell"> <div class="engagement-bar"></div> diff --git a/chromium/chrome/browser/resources/media/media_engagement.js b/chromium/chrome/browser/resources/media/media_engagement.js index 0ca0a3cd94c..a4a8af7841f 100644 --- a/chromium/chrome/browser/resources/media/media_engagement.js +++ b/chromium/chrome/browser/resources/media/media_engagement.js @@ -19,6 +19,7 @@ var engagementTableBody = null; var sortReverse = true; var sortKey = 'totalScore'; var configTableBody = null; +var showNoPlaybacks = false; /** * Creates a single row in the engagement table. @@ -37,8 +38,9 @@ function createRow(rowInfo) { new Date(rowInfo.lastMediaPlaybackTime).toISOString() : ''; td[6].textContent = rowInfo.isHigh ? 'Yes' : 'No'; - td[7].textContent = rowInfo.totalScore ? rowInfo.totalScore.toFixed(2) : '0'; - td[8].getElementsByClassName('engagement-bar')[0].style.width = + td[7].textContent = rowInfo.highScoreChanges; + td[8].textContent = rowInfo.totalScore ? rowInfo.totalScore.toFixed(2) : '0'; + td[9].getElementsByClassName('engagement-bar')[0].style.width = (rowInfo.totalScore * 50) + 'px'; return document.importNode(template.content, true); } @@ -77,7 +79,8 @@ function compareTableItem(sortKey, a, b) { if (sortKey == 'visits' || sortKey == 'mediaPlaybacks' || sortKey == 'lastMediaPlaybackTime' || sortKey == 'totalScore' || - sortKey == 'audiblePlaybacks' || sortKey == 'significantPlaybacks') { + sortKey == 'audiblePlaybacks' || sortKey == 'significantPlaybacks' || + sortKey == 'highScoreChanges') { return val1 - val2; } @@ -108,7 +111,7 @@ function renderConfigTable(config) { configTableBody.innerHTML = ''; configTableBody.appendChild( - createConfigRow('Min Visits', config.scoreMinVisits)); + createConfigRow('Min Sessions', config.scoreMinVisits)); configTableBody.appendChild( createConfigRow('Lower Threshold', config.highScoreLowerThreshold)); configTableBody.appendChild( @@ -121,7 +124,8 @@ function renderConfigTable(config) { function renderTable() { clearTable(); sortInfo(); - info.forEach(rowInfo => engagementTableBody.appendChild(createRow(rowInfo))); + info.filter(rowInfo => (showNoPlaybacks || rowInfo.mediaPlaybacks > 0)) + .forEach(rowInfo => engagementTableBody.appendChild(createRow(rowInfo))); } /** @@ -173,5 +177,27 @@ document.addEventListener('DOMContentLoaded', function() { renderTable(); }); } + + // Add handler to 'copy all to clipboard' button + var copyAllToClipboardButton = $('copy-all-to-clipboard'); + copyAllToClipboardButton.addEventListener('click', (e) => { + // Make sure nothing is selected + window.getSelection().removeAllRanges(); + + document.execCommand('selectAll'); + document.execCommand('copy'); + + // And deselect everything at the end. + window.getSelection().removeAllRanges(); + }); + + // Add handler to 'show no playbacks' checkbox + var showNoPlaybacksCheckbox = $('show-no-playbacks'); + showNoPlaybacksCheckbox.addEventListener('change', (e) => { + showNoPlaybacks = e.target.checked; + renderTable(); + }); + }); + })(); diff --git a/chromium/chrome/browser/resources/media/mei_preload/preloaded_data.pb b/chromium/chrome/browser/resources/media/mei_preload/preloaded_data.pb Binary files differindex 1302db9033b..1652cafc98a 100644 --- a/chromium/chrome/browser/resources/media/mei_preload/preloaded_data.pb +++ b/chromium/chrome/browser/resources/media/mei_preload/preloaded_data.pb diff --git a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css index 7ae1c39ff5a..9cc54236cca 100644 --- a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css +++ b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css @@ -137,7 +137,7 @@ paper-item:hover { border: 0; } -paper-menu { +paper-listbox { color: rgba(0, 0, 0, 0.87); overflow-x: hidden; overflow-y: auto; diff --git a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html index 9a9e85029f4..6b606df8ece 100644 --- a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html +++ b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html @@ -3,7 +3,7 @@ <link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-item/paper-item.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-menu/paper-menu.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-listbox/paper-listbox.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html"> <link rel="import" href="../media_router_header/media_router_header.html"> <link rel="import" href="../route_details/route_details.html"> @@ -57,7 +57,7 @@ </media-router-header> <div id="content"> <template is="dom-if" if="[[!computeCastModeListHidden_(currentView_)]]"> - <paper-menu id="cast-mode-list" role="presentation" + <paper-listbox id="cast-mode-list" role="presentation" selectable="paper-item" selected="{{selectedCastModeMenuItem_}}"> <template is="dom-repeat" id="presentationCastModeList" items="[[computePresentationCastModeList_(castModeList)]]"> @@ -94,7 +94,7 @@ <div><span>[[item.description]]</span></div> </paper-item> </template> - </paper-menu> + </paper-listbox> </template> <template is="dom-if" if="[[!computeRouteDetailsHidden_(currentView_, issue)]]"> @@ -121,7 +121,7 @@ </div> <template is="dom-if" if="[[!computeSinkListHidden_(sinksToShow_)]]"> <div id="sink-list" hidden$="[[hideSinkListForAnimation_]]"> - <paper-menu id="sink-list-paper-menu" role="presentation"> + <paper-listbox id="sink-list-paper-menu" role="presentation"> <template is="dom-repeat" id="sinkList" items="[[sinksToShow_]]"> <paper-item on-tap="onSinkClick_"> <div class="sink-content"> @@ -158,7 +158,7 @@ </div> </paper-item> </template> - </paper-menu> + </paper-listbox> </div> </template> <template is="dom-if" if="[[searchEnabled_]]"> @@ -185,7 +185,8 @@ </div> <div id="search-results" hidden$="[[computeSearchResultsHidden_(searchResultsToShow_, isSearchListHidden_)]]"> - <paper-menu id="search-results-paper-menu" selected="0" role="presentation"> + <paper-listbox id="search-results-paper-menu" selected="0" + role="presentation"> <template is="dom-repeat" id="searchResults" items="[[searchResultsToShow_]]"> <paper-item class="search-item" on-tap="onSinkClick_"> @@ -226,7 +227,7 @@ </div> </paper-item> </template> - </paper-menu> + </paper-listbox> </div> </div> </template> diff --git a/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css index 92f8b1a7dd1..e5b7f3e72da 100644 --- a/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css +++ b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css @@ -3,8 +3,8 @@ * found in the LICENSE file. */ #route-action-buttons { - @apply(--layout-horizontal); - @apply(--layout-end-justified); + @apply --layout-horizontal; + @apply --layout-end-justified; margin: 0 10px; padding: 0; white-space: nowrap; diff --git a/chromium/chrome/browser/resources/media_router/extension/BUILD.gn b/chromium/chrome/browser/resources/media_router/extension/BUILD.gn new file mode 100644 index 00000000000..3b002567f96 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/BUILD.gn @@ -0,0 +1,78 @@ +import("src/files.gni") + +group("all") { + deps = [ + ":media_router", + ] +} + +declare_args() { + # Determines whether JSCompiler should be used to typecheck + # JavaScript code for the Media Router extension. + enable_media_router_jscompile = false +} + +if (enable_media_router_jscompile) { + # Run JSCompiler for typechecking. + action("media_router_type_check") { + script = "//third_party/closure_compiler/compile2.py" + inputs = rebase_path(mr_module_files, ".", "src") + [ + "src/externs.js", + "src/mojo_externs.js", + ] + outputs = [ + target_gen_dir + "/$target_name.stamp", + ] + args = [ + "--out_file", + rebase_path(outputs[0], root_build_dir), + "--closure_args", + "dependency_mode=LOOSE", + "checks_only", + "--", + ] + rebase_path(inputs, root_build_dir) + } +} + +# Concatentate JS files to produce "module" JS files that can be +# loaded at runtime. This could be done by JSCompiler, but it depends +# on Java, and Java isn't always available. +action("media_router_modules") { + script = "concat_js_modules.py" + module_inputs = rebase_path(mr_module_files, ".", "src") + inputs = module_inputs + [ "prelude.js" ] + outputs = [] + foreach(module_name, mr_module_names) { + outputs += [ "${target_gen_dir}/${module_name}.js" ] + } + args = [ "--module-specs" ] + mr_module_specs + [ + "--prelude-file", + rebase_path("prelude.js", root_build_dir), + "--output-dir", + rebase_path(target_gen_dir + "/", root_build_dir), + "--", + ] + rebase_path(module_inputs, root_build_dir) +} + +# Produce the Media Router extension. At present, the extension isn't +# included in the Chromium distribution, but it can be sideloaded into +# Chromium for testing. +action("media_router") { + script = "assemble_extension.py" + inputs = [ + "manifest.yaml", + ] + outputs = [ + "$target_gen_dir/manifest.json", + ] + deps = [ + ":media_router_modules", + ] + if (enable_media_router_jscompile) { + deps += [ ":media_router_type_check" ] + } + args = [ + "--manifest_in=" + rebase_path("manifest.yaml", root_build_dir), + "--output_dir=" + rebase_path(target_gen_dir, root_build_dir), + ] +} diff --git a/chromium/chrome/browser/resources/media_router/extension/assemble_extension.py b/chromium/chrome/browser/resources/media_router/extension/assemble_extension.py new file mode 100644 index 00000000000..7b2df0a255b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/assemble_extension.py @@ -0,0 +1,41 @@ +# 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 script generates manifest.json from manifest.yaml. + +In the future it may copy other non-JS files into the target +directory, hence the name of the script. +""" + +import argparse +import json +import re + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--manifest_in") + parser.add_argument("--output_dir") + args = parser.parse_args() + + # Load the input file, removing YAML-style comment lines to produce + # valid JSON. + json_data = '' + with open(args.manifest_in) as manifest_in: + for line in manifest_in: + if re.match("^ *#", line): + # Insert an empty line so line numbers aren't changed. + json_data += '\n' + else: + json_data += line + + # Verify that the result is valid JSON. + json.loads(json_data) + + # Dump the output to the requested location. + with open(args.output_dir + "/manifest.json", "w") as manifest_out: + manifest_out.writelines(json_data) + + +main() diff --git a/chromium/chrome/browser/resources/media_router/extension/concat_js_modules.py b/chromium/chrome/browser/resources/media_router/extension/concat_js_modules.py new file mode 100644 index 00000000000..cda9ca1dd47 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/concat_js_modules.py @@ -0,0 +1,87 @@ +# 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 script concatenates JS files into a set of output files. + +Modules are specified as a list of strings of the form +module=<name>:<num>[:...], where <name> is the name of the module and +<num> is the number of JS files that should be concatenates to make +the module file, and if a second : is present, everything after it is +ignored. (This strange format is used because it is compatible with +JSCompiler's --module flag.) +""" + +import argparse +import os.path +import re + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--module-specs", nargs="+", + help="List of module specifiations.") + parser.add_argument( + "--output-dir", + help="Directory where output files are written.") + parser.add_argument( + "--prelude-file", + help="A prelude file included in every output module.") + parser.add_argument( + "sources", nargs="+", + help="JS input files.") + args = parser.parse_args() + + # Read the prelude file, which contains code to be placed at the + # start of each module. + with open(args.prelude_file, "r") as prelude_in: + prelude = prelude_in.read() + + # Loop over all specified modules, and simulaneously traverse the + # list of source files using `source_index`. + source_index = 0 + for spec in args.module_specs: + # Split the module spec into a module name and a count of source + # files to include in the module. + pre, sep, post = spec.partition("module=") + assert pre == "" + assert sep + parts = post.split(":") + module_name = parts[0] + input_count = int(parts[1]) + + # Write the module file. + module_file = os.path.join(args.output_dir, module_name + ".js") + with open(module_file, "w") as module_out: + module_out.write(prelude) + + # Append as many input files as requested by the module spec. + for i in range(input_count): + source_file = args.sources[source_index] + source_index += 1 + module_name = None + with open(source_file, "r") as source_in: + module_out.write("goog.scope(() => {\n") + # Copy input line by line. + for line in source_in: + m = re.match(r"goog\.module\('([^']+)'\);\n", line) + if m: + # Handle goog.module statements specially. + module_name = m.group(1) + module_out.write("let exports = {};\n") + else: + # Typical case: just copy the line verbatim. + module_out.write(line) + if module_name: + # Put exported names into the global namespace. That's + # not now goog.module is supposed to work, but it's close + # enough. + module_out.write( + "__setGlobal('{}', exports);\n".format(module_name)); + module_out.write("});\n") + + # Check that every source file has been appended to a module. + if source_index != len(args.sources): + sys.exit("Too many input files.") + +main() diff --git a/chromium/chrome/browser/resources/media_router/extension/manifest.yaml b/chromium/chrome/browser/resources/media_router/extension/manifest.yaml new file mode 100644 index 00000000000..4eeb52af9d8 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/manifest.yaml @@ -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. + +# NOTE: Only a subset of YAML syntax is allowed in this file. Lines +# containing comments are stripped, and the remaining lines must be +# valid JSON. + +{ + "name": "Chromium Media Router", + "version": "0.1", + "manifest_version": 2, + "description": "Provider for discovery and services for mirroring of Chromium Media Router", + "minimum_chrome_version": "37", + + "permissions": [ + "alarms", + "declarativeWebRequest", + "desktopCapture", + "dial", + "http://*/*", + "mediaRouterPrivate", + "metricsPrivate", + "storage", + "settingsPrivate", + "tabCapture", + "tabs" + ], + + # Background script. + "background": { + "scripts": [ + "common.js", + "mirroring_common.js", + "background_script.js" + ], + "persistent": false + }, + + # Google Feedback requires: + # script-src: https://feedback.googleusercontent.com https://www.google.com + # https://www.gstatic.com/feedback + # child-src: https://www.google.com + # + # Webview elements are implemented as a custom <object> elements that loads + # dynamic plugin data. Without an "object-src 'self'" permission in the CSP, + # webview elements fail to attach to extension pages (crbug.com/509854). + "content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' https://feedback.googleusercontent.com https://www.google.com https://www.gstatic.com; child-src https://www.google.com; connect-src 'self' http://*:* https://*:*; font-src https://fonts.gstatic.com; object-src 'self';", + + # Setting the public key fixes the extension id to: + # enhhojjnijigcajfphajepfemndkmdlo + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+hlN5FB+tjCsBszmBIvIcD/djLLQm2zZfFygP4U4/o++ZM91EWtgII10LisoS47qT2TIOg4Un4+G57elZ9PjEIhcJfANqkYrD3t9dpEzMNr936TLB2u683B5qmbB68Nq1Eel7KVc+F0BqhBondDqhvDvGPEV0vBsbErJFlNH7SQIDAQAB" +} diff --git a/chromium/chrome/browser/resources/media_router/extension/prelude.js b/chromium/chrome/browser/resources/media_router/extension/prelude.js new file mode 100644 index 00000000000..6140042e26d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/prelude.js @@ -0,0 +1,50 @@ +// 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. + +// Implementation of JSCompiler intrinsics for use when JSCompiler is +// not available. + +/** + * Sets the values of a global expression consisting of a + * dot-delimited list of identifiers. + */ +const __setGlobal = (name, value) => { + let parent = window; + const parts = name.split('.'); + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (i == parts.length - 1) { + parent[part] = value; + } else { + if (!parent[part]) { + parent[part] = {}; + } + parent = parent[part]; + } + } +}; + +const goog = { + provide(name) { + __setGlobal(name, {}); + }, + + require(name) { + let parent = window; + name.split('.').forEach(part => { + parent = parent[part]; + }); + return parent; + }, + + module: { + declareLegacyNamespace() {}, + }, + + forwardDeclare() {}, + + scope(body) { + body.call(window); + }, +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/background.js b/chromium/chrome/browser/resources/media_router/extension/src/background.js new file mode 100644 index 00000000000..e1b87123e7f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/background.js @@ -0,0 +1,9 @@ +// 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. + + +goog.require('mr.Init'); + + +mr.Init.init().then(undefined, err => window.close()); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/config.js b/chromium/chrome/browser/resources/media_router/extension/src/config.js new file mode 100644 index 00000000000..fc339414189 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/config.js @@ -0,0 +1,26 @@ +// 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 Configs. + */ + +goog.provide('mr.Config'); + + +/** + * Compiler flag used to enable debug/testing only components. The default + * value defined here is only used in open-source builds. + * @define {boolean} True if this extension was released through debug channel. + */ +mr.Config.isDebugChannel = true; + + +/** + * Compiler flag used to set logging level and other privacy sensitive config + * for public release. The default value defined here is only used in + * open-source builds. + * @define {boolean} True if this extension was released through public channel. + */ +mr.Config.isPublicChannel = false; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/event_listener.js b/chromium/chrome/browser/resources/media_router/extension/src/event_listener.js new file mode 100644 index 00000000000..8cef915b1a3 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/event_listener.js @@ -0,0 +1,188 @@ +// 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 Interface for registration of listeners of events that can + * wake the extension. All EventListeners must invoke + * |addOnStartup| in the first event loop in order to + * properly receive events that woke the extension. When adding a new + * EventListener, be sure to also add it to |mr.Init.addEventListeners_|. + */ + +goog.provide('mr.EventListener'); + +goog.require('mr.EventAnalytics'); +goog.require('mr.Module'); +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); + + + +/** + * Listens for an extension event conditionally, and forwards the event to a + * designated module. + * + * This is useful for event listeners that are not added when the extension + * initially starts up (mDNS listeners, for instance). When an event + * listener is ready to be added for the first time (and the module is ready + * to handle the event), |addListener()| should be called. By doing so, the + * event listener will be automatically added back at the top level the next + * time the extension wakes up from suspension, unless |removeListener()| is + * called. + * + * @implements {mr.PersistentData} + * @template EVENT + */ +mr.EventListener = class { + /** + * @param {!mr.EventAnalytics.Event} eventType The event type for this + * listener to record with analytics. + * @param {string} name Name of the handler for PersistentData. + * @param {mr.ModuleId} moduleId Name of the module handling the events. + * @param {!EVENT} eventHandler The event handler to listen to. + * @param {...*} listenerArgs Additional arguments when adding the listener, + * such as filters. + */ + constructor(eventType, name, moduleId, eventHandler, ...listenerArgs) { + /** @private @const {!mr.EventAnalytics.Event} */ + this.eventType_ = eventType; + + /** @private @const {string} */ + this.name_ = name; + + /** @private @const {mr.ModuleId} */ + this.moduleId_ = moduleId; + + /** @private @const {!EVENT} */ + this.eventHandler_ = eventHandler; + + /** @private @const {!Array<*>} */ + this.listenerArgs_ = listenerArgs; + + /** + * This field is stored as temporary data. Set to true if the listener was + * added, and will be added back the next time the extension wakes up via + * |addOnStartup()|. + * @private {boolean} + */ + this.hasListener_ = false; + + /** @private @const {?function(*)} */ + this.listener_ = (...args) => this.dispatchEvent_(...args); + } + + /** + * Adds back the event listener that was added before the last suspension. + * This method is called by mr.Init during the first event loop only. + */ + addOnStartup() { + mr.PersistentDataManager.register(this); + } + + /** + * Helper method to add the listener to the event. + * @private + */ + doAddListener_() { + this.eventHandler_.addListener(this.listener_, ...this.listenerArgs_); + } + + /** + * Adds event listener. No-ops if listener is already added. Event + * listeners will be added back automatically the next time extension wakes + * up, during |addOnStartup|. + */ + addListener() { + if (this.hasListener_) { + return; + } + this.hasListener_ = true; + this.doAddListener_(); + } + + /** + * Removes event listener. The next time extension wakes up, the event + * listener will not be added back during |addOnStartup|. + */ + removeListener() { + if (!this.hasListener_) { + return; + } + this.eventHandler_.removeListener(this.listener_); + this.hasListener_ = false; + } + + /** + * Implementations may override this method validate an incoming event before + * it is forwarded to the designated module. + * @param {...*} args + * @return {boolean} true if the event can be forwarded to the module. + */ + validateEvent(...args) { + return true; + } + + /** + * The entry point for incoming events. First the event will be validated. + * After that, the event will be forwarded to the module, which is loaded if + * necessary. Since asynchronous event handling is required, a value + * representing asynchronous handling will be returned synchronously, as + * required by the event. + * @param {...*} args Parameters for the incoming event. + * @return {*} false if the event is invalid. Otherwise, a value that + * represents that event will be handled asynchronously will be returned. + * @private + */ + dispatchEvent_(...args) { + mr.EventAnalytics.recordEvent(this.eventType_); + if (!this.validateEvent(...args)) { + return false; + } + mr.Module.load(this.moduleId_) + .then(module => module.handleEvent(this.eventHandler_, ...args)); + return this.deferredReturnValue(); + } + + /** + * Returns |true| if the event listener is already registered. + * @return {boolean} + */ + isRegistered() { + return this.hasListener_; + } + + /** + * @override + */ + getStorageKey() { + return 'mr.EventListener.' + this.name_; + } + + /** + * @override + */ + getData() { + return [this.hasListener_]; + } + + /** + * @override + */ + loadSavedData() { + const hadListener = + /** @type {boolean} */ ( + mr.PersistentDataManager.getTemporaryData(this)); + if (hadListener) { + this.addListener(); + } + } + + /** + * Implementations may override this method to provide a return value in the + * case the event is not handled synchronously. The default value is + * undefined. + * @return {*} + */ + deferredReturnValue() {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/event_listener_test.js b/chromium/chrome/browser/resources/media_router/extension/src/event_listener_test.js new file mode 100644 index 00000000000..a64895f5318 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/event_listener_test.js @@ -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. + +goog.setTestOnly('event_listener_test'); + +goog.require('mr.EventAnalytics'); +goog.require('mr.EventListener'); +goog.require('mr.Module'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.PromiseResolver'); +goog.require('mr.UnitTestUtils'); + + +describe('Tests event listeners', function() { + let mockEvent; + + beforeEach(function() { + mr.UnitTestUtils.mockChromeApi(); + mockEvent = jasmine.createSpyObj( + 'mockEvent', ['addListener', 'hasListener', 'removeListener']); + }); + + afterEach(function() { + mr.Module.clearForTest(); + mr.PersistentDataManager.clear(); + mr.UnitTestUtils.restoreChromeApi(); + }); + + it('EventListener addListener no listener args', function() { + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + listener.addListener(); + expect(mockEvent.addListener).toHaveBeenCalled(); + expect(listener.isRegistered()).toBe(true); + + listener.removeListener(); + expect(mockEvent.removeListener).toHaveBeenCalled(); + expect(listener.isRegistered()).toBe(false); + }); + + it('EventListener addListener with listener args', function() { + const listenerArgs = ['foo', 1]; + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent, ...listenerArgs); + listener.addListener(); + expect(mockEvent.addListener) + .toHaveBeenCalledWith(jasmine.any(Function), ...listenerArgs); + expect(listener.isRegistered()).toBe(true); + + listener.removeListener(); + expect(mockEvent.removeListener).toHaveBeenCalled(); + expect(listener.isRegistered()).toBe(false); + }); + + it('EventListener addOnStartup no prior register', function() { + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + listener.addOnStartup(); + expect(mockEvent.addListener).not.toHaveBeenCalled(); + }); + + it('EventListener addOnStartup registered before', function() { + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + listener.addOnStartup(); + expect(mockEvent.addListener.calls.count()).toBe(0); + listener.addListener(); + expect(mockEvent.addListener.calls.count()).toBe(1); + + mr.PersistentDataManager.suspendForTest(); + + const listener2 = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + // Registers with mr.PersistentDataManager again. It should see that it was + // previously registered before suspend, and re-adds the listener + // auatomatically. + listener2.addOnStartup(); + expect(mockEvent.addListener.calls.count()).toBe(2); + }); + + it('EventListener rejected invalid event', function() { + let savedListener = null; + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + mockEvent.addListener.and.callFake(listener => { + savedListener = listener; + }); + listener.addListener(); + expect(mockEvent.addListener).toHaveBeenCalled(); + expect(savedListener).not.toBeNull(); + spyOn(listener, 'validateEvent').and.returnValue(false); + + // Module won't be loaded since event did not pass validation. + spyOn(mr.Module, 'load'); + let returnedValue = savedListener('foo', 1); + expect(returnedValue).toBe(false); + expect(mr.Module.load.calls.count()).toBe(0); + }); + + it('EventListener dispatch event', function(done) { + spyOn(mr.EventAnalytics, 'recordEvent'); + let savedListener = null; + const listener = new mr.EventListener( + mr.EventAnalytics.Event.DIAL_ON_ERROR, 'MockEventListener', + 'SomeModule', mockEvent); + spyOn(listener, 'deferredReturnValue').and.returnValue(123); + mockEvent.addListener.and.callFake(listener => { + savedListener = listener; + }); + listener.addListener(); + expect(mockEvent.addListener).toHaveBeenCalled(); + expect(savedListener).not.toBeNull(); + + // Module not ready yet; events are queued up, and deferred value is + // returned synchronously. + let resolver = new mr.PromiseResolver(); + spyOn(mr.Module, 'load').and.returnValue(resolver.promise); + let returnedValue = savedListener('foo', 1); + expect(returnedValue).toBe(123); + returnedValue = savedListener('bar', 2); + expect(returnedValue).toBe(123); + expect(mr.Module.load.calls.count()).toBe(2); + + expect(mr.EventAnalytics.recordEvent) + .toHaveBeenCalledWith(mr.EventAnalytics.Event.DIAL_ON_ERROR); + expect(mr.EventAnalytics.recordEvent.calls.count()).toEqual(2); + + const module = jasmine.createSpyObj('mockModule', ['handleEvent']); + module.handleEvent.and.callFake((e, arg1, arg2) => { + if (arg1 == 'bar') { + expect(module.handleEvent).toHaveBeenCalledWith(mockEvent, 'foo', 1); + expect(module.handleEvent).toHaveBeenCalledWith(mockEvent, 'bar', 2); + done(); + } + }); + + // Fake loading the module. + resolver.resolve(module); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/extension_selector.js b/chromium/chrome/browser/resources/media_router/extension/src/extension_selector.js new file mode 100644 index 00000000000..652a20d0aa6 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/extension_selector.js @@ -0,0 +1,47 @@ +// 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 Selector for picking an MR extension to use. + + + */ + +goog.provide('mr.ExtensionId'); +goog.provide('mr.ExtensionSelector'); + + +/** + * @enum {string} + */ +mr.ExtensionId = { + PUBLIC: 'pkedcjkdefgpdelpbcmbmeomcjbeemfm', + DEV: 'enhhojjnijigcajfphajepfemndkmdlo' +}; + + +/** + * @return {Promise} Resolves if this extension should start itself, + * rejects otherwise. + */ +mr.ExtensionSelector.shouldStart = function() { + return new Promise((resolve, reject) => { + switch (window.location.host) { + case mr.ExtensionId.DEV: + resolve(); + break; + case mr.ExtensionId.PUBLIC: + chrome.management.get(mr.ExtensionId.DEV, result => { + if (chrome.runtime.lastError || !result.enabled) { + resolve(); + } else { + reject(Error('Dev extension is enabled')); + } + }); + break; + default: + reject(Error('Unknown extension id')); + } + }); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener.js b/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener.js new file mode 100644 index 00000000000..3f4e0800dfa --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener.js @@ -0,0 +1,80 @@ +// 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 Listener for messages received from external apps/extensions or + * web pages. + * + + */ + +goog.provide('mr.ExternalMessageListener'); + +goog.require('mr.EventAnalytics'); +goog.require('mr.EventListener'); +goog.require('mr.InternalMessageType'); +goog.require('mr.ModuleId'); + + +mr.ExternalMessageListener = class extends mr.EventListener { + constructor() { + super( + mr.EventAnalytics.Event.RUNTIME_ON_MESSAGE_EXTERNAL, + 'ExternalMessageListener', mr.ModuleId.PROVIDER_MANAGER, + chrome.runtime.onMessageExternal); + } + + /** + * @override + */ + validateEvent(message, sender, sendResponse) { + // Make sure all messages have a sender |id| and that the ID is whitelisted. + // If messages have a |tab| they are from a web page (most likely the Cast + // setup page). + if (!sender.id || + mr.ExternalMessageListener.WHITELIST_.indexOf(sender.id) == -1) { + return false; + } + + // Check if message type is valid. + return message.type == mr.InternalMessageType.START || + message.type == mr.InternalMessageType.STOP || + message.type == mr.InternalMessageType.SUBSCRIBE_LOG_DATA; + } + + /** + * @override + */ + deferredReturnValue() { + // Indicates the messaging channel should be kept open until + // sendResponse() is called. + return true; + } + + /** @return {!mr.ExternalMessageListener} */ + static get() { + if (!mr.ExternalMessageListener.listener_) { + mr.ExternalMessageListener.listener_ = new mr.ExternalMessageListener(); + } + return mr.ExternalMessageListener.listener_; + } +}; + + +/** @private {?mr.ExternalMessageListener} */ +mr.ExternalMessageListener.listener_ = null; + + +/** + * List of app ids which are allowed to use the command messages. + * These must also be included in the manifest 'externally_connectable' list. + * + * @private @const {!Array<string>} + */ +mr.ExternalMessageListener.WHITELIST_ = [ + // ghire kiosk app + 'idmofbkcelhplfjnmmdolenpigiiiecc', // prod + 'ggedfkijiiammpnbdadhllnehapomdge', // staging + 'njjegkblellcjnakomndbaloifhcoccg' // dev +]; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener_test.js b/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener_test.js new file mode 100644 index 00000000000..e1f5480a27a --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/external_message_listener_test.js @@ -0,0 +1,67 @@ +// 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. + +goog.setTestOnly('external_message_listener_test'); + +goog.require('mr.ExternalMessageListener'); +goog.require('mr.InternalMessageType'); +goog.require('mr.UnitTestUtils'); + +describe('Tests mr.ExternalMessageListener', () => { + let listener; + + beforeEach(() => { + mr.UnitTestUtils.mockChromeApi(); + listener = new mr.ExternalMessageListener(); + }); + + afterEach(() => { + mr.UnitTestUtils.restoreChromeApi(); + }); + + it('rejects sender not in whitelist', () => { + const sender = {'id': 'invalid'}; + const callback = response => { + fail('should not have called back'); + }; + + expect(listener.validateEvent({}, sender, callback)).toBe(false); + }); + + it('invalid type returns empty and closes channel', () => { + const sender = {'id': 'njjegkblellcjnakomndbaloifhcoccg'}; + const callback = response => { + fail('should not have called back'); + }; + + expect(listener.validateEvent({}, sender, callback)).toBe(false); + }); + + it('valid start message', () => { + const sender = {'id': 'njjegkblellcjnakomndbaloifhcoccg'}; + const callback = response => { + fail('should not have called back'); + }; + const message = {'type': mr.InternalMessageType.START}; + expect(listener.validateEvent(message, sender, callback)).toBe(true); + }); + + it('valid stop message', () => { + const sender = {'id': 'njjegkblellcjnakomndbaloifhcoccg'}; + const callback = response => { + fail('should not have called back'); + }; + const message = {'type': mr.InternalMessageType.STOP}; + expect(listener.validateEvent(message, sender, callback)).toBe(true); + }); + + it('valid log subscription message', () => { + const sender = {'id': 'njjegkblellcjnakomndbaloifhcoccg'}; + const callback = response => { + fail('should not have called back'); + }; + const message = {'type': mr.InternalMessageType.SUBSCRIBE_LOG_DATA}; + expect(listener.validateEvent(message, sender, callback)).toBe(true); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/externs.js b/chromium/chrome/browser/resources/media_router/extension/src/externs.js new file mode 100644 index 00000000000..eac0a60d527 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/externs.js @@ -0,0 +1,917 @@ +// 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. + +// NOTE: Througout this file, we use constructors instead of @typedefs to +// declare object properties. This prevents the JSCompiler from renaming these +// properties. These can be converted to shorter @typedef declarations when the +// JSCompiler adds full support for @typedef in externs. + +////////////////////////////////////////////////////////////////////////////// +// Externs for WebRTC PeerConnection as in +// http://www.w3.org/TR/2012/WD-webrtc-20120821/ +////////////////////////////////////////////////////////////////////////////// + + +/** @type {string} */ +MediaStreamEvent.prototype.type; + + +/** @type {string} */ +RTCPeerConnection.prototype.iceConnectionState; + +////////////////////////////////////////////////////////////////////////////// +// Externs for processes API +// See: https://developer.chrome.com/extensions/processes +////////////////////////////////////////////////////////////////////////////// + + +/** + * @const + */ +chrome.processes = {}; + + +/** + * @param {number} tabId + * @param {Function} callback + */ +chrome.processes.getProcessIdForTab = function(tabId, callback) {}; + + +/** + * @const + */ +chrome.processes.onUpdated = {}; + + +/** + * @param {Function} callback + */ +chrome.processes.onUpdated.addListener = function(callback) {}; + + +/** + * @param {Function} callback + */ +chrome.processes.onUpdated.removeListener = function(callback) {}; + +////////////////////////////////////////////////////////////////////////////// +// Externs for Tab Capture API +// See: https://developer.chrome.com/extensions/tabCapture.html +////////////////////////////////////////////////////////////////////////////// + + +/** @const {*} */ +chrome.tabCapture = {}; + + +/** + * @param {MediaConstraints} constraints + * @param {function(MediaStream)} callback + */ +chrome.tabCapture.capture = function(constraints, callback) {}; + + +/** + * @param {string} startUrl + * @param {MediaConstraints} constraints + * @param {function(MediaStream)} callback + */ +chrome.tabCapture.captureOffscreenTab = function( + startUrl, constraints, callback) {}; + + +/** + * @type {ChromeEvent} + */ +chrome.tabCapture.onStatusChanged; + +////////////////////////////////////////////////////////////////////////////// +// Externs for Desktop Capture API +// See: https://developer.chrome.com/extensions/desktopCapture.html +////////////////////////////////////////////////////////////////////////////// + + +/** @const */ +chrome.desktopCapture = {}; + + +/** + * @param {Array<string>} sources + * @param {function(string)} callback + * @return {number} desktopMediaRequestId + */ +chrome.desktopCapture.chooseDesktopMedia = function(sources, callback) {}; + + +/** + * @param {number} desktopMediaRequestId + */ +chrome.desktopCapture.cancelChooseDesktopMedia = function( + desktopMediaRequestId) {}; + +////////////////////////////////////////////////////////////////////////////// +// Externs for chrome.dial API +// IDL: http://goo.gl/qmKqro +////////////////////////////////////////////////////////////////////////////// + + +/** @const {*} */ +chrome.dial = {}; + + + +/** @constructor */ +chrome.dial.DialDevice = function() {}; + + +/** @type {string} */ +chrome.dial.DialDevice.prototype.deviceLabel; + + +/** @type {string} */ +chrome.dial.DialDevice.prototype.deviceDescriptionUrl; + + +/** @type {number} */ +chrome.dial.DialDevice.prototype.configId; + + +/** @constructor */ +chrome.dial.DialDeviceDescription = function() {}; + + +/** @type {string} */ +chrome.dial.DialDeviceDescription.prototype.deviceLabel; + + +/** @type {string} */ +chrome.dial.DialDeviceDescription.prototype.appUrl; + + +/** @type {string} */ +chrome.dial.DialDeviceDescription.prototype.deviceDescription; + + + +/** @constructor */ +chrome.dial.DialError = function() {}; + + +/** @type {string} */ +chrome.dial.DialError.prototype.code; + + +/** + * @param {function(boolean)} callback The result (true if successful). + */ +chrome.dial.discoverNow = function(callback) {}; + +/** + * @param {string} deviceLabel + * @param {function(?chrome.dial.DialDeviceDescription)} callback + */ +chrome.dial.fetchDeviceDescription = function(deviceLabel, callback) {}; + + +/** @type {ChromeEvent} */ +chrome.dial.onDeviceList; + + +/** @type {ChromeEvent} */ +chrome.dial.onError; + + +////////////////////////////////////////////////////////////////////////////// +// Externs for the chrome.cast.channel API +// IDL: http://goo.gl/G1hmAI +////////////////////////////////////////////////////////////////////////////// + + +/** @const */ +chrome.cast = chrome.cast || {}; + + +/** @const */ +chrome.cast.channel = {}; + + + +/** @constructor */ +chrome.cast.channel.ChannelInfo = function() {}; + + +/** @type {number} */ +chrome.cast.channel.ChannelInfo.prototype.channelId; + + +/** @type {string} */ +chrome.cast.channel.ChannelInfo.prototype.readyState; + + +/** @type {?string} */ +chrome.cast.channel.ChannelInfo.prototype.errorState; + + +/** @type {?boolean} */ +chrome.cast.channel.ChannelInfo.prototype.keepAlive; + + +/** @type {?boolean} */ +chrome.cast.channel.ChannelInfo.prototype.audioOnly; + + +/** @type {!chrome.cast.channel.ConnectInfo} */ +chrome.cast.channel.ChannelInfo.prototype.connectInfo; + + + +/** @constructor */ +chrome.cast.channel.MessageInfo = function() {}; + + +/** @type {string} */ +chrome.cast.channel.MessageInfo.prototype.namespace_; + + +/** @type {*} */ +chrome.cast.channel.MessageInfo.prototype.data; + + +/** @type {string} */ +chrome.cast.channel.MessageInfo.prototype.sourceId; + + +/** @type {string} */ +chrome.cast.channel.MessageInfo.prototype.destinationId; + + + +/** @constructor */ +chrome.cast.channel.ConnectInfo = function() {}; + + +/** @type {string} */ +chrome.cast.channel.ConnectInfo.prototype.ipAddress; + + +/** @type {number} */ +chrome.cast.channel.ConnectInfo.prototype.port; + + +/** @type {string} */ +chrome.cast.channel.ConnectInfo.prototype.auth; + + +/** @type {number} */ +chrome.cast.channel.ConnectInfo.prototype.timeout; + + +/** @type {number} */ +chrome.cast.channel.ConnectInfo.prototype.livenessTimeout; + + +/** @type {number} */ +chrome.cast.channel.ConnectInfo.prototype.pingInterval; + + + +/** @constructor */ +chrome.cast.channel.ErrorInfo = function() {}; + + +/** @type {string} */ +chrome.cast.channel.ErrorInfo.prototype.errorState; + + +/** @type {?number} */ +chrome.cast.channel.ErrorInfo.prototype.eventType; + + +/** @type {?number} */ +chrome.cast.channel.ErrorInfo.prototype.challengeReplyErrorType; + + +/** @type {?number} */ +chrome.cast.channel.ErrorInfo.prototype.netReturnValue; + + +/** @type {?number} */ +chrome.cast.channel.ErrorInfo.prototype.nssErrorCode; + + +/** + * @param {!chrome.cast.channel.ConnectInfo} connectInfo + * @param {function(!chrome.cast.channel.ChannelInfo=)} callback + */ +chrome.cast.channel.open = function(connectInfo, callback) {}; + + +/** + * @param {!chrome.cast.channel.ChannelInfo} channel + * @param {!chrome.cast.channel.MessageInfo} message + * @param {function(!chrome.cast.channel.ChannelInfo=)} callback + */ +chrome.cast.channel.send = function(channel, message, callback) {}; + + +/** + * @param {!chrome.cast.channel.ChannelInfo} channel + * @param {function(!chrome.cast.channel.ChannelInfo=)} callback + */ +chrome.cast.channel.close = function(channel, callback) {}; + + + +/** @const */ +chrome.cast.channel.onMessage; + + +/** + * @param {!function(!chrome.cast.channel.ChannelInfo, + * !chrome.cast.channel.MessageInfo)} listener + */ +chrome.cast.channel.onMessage.addListener = function(listener) {}; + + +/** + * @param {!function(!chrome.cast.channel.ChannelInfo, + * !chrome.cast.channel.MessageInfo)} listener + */ +chrome.cast.channel.onMessage.removeListener = function(listener) {}; + + +/** @const */ +chrome.cast.channel.onError; + + +/** + * @param {!function(!chrome.cast.channel.ChannelInfo, + * (!chrome.cast.channel.ErrorInfo|undefined))} listener + */ +chrome.cast.channel.onError.addListener = function(listener) {}; + + +/** + * @param {!function(!chrome.cast.channel.ChannelInfo, + * (!chrome.cast.channel.ErrorInfo|undefined))} listener + */ +chrome.cast.channel.onError.removeListener = function(listener) {}; + + +/** @const */ +chrome.cast.media = {}; + + +/** + * @param {function(ChromeWindow)} callback + */ +chrome.browserAction.openPopup = function(callback) {}; + +////////////////////////////////////////////////////////////////////////////// +// Externs for the chrome.cast.streaming APIs +// See: http://goo.gl/yInHUU +////////////////////////////////////////////////////////////////////////////// + + +/** @const {*} */ +chrome.cast.streaming = {}; + + +/** @const {*} */ +chrome.cast.streaming.session = {}; + + +/** + * @param {?MediaStreamTrack} audio + * @param {?MediaStreamTrack} video + * @param {!function(?number, ?number, !number)} callback + */ +chrome.cast.streaming.session.create = function(audio, video, callback) {}; + + +/** @const {*} */ +chrome.cast.streaming.rtpStream = {}; + + + +/** @constructor */ +chrome.cast.streaming.rtpStream.CodecSpecificParams = function() {}; + + + +/** @constructor */ +chrome.cast.streaming.rtpStream.RtpPayloadParams = function() {}; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.maxLatency; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.minLatency; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.animatedLatency; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.payloadType; + + +/** @type {string} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.codecName; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.ssrc; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.feedbackSsrc; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.clockRate; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.minBitrate; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.maxBitrate; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.channels; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.maxFrameRate; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.width; + + +/** @type {number} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.height; + + +/** @type {string} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.aesKey; + + +/** @type {string} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.aesIvMask; + + +/** @type {Array.<chrome.cast.streaming.rtpStream.CodecSpecificParams>} */ +chrome.cast.streaming.rtpStream.RtpPayloadParams.prototype.codecSpecificParams; + + + +/** @constructor */ +chrome.cast.streaming.rtpStream.RtpParams = function() {}; + + +/** @type {chrome.cast.streaming.rtpStream.RtpPayloadParams} */ +chrome.cast.streaming.rtpStream.RtpParams.prototype.payload; + + +/** @type {Array.<string>} */ +chrome.cast.streaming.rtpStream.RtpParams.prototype.rtcpFeatures; + + +/** + * @param {!number} streamId + */ +chrome.cast.streaming.rtpStream.destroy = function(streamId) {}; + + +/** + * @param {!number} streamId + * @return {!Array.<!chrome.cast.streaming.rtpStream.RtpParams>} + */ +chrome.cast.streaming.rtpStream.getSupportedParams = function(streamId) { + return [new chrome.cast.streaming.rtpStream.RtpParams]; +}; + + +/** + * @param {!number} streamId + * @param {!chrome.cast.streaming.rtpStream.RtpParams} params + */ +chrome.cast.streaming.rtpStream.start = function(streamId, params) {}; + + +/** + * @param {!number} streamId + */ +chrome.cast.streaming.rtpStream.stop = function(streamId) {}; + + +/** + * @param {!number} streamId + * @param {!boolean} enable + */ +chrome.cast.streaming.rtpStream.toggleLogging = function(streamId, enable) {}; + + +/** + * @param {!number} streamId + * @param {!string} extraData + * @param {!function(ArrayBuffer)} callback + */ +chrome.cast.streaming.rtpStream.getRawEvents = function( + streamId, extraData, callback) {}; + + +/** + * @param {!number} streamId + * @param {!function(Object)} callback + */ +chrome.cast.streaming.rtpStream.getStats = function(streamId, callback) {}; + + +/** @const {*} */ +chrome.cast.streaming.rtpStream.onStarted = {}; + + +/** + * @param {!function(!number)} listener + */ +chrome.cast.streaming.rtpStream.onStarted.addListener = function(listener) {}; + + +/** + * @param {!function(!number)} listener + */ +chrome.cast.streaming.rtpStream.onStarted.removeListener = function(listener) { +}; + + +/** @const {*} */ +chrome.cast.streaming.rtpStream.onStopped = {}; + + +/** + * @param {!function(!number)} listener + */ +chrome.cast.streaming.rtpStream.onStopped.addListener = function(listener) {}; + + +/** + * @param {!function(!number)} listener + */ +chrome.cast.streaming.rtpStream.onStopped.removeListener = function(listener) { +}; + + +/** @const {*} */ +chrome.cast.streaming.rtpStream.onError = {}; + + +/** + * @param {!function(number, string)} listener + */ +chrome.cast.streaming.rtpStream.onError.addListener = function(listener) {}; + + +/** + * @param {!function(number, string)} listener + */ +chrome.cast.streaming.rtpStream.onError.removeListener = function(listener) {}; + + + +/** @constructor */ +chrome.cast.streaming.udpTransport.IPEndPoint = function() {}; + + +/** @type {string} */ +chrome.cast.streaming.udpTransport.IPEndPoint.prototype.address; + + +/** @type {number} */ +chrome.cast.streaming.udpTransport.IPEndPoint.prototype.port; + + +/** + * @param {!number} streamId + */ +chrome.cast.streaming.udpTransport.destroy = function(streamId) {}; + + +/** @const {*} */ +chrome.cast.streaming.udpTransport = {}; + + +/** + * @param {!number} transportId + * @param {!chrome.cast.streaming.udpTransport.IPEndPoint} ipEndPoint + */ +chrome.cast.streaming.udpTransport.setDestination = function( + transportId, ipEndPoint) {}; + + +/** + * @param {!number} transportId + * @param {!Object} options + */ +chrome.cast.streaming.udpTransport.setOptions = function(transportId, options) { +}; + + +/** @const {*} */ +chrome.mojoPrivate = {}; + + +/** + * @param {!string} moduleName + * @return {!Promise.<T>} + * @template T + */ +chrome.mojoPrivate.requireAsync = function(moduleName) { + return Promise.resolve(Object.create(null)); +}; + + +////////////////////////////////////////////////////////////////////////////// +// Externs for UMA analytics +////////////////////////////////////////////////////////////////////////////// + + +/** @const {*} */ +chrome.metricsPrivate = {}; + + +/** + * Records an elapsed time of no more than 10 seconds. The sample value is + * specified in milliseconds. + * @param {string} metricName + * @param {number} value + */ +chrome.metricsPrivate.recordTime = function(metricName, value) {}; + + +/** + * Records an elapsed time of no more than 3 minutes. The sample value is + * specified in milliseconds. + * @param {string} metricName + * @param {number} value + */ +chrome.metricsPrivate.recordMediumTime = function(metricName, value) {}; + + +/** + * Records an elapsed time of no more than 1 hour. The sample value is + * specified in milliseconds. + * @param {string} metricName + * @param {number} value + */ +chrome.metricsPrivate.recordLongTime = function(metricName, value) {}; + + +/** + * Records an action performed by the user. + * @param {string} name + */ +chrome.metricsPrivate.recordUserAction = function(name) {}; + + +/** + * Records a value than can range from 1 to 100. + * @param {string} metricName + * @param {number} count + */ +chrome.metricsPrivate.recordSmallCount = function(metricName, count) {}; + + +/** + * Returns true if the user opted in to sending crash reports. + * @param {!function(boolean)} callback + */ +chrome.metricsPrivate.getIsCrashReportingEnabled = function(callback) {}; + + +/** + * Describes the type of metric that is to be collected. + * @typedef {{ + * metricName: string, + * type: string, + * min: number, + * max: number, + * buckets: number + * }} + */ +var MetricType; + + +/** + * Adds a value to the given metric. + * @param {MetricType} metricType + * @param {number} value + */ +chrome.metricsPrivate.recordValue = function(metricType, value) {}; + + +////////////////////////////////////////////////////////////////////////////// +// Externs subset for settingsPrivate API. +// see chromium/src/third_party/closure_compiler/externs/settings_private.js +////////////////////////////////////////////////////////////////////////////// + + +/** @const {*} */ +chrome.settingsPrivate = {}; + + +/** + * @enum {string} + */ +chrome.settingsPrivate.PrefType = { + BOOLEAN: 'BOOLEAN', + NUMBER: 'NUMBER', + STRING: 'STRING', + URL: 'URL', + LIST: 'LIST', + DICTIONARY: 'DICTIONARY', +}; + + +/** + * @typedef {{ + * key: string, + * type: !chrome.settingsPrivate.PrefType, + * value: *, + * }} + */ +chrome.settingsPrivate.PrefObject; + + +/** + * @param {string} name + * @param {function(!chrome.settingsPrivate.PrefObject):void} callback + */ +chrome.settingsPrivate.getPref = function(name, callback) {}; + + +/** + * @const + */ +chrome.settingsPrivate.onPrefsChanged = {}; + + +/** + * @param {function(!Array<!Object>):void} callback + */ +chrome.settingsPrivate.onPrefsChanged.hasListener = function(callback) {}; + + +/** + * @param {function(!Array<!Object>):void} callback + */ +chrome.settingsPrivate.onPrefsChanged.addListener = function(callback) {}; + + +/** + * @param {function(!Array<!Object>):void} callback + */ +chrome.settingsPrivate.onPrefsChanged.removeListener = function(callback) {}; + + +////////////////////////////////////////////////////////////////////////////// +// Externs for Google Calendar v3 API as in +// developers.google.com/google-apps/calendar/v3/reference/events#resource +////////////////////////////////////////////////////////////////////////////// + + +/** @const */ +chrome.cast.calendar = {}; + + + +/** @constructor */ +chrome.cast.calendar.Calendar = function() {}; + + +/** @type {string} */ +chrome.cast.calendar.Calendar.prototype.id; + + + +/** @constructor */ +chrome.cast.calendar.Event = function() {}; + + +/** @type {string} */ +chrome.cast.calendar.Event.prototype.summary; + + +/** @type {string} */ +chrome.cast.calendar.Event.prototype.hangoutLink; + + +/** + * @typedef {{ + * date: string, + * dateTime: string + * }} + */ +chrome.cast.calendar.Event.prototype.start; + + +/** + * @typedef {{ + * date: string, + * dateTime: string + * }} + */ +chrome.cast.calendar.Event.prototype.end; + + +////////////////////////////////////////////////////////////////////////////// +// Externs for Google Hangouts v1 API +////////////////////////////////////////////////////////////////////////////// + + +/** @const */ +chrome.cast.hangout = {}; + + +/** + * @typedef {{ + * 'service': (string|undefined), + * 'value': (string|undefined) + * }} + */ +chrome.cast.hangout.ExternalKey; + + +/** + * @typedef {{ + * 'hangout_id': (string|undefined), + * 'participant_id': (string|undefined), + * 'user_id': (string|undefined), + * 'display_name': (string|undefined), + * 'role': (string|undefined), + * 'client_type': (string|undefined), + * 'participant_state': (string|undefined), + * 'joined': (boolean|undefined) + * }} + */ +chrome.cast.hangout.Participant; + + +/** + * @typedef {{ + * 'hangout_id': (string|undefined), + * 'type': (string|undefined), + * 'external_key': (chrome.cast.hangout.ExternalKey|undefined), + * 'company_title': (string|undefined), + * 'meeting_room_name': (string|undefined), + * 'meeting_domain': (string|undefined), + * "sharing_url": (string|undefined), + * }} + */ +chrome.cast.hangout.Hangout; + + + +/** + + * @see https://developer.chrome.com/extensions/tabs#event-onUpdated + * @constructor + */ +function TabChangeInfo() {} + + +/** @type {string} */ +TabChangeInfo.prototype.status; + + +/** @type {string} */ +TabChangeInfo.prototype.url; + + +/** @type {boolean} */ +TabChangeInfo.prototype.pinned; + + +/** @type {boolean} */ +TabChangeInfo.prototype.audible; + + +/** @type {string} */ +TabChangeInfo.prototype.favIconUrl; + +////////////////////////////////////////////////////////////////////////////// +// Externs for declarativeWebRequest (not used except for channel checking) +////////////////////////////////////////////////////////////////////////////// + + +/** @type {Object} */ +chrome.declarativeWebRequest; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/files.gni b/chromium/chrome/browser/resources/media_router/extension/src/files.gni new file mode 100644 index 00000000000..3770bbca1cd --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/files.gni @@ -0,0 +1,145 @@ +# Generated file. Do not modify! + +mr_module_names = [ + "common", + "mirroring_common", + "background_script", +] +mr_module_specs = [ + "module=common:8", + "module=mirroring_common:73:common", + "module=background_script:4:mirroring_common", +] +mr_module_files = [ + # common files + "config.js", + "utils/assertions.js", + "utils/logger.js", + "utils/analytics.js", + "utils/promise_resolver.js", + "providers/common/retry.js", + "utils/xhr_manager.js", + "internal_message.js", + + # mirroring_common files + "manager/route_message_port.js", + "interface_data/issue.js", + "manager/cancellable_promise.js", + "mirror_services/mirror_analytics.js", + "utils/platform_utils.js", + "mirror_services/mirror_config.js", + "mirror_services/stream_capture/capture_parameters.js", + "mirror_services/stream_capture/mirror_media_stream.js", + "module.js", + "utils/event_analytics.js", + "utils/media_source_utils.js", + "mirror_services/mirror_service.js", + "mirror_services/mirror_service_name.js", + "mirror_services/mirror_activity.js", + "utils/tab_utils.js", + "mirror_services/mirror_session.js", + "mirror_services/mirror_settings.js", + "webrtc/messages.js", + "webrtc/peer_connection_analytics.js", + "webrtc/peer_connection.js", + "interface_data/sink.js", + "persistent_data.js", + "utils/base64.js", + "utils/sha1.js", + "utils/string_utils.js", + "providers/common/sink_utils.js", + "providers/dial/sink_app_status.js", + "providers/dial/dial_sink.js", + "event_listener.js", + "utils/device_counts.js", + "utils/device_counts_provider.js", + "utils/promise_utils.js", + "providers/common/id_generator.js", + "interface_data/media_route_controller.js", + "utils/mojo_utils.js", + "manager/route_id.js", + "interface_data/route.js", + "manager/provider.js", + "providers/common/runtime_error_utils.js", + "interface_data/route_request_error.js", + "interface_data/sink_list.js", + "manager/presentation_enums.js", + "manager/sink_availability.js", + "providers/dial/dial_analytics.js", + "providers/dial/dial_provider_callbacks.js", + "utils/event_target.js", + "manager/provider_manager_callbacks.js", + "providers/common/net_utils.js", + "providers/common/xhr_utils.js", + "providers/dial/dial_client.js", + "providers/dial/dial_activity.js", + "providers/dial/dial_activity_records.js", + "providers/dial/dial_sink_discovery_service.js", + "providers/dial/dial_app_discovery_service.js", + "providers/dial/presentation_url.js", + "providers/dial/dial_provider.js", + "interface_data/sink_search_criteria.js", + "utils/fixed_size_queue.js", + "log_manager.js", + "utils/object_utils.js", + "presentation_services/presentation_session.js", + "presentation_services/cloud_webrtc/webrtc_presentation_session.js", + "external_message_listener.js", + "internal_message_listener.js", + "init_helper.js", + "interface_data/route_message.js", + "interface_data/mojo.js", + "manager/mr_event_senders/throttling_sender.js", + "manager/mr_event_senders/route_message_sender.js", + "manager/provider_events.js", + "manager/route_message_port_impl.js", + "utils/throttle.js", + "manager/provider_manager.js", + + # background_script files + "extension_selector.js", + "providers/test/test_provider.js", + "init.js", + "background.js", +] +mr_test_files = [ + "event_listener_test.js", + "external_message_listener_test.js", + "init_test.js", + "interface_data/media_route_controller_test.js", + "internal_message_listener_test.js", + "manager/mr_event_senders/route_message_sender_test.js", + "manager/mr_event_senders/throttling_sender_test.js", + "manager/provider_manager_test.js", + "manager/route_id_test.js", + "mirror_services/mirror_activity_test.js", + "mirror_services/mirror_analytics_test.js", + "mirror_services/mirror_session_test.js", + "mirror_services/mirror_settings_test.js", + "mirror_services/stream_capture/mirror_media_stream_test.js", + "module_test.js", + "persistent_data_test.js", + "providers/common/id_generator_test.js", + "providers/common/net_utils_test.js", + "providers/dial/dial_activity_records_test.js", + "providers/dial/dial_analytics_test.js", + "providers/dial/dial_app_discovery_service_test.js", + "providers/dial/dial_client_test.js", + "providers/dial/dial_provider_test.js", + "providers/dial/dial_sink_discovery_service_test.js", + "providers/dial/dial_sink_test.js", + "providers/dial/presentation_url_test.js", + "providers/test/test_provider_test.js", + "utils/analytics_test.js", + "utils/base64_test.js", + "utils/event_analytics_test.js", + "utils/fixed_size_queue_test.js", + "utils/logger_test.js", + "utils/media_source_utils_test.js", + "utils/mock_promise_test.js", + "utils/object_utils_test.js", + "utils/sha1_test.js", + "utils/throttle_test.js", + "utils/xhr_manager_test.js", + "webrtc/peer_connection_test.js", +] diff --git a/chromium/chrome/browser/resources/media_router/extension/src/init.js b/chromium/chrome/browser/resources/media_router/extension/src/init.js new file mode 100644 index 00000000000..6e6140e6c94 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/init.js @@ -0,0 +1,157 @@ +// 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. + +goog.provide('mr.Init'); + +goog.require('mr.Config'); +goog.require('mr.ExtensionSelector'); +goog.require('mr.InitHelper'); +goog.require('mr.LogManager'); +goog.require('mr.Logger'); +goog.require('mr.MediaRouterService'); +goog.require('mr.MediumTiming'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.ProviderManager'); +goog.require('mr.TestProvider'); + + +/** @private {mr.Logger} */ +mr.Init.logger_ = mr.Logger.getInstance('mr.Init'); + + +/** @type {string} */ +mr.Init.FIRST_WAKE_DURATION = 'MediaRouter.Provider.FirstWakeDuration'; + + +/** @type {string} */ +mr.Init.WAKE_DURATION = 'MediaRouter.Provider.WakeDuration'; + + +/** @private {?mr.MediumTiming} */ +mr.Init.wakeTiming_; + + +/** @private {?mr.ProviderManager} */ +mr.Init.providerManager_; + + +/** + * @param {!mr.ProviderManager} providerManager + * @return {!Array.<!mr.Provider>} + * @private + */ +mr.Init.getProviders_ = function(providerManager) { + const providers = mr.InitHelper.getProviders(providerManager); + if (!mr.Config.isPublicChannel) { + providers.push(new mr.TestProvider(providerManager)); + } + return providers; +}; + + +/** + * @return {!Promise} + * @private + */ +mr.Init.initProviderManager_ = function() { + return mr.ExtensionSelector.shouldStart() + .then(mr.MediaRouterService.getInstance) + .then(result => { + if (!result['mrService']) { + throw Error('Failed to get MR service'); + } + const mrInstanceId = result['mrInstanceId']; + if (!mrInstanceId) { + throw Error('Failed to get MR instance ID.'); + } + mr.Init.logger_.info('MR instance ID: ' + mrInstanceId); + const mediaRouterService = + /** @type {!mr.MediaRouterService} */ (result['mrService']); + if (!mr.Init.providerManager_) { + throw Error('providerManager not initialized.'); + } + /** @type {!mr.ProviderManager} */ + const providerManager = mr.Init.providerManager_; + mediaRouterService.setHandlers(providerManager); + + if (mr.PersistentDataManager.isChromeReloaded(mrInstanceId)) { + mr.Init.wakeTiming_.setName(mr.Init.FIRST_WAKE_DURATION); + } + chrome.runtime.onSuspend.addListener( + mr.Init.wakeTiming_.end.bind(mr.Init.wakeTiming_)); + + mr.PersistentDataManager.initialize(mrInstanceId); + + mr.LogManager.getInstance().registerDataManager(); + + const providers = mr.Init.getProviders_(providerManager); + if (!mr.Config.isDebugChannel) { + // Log unhandled promise rejections for external channels, + // but leave them as thrown exceptions for internal. + window.addEventListener('unhandledrejection', event => { + let e = event.reason; + if (!e.stack) { + e = Error(e); + } + mr.Init.logger_.error( + 'Unhandled promise rejection.', + /** @type {Error} */ (e)); + }); + } + providerManager.initialize( + mediaRouterService, providers, result['mrConfig']); + }) + .then(undefined, error => { + mr.Init.logger_.warning(error.message); + throw error; + }); +}; + + +/** + * Exposed for testing. + + * @return {!Array<!mr.EventListener>} + * @private + */ +mr.Init.getAllListeners_ = function() { + return [ + ...mr.InitHelper.getListeners(), + ]; +}; + + +/** + * Registers all event listeners. + * @private + */ +mr.Init.addEventListeners_ = function() { + mr.Init.getAllListeners_().forEach( + eventListener => eventListener.addOnStartup()); + mr.InitHelper.addEventListeners(); + + // Listen for an event that always get invoked on browser startup. This is + // necessary because Media Router must know the extension ID in order to wake + // the extension up, and MR gets the ID when the event page activates for the + // first time. If the event page never activates, then MR will never be able + // to connect to it. + + chrome.runtime.onStartup.addListener(() => {}); +}; + + +/** + * @return {!Promise} + */ +mr.Init.init = function() { + mr.LogManager.getInstance().init(); + mr.Init.wakeTiming_ = new mr.MediumTiming(mr.Init.WAKE_DURATION); + + /** @type {!mr.ProviderManager} */ + const providerManager = new mr.ProviderManager(); + mr.Init.providerManager_ = providerManager; + const promise = mr.Init.initProviderManager_(); + mr.Init.addEventListeners_(); + return promise; +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/init_helper.js b/chromium/chrome/browser/resources/media_router/extension/src/init_helper.js new file mode 100644 index 00000000000..320629a8547 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/init_helper.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Definitions that are specific to the open-source + * version of the extension. The functions here don't do anything + * useful, but they need to be here because they're called by code + * that's shared with the closed-source version. + */ +goog.module('mr.InitHelper'); +goog.module.declareLegacyNamespace(); + +const DialProvider = goog.require('mr.DialProvider'); +const EventListener = goog.require('mr.EventListener'); +const Provider = goog.require('mr.Provider'); +const ProviderManager = goog.forwardDeclare('mr.ProviderManager'); + + +/** + * @param {!ProviderManager} providerManager + * @return {!Array<!Provider>} + */ +function getProviders(providerManager) { + return [new DialProvider(providerManager)]; +} + + +/** + * @return {!Array<!EventListener>} + */ +function getListeners() { + return []; +} + + +/** + * @return {void} + */ +function addEventListeners() {} + + +/** + * @return {!Function} + */ +function getInternalMessageHandler() { + return () => {}; +} + + +/** + * @return {!Function} + */ +function getExternalMessageHandler() { + return () => {}; +} + + +exports = { + getProviders, + getListeners, + addEventListeners, + getInternalMessageHandler, + getExternalMessageHandler, +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/init_test.js b/chromium/chrome/browser/resources/media_router/extension/src/init_test.js new file mode 100644 index 00000000000..25ba7d36419 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/init_test.js @@ -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. + +goog.setTestOnly('init_test'); + +goog.require('mr.Init'); +goog.require('mr.MockClock'); +goog.require('mr.Module'); +goog.require('mr.UnitTestUtils'); + + + +describe('Tests init', function() { + let mockClock; + let savedCallback; + + beforeEach(function() { + mr.Module.clearForTest(); + mockClock = new mr.MockClock(true); + mr.UnitTestUtils.mockChromeApi(); + + for (let listener of mr.Init.getAllListeners_()) { + spyOn(listener, 'addOnStartup').and.callThrough(); + } + + spyOn(mr.ExtensionSelector, 'shouldStart') + .and.returnValue(Promise.resolve()); + spyOn(mr.MediaRouterService, 'getInstance').and.returnValue({ + 'mrService': jasmine.createSpyObj( + 'mrService', ['setHandlers', 'onRouteMessagesReceived']), + 'mrInstanceId': 'mrInstanceId' + }); + + spyOn(mr.PersistentDataManager, 'initialize'); + spyOn(mr.PersistentDataManager, 'register'); + spyOn(mr.Init, 'getProviders_').and.returnValue([]); + + savedCallback = null; + chrome.runtime.onSuspend.addListener.and.callFake(callback => { + savedCallback = callback; + }); + }); + + afterEach(function() { + mockClock.uninstall(); + mr.UnitTestUtils.restoreChromeApi(); + }); + + it('records first wake duration after Chrome reload', function(done) { + spyOn(mr.PersistentDataManager, 'isChromeReloaded').and.returnValue(true); + mr.Init.init().then(() => { + expect(chrome.runtime.onSuspend.addListener).toHaveBeenCalled(); + expect(savedCallback).not.toBeNull(); + mockClock.tick(12345); + savedCallback(); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(mr.Init.FIRST_WAKE_DURATION, 12345); + done(); + }); + }); + + it('records wake duration after Chrome reload', function(done) { + spyOn(mr.PersistentDataManager, 'isChromeReloaded').and.returnValue(false); + mr.Init.init().then(() => { + expect(chrome.runtime.onSuspend.addListener).toHaveBeenCalled(); + expect(savedCallback).not.toBeNull(); + mockClock.tick(54321); + savedCallback(); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(mr.Init.WAKE_DURATION, 54321); + done(); + }); + }); + + it('Registers event listeners on bootstrap', function(done) { + mr.Init.init().then(done, done.fail); + for (let listener of mr.Init.getAllListeners_()) { + expect(listener.addOnStartup).toHaveBeenCalled(); + } + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/issue.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/issue.js new file mode 100644 index 00000000000..c0da74b3fd1 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/issue.js @@ -0,0 +1,150 @@ +// 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 The issue object shared between component extension + * and Chrome media router. + */ + +goog.provide('mr.Issue'); +goog.provide('mr.IssueAction'); +goog.provide('mr.IssueSeverity'); + +goog.require('mr.Assertions'); + +/** + * Note: keep synced with the issue severity supported by MR's issue manager. + * @enum {string} + * @export + */ +mr.IssueSeverity = { + FATAL: 'fatal', + WARNING: 'warning', + NOTIFICATION: 'notification' +}; + + +/** + * Note: keep synced with the issue actions supported by MR's issue manager. + * @enum {string} + * @export + */ +mr.IssueAction = { + DISMISS: 'dismiss', + LEARN_MORE: 'learn_more' +}; + +mr.Issue = class { + /** + * @param {string} title + * @param {mr.IssueSeverity} severity + */ + constructor(title, severity) { + /** + * @type {?string} + * @export + */ + this.routeId = null; + + /** + * @type {mr.IssueSeverity} + * @export + */ + this.severity = severity; + + /** + * When true, this issue takes the whole dialog. + * @type {boolean} + * @export + */ + this.isBlocking = this.severity == mr.IssueSeverity.FATAL ? true : false; + + /** + * Short description about the issue. Localized string. + * @type {string} + * @export + */ + this.title = title; + + /** + * Message about issue detail or how to handle issue. + * Messages should be suitable for end users to decide which actions to + * take. + * @type {?string} + * @export + */ + this.message = null; + + /** + * @type {mr.IssueAction} + * @export + */ + this.defaultAction = mr.IssueAction.DISMISS; + + /** + * @type {Array.<mr.IssueAction>} + * @export + */ + this.secondaryActions = null; + + /** + * Required if one action is LEARN_MORE. + * @type {?number} + * @export + */ + this.helpPageId = null; + } + + /** + * Sets the action to LEARN_MORE and sets the pageId that is required by the + * action for targeting. + * @param {number} pageId + * @return {!mr.Issue} This object. + */ + setDefaultActionLearnMore(pageId) { + mr.Assertions.assert(pageId > 0); + this.helpPageId = pageId; + this.defaultAction = mr.IssueAction.LEARN_MORE; + return this; + } + + /** + * @param {Array.<mr.IssueAction>} secondaryActions + * @return {!mr.Issue} This object. + */ + setSecondaryActions(secondaryActions) { + this.secondaryActions = secondaryActions; + return this; + } + + /** + * @param {string} message + * @return {!mr.Issue} This object. + */ + setMessage(message) { + this.message = message; + return this; + } + + /** + * @param {boolean} isBlocking + * @return {!mr.Issue} This object. + */ + setIsBlocking(isBlocking) { + if (!isBlocking && this.severity == mr.IssueSeverity.FATAL) { + throw Error('All FATAL issues must be blocking.'); + } + this.isBlocking = isBlocking; + return this; + } + + /** + * @param {string} routeId + * @return {!mr.Issue} This object. + */ + setRouteId(routeId) { + this.routeId = routeId; + return this; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller.js new file mode 100644 index 00000000000..0b9494b4194 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller.js @@ -0,0 +1,166 @@ +goog.module('mr.MediaRouteController'); +goog.module.declareLegacyNamespace(); + +const Logger = goog.require('mr.Logger'); + + +/** + * Controls a media that is being routed. This base class defines the same set + * of APIs as the MediaController Mojo interface defined in + * media_controller.mojom in Chromium, and contains a MojoBinding to the + * browser (so that MediaController commands from the browser are routed here). + * MRPs may extend this class with additional commands. + */ +const MediaRouteController = class { + /** + * @param {string} name Name of the controller (e.g. HangoutsRouteController). + * @param {!mojo.InterfaceRequest} controllerRequest The Mojo interface + * request to be bound to this controller. + * @param {!mojo.MediaStatusObserverPtr} observer The Mojo pointer to the + * MediaStatusObserver. + */ + constructor(name, controllerRequest, observer) { + /** @protected @const {!Logger} */ + this.logger = Logger.getInstance('mr.MediaRouteController.' + name); + + /** + * The binding used to maintain a Mojo connection with the browser process. + * @private {?mojo.Binding} + */ + this.binding_ = + new mojo.Binding(mojo.MediaController, this, controllerRequest); + + /** + * The observer to send media status updates to. + * @private {?mojo.MediaStatusObserverPtr} + */ + this.observer_ = observer; + + /** + * @protected @const {!mojo.MediaStatus} + */ + this.currentMediaStatus = new mojo.MediaStatus({ + title: '', + description: '', + duration: new mojo.TimeDelta({microseconds: 0}), + current_time: new mojo.TimeDelta({microseconds: 0}) + }); + + /** + * @private {boolean} + */ + this.disposed_ = false; + + this.binding_.setConnectionErrorHandler( + this.onMojoConnectionError.bind(this)); + this.observer_.ptr.setConnectionErrorHandler( + this.onMojoConnectionError.bind(this)); + } + + /** + * Drops the reference to the binding. + * @protected + */ + onMojoConnectionError() { + this.dispose(); + this.onControllerInvalidated(); + } + + /** + * Closes the connection in the binding and the observer. There will be no + * more incoming or outgoing calls after this. + * @final + */ + dispose() { + if (this.disposed_) { + return; + } + this.disposed_ = true; + this.disposeInternal(); + if (this.binding_) { + this.binding_.close(); + this.binding_ = null; + } + if (this.observer_) { + this.observer_.ptr.reset(); + this.observer_ = null; + } + } + + /** + * Notifies the observer of media status update, if it exists. + * @protected + */ + notifyObserver() { + if (this.observer_) { + this.observer_.onMediaStatusUpdated(this.currentMediaStatus); + } + } + + /** + * Performs additional cleanup when the controller is being disposed. + * @protected + */ + disposeInternal() {} + + /** + * Performs final cleanup after the controller is invalidated by a Mojo + * connection error. By the time this is called, dispose() has already + * happened. This gives an opportunity for clients to drop their references + * to the controller. + * @protected + */ + onControllerInvalidated() {} + + // The following are methods for handling incoming media commands. The method + // names must match the ones defined in media_controller.mojom in Chromium + // and be exported. + + /** + * Plays the media. + * @export + */ + play() {} + + /** + * Pauses the media. + * @export + */ + pause() {} + + /** + * Mutes or unmutes the media. + * @param {boolean} mute + * @export + */ + setMute(mute) {} + + /** + * Sets the volume on the media. The given volume must be between 0 and 1. + * @param {number} volume + * @export + */ + setVolume(volume) {} + + /** + * Seeks to the given time. The given time must be non-negative and less than + * or equal to the duration of the media. + * @param {!mojo.TimeDelta} time + * @export + */ + seek(time) {} + + /** + * Binds the given request to an implementation that accepts Hangouts-specific + * commands. + * @param {!mojo.InterfaceRequest} hangoutsControllerRequest Interface request + * for Hangouts controller. + * @export + */ + connectHangoutsMediaRouteController(hangoutsControllerRequest) { + hangoutsControllerRequest.close(); + throw new Error('Not implemented'); + } +}; + +exports = MediaRouteController; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller_test.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller_test.js new file mode 100644 index 00000000000..b0fd150f7e3 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/media_route_controller_test.js @@ -0,0 +1,70 @@ +goog.module('mr.MediaRouteControllerTest'); +goog.setTestOnly('mr.MediaRouteControllerTest'); + +const MediaRouteController = goog.require('mr.MediaRouteController'); +const UnitTestUtils = goog.require('mr.UnitTestUtils'); + +describe('MediaRouteController test', () => { + let binding; + let observer; + + beforeEach(() => { + UnitTestUtils.mockMojoApi(); + binding = UnitTestUtils.createMojoBindingSpyObj(); + observer = UnitTestUtils.createMojoMediaStatusObserverSpyObj(); + spyOn(mojo, 'Binding').and.returnValue(binding); + }); + + function createController() { + const controllerRequest = {}; + const controller = + new MediaRouteController('TestController', controllerRequest, observer); + expect(mojo.Binding) + .toHaveBeenCalledWith( + mojo.MediaController, controller, controllerRequest); + expect(binding.setConnectionErrorHandler).toHaveBeenCalled(); + expect(observer.ptr.setConnectionErrorHandler).toHaveBeenCalled(); + return controller; + } + + it('Sets things up on construction', () => { + createController(); + }); + + it('Cleans up on Mojo connection error', () => { + const controller = createController(); + spyOn(controller, 'onControllerInvalidated').and.callThrough(); + const onMojoConnectionError = + binding.setConnectionErrorHandler.calls.argsFor(0)[0]; + onMojoConnectionError(); + expect(binding.close).toHaveBeenCalled(); + expect(observer.ptr.reset).toHaveBeenCalled(); + expect(controller.onControllerInvalidated.calls.count()).toBe(1); + }); + + it('Cleans up on calling dispose()', () => { + const controller = createController(); + spyOn(controller, 'disposeInternal').and.callThrough(); + controller.dispose(); + expect(binding.close).toHaveBeenCalled(); + expect(observer.ptr.reset).toHaveBeenCalled(); + expect(controller.disposeInternal.calls.count()).toBe(1); + // Calling dispose twice has no effect. + controller.dispose(); + expect(controller.disposeInternal.calls.count()).toBe(1); + }); + + it('Notifies observer', () => { + const controller = createController(); + controller.notifyObserver(); + expect(observer.onMediaStatusUpdated) + .toHaveBeenCalledWith(controller.currentMediaStatus); + }); + + it('Does not notify observer after cleanup', () => { + const controller = createController(); + controller.dispose(); + controller.notifyObserver(); + expect(observer.onMediaStatusUpdated).not.toHaveBeenCalled(); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/mojo.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/mojo.js new file mode 100644 index 00000000000..3cacd0478af --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/mojo.js @@ -0,0 +1,415 @@ +// 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 Closure definitions of Mojo service objects. + */ + +goog.provide('mr.MediaRouterRequestHandler'); +goog.provide('mr.MediaRouterService'); + +goog.require('mr.RouteMessage'); + + +/** + * @interface + * + * Don't convert this interface to an ES6 class! Doing so causes things to + * break in strange ways. + */ +mr.MediaRouterService = function() {}; + + + +/** + * @return {!Promise<!Object>} + */ +mr.MediaRouterService.getInstance = function() { + if (!chrome.mojoPrivate || !chrome.mojoPrivate.requireAsync) { + return Promise.reject(Error('No mojo service loaded')); + } + return new Promise((resolve, reject) => { + // requireAsync does not return failures by design, so there is no error + // handler here. + chrome.mojoPrivate.requireAsync('media_router_bindings').then(mr => { + const mediaRouter = /** @type {!mr.MediaRouterService} */ (mr); + // Import all definitions into mojo namespace. + + mojo = mediaRouter.getMojoExports && mediaRouter.getMojoExports(); + mediaRouter.start().then(result => { + resolve({ + 'mrService': mediaRouter, + + 'mrInstanceId': result['instance_id'] || result, + 'mrConfig': result['config'] + }); + }); + }); + }); +}; + + +/** + * @param {!mr.MediaRouterRequestHandler} handlers + * @export + */ +mr.MediaRouterService.prototype.setHandlers; + + +/** + * Starts the MediaRouterService. + * @return {!Promise<!{ + * instance_id: string, + * config: !mojo.MediaRouteProviderConfig + * }>} Resolved with an Object containing the result when MediaRouterService is + * started. + * @export + */ +mr.MediaRouterService.prototype.start; + + +/** + * Returns an Object containing Mojo definitions exported by + * media_router_bindings. + * @return {!Object} + * @export + */ +mr.MediaRouterService.prototype.getMojoExports; + + +/** + * @param {string} source + * @param {!Array.<!mr.Sink>} sinks + * @param {!Array<!mojo.Origin>} origins + * @export + */ +mr.MediaRouterService.prototype.onSinksReceived; + + +/** + * @param {string} pseudoSinkId + * @param {string} sinkId + * @export + */ +mr.MediaRouterService.prototype.onSearchSinkIdReceived; + + +/** + * Used by Media Router Provider Manager to inform Media Router that there is an + * issue. + * @param {!mr.Issue} issue + * @export + */ +mr.MediaRouterService.prototype.onIssue; + + +/** + * Used by Media Router Provider Manager to inform Media Router that the list + * of routes has been updated. + * @param {Array.<mr.Route>} routes + * @param {string=} opt_source + * @param {Array.<string>=} opt_nonLocalJoinableRouteIds + * @export + */ +mr.MediaRouterService.prototype.onRoutesUpdated; + + +/** + * Used by Media Router Provider Manager to inform Media Router that sink + * availability has changed. + * @param {!mr.SinkAvailability} availability + * @export + */ +mr.MediaRouterService.prototype.onSinkAvailabilityUpdated; + + +/** + * Used by Media Router Provider Manager to inform Media Router that the state + * of a presentation connected to a route has changed. + * @param {string} routeId + * @param {string} state + * @export + */ +mr.MediaRouterService.prototype.onPresentationConnectionStateChanged; + + +/** + * Used by Media Router Provider Manager to inform Media Router that the + * presentation connected to a route has closed. + * @param {string} routeId + * @param {string} reason + * @param {string} message + * @export + */ +mr.MediaRouterService.prototype.onPresentationConnectionClosed; + + +/** + * Informs Media Router of route messages received from the media sink to which + * the route is connected. + * @param {string} routeId + * @param {!Array<!mr.RouteMessage>} messages + * @export + */ +mr.MediaRouterService.prototype.onRouteMessagesReceived; + + +/** + * Disables or enables extension event page suspension. + * @param {boolean} keepAlive + * @export + */ +mr.MediaRouterService.prototype.setKeepAlive; + + +/** + * Gets the current keep alive state. + * @return {boolean} + * @export + */ +mr.MediaRouterService.prototype.getKeepAlive; + + +/** + * Informs Media Router that a MirrorServiceRemoter is created for the given + * tab. The Media Router may use remoterPtr to control media remoting. The Media + * Router should also bind sourceRequest to an implementation to receive updates + * on the remoting session. + * @param {number} tabId + * @param {!mojo.MirrorServiceRemoterPtr} remoterPtr + * @param {!mojo.InterfaceRequest} sourceRequest + * @export + */ +mr.MediaRouterService.prototype.onMediaRemoterCreated; + + +/** + * @interface + */ +mr.MediaRouterRequestHandler = function() {}; + + +/** + * Will be called immediately before any other handler method is invoked. + * @export + */ +mr.MediaRouterRequestHandler.prototype.onBeforeInvokeHandler; + + +/** + * Creates a route for |mediaSource| to |sinkId|. + * @param {string} sourceUrn The URN of the media being displayed. + * @param {string} sinkId + * @param {string} presentationId A presentation ID to use. + + * @param {!mojo.Origin|string=} origin + * @param {number=} tabId + * @param {number=} timeoutMillis If positive, the timeout to use in place + * of default timeout. + * @param {boolean=} offTheRecord If true, the request is from an off the + * record (incognito) browser profile. + * @return {!Promise<!mr.Route>} Fulfilled with route created if successful. + * Rejected otherwise. + * @export + */ +mr.MediaRouterRequestHandler.prototype.createRoute; + + +/** + * Joins an existing route. + * + * @param {string} sourceUrn + * @param {string} presentationId A presentation ID for Presentation API + * client; A Cast session ID for Cast join; and 'autojoin' for Cast auto Join + * case; + * @param {!mojo.Origin|string} origin + * @param {number} tabId + * @param {number=} timeoutMillis If positive, the timeout to use in place + * of default timeout. + * @param {boolean=} offTheRecord If true, the request is from an off the + * record (incognito) browser profile. + * @return {!Promise<!mr.Route>} Fulfilled with route joined if successful. + * Rejected otherwise. + * @export + */ +mr.MediaRouterRequestHandler.prototype.joinRoute; + + +/** + * Joins an existing route by route Id. + * + * @param {string} sourceUrn + * @param {string} routeId An existing route Id to join. + * @param {string} presentationId The presentation Id of the route being + * created. + * @param {!mojo.Origin|string} origin + * @param {number} tabId + * @param {number=} timeoutMillis If positive, the timeout to use in place + * of default timeout. + * @return {!Promise<!mr.Route>} Fulfilled with route connected if successful. + * Rejected otherwise. + * @export + */ +mr.MediaRouterRequestHandler.prototype.connectRouteByRouteId; + + +/** + * Terminates the route specified by |routeId|. + * @param {string} routeId The ID of the route to be terminated. + * @return {!Promise<void>} Resolved if the route was terminated, rejected + * otherwise. + * @export + */ +mr.MediaRouterRequestHandler.prototype.terminateRoute; + + +/** + * Starts querying for sinks capable of displaying |sourceUrn|. + * @param {string} sourceUrn The URN of the media. + * @export + */ +mr.MediaRouterRequestHandler.prototype.startObservingMediaSinks; + + +/** + * Stops querying for sinks capable of displaying |sourceUrn|. + * @param {string} sourceUrn The URN of the media. + * @export + */ +mr.MediaRouterRequestHandler.prototype.stopObservingMediaSinks; + + +/** + * Sends a message to the sink via a media route. + * @param {string} routeId + * @param {!Object|string} message The message to post. Object will be + * serialized to a JSON string. + * @param {Object=} opt_extraInfo Extra info about how to send a message. + * @return {!Promise} Fulfilled when the message is posted, or rejected + * if there an error. + * @export + */ +mr.MediaRouterRequestHandler.prototype.sendRouteMessage; + + +/** + * Sends a binary message to the sink via a media route. + * @param {string} routeId + * @param {!Uint8Array} data The binary message to send. + * @return {!Promise} Fulfilled when the message is sent, or rejected + * if there an error. + * @export + */ +mr.MediaRouterRequestHandler.prototype.sendRouteBinaryMessage; + + +/** + * Called when the MediaRouter wants to start receiving messages from the media + * sink for the route identified by |routeId|. + * @param {!string} routeId + * @export + */ +mr.MediaRouterRequestHandler.prototype.startListeningForRouteMessages; + + +/** + * Called when the MediaRouter wants to stop getting further messages + * associated with the routeId. + * @param {!string} routeId + * @export + */ +mr.MediaRouterRequestHandler.prototype.stopListeningForRouteMessages; + + +/** + * Informs the Media Router Provider Manager to send updates on routes list. + * @param {string} sourceUrn The URN of the media. + * @export + */ +mr.MediaRouterRequestHandler.prototype.startObservingMediaRoutes; + + +/** + * Informs the Media Router Provider Manager to stop sending updates on routes + * list. + * @param {string} sourceUrn The URN of the media. + * @export + */ +mr.MediaRouterRequestHandler.prototype.stopObservingMediaRoutes; + + +/** + * Informs the Media Router Provider Manager that a presentation connection has + * detached from its underlying media route due to garbage collection or + * explicit close(). + * @param {!string} routeId + * @export + */ +mr.MediaRouterRequestHandler.prototype.detachRoute; + + +/** + * Enables mDNS discovery. No-ops if it is already enabled. Calling this will + * trigger a firewall prompt on Windows if there is not already a firewall rule + * for mDNS. + * @export + */ +mr.MediaRouterRequestHandler.prototype.enableMdnsDiscovery; + + +/** + * Searches the appropriate provider for a sink matching |searchCriteria| that + * is compatible with |sourceUrn|. The provider that is searched is chosen to be + * the one that owns the pseudo sink identified by |sinkId|. If any sinks are + * found, sinks observers for |sourceUrn| will be invoked via onSinksReceived() + * with those sinks included. The function will return the ID of a sink to which + * a route can be created or the empty string if no such sink exists. + * + * Note that returning the empty string does not necessarily mean no matching + * sinks were found. The provider could find multiple matching sinks and not + * know how to choose a single one for the route creation. The MRPM will return + * the sink on which the user is most likely to create the next route. However, + * the manager does not enforce that the provider creates at most one sink in + * response to a search. + * + * @param {string} sinkId Sink ID of the pseudo sink that generated the request. + * @param {string} sourceUrn Source to be used with the sink. + * @param {!mr.SinkSearchCriteria} searchCriteria Sink search criteria for the + * MRP's which includes the user's current domain. + * @return {!Promise<string>} Fulfilled with the ID of a sink to which a route + * can be created. Rejected otherwise. + * @export + */ +mr.MediaRouterRequestHandler.prototype.searchSinks; + +/** + * Called when Media Router finishes sink discovery. + * @param {string} providerName Name of provider where the sinks come from. + * @param {!Array<!mojo.Sink>} list of sinks discovered by Media Router. + * @export + */ +mr.MediaRouterRequestHandler.prototype.provideSinks; + +/** + * Updates sinks even if sinkAvailability is UNAVAILABLE to allow for query + * based discovery providers an opportuntity to find sinks. + * @param {string} sourceUrn The URN of the media. + * @export + */ +mr.MediaRouterRequestHandler.prototype.updateMediaSinks; + + +/** + * Creates and returns the MediaRouteController instance for the given route. + * Also sets the media status observer for the given route. + * Rejects if the controller cannot be created, or if the controller + * already exists. + * @param {string} routeId + * @param {!mojo.InterfaceRequest} controllerRequest The Mojo request object to + * be bound to the controller created. + * @param {!mojo.MediaStatusObserverPtr} observer The observer's Mojo pointer. + * @return {!Promise<void>} + * @export + */ +mr.MediaRouterRequestHandler.prototype.createMediaRouteController; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route.js new file mode 100644 index 00000000000..b42e4fe42b1 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route.js @@ -0,0 +1,178 @@ +// 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 The media route data object shared between component extension + * and Chrome media router. + */ + +goog.provide('mr.Route'); +goog.require('mr.RouteId'); + +mr.Route = class { + /** + * @param {string} id The route ID. + * @param {string} presentationId The ID of the associated presentation. + * @param {string} sinkId The ID of the sink running this route. + * @param {?string} sourceUrn The media source being sent through this route. + * @param {boolean} isLocal Whether the route is requested locally. + * @param {string} description + * @param {?string} iconUrl + */ + constructor( + id, presentationId, sinkId, sourceUrn, isLocal, description, iconUrl) { + /** + * @type {string} + * @export + */ + this.id = id; + + /** + * The ID of the presentation associated with this route. + * @type {string} + * @export + */ + this.presentationId = presentationId; + + /** + * The ID of the sink associated with this route. + * @type {string} + * @export + */ + this.sinkId = sinkId; + + /** + * The media source being sent through this route. + * Non-null if |isLocal| is true. + * For discovered routes, the media source may not be available. + * @type {?string} + * @export + */ + this.mediaSource = sourceUrn; + + /** + * Whether the route was created locally or is associated with a Cast + * session + * that was created locally. + * @type {boolean} + * @export + */ + this.isLocal = isLocal; + + /** + * @type {string} + * @export + */ + this.description = description; + + /** + * @type {?string} + * @export + */ + this.iconUrl = iconUrl; + + /** + * When false, this route cannot be stopped by the provider managing it. + * @type {boolean} + * @export + */ + this.allowStop = true; + + /** + + * @type {?string} + * @export + */ + this.customControllerPath = null; + + /** + + * @type {boolean} + * @export + */ + this.supportsMediaRouteController = false; + + /** + * The type of controller associated with this route, or kNone if controller + * is not supported for the route. + + * @type {mojo.RouteControllerType} + * @export + */ + this.controllerType = + mojo && mojo.RouteControllerType && mojo.RouteControllerType.kNone; + + /** + * If set to true, this route should be displayed for |sinkId| in UI. + * @type {boolean} + * @export + */ + this.forDisplay = true; + + /** + * If true, the route was created by an off the record (incognito) browser + * profile. + * @type {boolean} + * @export + */ + this.offTheRecord = false; + + /** + * This field is used to identify routes that were created locally as + * opposed + * to isLocal which can identify both locally created routes and non-local + * routes associated with a Cast session that was created locally. The + * initial + * value of this field is the same as isLocal. + * @type {boolean} + */ + this.createdLocally = isLocal; + + /** + * If true, the route was created for offscreen presentation (1-UA mode). + * @type {boolean} + * @export + */ + this.isOffscreenPresentation = false; + } + + /** + * @param {!string} presentationId + * @param {!string} providerName + * @param {!string} sinkId The ID of the sink running this route. + * @param {?string} source The media source being sent through this route. + * @param {!boolean} isLocal Whether the route is requested locally. + * @param {!string} description + * @param {?string} iconUrl + * @return {!mr.Route} + */ + static createRoute( + presentationId, providerName, sinkId, source, isLocal, description, + iconUrl) { + return new mr.Route( + mr.RouteId.getRouteId(presentationId, providerName, sinkId, source), + presentationId, sinkId, source, isLocal, description, iconUrl); + } +}; + +/** + * @typedef {{ + * tabId: (?number|undefined), + * sessionId: string, + * sinkIpAddress: string, + * sinkModelName: string, + * sinkFriendlyName: (string|undefined), + * activity: (!mr.mirror.Activity|undefined) + * }} + */ +mr.Route.MirrorInitData; + + +/** + * The field is only used inside component extension to pass MRP specific data + * to the corresponding mirroring service. For example, cast streaming needs + * sink IP address and session ID. + * @type {mr.Route.MirrorInitData} + */ +mr.Route.prototype.mirrorInitData; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_message.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_message.js new file mode 100644 index 00000000000..a796a271b80 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_message.js @@ -0,0 +1,48 @@ +// 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 The route message object shared between component extension + * and Chrome media router. + */ + +goog.provide('mr.RouteMessage'); + +mr.RouteMessage = class { + /** + * @param {string} routeId + * @param {string|!Uint8Array} message + */ + constructor(routeId, message) { + /** + * @type {string} + * @export + */ + this.routeId = routeId; + + /** + * @type {string|!Uint8Array} + * @export + */ + this.message = message; + } + + /** + * @param {!mr.RouteMessage} routeMessage + * @return {boolean} true if the message is in binary format. + */ + static isBinary(routeMessage) { + return typeof routeMessage.message != 'string'; + } + + /** + * @param {!mr.RouteMessage} routeMessage + * Returns the length of the message if it is a string, otherwise 0. + * @return {number} + */ + static stringLength(routeMessage) { + return mr.RouteMessage.isBinary(routeMessage) ? 0 : + routeMessage.message.length; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_request_error.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_request_error.js new file mode 100644 index 00000000000..ac0092f098e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/route_request_error.js @@ -0,0 +1,89 @@ +// 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 Extension of Error for route request errors. + */ + +goog.provide('mr.RouteRequestError'); +goog.provide('mr.RouteRequestResultCode'); + + +/** + * Keep in sync with: + * - RouteRequestResultCode in media_router.mojom + * - RouteRequestResult::ResultCode in route_request_result.h + * - MediaRouteProviderResult enum in tools/metrics/histograms.xml + * @enum {number} + */ +mr.RouteRequestResultCode = { + UNKNOWN_ERROR: 0, + OK: 1, + TIMED_OUT: 2, + ROUTE_NOT_FOUND: 3, + SINK_NOT_FOUND: 4, + INVALID_ORIGIN: 5, + OFF_THE_RECORD_MISMATCH: 6, + NO_SUPPORTED_PROVIDER: 7, + CANCELLED: 8, +}; + +mr.RouteRequestError = class extends Error { + /** + * @param {!mr.RouteRequestResultCode} errorCode + * @param {string=} opt_message + * @param {string=} opt_stack + */ + constructor(errorCode, opt_message, opt_stack) { + super(); + + this.name = 'RouteRequestError'; + this.message = opt_message || ''; + if (opt_stack) { + this.stack = opt_stack; + } else { + // Attempt to ensure there is a stack trace. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, mr.RouteRequestError); + } else { + const stack = new Error().stack; + if (stack) { + this.stack = stack; + } + } + } + + /** @type {!mr.RouteRequestResultCode} */ + this.errorCode = errorCode; + } + + /** + * If the given error is a mr.RouteRequestError, returns it. Otherwise, a + * returns a mr.RouteRequestError with the error's message and UNKNOWN error + * code. + * @param {*} error + * @return {!mr.RouteRequestError} + */ + static wrap(error) { + if (error instanceof mr.RouteRequestError) { + return error; + } else if (error instanceof Error) { + return new mr.RouteRequestError( + mr.RouteRequestResultCode.UNKNOWN_ERROR, error.message, error.stack); + } else { + return new mr.RouteRequestError(mr.RouteRequestResultCode.UNKNOWN_ERROR); + } + } + + /** + * @param {*} error Possibly an instance of mr.RouteRequestError. + * @return {boolean} True if the argument represents a timeout. + */ + static isTimeout(error) { + if (error instanceof mr.RouteRequestError) { + return error.errorCode == mr.RouteRequestResultCode.TIMED_OUT; + } + return false; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink.js new file mode 100644 index 00000000000..e11b6dcc597 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink.js @@ -0,0 +1,79 @@ +// 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. + +goog.provide('mr.Sink'); +goog.provide('mr.SinkIconType'); + + +/** + * Sink icon types defined in Chrome. + * To define a new icon, add it to Chrome first and then here. + * @enum {string} + */ +mr.SinkIconType = { + CAST: 'cast', + CAST_AUDIO_GROUP: 'cast_audio_group', + CAST_AUDIO: 'cast_audio', + MEETING: 'meeting', + HANGOUT: 'hangout', + EDUCATION: 'education', + GENERIC: 'generic', +}; + + + +/** + * Represents a device which can be a destination for a media route. + */ +mr.Sink = class { + /** + * @param {string} id The ID of the sink. + * @param {string} friendlyName The human readable name of the sink. + * @param {mr.SinkIconType=} iconType + * @param {?string=} description The human readable description of + * the sink. The description is displayed below the sink name, + * and may contain more detailed information about the sink + * (e.g., meeting description). + * @param {?string=} domain The Dasher domain associated with the + * sink. + */ + constructor( + id, friendlyName, iconType = undefined, description = null, + domain = null) { + // For some reason the current public release of jscompiler gets upset if + // mr.SinkIconType.GENERIC is specified as a default argument. + iconType = iconType || mr.SinkIconType.GENERIC; + + /** + * @type {string} + * @export + */ + this.id = id; + + /** + * @type {string} + * @export + */ + this.friendlyName = friendlyName; + + /** + * @type {mr.SinkIconType} + * @export + */ + this.iconType = iconType; + + /** + * @type {?string} + * @export + */ + this.description = description; + + /** + * A non-null domain signifies the sink is tied to a Dasher domain. + * @type {?string} + * @export + */ + this.domain = domain; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_list.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_list.js new file mode 100644 index 00000000000..f430d2f0428 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_list.js @@ -0,0 +1,34 @@ +// 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 Data structure returned by mr.Provider.getAvailableSinks. + */ + +goog.provide('mr.SinkList'); + +mr.SinkList = class { + /** + * @param {!Array<mr.Sink>} sinks + * @param {Array<string>=} opt_origins List of origins that have access to the + * sink list. If not provided, the sink list is accessible from all origins. + */ + constructor(sinks, opt_origins) { + /** + * @type {!Array<!mr.Sink>} + * @export + */ + this.sinks = sinks; + + /** + * @type {?Array<string>} + * @export + */ + this.origins = opt_origins || null; + } +}; + + +/** @const {!mr.SinkList} */ +mr.SinkList.EMPTY = new mr.SinkList([]); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_search_criteria.js b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_search_criteria.js new file mode 100644 index 00000000000..9dd8ea094af --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/interface_data/sink_search_criteria.js @@ -0,0 +1,30 @@ +// 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 The sink search criteria shared between component extension and + * Chrome media router. + */ + +goog.provide('mr.SinkSearchCriteria'); + +mr.SinkSearchCriteria = class { + /** + * @param {string} input + * @param {?string} domain + */ + constructor(input, domain) { + /** + * @type {string} + * @export + */ + this.input = input; + + /** + * @type {?string} + * @export + */ + this.domain = domain; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/internal_message.js b/chromium/chrome/browser/resources/media_router/extension/src/internal_message.js new file mode 100644 index 00000000000..debdaab4d6f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/internal_message.js @@ -0,0 +1,62 @@ +// 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 Internal messages between parts of the extension, e.g., event + * page, background page, e2e test page. + * + + */ + +goog.provide('mr.InternalMessage'); +goog.provide('mr.InternalMessageType'); + + +/** + * @enum {string} + */ +mr.InternalMessageType = { + // Feedback ==> event page + SUBSCRIBE_LOG_DATA: 'subscribe_log_data', + RETRIEVE_LOG_DATA: 'retrieve_log_data', + // Control Messages + // App ==> Cloud MRP + START: 'start', + STOP: 'stop', + // Responses + // Cloud MRP ==> App + ROUTE: 'route', + STOPPED: 'stopped', + ERROR: 'error', + // App ==> Log Subscribers + SUBSCRIBED: 'subscribed', + LOG_MESSAGE: 'log_message', +}; + +mr.InternalMessage = class { + /** + * @param {string} source ID of source of message. + * @param {mr.InternalMessageType} type The message type. + * @param {*=} opt_message + */ + constructor(source, type, opt_message) { + /** + * @type {string} + * @export + */ + this.source = source; + + /** + * @type {mr.InternalMessageType} + * @export + */ + this.type = type; + + /** + * @type {*} + * @export + */ + this.message = opt_message; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener.js b/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener.js new file mode 100644 index 00000000000..c55869c2b4a --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener.js @@ -0,0 +1,61 @@ +// 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 Handles internal messages that arrive in the + * chrome.runtime.onMessage event. It is assumed that all incoming messages have + * type mr.InternalMessage. + */ + +goog.provide('mr.InternalMessageListener'); + +goog.require('mr.EventAnalytics'); +goog.require('mr.EventListener'); +goog.require('mr.InternalMessage'); +goog.require('mr.InternalMessageType'); + + +/** + * @extends {mr.EventListener<ChromeEvent>} + */ +mr.InternalMessageListener = class extends mr.EventListener { + constructor() { + super( + mr.EventAnalytics.Event.RUNTIME_ON_MESSAGE, 'InternalMessageListener', + mr.ModuleId.PROVIDER_MANAGER, chrome.runtime.onMessage); + } + + /** + * @override + */ + validateEvent(message, sender, sendResponse) { + const internalMessage = /** @type {mr.InternalMessage} */ (message); + return internalMessage.type == mr.InternalMessageType.RETRIEVE_LOG_DATA && + sender.id == chrome.runtime.id && + sender.url == `chrome-extension://${sender.id}/feedback.html`; + } + + /** + * @override + */ + deferredReturnValue() { + // Indicates the messaging channel should be kept open until + // sendResponse() is called. + return true; + } + + /** + * @return {!mr.InternalMessageListener} + */ + static get() { + if (!mr.InternalMessageListener.listener_) { + mr.InternalMessageListener.listener_ = new mr.InternalMessageListener(); + } + return mr.InternalMessageListener.listener_; + } +}; + + +/** @private {?mr.InternalMessageListener} */ +mr.InternalMessageListener.listener_ = null; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener_test.js b/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener_test.js new file mode 100644 index 00000000000..f9e64ae45cb --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/internal_message_listener_test.js @@ -0,0 +1,52 @@ +// 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. + +goog.setTestOnly('internal_message_listener_test'); + +goog.require('mr.InternalMessageListener'); + +describe('Tests mr.InternalMessageListener', () => { + let listener; + + const failCallback = response => { + fail('should not have called back'); + }; + const validEvent = {'type': mr.InternalMessageType.RETRIEVE_LOG_DATA}; + const validSender = { + 'id': 'foo', + 'url': 'chrome-extension://foo/feedback.html' + }; + beforeEach(() => { + chrome.runtime = {id: 'foo'}; + listener = new mr.InternalMessageListener(); + }); + + it('rejects invalid extension id', () => { + const sender = { + 'id': 'invalid', + 'url': 'chrome-extension://invalid/feedback.html' + }; + + const result = listener.validateEvent(validEvent, sender, failCallback); + expect(result).toBe(false); + }); + + it('rejects invalid extension url', () => { + const sender = {'id': 'foo', 'url': 'chrome-extension://foo/invalid.html'}; + + const result = listener.validateEvent(validEvent, sender, failCallback); + expect(result).toBe(false); + }); + + it('rejects invalid message type', () => { + const result = listener.validateEvent( + {'type': mr.InternalMessageType.START}, validSender, failCallback); + expect(result).toBe(false); + }); + + it('passes validation', () => { + const result = listener.validateEvent(validEvent, validSender, () => {}); + expect(result).toBe(true); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/log_manager.js b/chromium/chrome/browser/resources/media_router/extension/src/log_manager.js new file mode 100644 index 00000000000..791aed8c247 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/log_manager.js @@ -0,0 +1,241 @@ +// 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 Log manager which enables and collects both fine and info logs, + * and provides method to get logs with parameter to include or exclude + * fine logs. + + */ + +goog.provide('mr.LogManager'); + +goog.require('mr.Config'); +goog.require('mr.FixedSizeQueue'); +goog.require('mr.Logger'); +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); + + +/** + * @implements {mr.PersistentData} + */ +mr.LogManager = class { + constructor() { + /** @private @const */ + this.buffer_ = new mr.FixedSizeQueue(mr.LogManager.BUFFER_SIZE); + + /** @private @const */ + this.startTime_ = Date.now(); + } + + /** + * @return {!mr.LogManager} + */ + static getInstance() { + if (mr.LogManager.instance_ == null) { + mr.LogManager.instance_ = new mr.LogManager(); + } + return mr.LogManager.instance_; + } + + /** + * Init + */ + init() { + mr.Logger.level = this.getDefaultLogLevel_(); + const browserLogger = mr.Logger.getInstance('browser'); + const oldErrorHandler = window.onerror; + /** + * @param {string} message + * @param {string} url + * @param {number} line + * @param {number=} col + * @param {*=} error + */ + window.onerror = (message, url, line, col, error) => { + if (oldErrorHandler) { + oldErrorHandler(message, url, line, col, error); + } + browserLogger.error(`Error: ${message} (${url} @ Line: ${line})`, error); + }; + mr.Logger.addHandler(this.onNewLog_.bind(this)); + + // Override log level via localStorage setting + const debugKey = 'debug.logs'; + const debugLevel = window.localStorage[debugKey]; + if (debugLevel) { + mr.Logger.level = mr.Logger.stringToLevel( + debugLevel.toUpperCase(), mr.Logger.Level.FINE); + } else if (!mr.Config.isPublicChannel) { + // Record the default local level in local settings so developers can + // easily change it without having to look up the name of the setting. + window.localStorage[debugKey] = + mr.Logger.levelToString(mr.Logger.DEFAULT_LEVEL); + } + + const consoleKey = 'debug.console'; + if (!mr.Config.isPublicChannel && window.localStorage[consoleKey] == null) { + // Enable console logging by default in internal builds. Any value other + // than 'false' or '' is treated as true. + window.localStorage[consoleKey] = 'true'; + } + const consoleValue = window.localStorage[consoleKey]; + if (consoleValue && consoleValue.toLowerCase() != 'false') { + mr.Logger.addHandler(this.logToConsole_.bind(this)); + } + } + + /** + * Saves logs in the internal buffer. + * + * @param {mr.Logger.Record} logRecord The log entry. + * @private + */ + onNewLog_(logRecord) { + this.buffer_.enqueue(this.formatRecord_(logRecord, false)); + const exception = logRecord.exception; + if (exception instanceof Error && exception.stack) { + this.buffer_.enqueue(exception.stack); + } + } + + /** + * @param {mr.Logger.Record} logRecord The log entry. + * @private + */ + logToConsole_(logRecord) { + const args = [this.formatRecord_(logRecord, true)]; + if (logRecord.exception) { + args.push(logRecord.exception); + } + switch (logRecord.level) { + case mr.Logger.Level.SEVERE: + console.error(...args); + break; + case mr.Logger.Level.WARNING: + console.warn(...args); + break; + case mr.Logger.Level.INFO: + console.log(...args); + break; + default: + console.debug(...args); + } + } + + /** + * @param {!mr.Logger.Record} record + * @param {boolean} forConsole + * @return {string} + * @private + */ + formatRecord_(record, forConsole) { + const sb = ['[']; + if (forConsole) { + // Format relative timestamp. + const seconds = (Date.now() - this.startTime_) / 1000; + sb.push((' ' + seconds.toFixed(3)).slice(-7)); + } else { + // Format absolute timestamp. + const date = new Date(record.time); + const twoDigitStr = num => num < 10 ? '0' + num : num; + sb.push( + date.getFullYear().toString(), '-', twoDigitStr(date.getMonth() + 1), + '-', twoDigitStr(date.getDate()), ' ', twoDigitStr(date.getHours()), + ':', twoDigitStr(date.getMinutes()), ':', + twoDigitStr(date.getSeconds()), '.', + twoDigitStr(Math.floor(date.getMilliseconds() / 10))); + } + sb.push( + '][', mr.Logger.levelToString(record.level), '][', record.logger, '] ', + record.message); + // Don't append the exception when logging to the console, because it will + // be handled specially later. + if (!forConsole && record.exception != null) { + sb.push('\n'); + if (record.exception instanceof Error) { + sb.push(record.exception.message); + } else { + try { + sb.push(JSON.stringify(record.exception)); + } catch (e) { + sb.push(record.exception.toString()); + } + } + } + sb.push('\n'); + return sb.join(''); + } + + /** + * Get the logs in log buffer. + * @return {string} + */ + getLogs() { + if (this.buffer_.getCount() == 0) { + return 'NA'; + } + + return this.buffer_.getValues().join(''); + } + + /** + * @return {!mr.Logger.Level} The default log level. + * @private + */ + getDefaultLogLevel_() { + return mr.Config.isPublicChannel ? mr.Logger.Level.INFO : + mr.Logger.Level.FINE; + } + + /** + * Registers with the data manager and loads any previous logs. + */ + registerDataManager() { + mr.PersistentDataManager.register(this); + } + + /** + * @override + */ + getStorageKey() { + return 'LogManager'; + } + + /** + * @override + */ + getData() { + return [this.buffer_.getValues()]; + } + + /** + * @override + */ + loadSavedData() { + const currentLogs = this.buffer_.getValues(); + this.buffer_.clear(); + for (let log of mr.PersistentDataManager.getTemporaryData(this) || []) { + this.buffer_.enqueue(log); + } + for (let log of currentLogs) { + this.buffer_.enqueue(log); + } + } +}; + + +/** + * @private {mr.LogManager} + */ +mr.LogManager.instance_ = null; + + +/** + * The max number of logs in buffer. The old logs get pushed out when the buffer + * is full. + * @const + */ +mr.LogManager.BUFFER_SIZE = 1000; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/cancellable_promise.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/cancellable_promise.js new file mode 100755 index 00000000000..0e0807b4575 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/cancellable_promise.js @@ -0,0 +1,255 @@ +// 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. + +goog.module('mr.CancellablePromise'); +goog.module.declareLegacyNamespace(); + + +/** + * A promise packaged together with a cancel() method. + * + * To understand cancellation, for consider a chain of ordinary Promise objects + * P, Q, and R, where Q is the result of calling P.then, and R is the result of + * calling Q.then. Whenever Q is rejected, the rejection is propagated "down" + * the chain to R, but P is unaffected. + * + * Now consider a chain of CancellablePromise objects P, Q, and R, created using + * the chain() method below. Rejection behaves the same as with ordinary + * Promise objects, but whenever Q is cancelled, the cancellation is propagated + * "up" the chain to P. Because a cancelled promise is also rejected, calling + * Q.cancel() also causes the rejection to be propagated to R. In this way, + * cancellation propagates both up and down a chain of related promises. + * + * For an explanation of how this class is used in practice (in the interaction + * between ProviderManager and RouteProvider classes), see the diagram in + * <route-creation-timeout.svg.gz>. + * + * @template T + */ +class CancellablePromise { + /** + * @param {function(function(T), function(*))} init Function + * called immediatiately with resolve and reject functions passed as + * arguments. + * @param {?function(*)=} onCancelled Optional function to be called when this + * CancellablePromise is cancelled. + */ + constructor(init, onCancelled = null) { + /** + * @private {function(*)} + */ + this.reject_; + + /** + * @private {?function(*)} + */ + this.onCancelled_ = onCancelled; + + /** + * @const + */ + this.promise = new Promise((resolve, reject) => { + const resolve1 = value => { + this.onCancelled_ = null; + resolve(value); + }; + const reject1 = reason => { + this.onCancelled_ = null; + reject(reason); + }; + this.reject_ = reject1; + init(resolve1, reject1); + }); + } + + /** + * Cancels this promise. + * + * Does nothing if |this.promise| is already settled. Otherwise, causes + * |this.promise| to be rejected with |reason|, and causes the |onHandled| + * function passed to this class's constructor to be called with |reason|. + * + * @param {*} reason + */ + cancel(reason) { + this.reject_(reason); + if (this.onCancelled_) { + const onCancelled = this.onCancelled_; + this.onCancelled_ = null; // ensure cancel() method is idempotent + setTimeout(() => onCancelled(reason), 0); + } + } + + /** + * Chains CancellablePromises together like the then() method of ordinary + * promises, with one extra feature: if the "child" promise returned by this + * method is cancelled, then this promise is automatically cancelled as well. + * + * @param {?function(T):U} onResolved Function called when this promise is + * resolved. The argument is the value with which this promise was + * resolved. If the function returns, the return value is used to resolve + * the child promise. If the function throws an error, the child promise + * is rejected with that error. If this argument is null, it is + * equivalent to passing a function that returns its argument. + * @param {?function(*):U=} onRejected Function called when this promise is + * rejected. The argument is the reason with which this promise was + * rejected. If the function returns, the return value is used to resolve + * the child promise. If the function throws an error, the child promise + * is rejected with that error. If this argument is missing or null, it + * is equivalent to passing a function that throws its argument. + * @return {!CancellablePromise<U>} A child promise which is resolved or + * rejected depending on the result of calling |onResolved| or + * |onRejected|. + * @template U + */ + chain(onResolved, onRejected = null) { + return new CancellablePromise( + (resolve, reject) => { + this.promise.then( + value => { + if (onResolved) { + try { + resolve(onResolved(value)); + } catch (reason) { + reject(reason); + } + } else { + resolve(value); + } + }, + reason => { + if (onRejected) { + try { + resolve(onRejected(reason)); + } catch (reason2) { + reject(reason2); + } + } else { + reject(reason); + } + }); + }, + reason => { + this.cancel(reason); + }); + } + + /** + * Make it Promise by expose then. + * @param {?function(T):U} onResolved Function called when this promise is + * resolved. The argument is the value with which this promise was + * resolved. If the function returns, the return value is used to resolve + * the child promise. If the function throws an error, the child promise + * is rejected with that error. If this argument is null, it is + * equivalent to passing a function that returns its argument. + * @param {?function(*):U=} onRejected Function called when this promise is + * rejected. The argument is the reason with which this promise was + * rejected. If the function returns, the return value is used to resolve + * the child promise. If the function throws an error, the child promise + * is rejected with that error. If this argument is missing or null, it + * is equivalent to passing a function that throws its argument. + * @return {!CancellablePromise<U>} A child promise which is resolved or + * rejected depending on the result of calling |onResolved| or + * |onRejected|. + * @template U + */ + then(onResolved, onRejected = null) { + return this.chain(onResolved, onRejected); + } + + /** + * Shorthand for .chain(null, onRejected). + * @param {?function(*):T} onRejected + * @return {!CancellablePromise<T>} + */ + catch(onRejected) { + return this.chain(null, onRejected); + } + + /** + * Utility function create a promise in a resolved state. + * @param {T} value + * @return {!CancellablePromise<T>} + * @template T + */ + static resolve(value) { + return new CancellablePromise((resolve, reject) => { + resolve(value); + }); + } + + /** + * Utility function create a promise in a rejected state. + * @param {*} reason + * @return {!CancellablePromise<T>} + * @template T + */ + static reject(reason) { + return new CancellablePromise((resolve, reject) => { + reject(reason); + }); + } + + /** + * Utility function to wrap a Promise. + * + + * + * @param {!Promise<T>} promise + * @return {!CancellablePromise<T>} + * @template T + */ + static forPromise(promise) { + return new CancellablePromise((resolve, reject) => { + promise.then(resolve, reject); + }); + } + + /** + * Produces a CancellablePromise |outer| that runs the following steps: + * + * In the normal case, |outer| waits for a regular (non-cancellable) Promise + * |promise| to resolve to |value|. Then it calls |startCancellableStep|, + * passing |value| as the argument, to produce a CancellablePromise |inner|. + * When |inner| is settled, the result is used to settle |outer|. + * + * If |outer| is cancelled before |promise| is resolved, then |value| is + * discarded and |startCancellableStep| is not called. + * + * If |outer| is cancelled after |promise| is resolved, then |inner| is + * cancelled as well. + * + * If |promise| is rejected, then |outer| is rejected as well. + * + * @param {!Promise<A>} promise + * @param {function(A):!CancellablePromise<B>} startCancellableStep + * @return {!CancellablePromise<B>} + * @template A, B + */ + static withUncancellableStep(promise, startCancellableStep) { + /** @type {boolean} */ + let wasCancelled = false; + /** @type {CancellablePromise} */ + let innerPromise = null; + + return new CancellablePromise( + (resolve, reject) => { + promise.then(value => { + if (!wasCancelled) { + innerPromise = startCancellableStep(value); + innerPromise.promise.then(resolve, reject); + } + }, reject); + }, + reason => { + if (innerPromise) { + innerPromise.cancel(reason); + } else { + wasCancelled = true; + } + }); + } +} + +exports = CancellablePromise; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender.js new file mode 100644 index 00000000000..ca16b6a578c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender.js @@ -0,0 +1,344 @@ +// 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 sender for sending route message to MR. When MR asks for + * the next batch of messages, this sender sends matching messages if + * available, or buffer the request till message arrives. + * + + */ + +goog.provide('mr.RouteMessageSender'); + +goog.require('mr.Assertions'); +goog.require('mr.Logger'); +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.RouteMessage'); +goog.require('mr.ThrottlingSender'); + + +/** + * @implements {mr.PersistentData} + */ +mr.RouteMessageSender = class extends mr.ThrottlingSender { + /** + * @param {!mr.ProviderManagerCallbacks} providerManagerCallbacks + * @param {number} messageSizeKeepAliveThreshold + * @final + */ + constructor(providerManagerCallbacks, messageSizeKeepAliveThreshold) { + super(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + + /** + * Route ID to queue map. One queue per route. + * @private {!Map<string, !Array<!mr.RouteMessage>>} + */ + this.queues_ = new Map(); + + /** + * Set of routes being listened for messages. + * @private {!Set<string>} + */ + this.listeningRouteIds_ = new Set(); + + /** + * Callback to invoke to send a batch of route messages. Set during + * |init()|. The first argument is the route id, and the second argument is + * the batch of messages to send. + * @private {?function(string, !Array<!mr.RouteMessage>)} + */ + this.sendMessagesCallback_ = null; + + /** + * Sum of sizes of all string messages currently queued up. When this is + * above a certain threshold, the extension will be kept alive due to + * performance reasons. + * @private {number} + */ + this.totalMessageSize_ = 0; + + /** + * Number of enqueued binary messages. The extension will be kept alive + * while there are enqueued binary messages. + * @private {number} + */ + this.binaryMessageCount_ = 0; + + /** + * See |totalMessageSize_| and |binaryMessageCount_|. + * @private {boolean} + */ + this.shouldKeepAlive_ = false; + + /** + * @private {!mr.ProviderManagerCallbacks} + */ + this.providerManagerCallbacks_ = providerManagerCallbacks; + + /** + * @private {number} + */ + this.messageSizeKeepAliveThreshold_ = messageSizeKeepAliveThreshold; + + /** @private {mr.Logger} */ + this.logger_ = mr.Logger.getInstance('mr.RouteMessageSender'); + } + + /** + * @param {!function(string, !Array<!mr.RouteMessage>)} sendMessagesCallback + * The callback to invoke to send a batch of route messages. See comments + * on |sendMessagesCallback_|. + */ + init(sendMessagesCallback) { + this.sendMessagesCallback_ = sendMessagesCallback; + mr.PersistentDataManager.register(this); + } + + /** + * Starts listening for route messages associated with |routeId|, and schedule + * a task to send any available messages to the Media Router. + * + * @param {string} routeId + */ + listenForRouteMessages(routeId) { + if (this.listeningRouteIds_.has(routeId)) { + return; + } + + this.listeningRouteIds_.add(routeId); + if (this.hasMessageFrom_(routeId)) { + this.scheduleSend(); + } + } + + /** + * The media router wants to stop getting further messages associated with the + * routeId until it issues listenForRouteMessages again. + * + * @param {string} routeId + */ + stopListeningForRouteMessages(routeId) { + this.listeningRouteIds_.delete(routeId); + } + + /** + * Called when there is a |message| for |routeId| available to be sent. + * @param {string} routeId + * @param {string|!Uint8Array} message + */ + send(routeId, message) { + let queue = this.queues_.get(routeId); + if (!queue) { + queue = []; + this.queues_.set(routeId, queue); + } + + const routeMessage = new mr.RouteMessage(routeId, message); + queue.push(routeMessage); + + // If the queue size for this route has grown suspiciously large, log + // warnings as the queue size grows past the warning threshold. + // + + if (queue.length > mr.RouteMessageSender.QUEUE_SIZE_WARNING_THRESHOLD_ && + queue.length % mr.RouteMessageSender.QUEUE_SIZE_WARNING_THRESHOLD_ == + 1) { + this.logger_.warning( + () => `Message queue length is excessively large ` + + `(${queue.length}) for route ${routeId}`); + } + + this.totalMessageSize_ += mr.RouteMessage.stringLength(routeMessage); + if (mr.RouteMessage.isBinary(routeMessage)) { + this.binaryMessageCount_++; + } + + this.updateShouldKeepAlive_(); + if (this.listeningRouteIds_.has(routeId)) { + this.scheduleSend(); + } + } + + /** + * Removes queue on route removal. + * @param {string} routeId + */ + onRouteRemoved(routeId) { + this.listeningRouteIds_.delete(routeId); + const queue = this.queues_.get(routeId); + if (queue) { + this.queues_.delete(routeId); + this.onMessagesRemoved_(queue); + this.updateShouldKeepAlive_(); + } + } + + /** + * Update message size and binary message counters as |messages| are being + * removed from the queue. + * @param {!Array<!mr.RouteMessage>} messages + * @private + */ + onMessagesRemoved_(messages) { + if (messages.length == 0) { + return; + } + + for (let message of messages) { + this.totalMessageSize_ -= mr.RouteMessage.stringLength(message); + if (mr.RouteMessage.isBinary(message)) { + this.binaryMessageCount_--; + } + } + } + + /** + * @param {string} routeId + * @return {boolean} True if there is at least one message from the route. + * @private + */ + hasMessageFrom_(routeId) { + const queue = this.queues_.get(routeId); + return !!queue && queue.length > 0; + } + + /** + * Computes whether the extension should be kept alive, and informs the + * Provider Manager if that value changed. + * @private + */ + updateShouldKeepAlive_() { + const newShouldKeepAlive = this.binaryMessageCount_ > 0 || + this.totalMessageSize_ > this.messageSizeKeepAliveThreshold_; + if (newShouldKeepAlive != this.shouldKeepAlive_) { + this.shouldKeepAlive_ = newShouldKeepAlive; + this.providerManagerCallbacks_.requestKeepAlive( + this.getStorageKey(), newShouldKeepAlive); + } + } + + /** + * @override + */ + doSend() { + if (!this.sendMessagesCallback_) { + this.logger_.error( + 'sendMessagesCallback not set. Messages not delivered.'); + return; + } + + for (const routeId of this.listeningRouteIds_) { + const queue = this.queues_.get(routeId); + if (!queue || (queue.length == 0)) { + continue; + } + this.sendMessagesCallback_(routeId, queue); + this.onMessagesRemoved_(queue); + this.queues_.set(routeId, []); + } + this.updateShouldKeepAlive_(); + } + + /** + * @override + */ + getStorageKey() { + return 'mr.RouteMessageSender'; + } + + /** + * @override + */ + getData() { + // Assumption: While there are binary messages in any queue, the extension + // keep-alive should be turned on. Thus, we should not encounter binary + // messages while persisting the queues here. + const persistableQueues = [...this.queues_.entries()].map(entry => { + return [ + entry[0], + entry[1].map( + message => mr.Assertions.assertString( + message.message, 'No support for persisting binary messages')) + ]; + }); + return [new mr.RouteMessageSender.PersistentData_( + persistableQueues, Array.from(this.listeningRouteIds_), + this.totalMessageSize_)]; + } + + /** + * @override + */ + loadSavedData() { + const savedData = /** @type {?mr.RouteMessageSender.PersistentData_} */ + (mr.PersistentDataManager.getTemporaryData(this)); + if (savedData) { + this.queues_ = new Map(); + for (const entry of savedData.queues) { + const routeId = /** @type {string} */ (entry[0]); + // Assumption: In getData(), there should not have been any binary + // messages persisted. Therefore, only string messages should be + // restored here. + const queue = (/** @type {!Array<*>} */ (entry[1])).map(message => { + return new mr.RouteMessage( + routeId, + mr.Assertions.assertString( + message, 'No support for restoring binary messages')); + }); + this.queues_.set(routeId, queue); + } + this.listeningRouteIds_ = new Set(savedData.listeningRouteIds); + this.totalMessageSize_ = savedData.totalMessageSize; + } + } +}; + + +/** + * The interval at which messages will be sent back to Media Router. + * @const {number} + */ +mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS = 20; + + +/** + * Generally, no route should have more than 50 messages in queue. However, + * there may be momentary spikes for high-volume communications (e.g., RPC + * traffic). + * @private @const {number} + */ +mr.RouteMessageSender.QUEUE_SIZE_WARNING_THRESHOLD_ = 50; + + +/** + * If the total number of characters in all enqueued string messages exceeds + * this threshold, the extension will be kept alive for performance reasons. + * @const {number} + */ +mr.RouteMessageSender.MESSAGE_SIZE_KEEP_ALIVE_THRESHOLD = 512 * 1024; + + +/** + * @private + */ +mr.RouteMessageSender.PersistentData_ = class { + /** + * @param {!Array<Array<*>>} queues An array where each element is an + * 2-element array of [routeId, messages]. + * @param {!Array<string>} listeningRouteIds + * @param {number} totalMessageSize + */ + constructor(queues, listeningRouteIds, totalMessageSize) { + /** @type {!Array<Array<*>>} */ + this.queues = queues; + + /** @type {!Array<string>} */ + this.listeningRouteIds = listeningRouteIds; + + /** @type {number} */ + this.totalMessageSize = totalMessageSize; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender_test.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender_test.js new file mode 100644 index 00000000000..31b885e82dc --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/route_message_sender_test.js @@ -0,0 +1,173 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.MockClock'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.RouteMessage'); +goog.require('mr.RouteMessageSender'); +goog.require('mr.UnitTestUtils'); + + + +describe('Tests RouteMessageSender', function() { + let mockClock; + let providerManagerCallbacks; + let callback; + let sender; + const routeId1 = 'r1'; + const routeId2 = 'r2'; + const text1 = 'message1'; + const messageSizeThreshold = 100; + + beforeEach(function() { + mr.UnitTestUtils.mockChromeApi(); + chrome.runtime.onSuspend.addListener = l => { + onSuspendListener = l; + }; + + mr.PersistentDataManager.clear(); + mockClock = new mr.MockClock(true); + providerManagerCallbacks = + jasmine.createSpyObj('pmCallbacks', ['requestKeepAlive']); + sender = new mr.RouteMessageSender( + providerManagerCallbacks, messageSizeThreshold); + callback = jasmine.createSpy('sendCallback'); + sender.init(callback); + }); + + afterEach(function() { + mr.PersistentDataManager.clear(); + mockClock.uninstall(); + mr.UnitTestUtils.restoreChromeApi(); + }); + + it('hasMessageFrom_', function() { + expect(sender.hasMessageFrom_(routeId1)).toBe(false); + sender.send(routeId1, text1); + expect(sender.hasMessageFrom_(routeId1)).toBe(true); + }); + + it('No msg when requested; new msg sent when arrives', function() { + sender.listenForRouteMessages(routeId1); + sender.send(routeId1, text1); + expect(callback).toHaveBeenCalledWith( + routeId1, [new mr.RouteMessage(routeId1, text1)]); + expect(sender.hasMessageFrom_(routeId1)).toBe(false); + }); + + it('Has msg when requested', function() { + sender.send(routeId1, text1); + expect(callback).not.toHaveBeenCalled(); + + sender.listenForRouteMessages(routeId1); + expect(callback).toHaveBeenCalledWith( + routeId1, [new mr.RouteMessage(routeId1, text1)]); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(sender.hasMessageFrom_(routeId1)).toBe(false); + }); + + it('onRouteRemoved removes messages', function() { + sender.send(routeId1, text1); + expect(sender.hasMessageFrom_(routeId1)).toBe(true); + sender.onRouteRemoved(routeId1); + expect(sender.hasMessageFrom_(routeId1)).toBe(false); + + expect(sender.totalMessageSize_).toEqual(0); + expect(sender.binaryMessageCount_).toEqual(0); + expect(sender.shouldKeepAlive_).toBe(false); + }); + + it('stopListeningForRouteMessages does not remove messages', function() { + sender.send(routeId1, text1); + expect(sender.hasMessageFrom_(routeId1)).toBe(true); + sender.stopListeningForRouteMessages(routeId1); + expect(sender.hasMessageFrom_(routeId1)).toBe(true); + }); + + it('requestKeepAlive due to binary message', function() { + const binaryArray1 = new Uint8Array(12); + const binaryArray2 = new Uint8Array(34); + sender.send(routeId1, binaryArray1); + expect(providerManagerCallbacks.requestKeepAlive) + .toHaveBeenCalledWith(sender.getStorageKey(), true); + providerManagerCallbacks.requestKeepAlive.calls.reset(); + + sender.send(routeId2, binaryArray2); + expect(providerManagerCallbacks.requestKeepAlive).not.toHaveBeenCalled(); + + sender.listenForRouteMessages(routeId1); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(callback).toHaveBeenCalledWith( + routeId1, [new mr.RouteMessage(routeId1, binaryArray1)]); + expect(providerManagerCallbacks.requestKeepAlive).not.toHaveBeenCalled(); + + sender.listenForRouteMessages(routeId2); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(callback).toHaveBeenCalledWith( + routeId2, [new mr.RouteMessage(routeId2, binaryArray2)]); + expect(providerManagerCallbacks.requestKeepAlive) + .toHaveBeenCalledWith(sender.getStorageKey(), false); + + expect(sender.totalMessageSize_).toEqual(0); + expect(sender.binaryMessageCount_).toEqual(0); + expect(sender.shouldKeepAlive_).toBe(false); + }); + + it('requestKeepAlive due to message size threshold', function() { + const message1 = 'a'.repeat(messageSizeThreshold / 2); + const message2 = 'b'.repeat(messageSizeThreshold / 2 + 1); + + sender.send(routeId1, message1); + expect(providerManagerCallbacks.requestKeepAlive).not.toHaveBeenCalled(); + + sender.send(routeId2, message2); + expect(providerManagerCallbacks.requestKeepAlive) + .toHaveBeenCalledWith(sender.getStorageKey(), true); + providerManagerCallbacks.requestKeepAlive.calls.reset(); + + sender.listenForRouteMessages(routeId1); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(callback).toHaveBeenCalledWith( + routeId1, [new mr.RouteMessage(routeId1, message1)]); + expect(providerManagerCallbacks.requestKeepAlive) + .toHaveBeenCalledWith(sender.getStorageKey(), false); + + sender.listenForRouteMessages(routeId2); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(callback).toHaveBeenCalledWith( + routeId2, [new mr.RouteMessage(routeId2, message2)]); + + expect(sender.totalMessageSize_).toEqual(0); + expect(sender.binaryMessageCount_).toEqual(0); + expect(sender.shouldKeepAlive_).toBe(false); + }); + + it('saves pending messages, then restores and sends them', function() { + const textMessage = 'this is a text message'; + const textMessage2 = 'this is another text message'; + + // Queue up the messages in the RouteMessageSender. They will not be sent + // because listenForRouteMessages() has not been called yet. + sender.send(routeId1, textMessage); + sender.send(routeId1, textMessage2); + + // Persists pending messages. + mr.PersistentDataManager.suspendForTest(); + + // Re-creating a new RouteMessageSender restores the pending messages. + sender = new mr.RouteMessageSender( + providerManagerCallbacks, messageSizeThreshold); + sender.init(callback); + + // Now, call listenForRouteMessages() and the restored pending messages + // should be processed. + sender.listenForRouteMessages(routeId1); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(callback).toHaveBeenCalledWith(routeId1, [ + new mr.RouteMessage(routeId1, textMessage), + new mr.RouteMessage(routeId1, textMessage2), + ]); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender.js new file mode 100644 index 00000000000..730959a9b91 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender.js @@ -0,0 +1,89 @@ +// 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 An abstract throttling sender. It sends right away if there is + * no sending in the past 'interval' time. Otherwise, it waits till 'interval' + * is passed. + * + + */ + +goog.provide('mr.ThrottlingSender'); + + +/** + * Note: Strictly speaking, this class should implement PersistentData to store + * lastSendTime_. But since we never specify an interval large enough for the + * extension to become suspended in between, it is not needed in practice. + */ +mr.ThrottlingSender = class { + /** + * @param {number} interval in milliseconds. + */ + constructor(interval) { + /** @private {number} */ + this.interval_ = interval; + + /** @private {?number} */ + this.lastSendTime_ = null; + + /** @private {?number} */ + this.timerId_ = null; + } + + /** + * Clears the sender timer and sets it to null. + * @private + */ + clearTimer_() { + if (this.timerId_ != null) { + clearTimeout(this.timerId_); + this.timerId_ = null; + } + } + + /** + * Schedule a send. + * @protected + */ + scheduleSend() { + if (this.timerId_ != null) { + return; + } + if (this.lastSendTime_ == null || + Date.now() - this.lastSendTime_ >= this.interval_) { + // Send right away + this.send_(); + } else { + // Delay a while + const delay = + Math.max(this.lastSendTime_ + this.interval_ - Date.now(), 5); + this.timerId_ = setTimeout(this.send_.bind(this), delay); + } + } + + /** + * Sends messages immediately. + */ + sendImmediately() { + this.send_(); + } + + /** + * Sends right away and schedule another send if there is more to send. + * @private + */ + send_() { + this.clearTimer_(); + this.doSend(); + this.lastSendTime_ = Date.now(); + } + + /** + * Sends the message if available. + * @protected + */ + doSend() {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender_test.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender_test.js new file mode 100644 index 00000000000..fd3de97a161 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/mr_event_senders/throttling_sender_test.js @@ -0,0 +1,69 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.MockClock'); +goog.require('mr.ThrottlingSender'); + +describe('Tests ThrottlingSender', function() { + let mockClock; + let sender; + const interval = 50; + let numOfMessages; + + beforeEach(function() { + mockClock = new mr.MockClock(true); + sender = new mr.ThrottlingSender(interval); + numOfMessages = 0; + sender.doSend = jasmine.createSpy('doSend').and.callFake(() => { + numOfMessages--; + }); + }); + + afterEach(function() { + mockClock.uninstall(); + }); + + it('first msg is sent right away', function() { + numOfMessages++; + sender.scheduleSend(); + expect(sender.doSend.calls.count()).toEqual(1); + expect(numOfMessages).toEqual(0); + mockClock.tick(interval); + expect(sender.doSend.calls.count()).toEqual(1); + expect(numOfMessages).toEqual(0); + }); + + it('messages are throttled', function() { + numOfMessages++; + sender.scheduleSend(); + expect(sender.doSend.calls.count()).toEqual(1); + expect(numOfMessages).toEqual(0); + + numOfMessages++; + sender.scheduleSend(); + expect(sender.doSend.calls.count()).toEqual(1); + expect(numOfMessages).toEqual(1); + + mockClock.tick(interval); + expect(sender.doSend.calls.count()).toEqual(2); + expect(numOfMessages).toEqual(0); + }); + + it('messages are sent gradually 1', function() { + numOfMessages++; + sender.scheduleSend(); + expect(sender.doSend.calls.count()).toEqual(1); + mockClock.tick(interval / 2); + expect(sender.doSend.calls.count()).toEqual(1); + + numOfMessages++; + sender.scheduleSend(); + expect(sender.doSend.calls.count()).toEqual(1); + mockClock.tick(interval / 2); + expect(sender.doSend.calls.count()).toEqual(2); + mockClock.tick(interval); + expect(sender.doSend.calls.count()).toEqual(2); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/presentation_enums.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/presentation_enums.js new file mode 100644 index 00000000000..3ce170b66ea --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/presentation_enums.js @@ -0,0 +1,35 @@ +// 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 Defines presentation connection related enums. + */ + +goog.provide('mr.PresentationConnectionCloseReason'); +goog.provide('mr.PresentationConnectionState'); + + +/** + * Presentation connection states. Keep in sync with PresentationConnection.idl + * in the Chromium code base. + * + * @enum {string} + */ +mr.PresentationConnectionState = { + CONNECTED: 'connected', + TERMINATED: 'terminated', + CLOSED: 'closed' +}; + + +/** + * Keep in sync with PresentationConnectionCloseEvent.idl in Chromium code base. + * + * @enum {string} + */ +mr.PresentationConnectionCloseReason = { + ERROR: 'error', + CLOSED: 'closed', + WENT_AWAY: 'went_away' +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/provider.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider.js new file mode 100644 index 00000000000..fe0217eff71 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider.js @@ -0,0 +1,258 @@ +// 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 API for media route providers. + * + + */ + +goog.provide('mr.Provider'); +goog.provide('mr.ProviderName'); + +goog.require('mr.CancellablePromise'); + +/** + * @enum {string} + */ +mr.ProviderName = { + CAST: 'cast', + DIAL: 'dial', + CLOUD: 'cloud', + TEST: 'test' +}; + + + +/** + * @record + */ +mr.Provider = class { + /** + * Gets provider name. + * @return {string} + */ + getName() {} + + /** + * Called to send a message to the sink via a media route. + * @param {string} routeId + * @param {!Object|string} message The message to send. Object will be + * serialized to a JSON string. + * @param {Object=} opt_extraInfo Extra info about how to send a message. + * @return {!Promise<void>} Fulfilled when the message is posted, or rejected + * if there an error. + */ + sendRouteMessage(routeId, message, opt_extraInfo) {} + + /** + * Called to send a binary message to the sink via a media route. + * @param {string} routeId + * @param {!Uint8Array} data The message to send. + * @return {!Promise<void>} Fulfilled when the message is posted, or rejected + * if there an error. + */ + sendRouteBinaryMessage(routeId, data) {} + + /** + * Called the first time the provider is loaded. + * Should do any one-time initialization (i.e. register event filters, etc.) + * @param {!mojo.MediaRouteProviderConfig=} config The config object from the + * browser to initialize the provider with. + */ + initialize(config = undefined) {} + + /** + * Queries this provider for existing routes. + * + * @return {!Array.<!mr.Route>} + */ + getRoutes() {} + + /** + * Queries this provider for available sinks. + * + * @param {string} sourceUrn + * @return {!mr.SinkList} + */ + getAvailableSinks(sourceUrn) {} + + /** + * Starts querying for sinks capable of displaying |sourceUrn|. + * @param {string} sourceUrn The URN of the media. + */ + startObservingMediaSinks(sourceUrn) {} + + /** + * Stops querying for sinks capable of displaying |sourceUrn|. + * @param {string} sourceUrn The URN of the media. + */ + stopObservingMediaSinks(sourceUrn) {} + + /** + * Informs the provider to send updates on routes list. + * @param {string} sourceUrn The URN of the media. + */ + startObservingMediaRoutes(sourceUrn) {} + + /** + * Informs the provider to stop sending updates on routes list. + * @param {string} sourceUrn The URN of the media. + */ + stopObservingMediaRoutes(sourceUrn) {} + + /** + * Queries this provider for a sink by id. + * + * @param {string} sinkId + * @return {?mr.Sink} + * Null if no sink exists with sinkId. + */ + getSinkById(sinkId) {} + + /** + * Creates a new route to the sink. + * @param {string} sourceUrn + * @param {string} sinkId The ID of the target sink. + * @param {string} presentationId A presentation ID to use. + * @param {boolean} offTheRecord True if the request is from an + * off the record (incognito) browser profile. + * @param {number} timeoutMillis Request timeout in milliseconds. + * @param {string=} opt_origin + * @param {number=} opt_tabId + * @return {!mr.CancellablePromise<!mr.Route>} Fulfilled with route created if + * successful. Rejected otherwise. + */ + createRoute( + sourceUrn, sinkId, presentationId, offTheRecord, timeoutMillis, + opt_origin, opt_tabId) {} + + /** + * Terminates the media route owned by this provider. + * @param {string} routeId The media route id. + * @return {!Promise} Fulfilled when route is terminated, or rejected with + * an error. + */ + terminateRoute(routeId) {} + + /** + * Creates and computes mirror settings appropriate for the given sink (and + * the sender's capabilities). See class comments for mr.mirror.Settings when + * overriding this method. Returns null if this provider does not support the + * sink. + * + * The provider must return valid, frozen settings if + * provider.canRoute(sourceUrn, sinkId) is true. + * + * @param {string} sinkId The ID of the sink to mirror to. + * @return {!mr.mirror.Settings} + */ + getMirrorSettings(sinkId) {} + + /** + * Gets the name of the best mirror service supported by this provider + * on the sink |sinkId|. + * + * Note that provider must return a valid mirror service name if + * provider.canRoute(sourceUrn, sinkId) is true, where sourcerUrn is any + * valid mirroring URN. + * + * @param {string} sinkId + * @return {?mr.mirror.ServiceName} + * Null if no mirror service is supported on the sink. + */ + getMirrorServiceName(sinkId) {} + + /** + * Tells the provider that the mirroring activity description for the + * mirroring route |routeId| has changed. The provider can synchronize this + * with its own state. + * @param {string} routeId + */ + onMirrorActivityUpdated(routeId) {} + + /** + * Whether this provider can route media |sourceUrn| to sink |sinkId|. + * @param {string} sourceUrn The URN of the media being displayed. + * @param {string} sinkId + * @return {boolean} True if the provider can handle it. + */ + canRoute(sourceUrn, sinkId) {} + + /** + * Whether this provider can join a given route from |sourceUrn| and, + * optionally, the specific |route| in question. + * @param {string} sourceUrn The URN of the media being displayed. + * @param {string=} presentationId The presentation ID to join. + * @param {mr.Route=} route The route to join. + * @return {boolean} True if the provider can handle it. + */ + canJoin(sourceUrn, presentationId = undefined, route = undefined) {} + + /** + * Joins a route identified by by |sourceUrn| and |presentationId|. + * @param {string} sourceUrn + * @param {string} presentationId A presentation ID for Presentation API + * client; A Cast session ID for Cast join; and 'autojoin' for Cast auto Join + * case; + * @param {boolean} offTheRecord True if the request is from an + * off the record (incognito) browser profile. + * @param {number} timeoutMillis Request timeout in milliseconds. + * @param {string} origin + * @param {?number} tabId null for packaged app. + * @return {!mr.CancellablePromise<!mr.Route>} Fulfilled with the route if + * joined; Rejected otherwise. + */ + joinRoute( + sourceUrn, presentationId, offTheRecord, timeoutMillis, origin, tabId) {} + + /** + * Joins a route identified by by |sourceUrn| and |routeId|. + * @param {string} sourceUrn + * @param {string} routeId A route ID to join. + * @param {string} presentationId The presentation ID of the route to be + * created. + * @param {string} origin + * @param {?number} tabId null for packaged app. + * @param {number=} opt_timeoutMillis If positive, the timeout to use in place + * of default timeout. + * @return {!mr.CancellablePromise<!mr.Route>} Fulfilled with the route if + * joined; Rejected otherwise. + */ + connectRouteByRouteId( + sourceUrn, routeId, presentationId, origin, tabId, opt_timeoutMillis) {} + + /** + * Detaches a presentation connection from the underlying media route given by + * |routeId|. + * @param {!string} routeId + */ + detachRoute(routeId) {} + + /** + * Searches this provider for a sink that matches |searchCriteria| that is + * compatible with the source |sourceUrn|. Returns a promise which resolves + * to the matching sink, or rejected if not found. + * @param {string} sourceUrn + * @param {!mr.SinkSearchCriteria} searchCriteria + * @return {!Promise<!mr.Sink>} + */ + searchSinks(sourceUrn, searchCriteria) {} + + /** + * Called when Media Router finishes sink discovery. Store |sinks| in this + * provider. + * @param {!Array<!mojo.Sink>} sinks list of discovered sinks + */ + provideSinks(sinks) {} + + /** + * See documentation in interface_data/mojo.js. + * @param {string} routeId + * @param {!mojo.InterfaceRequest} controllerRequest + * @param {!mojo.MediaStatusObserverPtr} observer + * @return {!Promise<void>} + */ + createMediaRouteController(routeId, controllerRequest, observer) {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_events.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_events.js new file mode 100644 index 00000000000..564180e723f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_events.js @@ -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. + +/** + * @fileoverview Provider events. + + */ + +goog.provide('mr.InternalMessageEvent'); +goog.provide('mr.ProviderEventType'); + + +/** + * @enum {string} + */ +mr.ProviderEventType = { + INTERNAL_MESSAGE: 'internal_message' +}; + + +/** + * @template T + */ +mr.InternalMessageEvent = class { + /** + * @param {string} routeId + * @param {T} message + */ + constructor(routeId, message) { + /** + * @const + */ + this.type = mr.ProviderEventType.INTERNAL_MESSAGE; + + /** + * @type {string} + */ + this.routeId = routeId; + + /** + * @type {T} + */ + this.message = message; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager.js new file mode 100755 index 00000000000..94e50f9792f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager.js @@ -0,0 +1,1280 @@ +// 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 API for registering Media Route Providers. + * + + * + * For now, we will always load every provider each time the extension is + * loaded. + */ + +goog.provide('mr.ProviderManager'); + +goog.require('mr.Assertions'); +goog.require('mr.CancellablePromise'); +goog.require('mr.EventAnalytics'); +goog.require('mr.EventTarget'); +goog.require('mr.InitHelper'); +goog.require('mr.InternalMessageEvent'); +goog.require('mr.Logger'); +goog.require('mr.MediaRouterRequestHandler'); +goog.require('mr.MessagePortService'); +goog.require('mr.MessagePortServiceImpl'); +goog.require('mr.MirrorAnalytics'); +goog.require('mr.Module'); +goog.require('mr.MojoUtils'); +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.ProviderManagerCallbacks'); +goog.require('mr.RouteMessageSender'); +goog.require('mr.RouteRequestError'); +goog.require('mr.RouteRequestResultCode'); +goog.require('mr.Sink'); +goog.require('mr.SinkAvailability'); +goog.require('mr.SinkSearchCriteria'); +goog.require('mr.Throttle'); +goog.require('mr.mirror.Error'); +goog.require('mr.mirror.ServiceName'); + + +/** + * Tracks registered MediaRouteProviders and loads them on-demand. + * @implements {mr.MediaRouterRequestHandler} + * @implements {mr.PersistentData} + * @implements {mr.ProviderManagerCallbacks} + */ +mr.ProviderManager = class extends mr.Module { + constructor() { + super(); + + /** + * @private {mr.Logger} + */ + this.logger_ = mr.Logger.getInstance('mr.ProviderManager'); + + /** + * Holds a an array of registered Media Route Providers. + * @private {!Array<!mr.Provider>} + */ + this.providers_ = []; + + /** + * Holds a map of route ID to the provider that is managing the route. + * @private {!Map<string, mr.Provider>} + */ + this.routeIdToProvider_ = new Map(); + + /** + * Holds a map of route ID to the mirror service name if the route is for + * tab/desktop mirroring. + * @private {!Map<string, mr.mirror.ServiceName>} + */ + this.routeIdToMirrorServiceName_ = new Map(); + + /** + * Holds a set of active sink queries. + * @private {!Set<string>} + */ + this.sinkQueries_ = new Set(); + + /** + * Holds a set of active route queries. + * @private {!Set<string>} + */ + this.routeQueries_ = new Set(); + + /** + * Holds a map of mirror service names to modules. + * @private {!Map<mr.mirror.ServiceName, mr.ModuleId>} + */ + this.mirrorServiceModules_ = new Map(); + + /** + * Keeps track of last used mirror service for gathering logs for feedback. + * @private {?mr.mirror.ServiceName} + */ + this.lastUsedMirrorService_ = null; + + /** @private {?mr.MediaRouterService} */ + this.mediaRouterService_ = null; + + /** @private {!mr.EventTarget} */ + this.routeMessageEventTarget_ = new mr.EventTarget(); + + + /** @private {!mr.Throttle} */ + this.routeUpdateEventThrottle_ = new mr.Throttle( + this.sendRoutesQueryResultToMr_, + mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_, this); + + /** @private {!mr.Throttle} */ + this.sinkUpdateEventThrottle_ = new mr.Throttle( + this.executeSinkQueries_, mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_, + this); + + /** + * MR message sender with build-in rate throttle. + * @private {!mr.RouteMessageSender} + */ + this.mrRouteMessageSender_ = new mr.RouteMessageSender( + this, mr.RouteMessageSender.MESSAGE_SIZE_KEEP_ALIVE_THRESHOLD); + + /** + * The number of pending createRoute + * @private {number} + */ + this.pendingRequestRoutes_ = 0; + + /** + * Maps provider name to that provider's last reported sink availability. + * @private {!Map<string, mr.SinkAvailability>} + */ + this.sinkAvailabilityMap_ = new Map(); + + /** + * Whether mDNS is currently enabled. + * @private {boolean} + */ + this.mdnsEnabled_ = window.navigator.userAgent.indexOf('Windows') == -1; + + /** + * Functions that should be executed when |mdnsEnabled_| goes from |false| + * to + * |true|. + * @private {!Array<function()>} + */ + this.mdnsEnabledCallbacks_ = []; + + /** + * Names of components that have requested to keep the extension alive. + * @private {!Array<string>} + */ + this.keepAliveComponents_ = []; + + /** + * Handler for chrome.runtime.onMessage events. + * @private @const + */ + this.internalMessageHandler_ = + mr.InitHelper.getInternalMessageHandler(this); + + /** + * Handler for chrome.runtime.onMessageExternal events. + * @private @const + */ + this.externalMessageHandler_ = + mr.InitHelper.getExternalMessageHandler(this); + + mr.ProviderManager.exportProperties_(this); + } + + /** + * @override + */ + handleEvent(event, ...args) { + if (event == chrome.runtime.onMessage) { + this.internalMessageHandler_(...args); + } else if (event == chrome.runtime.onMessageExternal) { + this.externalMessageHandler_(...args); + } else { + throw new Error('Unhandled event'); + } + } + + /** + * Gets the mirror service with the given name. + * @param {mr.mirror.ServiceName} serviceName Name of the mirror service. + * @return {!Promise<!mr.mirror.Service>} Resolved with the requested + * mirror service. + */ + getMirrorService(serviceName) { + const moduleId = this.mirrorServiceModules_.get(serviceName); + return mr.Module.load(moduleId).then(module => { + let service = /** @type {!mr.mirror.Service} */ (module); + service.initialize(this); + return service; + }); + } + + /** + * Registers and initalizes providers. + * + * @param {!Array<!mr.Provider>} providers + * @param {!mojo.MediaRouteProviderConfig=} config + */ + registerAllProviders(providers, config = undefined) { + providers.forEach(provider => { + this.registerProvider_(provider, config); + }); + } + + /** + * Initializes the provider manager, register / initialize given providers, + * and registers itself with PersistentDataManager. + * @param {!mr.MediaRouterService} mediaRouterService + * @param {!Array<!mr.Provider>} providers + * @param {!mojo.MediaRouteProviderConfig=} config + */ + initialize(mediaRouterService, providers, config = undefined) { + this.mirrorServiceModules_.set( + mr.mirror.ServiceName.WEBRTC, mr.ModuleId.WEBRTC_STREAMING_SERVICE); + this.mirrorServiceModules_.set( + mr.mirror.ServiceName.CAST_STREAMING, + mr.ModuleId.CAST_STREAMING_SERVICE); + this.mirrorServiceModules_.set( + mr.mirror.ServiceName.HANGOUTS, mr.ModuleId.HANGOUTS_SERVICE); + this.mirrorServiceModules_.set( + mr.mirror.ServiceName.MEETINGS, mr.ModuleId.MEETINGS_SERVICE); + + mr.MessagePortService.setService(new mr.MessagePortServiceImpl(this)); + + this.mediaRouterService_ = mediaRouterService; + this.mrRouteMessageSender_.init( + this.mediaRouterService_.onRouteMessagesReceived.bind( + this.mediaRouterService_)); + this.registerAllProviders(providers, config); + + mr.PersistentDataManager.register(this); + mr.Module.onModuleLoaded(mr.ModuleId.PROVIDER_MANAGER, this); + } + + /** + * Registers a provider and initializes it. + * @param {!mr.Provider} provider + * @param {!mojo.MediaRouteProviderConfig=} config + * @private + */ + registerProvider_(provider, config = undefined) { + if (this.getProviderByName(provider.getName())) { + this.logger_.warning( + 'Provider ' + provider.getName() + ' already registered.'); + return; + } + + try { + provider.initialize(config); + this.providers_.push(provider); + this.sinkAvailabilityMap_.set( + provider.getName(), mr.SinkAvailability.UNAVAILABLE); + } catch (/** Error */ error) { + this.logger_.warning( + 'Provider ' + provider.getName() + ' failed to initialize.', error); + } + } + + /** + * @return {!Array<!mr.Provider>} Registered Media Route Providers. + */ + getProviders() { + return this.providers_; + } + + /** + * @param {!mr.CancellablePromise<!mr.Route>} routePromise + * @param {number} timeout Timeout in milliseconds. When timeout + * fires, the return value of this method is rejected. Must be a positive + * number. + * @return {!Promise<!mr.Route>} + * @private + */ + addTimeout_(routePromise, timeout) { + return new Promise((resolve, reject) => { + let timerId = null; + this.preventSuspend_(); + timerId = window.setTimeout(() => { + timerId = null; + // Refer to the CancellablePromise class for more details on the + // cancel() method, and <route-creation-timeout.svg.gz> for a diagram of + // how promise cancellation works with this class. + routePromise.cancel(new mr.RouteRequestError( + mr.RouteRequestResultCode.TIMED_OUT, + 'timeout after ' + timeout + ' ms.')); + }, timeout); + const cleanup = () => { + this.allowSuspend_(); + if (timerId != null) { + window.clearTimeout(timerId); + } + }; + routePromise.promise.then( + route => { + cleanup(); + resolve(route); + }, + err => { + cleanup(); + reject(mr.RouteRequestError.wrap(err)); + }); + }); + } + + /** + * @param {!Promise<T>} promise + * @param {number} timeout Timeout in milliseconds. When timeout + * fires, the return value of this method is rejected. Must be a positive + * number. + * @return {!Promise<T>} + * @template T + * @private + */ + addIgnoredTimeout_(promise, timeout) { + return this.addTimeout_(mr.CancellablePromise.forPromise(promise), timeout); + } + + /** + * Increments the number of pending request for routes and checks if the + * extension should be kept alive. + * @private + */ + preventSuspend_() { + this.pendingRequestRoutes_++; + this.maybeUpdateKeepAlive_(); + } + + /** + * Decrements the number of pending request for routes and checks if the + * extension should be kept alive. + * @private + */ + allowSuspend_() { + this.pendingRequestRoutes_--; + this.maybeUpdateKeepAlive_(); + } + + /** + * If override timeout is provided and is positive, returns it. + * Otherwise, returns the default timeout. + * @param {number} defaultTimeoutMillis Default timeout. + * @param {number=} opt_overrideTimeoutMillis Optional override timeout. + * @return {number} + * @private + */ + static getTimeoutToUse_(defaultTimeoutMillis, opt_overrideTimeoutMillis) { + return opt_overrideTimeoutMillis && opt_overrideTimeoutMillis > 0 ? + opt_overrideTimeoutMillis : + defaultTimeoutMillis; + } + + /** @override */ + onBeforeInvokeHandler() { + mr.EventAnalytics.recordEvent(mr.EventAnalytics.Event.MEDIA_ROUTER); + } + + /** + * Helper method to return a string origin from a string or a mojo.Origin + * object. + + * @param {!mojo.Origin|string} origin + * @return {string} + * @private + */ + mojoOriginToString_(origin) { + if (typeof origin == 'string') { + return origin; + } + return mr.MojoUtils.mojoOriginToString(/** @type{!mojo.Origin} */ (origin)); + } + + /** + * @override + */ + createRoute( + sourceUrn, sinkId, presentationId, origin = undefined, tabId = undefined, + timeoutMillis = undefined, offTheRecord = false) { + const provider = this.getProviderFor_(sourceUrn, sinkId); + if (!provider) { + return Promise.reject(new mr.RouteRequestError( + mr.RouteRequestResultCode.NO_SUPPORTED_PROVIDER, + 'No provider supports createRoute with source: ' + sourceUrn + + ' and sink: ' + sinkId)); + } + let originString = undefined; + if (origin !== undefined) { + originString = this.mojoOriginToString_(origin); + } + timeoutMillis = mr.ProviderManager.getTimeoutToUse_( + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, timeoutMillis); + const routePromise = provider.createRoute( + sourceUrn, sinkId, presentationId, offTheRecord, timeoutMillis, + originString, tabId); + return this.addTimeout_(routePromise, timeoutMillis) + .then( + route => route, + /** Error */ err => { + this.logger_.error('Error creating route.', err); + throw err; + }); + } + + /** + * @override + */ + connectRouteByRouteId( + sourceUrn, routeId, presentationId, origin, tabId, + timeoutMillis = undefined) { + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + return Promise.reject(new mr.RouteRequestError( + mr.RouteRequestResultCode.NO_SUPPORTED_PROVIDER, + 'No provider supports join ' + routeId)); + } + + // connectRouteByRouteId may take a while. Prevent extension suspension. + const routePromise = provider.connectRouteByRouteId( + sourceUrn, routeId, presentationId, this.mojoOriginToString_(origin), + tabId); + timeoutMillis = mr.ProviderManager.getTimeoutToUse_( + mr.ProviderManager.JOIN_ROUTE_TIMEOUT_MS_, timeoutMillis); + return this.addTimeout_(routePromise, timeoutMillis); + } + + /** + * @override + */ + joinRoute( + sourceUrn, presentationId, origin, tabId, timeoutMillis = undefined, + offTheRecord = false) { + const provider = this.getProviderForJoin_(sourceUrn, presentationId); + if (!provider) { + return Promise.reject(new mr.RouteRequestError( + mr.RouteRequestResultCode.NO_SUPPORTED_PROVIDER, + 'No provider supports join ' + presentationId)); + } + + // joinRoute may take a while. Prevent extension suspension. + timeoutMillis = mr.ProviderManager.getTimeoutToUse_( + mr.ProviderManager.JOIN_ROUTE_TIMEOUT_MS_, timeoutMillis); + const routePromise = provider.joinRoute( + sourceUrn, presentationId, offTheRecord, timeoutMillis, + this.mojoOriginToString_(origin), tabId); + return this.addTimeout_(routePromise, timeoutMillis); + } + + /** + * @override + */ + terminateRoute(routeId) { + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + return Promise.reject(new mr.RouteRequestError( + mr.RouteRequestResultCode.ROUTE_NOT_FOUND, + 'Route not found for routeId ' + routeId)); + } + return this.maybeStopMirrorSession_(routeId).then( + () => provider.terminateRoute(routeId)); + } + + /** + * @override + */ + startObservingMediaSinks(sourceUrn) { + if (!this.sinkQueries_.has(sourceUrn)) { + this.sinkQueries_.add(sourceUrn); + this.providers_.forEach(p => { + p.startObservingMediaSinks(sourceUrn); + }); + } + this.querySinks_(sourceUrn); + } + + /** + * @override + */ + stopObservingMediaSinks(sourceUrn) { + if (!this.sinkQueries_.delete(sourceUrn)) { + this.logger_.info('No existing query ' + sourceUrn); + } else { + this.doStopObservingMediaSinks_(sourceUrn); + } + } + + /** + * Helper method to stop a sink query on all providers. + * @param {string} sourceUrn The URN of the media. + * @private + */ + doStopObservingMediaSinks_(sourceUrn) { + this.providers_.forEach(p => { + p.stopObservingMediaSinks(sourceUrn); + }); + } + + /** + * Queries for sinks capable of displaying |sourceUrn|. + * @param {string} sourceUrn The URN of the media. + * @private + */ + querySinks_(sourceUrn) { + + if (sourceUrn == 'urn:x-org.chromium.media:source:tab:-1') { + this.logger_.warning('No sinks for sourceUrn: ' + sourceUrn); + } else { + /** @type {!Map<string, !mr.Sink>} */ + const sinks = new Map(); + let origins = []; + // Get available sinks from current providers. + this.providers_.forEach(p => { + const sinkList = p.getAvailableSinks(sourceUrn); + if (sinkList.sinks.length > 0) { + origins = sinkList.origins; + } + sinkList.sinks.forEach(sink => { + // There shouldn't be duplicate sinks. Log a message if we encounter + // it. + if (sinks.has(sink.id)) { + this.logger_.warning( + 'Detected duplicate sink ' + sink.id + + ' from provider: ' + p.getName()); + } else { + sinks.set(sink.id, sink); + } + }); + }); + + this.sendSinksToMr_(sourceUrn, Array.from(sinks.values()), origins || []); + } + } + + /** + * Sends sinks that support |sourceUrn| to media router. + * @param {string} sourceUrn + * @param {!Array<!mr.Sink>} sinkList Sinks that support |sourceUrn| + * @param {!Array<string>} origins Origins that can access the sink list. + * @private + */ + sendSinksToMr_(sourceUrn, sinkList, origins) { + this.logger_.info( + 'Sending ' + sinkList.length + ' sinks to MR for ' + sourceUrn); + this.mediaRouterService_.onSinksReceived( + sourceUrn, sinkList, origins.map(mr.MojoUtils.stringToMojoOrigin)); + } + + /** + * @override + */ + sendRouteMessage(routeId, message, opt_extraInfo) { + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + return Promise.reject(Error(`Invalid route ID ${routeId}`)); + } + return this.addIgnoredTimeout_( + provider.sendRouteMessage(routeId, message, opt_extraInfo), + mr.ProviderManager.SEND_MESSAGE_TIMEOUT_MS_); + } + + /** + * @override + */ + sendRouteBinaryMessage(routeId, data) { + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + return Promise.reject(Error(`Invalid route ID ${routeId}`)); + } + return this.addIgnoredTimeout_( + provider.sendRouteBinaryMessage(routeId, data), + mr.ProviderManager.SEND_MESSAGE_TIMEOUT_MS_); + } + + /** + * @override + */ + startListeningForRouteMessages(routeId) { + this.mrRouteMessageSender_.listenForRouteMessages(routeId); + } + + /** + * @override + */ + stopListeningForRouteMessages(routeId) { + this.mrRouteMessageSender_.stopListeningForRouteMessages(routeId); + } + + /** + * @override + */ + detachRoute(routeId) { + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + this.logger_.info('Route ' + routeId + ' does not exist.'); + return; + } + provider.detachRoute(routeId); + } + + /** + * @override + */ + enableMdnsDiscovery() { + this.mdnsEnabled_ = true; + this.mdnsEnabledCallbacks_.forEach(callback => { + callback(); + }); + this.mdnsEnabledCallbacks_.length = 0; + } + + /** + * @param {string} sourceUrn The URN of the media being displayed. + * @param {string} sinkId + * @return {?mr.Provider} The provider that can handle |sourceUrn| on |sinkId|. + * Null if none exists. + * @private + */ + getProviderFor_(sourceUrn, sinkId) { + return this.providers_.find( + provider => provider.canRoute(sourceUrn, sinkId)) || + null; + } + + /** + * @param {string} sourceUrn The URN of the media being displayed. + * @param {string} presentationId The presentation ID to join. + * @return {?mr.Provider} The provider that can join a route identified by + * |sourceUrn| and |presentationId|. Null if none exists. + * @private + */ + getProviderForJoin_(sourceUrn, presentationId) { + return this.providers_.find( + provider => provider.canJoin(sourceUrn, presentationId)) || + null; + } + + /** + * @private + */ + executeSinkQueries_() { + this.sinkQueries_.forEach(sourceUrn => { + this.querySinks_(sourceUrn); + }); + } + + /** + * @private + */ + maybeUpdateKeepAlive_() { + this.mediaRouterService_.setKeepAlive( + this.pendingRequestRoutes_ > 0 || this.keepAliveComponents_.length > 0); + } + + /** + * @override + */ + startObservingMediaRoutes(sourceUrn) { + if (!this.routeQueries_.has(sourceUrn)) { + this.routeQueries_.add(sourceUrn); + this.providers_.forEach(p => { + p.startObservingMediaRoutes(sourceUrn); + }); + } + this.routeUpdateEventThrottle_.fire(); + } + + /** + * @override + */ + stopObservingMediaRoutes(sourceUrn) { + if (!this.routeQueries_.delete(sourceUrn)) { + this.logger_.info('No existing route query ' + sourceUrn); + } else { + this.providers_.forEach(provider => { + provider.stopObservingMediaRoutes(sourceUrn); + }); + } + } + + /** + * Send routes query result to MR immediately. + * @private + */ + sendRoutesQueryResultToMr_() { + if (this.routeQueries_.size == 0) return; + + let routeList = []; + this.providers_.forEach(p => { + routeList = routeList.concat(p.getRoutes()); + }); + + this.routeQueries_.forEach(sourceUrn => { + const nonLocalJoinableRouteIds = []; + this.providers_.forEach(p => { + const routes = p.getRoutes(); + routes.forEach(route => { + if (!route.createdLocally && p.canJoin(sourceUrn, undefined, route)) { + nonLocalJoinableRouteIds.push(route.id); + } + }); + }); + this.mediaRouterService_.onRoutesUpdated( + routeList, sourceUrn, nonLocalJoinableRouteIds); + }); + } + + /** + * @override + */ + getStorageKey() { + return 'ProviderManager'; + } + + /** + * @override + */ + getData() { + return [new mr.ProviderManager.PersistentData_( + this.providers_.map(p => p.getName()), Array.from(this.sinkQueries_), + Array.from(this.routeQueries_), + Array.from( + this.routeIdToProvider_, + ([routeId, provider]) => [routeId, provider.getName()]), + Array.from(this.sinkAvailabilityMap_), this.mdnsEnabled_, + this.lastUsedMirrorService_)]; + } + + /** + * @override + */ + loadSavedData() { + const savedData = /** @type {mr.ProviderManager.PersistentData_} */ + (mr.PersistentDataManager.getTemporaryData(this)); + if (savedData) { + this.sinkQueries_ = new Set(savedData.sinkQueries); + this.routeQueries_ = new Set(savedData.routeQueries); + + for (let [routeId, providerName] of savedData.routeIdToProviderName) { + const provider = this.getProviderByName(providerName); + mr.Assertions.assert(provider, 'Provider not found: ' + providerName); + this.routeIdToProvider_.set(routeId, provider); + } + this.sinkAvailabilityMap_ = new Map(savedData.sinkAvailabilityMap); + this.lastUsedMirrorService_ = savedData.lastUsedMirrorService || null; + if (savedData.mdnsEnabled) { + this.enableMdnsDiscovery(); + } + } + } + + /** + * @override + */ + getProviderFromRouteId(routeId) { + return this.routeIdToProvider_.get(routeId); + } + + /** + * @override + */ + onRouteAdded(provider, route) { + this.routeIdToProvider_.set(route.id, provider); + this.onRouteUpdated(provider, route); + } + + /** + * @override + */ + onRouteRemoved(provider, route) { + // Stopping mirroring does not affect message delivery. + this.maybeStopMirrorSession_(route.id); + + this.routeIdToProvider_.delete(route.id); + this.mrRouteMessageSender_.onRouteRemoved(route.id); + this.onRouteUpdated(provider, route); + } + + /** + * @override + */ + onRouteUpdated(provider, route) { + if (this.routeQueries_.size > 0) this.routeUpdateEventThrottle_.fire(); + } + + /** + * @override + */ + handleMirrorActivityUpdate(route, mirrorActivity) { + const provider = this.routeIdToProvider_.get(route.id); + if (provider) { + provider.onMirrorActivityUpdated(route.id); + // Bypass the throttle, since an update to the route description may have + // happened before the throttle interval has elapsed. + if (this.routeQueries_.size > 0) this.sendRoutesQueryResultToMr_(); + } + } + + /** + * @override + */ + onRouteMessage(provider, routeId, message) { + if (!this.routeIdToProvider_.has(routeId)) { + this.logger_.warning('Got route message for closed route ' + routeId); + return; + } + // If message is not a string or an Uint8Array, encode it into a JSON string + // for mr.cast.InternalMessage. + if ((typeof message !== 'string') && !(message instanceof Uint8Array)) { + message = JSON.stringify(message); + } + this.mrRouteMessageSender_.send(routeId, message); + } + + /** + * @override + */ + onPresentationConnectionStateChanged(routeId, state) { + if (state == mr.PresentationConnectionState.TERMINATED) { + this.mrRouteMessageSender_.sendImmediately(); + } + this.mediaRouterService_.onPresentationConnectionStateChanged( + routeId, /** @type {string} */ (state)); + } + + /** + * @override + */ + onPresentationConnectionClosed(routeId, reason, message) { + this.mrRouteMessageSender_.sendImmediately(); + this.mediaRouterService_.onPresentationConnectionClosed( + routeId, /** @type {string} */ (reason), message); + } + + /** + * @override + */ + onMirrorSessionEnded(routeId) { + // When mirror service invokes this method, it already cleaned its session. + // So provider manager only needs to close the route via provider and does + // not + // need to invoke mirrorService.stopCurrentMirroring. + this.routeIdToMirrorServiceName_.delete(routeId); + const provider = this.routeIdToProvider_.get(routeId); + if (provider) { + provider.terminateRoute(routeId); + } + } + + /** + * @override + */ + onInternalMessage(provider, routeId, message) { + this.routeMessageEventTarget_.dispatchEvent( + new mr.InternalMessageEvent(routeId, message)); + } + + /** + * @override + */ + onSinksUpdated() { + this.sinkUpdateEventThrottle_.fire(); + } + + /** + * @override + */ + onSinkAvailabilityUpdated(provider, availability) { + const oldValue = this.sinkAvailabilityMap_.get(provider.getName()); + mr.Assertions.assert(oldValue !== undefined, 'oldValue != undefined'); + if (oldValue == availability) { + return; + } + + const currentAvailability = this.computeSinkAvailability_(); + this.sinkAvailabilityMap_.set(provider.getName(), availability); + const newAvailability = this.computeSinkAvailability_(); + if (currentAvailability == newAvailability) { + return; + } + + // When the overall SinkAvailability becomes UNAVAILABLE, all sink queries + // will be removed. Before that happens, we need to update the sink query + // results with empty results. Importantly, however, this also allows + // pseudo sinks to be sent as well, which clearing the sinks on the browser + // side would not maintain. + if (newAvailability == mr.SinkAvailability.UNAVAILABLE) { + this.executeSinkQueries_(); + } + this.mediaRouterService_.onSinkAvailabilityUpdated(newAvailability); + if (newAvailability == mr.SinkAvailability.UNAVAILABLE) { + this.sinkQueries_.forEach(sourceUrn => { + this.doStopObservingMediaSinks_(sourceUrn); + }); + this.sinkQueries_.clear(); + } + } + + /** + * @return {!mr.SinkAvailability} Overall sink availability based on providers' + * availability. + * @private + */ + computeSinkAvailability_() { + return Array.from(this.sinkAvailabilityMap_.values()) + .reduce( + (prev, cur) => Math.max(prev, cur), + mr.SinkAvailability.UNAVAILABLE); + } + + /** + * @override + */ + getRouteMessageEventTarget() { + return this.routeMessageEventTarget_; + } + + /** + * @override + */ + sendIssue(issue) { + this.mediaRouterService_.onIssue(issue); + } + + /** + * @param {string} routeId + * @return {!Promise<boolean>} Fulfilled with true if the route is for + * a mirror session and the mirroring was stopped, and with false + * otherwise. + * @private + */ + maybeStopMirrorSession_(routeId) { + if (this.routeIdToMirrorServiceName_.has(routeId)) { + // Stop mirroring first + return this + .getMirrorService(this.routeIdToMirrorServiceName_.get(routeId)) + .then(mirrorService => { + this.routeIdToMirrorServiceName_.delete(routeId); + return mirrorService.stopCurrentMirroring(); + }); + } + return Promise.resolve(false); + } + + /** + * @override + */ + startMirroring( + provider, route, opt_presentationId, opt_streamStartedCallback) { + // The provider can handle the mirroring URN, thus the mirror + // service name is non-null. + const mirrorServiceName = + /** @type {mr.mirror.ServiceName} */ (mr.Assertions.assertString( + provider.getMirrorServiceName(route.sinkId))); + this.logger_.info(`Starting mirroring using service: ${mirrorServiceName}`); + this.routeIdToMirrorServiceName_.set(route.id, mirrorServiceName); + let innerPromise = null; + let cancellationReason = null; + return mr.CancellablePromise.withUncancellableStep( + this.getMirrorService(mirrorServiceName), mirrorService => { + this.lastUsedMirrorService_ = mirrorServiceName; + return mirrorService + .startMirroring( + + route, mr.Assertions.assertString(route.mediaSource), + provider.getMirrorSettings(route.sinkId), opt_presentationId, + opt_streamStartedCallback) + .catch(err => { + if (err instanceof mr.mirror.Error && + err.reason == + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL) { + throw new mr.RouteRequestError( + mr.RouteRequestResultCode.CANCELLED); + } + throw err; + }); + }); + } + + /** + * @override + */ + updateMirroring( + provider, route, sourceUrn, opt_presentationId, opt_tabId, + opt_streamStartedCallback) { + const mirrorServiceName = this.routeIdToMirrorServiceName_.get(route.id); + if (!mirrorServiceName) { + return mr.CancellablePromise.reject( + Error('Route ' + route.id + ' is not mirroring')); + } + return mr.CancellablePromise.withUncancellableStep( + this.getMirrorService(mirrorServiceName), mirrorService => { + this.lastUsedMirrorService_ = mirrorServiceName; + return mirrorService.updateMirroring( + route, sourceUrn, provider.getMirrorSettings(route.sinkId), + opt_presentationId, opt_tabId, opt_streamStartedCallback); + }); + } + + /** + * @override + */ + registerMdnsDiscoveryEnabledCallback(callback) { + if (this.mdnsEnabled_) { + callback(); + return; + } + if (this.mdnsEnabledCallbacks_.indexOf(callback) != -1) { + return; + } + this.mdnsEnabledCallbacks_.push(callback); + } + + /** + * @override + */ + isMdnsDiscoveryEnabled() { + return this.mdnsEnabled_; + } + + /** + * @override + */ + requestKeepAlive(componentId, keepAlive) { + const index = this.keepAliveComponents_.indexOf(componentId); + const componentKeepAlive = (index >= 0); + if (keepAlive && !componentKeepAlive) { + this.keepAliveComponents_.push(componentId); + } else if (!keepAlive && componentKeepAlive) { + this.keepAliveComponents_.splice(index, 1); + } + this.maybeUpdateKeepAlive_(); + } + + /** + * @param {!string} name + * @return {?mr.Provider} + */ + getProviderByName(name) { + return this.providers_.find(p => p.getName() == name) || null; + } + + /** + * Returns the name of the provider that created the pseudo sink ID |sinkId| + * or null if it is not a valid pseudo sink ID. + * @param {string} sinkId + * @return {?string} + * @private + */ + getProviderNameFromPseudoSinkId_(sinkId) { + if (!sinkId.startsWith(mr.ProviderManager.PSEUDO_SINK_NAME_PREFIX_)) { + return null; + } + return sinkId.substring(mr.ProviderManager.PSEUDO_SINK_NAME_PREFIX_.length); + } + + /** + * Get the rejection promise when sink is not found. + * @param {string} sinkId Sink ID of the pseudo sink that generated the + * request. + * @param {string} sourceUrn Source to be used with the sink. + * @param {!mr.SinkSearchCriteria} searchCriteria Sink search criteria for the + * MRP's which includes the user's current domain. + * @return {!Promise} + * @private + */ + rejectWithSinkNotFoundError_(sinkId, sourceUrn, searchCriteria) { + this.mediaRouterService_.onSearchSinkIdReceived(sinkId, ''); + return Promise.reject(new mr.RouteRequestError( + mr.RouteRequestResultCode.UNKNOWN_ERROR, + 'No sink found for search input: ' + searchCriteria.input + + ' and source: ' + sourceUrn)); + } + + /** + * @override + */ + searchSinks(sinkId, sourceUrn, searchCriteria) { + const providerName = this.getProviderNameFromPseudoSinkId_(sinkId); + const searchOwner = + providerName ? this.getProviderByName(providerName) : null; + if (!searchOwner) { + return Promise.reject(new Error('No provider supports ' + sinkId)); + } + return searchOwner.searchSinks(sourceUrn, searchCriteria) + .then((sink) => sink.id); + } + + /** + * @override + */ + provideSinks(providerName, sinks) { + const provider = this.getProviderByName(providerName); + if (!provider) { + this.logger_.error( + `provideSinks: Provider not found for providerName ${providerName}`); + return; + } + provider.provideSinks(sinks); + } + + /** + * @override + */ + updateMediaSinks(sourceUrn) { + // Don't add to sinkQueries as the providers may already be unavailable, but + // if the sink queries already contain the sourceUrn just return as + // discovery + // is already ongoing. + if (this.sinkQueries_.has(sourceUrn)) { + return; + } + this.querySinks_(sourceUrn); + } + + /** + * @override + */ + createMediaRouteController(routeId, controllerRequest, observer) { + const cleanUpOnError = () => { + controllerRequest.close(); + observer.ptr.reset(); + }; + const provider = this.routeIdToProvider_.get(routeId); + if (!provider) { + const errorMessage = + `createMediaRouteController: Provider not found for ${routeId}`; + this.logger_.error(errorMessage); + cleanUpOnError(); + return Promise.reject(new Error(errorMessage)); + } + return provider + .createMediaRouteController(routeId, controllerRequest, observer) + .catch(e => { + this.logger_.error(`createMediaRouteController failed: ${e.message}`); + cleanUpOnError(); + throw e; + }); + } + + /** + * @param {number} tabId + * @param {!mojo.MirrorServiceRemoterPtr} remoter + * @param {!mojo.InterfaceRequest} remotingSourceRequest + */ + onMediaRemoterCreated(tabId, remoter, remotingSourceRequest) { + this.mediaRouterService_.onMediaRemoterCreated( + tabId, remoter, remotingSourceRequest); + } + + /** + * @return {?mr.mirror.ServiceName} The name of most recently used mirror + * service, or null if mirror service has not been used. + */ + getLastUsedMirrorService() { + return this.lastUsedMirrorService_; + } + + /** + * Exports methods for Mojo handler. + * @param {!mr.ProviderManager} providerManager + * @private + */ + static exportProperties_(providerManager) { + // Cast to {!Object} so the compiler doesn't complain about accessing fields + // using subscript notation. + const obj = /** @type {!Object} */ (providerManager); + obj['onBeforeInvokeHandler'] = providerManager.onBeforeInvokeHandler; + obj['createRoute'] = providerManager.createRoute; + obj['joinRoute'] = providerManager.joinRoute; + obj['connectRouteByRouteId'] = providerManager.connectRouteByRouteId; + obj['terminateRoute'] = providerManager.terminateRoute; + obj['startObservingMediaSinks'] = providerManager.startObservingMediaSinks; + obj['stopObservingMediaSinks'] = providerManager.stopObservingMediaSinks; + obj['sendRouteMessage'] = providerManager.sendRouteMessage; + obj['sendRouteBinaryMessage'] = providerManager.sendRouteBinaryMessage; + obj['startListeningForRouteMessages'] = + providerManager.startListeningForRouteMessages; + obj['stopListeningForRouteMessages'] = + providerManager.stopListeningForRouteMessages; + obj['startObservingMediaRoutes'] = + providerManager.startObservingMediaRoutes; + obj['stopObservingMediaRoutes'] = providerManager.stopObservingMediaRoutes; + obj['detachRoute'] = providerManager.detachRoute; + obj['enableMdnsDiscovery'] = providerManager.enableMdnsDiscovery; + obj['searchSinks'] = providerManager.searchSinks; + obj['provideSinks'] = providerManager.provideSinks; + obj['updateMediaSinks'] = providerManager.updateMediaSinks; + obj['createMediaRouteController'] = + providerManager.createMediaRouteController; + } +}; + +/** + * Provider may fire sink/route list change event frequently. To avoid run all + * sink queries too frequently, only one set of queries at the end of each + * interval if there is an update event in the interval. + * @private @const {number} + */ +mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ = 500; + +// The timeout values below are to ensure provider manager resolve or reject +// a pending MR request after a reasonable delay. This is a safe guard in case +// provider has some unforeseen error. + +/** @const {number} */ +mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS = 60 * 1000; + +/** @private @const {number} */ +mr.ProviderManager.JOIN_ROUTE_TIMEOUT_MS_ = 30 * 1000; + +/** @private @const {number} */ +mr.ProviderManager.SEND_MESSAGE_TIMEOUT_MS_ = 30 * 1000; + +/** @private @const {string} */ +mr.ProviderManager.PSEUDO_SINK_NAME_PREFIX_ = 'pseudo:'; + + +/** + * The Data to be saved in storage when event page suspends. + * @private + */ +mr.ProviderManager.PersistentData_ = class { + /** + * @param {!Array<string>} providerNames + * @param {!Array<string>} sinkQueries + * @param {!Array<string>} routeQueries + * @param {!Array<!Array>} routeIdToProviderName + * @param {!Array<!Array>} sinkAvailabilityMap + * @param {boolean} mdnsEnabled + * @param {?mr.mirror.ServiceName} lastUsedMirrorService + */ + constructor( + providerNames, sinkQueries, routeQueries, routeIdToProviderName, + sinkAvailabilityMap, mdnsEnabled, lastUsedMirrorService) { + /** + * @type {!Array<string>} + */ + this.providerNames = providerNames; + + /** + * @type {!Array<string>} + */ + this.sinkQueries = sinkQueries; + + /** + * @type {!Array<string>} + */ + this.routeQueries = routeQueries; + + /** + * Map encoded as an array. + * @type {!Array<!Array>} + */ + this.routeIdToProviderName = routeIdToProviderName; + + /** + * Map encoded as an array. + * @type {!Array<!Array>} + */ + this.sinkAvailabilityMap = sinkAvailabilityMap; + + /** + * @type {boolean} + */ + this.mdnsEnabled = mdnsEnabled; + + /** + * @type {?mr.mirror.ServiceName} + */ + this.lastUsedMirrorService = lastUsedMirrorService; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_callbacks.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_callbacks.js new file mode 100644 index 00000000000..01ff1f5d3fa --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_callbacks.js @@ -0,0 +1,223 @@ +// 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 API used by Media Route Providers to interact with the Media + * Route Provider Manager. + */ + +goog.provide('mr.ProviderManagerCallbacks'); +goog.provide('mr.ProviderManagerIssueCallbacks'); +goog.provide('mr.ProviderManagerMirrorServiceCallbacks'); +goog.provide('mr.ProviderManagerRouteCallbacks'); +goog.provide('mr.ProviderManagerSinkCallbacks'); + +goog.require('mr.EventTarget'); +goog.require('mr.mirror.Activity'); + + +/** + * @record + */ +mr.ProviderManagerSinkCallbacks = class { + /** + * Called to notify that sinks have been added, removed, or updated. + */ + onSinksUpdated() {} + + /** + * Called to notify that sink availability has changed. + * + + * + * @param {!mr.Provider} provider + * @param {!mr.SinkAvailability} availability + */ + onSinkAvailabilityUpdated(provider, availability) {} +}; + + + +/** + * @record + */ +mr.ProviderManagerRouteCallbacks = class { + /** + * @param {!mr.Provider} provider + * @param {!mr.Route} route + */ + onRouteAdded(provider, route) {} + + /** + * @param {!mr.Provider} provider + * @param {!mr.Route} route + */ + onRouteRemoved(provider, route) {} + + /** + * @param {!mr.Provider} provider + * @param {!mr.Route} route + */ + onRouteUpdated(provider, route) {} + + /** + * Sends the provided message to the web app. If the message should also be + * sent internally via the MessagePort, also call onInternalMessage. + * @param {!mr.Provider} provider + * @param {string} routeId + * @param {string|!Uint8Array|!Object} message If not a string (text message) + * or Uint8Array (binary message), then the message will be serialized to + * a JSON string and sent as a text message. + */ + onRouteMessage(provider, routeId, message) {} + + /** + * Called to notify the presentation connected to a route has changed state. + * If the state is CLOSED, onPresentationConnectionClosed is called instead of + * this method. + * @param {!string} routeId + * @param {!mr.PresentationConnectionState} state + */ + onPresentationConnectionStateChanged(routeId, state) {} + + /** + * Called to notify the presentation connected to a route has closed. + * @param {string} routeId + * @param {!mr.PresentationConnectionCloseReason} reason + * @param {string} message + */ + onPresentationConnectionClosed(routeId, reason, message) {} +}; + + + +/** + * @record + */ +mr.ProviderManagerIssueCallbacks = class { + /** + * Sends the given issue to MediaRouter. + * @param {!mr.Issue} issue + */ + sendIssue(issue) {} +}; + + + +/** + * @record + * @extends {mr.ProviderManagerIssueCallbacks} + */ +mr.ProviderManagerMirrorServiceCallbacks = class { + /** + * Invoked by mirror service when it stops a mirror session due to error, + * the end of a stream etc. Because provider manager is unaware of mirror + * session's internal state, this method is used by mirror service to tell + * provider manager to close the corresponding route. + * @param {string} routeId + */ + onMirrorSessionEnded(routeId) {} + + /** + * Invoked by the mirror service when the mirroring activity description for + * mirroring route |route| has changed. + * + * @param {!mr.Route} route + * @param {!mr.mirror.Activity} mirrorActivity + */ + handleMirrorActivityUpdate(route, mirrorActivity) {} +}; + + + +/** + * @record + * @extends {mr.ProviderManagerIssueCallbacks} + * @extends {mr.ProviderManagerSinkCallbacks} + * @extends {mr.ProviderManagerRouteCallbacks} + * @extends {mr.ProviderManagerMirrorServiceCallbacks} + */ +mr.ProviderManagerCallbacks = class { + /** + * @param {string} routeId + * @return {mr.Provider} + */ + getProviderFromRouteId(routeId) {} + + /** + * @return {!mr.EventTarget} + */ + getRouteMessageEventTarget() {} + + /** + * Sends the message internally, to the mirror session associated with the + * provided route ID, via the MessagePort. + * @param {!mr.Provider} provider + * @param {!string} routeId + * @param {!Object|string} message + */ + onInternalMessage(provider, routeId, message) {} + + /** + * Called by a provider to request the mirroring service to start mirroring. + * @param {!mr.Provider} provider + * @param {!mr.Route} route + * @param {string=} opt_presentationId + * @param {(function(!mr.Route): !mr.CancellablePromise<!mr.Route>)=} + * opt_streamStartedCallback Callback to invoke after stream capture + * succeeded and before the mirror session is created. This allows the + * provider to perform additional setup and update the route for the + * mirror session. + * @return {!mr.CancellablePromise<!mr.Route>} + */ + startMirroring( + provider, route, opt_presentationId, opt_streamStartedCallback) {} + + /** + * Called by a provider to request the mirroring service to update |route| to + * the new source |sourceUrn|. + * @param {!mr.Provider} provider + * @param {!mr.Route} route + * @param {string} sourceUrn + * @param {string=} opt_presentationId + * @param {number=} opt_tabId + * @param {(function(!mr.Route): !mr.CancellablePromise)=} + * opt_streamStartedCallback Callback to invoke after stream capture + * succeeded and before the mirror session is created. This allows the + * provider to perform additional setup and update the route for the + * mirror session. + * @return {!mr.CancellablePromise<!mr.Route>} + */ + updateMirroring( + provider, route, sourceUrn, opt_presentationId, opt_tabId, + opt_streamStartedCallback) {} + + /** + * Register a callback with the provider manager that will either be executed + * immediately if mDNS discovery is currently enabled or saved to be executed + * when mDNS discovery becomes enabled. This should allow duplicate calls to + * be + * made with the same function address but only call the function once. + * @param {function()} callback Callback that depends on mDNS discovery. + */ + registerMdnsDiscoveryEnabledCallback(callback) {} + + /** + * Called by a component with |keepAlive| set to true when it requires the + * extension to be kept alive. When a component no longer requires the + * extension + * to be kept alive, this method should be called with |keepAlive| set to + * false. + * The extension will be prevented from suspending as long as at least one + * component requested keep alive. + * @param {string} componentId Globally unique id of the requesting component. + * @param {boolean} keepAlive + */ + requestKeepAlive(componentId, keepAlive) {} + + /** + * @return {boolean} Whether mDNS discovery is currently enabled. + */ + isMdnsDiscoveryEnabled() {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_test.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_test.js new file mode 100644 index 00000000000..9453ed678b2 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/provider_manager_test.js @@ -0,0 +1,1076 @@ +/** + * @fileoverview Tests for provider_manager. + */ +goog.setTestOnly('provider_manager_test'); + +goog.require('mr.CancellablePromise'); +goog.require('mr.MediaSourceUtils'); +goog.require('mr.MirrorAnalytics'); +goog.require('mr.Module'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.PresentationConnectionCloseReason'); +goog.require('mr.PresentationConnectionState'); +goog.require('mr.ProviderManager'); +goog.require('mr.Route'); +goog.require('mr.RouteMessage'); +goog.require('mr.RouteRequestError'); +goog.require('mr.RouteRequestResultCode'); +goog.require('mr.Sink'); +goog.require('mr.SinkAvailability'); +goog.require('mr.SinkList'); +goog.require('mr.UnitTestUtils'); +goog.require('mr.mirror.Error'); +goog.require('mr.mirror.ServiceName'); + +describe('Tests ProviderManager', function() { + let sourceUrn; + let mockMediaRouterService; + let providerManager; + let mockProvider1; + let mockProvider1Name; + let mockProvider2; + let mockBrokenProvider; + let mockCastMirrorService; + let mockWebrtcMirrorService; + let mirrorServiceMap; + const provider1Routes = []; + const provider2Routes = []; + const providerMethods = [ + 'getName', 'initialize', 'getAvailableSinks', 'createRoute', + 'terminateRoute', 'sendRouteMessage', 'getSinkById', 'getMirrorSettings', + 'getMirrorServiceName', 'canRoute', 'startObservingMediaSinks', + 'stopObservingMediaSinks', 'startObservingMediaRoutes', + 'stopObservingMediaRoutes', 'getRoutes', 'canJoin', 'searchSinks', + 'createMediaRouteController' + ]; + const mirrorServiceMethods = [ + 'initialize', + 'getName', + 'startMirroring', + 'stopCurrentMirroring', + 'createMirrorSession', + 'updateMirroring', + ]; + const castMirrorServiceMethods = mirrorServiceMethods.slice(); + const presentationId = 'presentationId'; + const routeId = '123'; + + let mockClock; + + beforeEach(function() { + mr.PersistentDataManager.clear(); + mr.Module.clearForTest(); + mr.UnitTestUtils.mockMojoApi(); + sourceUrn = 'urn:dial-multiscreen-org:dial:application:YouTube'; + mockClock = null; + window['chrome'] = chrome || {}; + chrome['runtime'] = chrome.runtime || {}; + chrome.runtime.id = '123'; + chrome.runtime.getManifest = function() { + return {version: 'fakeVersion'}; + }; + mockProvider1 = jasmine.createSpyObj('provider1', providerMethods); + mockProvider1Name = 'p1'; + mockProvider1.getName.and.returnValue(mockProvider1Name); + mockProvider1.getRoutes.and.callFake(() => provider1Routes); + mockProvider2 = jasmine.createSpyObj('provider2', providerMethods); + mockProvider2.getName.and.returnValue('p2'); + mockProvider2.getRoutes.and.callFake(() => provider2Routes); + mockBrokenProvider = + jasmine.createSpyObj('brokenProvider', providerMethods); + mockBrokenProvider.initialize.and.throwError( + new Error('I forgot how to initialize. @_@')); + mockCastMirrorService = + jasmine.createSpyObj('castMirrorService', castMirrorServiceMethods); + mockCastMirrorService.getName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + mockWebrtcMirrorService = + jasmine.createSpyObj('webrtcMirrorService', mirrorServiceMethods); + mockWebrtcMirrorService.getName.and.returnValue( + mr.mirror.ServiceName.WEBRTC); + mirrorServiceMap = new Map(); + mirrorServiceMap.set( + mr.mirror.ServiceName.CAST_STREAMING, mockCastMirrorService); + mirrorServiceMap.set(mr.mirror.ServiceName.WEBRTC, mockWebrtcMirrorService); + + // These two not needed? + spyOn(mr.mirror.cast, 'Service').and.returnValue(mockCastMirrorService); + spyOn(mr.mirror.webrtc, 'WebRtcService') + .and.returnValue(mockWebrtcMirrorService); + mockMediaRouterService = jasmine.createSpyObj('mrService', [ + 'setKeepAlive', 'getKeepAlive', 'setHandlers', + 'onPresentationConnectionClosed', 'onPresentationConnectionStateChanged', + 'onRoutesUpdated', 'onSinkAvailabilityUpdated', 'onSinksReceived', + 'start', 'onSearchSinkIdReceived', 'onRouteMessagesReceived' + ]); + const mockCloudComponentProvider = { + getIdentityService: function() { + return {}; + } + }; + spyOn(mr.cloud.CloudComponentProvider, 'getInstance') + .and.returnValue(mockCloudComponentProvider); + providerManager = new mr.ProviderManager(); + expect(mockMediaRouterService.start.calls.count()).toBe(0); + providerManager.initialize(mockMediaRouterService, []); + mockMediaRouterService.start.and.returnValue('instance123'); + spyOn(providerManager.mrRouteMessageSender_, 'send').and.callThrough(); + spyOn(providerManager, 'getMirrorService').and.callFake(serviceName => { + return Promise.resolve(mirrorServiceMap.get(serviceName)); + }); + }); + + afterEach(function() { + if (mockClock) { + mr.UnitTestUtils.restoreRealClockAndPromises(); + } + }); + + describe('Test presentation connection state changes', function() { + it('onPresentationConnectionStateChanged', function() { + const state = mr.PresentationConnectionState.TERMINATED; + providerManager.onPresentationConnectionStateChanged(routeId, state); + expect(mockMediaRouterService.onPresentationConnectionStateChanged) + .toHaveBeenCalledWith(routeId, state); + }); + + it('onPresentationConnectionStateClosed', function() { + const closeReason = mr.PresentationConnectionCloseReason.WENT_AWAY; + const message = 'Connection went away'; + providerManager.onPresentationConnectionClosed( + routeId, closeReason, message); + expect(mockMediaRouterService.onPresentationConnectionClosed) + .toHaveBeenCalledWith(routeId, closeReason, message); + }); + }); + + describe('Test registerAllProviders', function() { + it('Provider is initialized', function() { + providerManager.registerAllProviders( + [mockProvider1, mockProvider2, mockBrokenProvider]); + expect(mockProvider1.initialize.calls.count()).toBe(1); + expect(mockProvider2.initialize.calls.count()).toBe(1); + expect(mockBrokenProvider.initialize.calls.count()).toBe(1); + expect(providerManager.getProviders().some( + p => p.getName() == mockProvider1.getName())) + .toBe(true); + expect(providerManager.getProviders().some( + p => p.getName() == mockProvider2.getName())) + .toBe(true); + expect(!providerManager.getProviders().some( + p => p.getName() == mockBrokenProvider.getName())) + .toBe(true); + }); + + it('Route message event is listened to', function() { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + const route = new mr.Route(routeId, 'pId', '0', null, false, '', null); + const message = 'msg'; + providerManager.registerAllProviders([mockProvider1]); + providerManager.onRouteAdded(mockProvider1, route); + providerManager.onRouteMessage(mockProvider1, routeId, message, true); + providerManager.startListeningForRouteMessages(routeId); + mockClock.tick(mr.RouteMessageSender.SEND_MESSAGE_INTERVAL_MILLIS); + expect(mockMediaRouterService.onRouteMessagesReceived) + .toHaveBeenCalledWith( + routeId, [new mr.RouteMessage(routeId, message)]); + }); + + it('Message of a closed route is not forwarded', function() { + const message = 'msg'; + providerManager.registerAllProviders([mockProvider1]); + providerManager.onRouteMessage(mockProvider1, routeId, message); + expect(providerManager.mrRouteMessageSender_.send).not.toHaveBeenCalled(); + }); + + it('InternalMessage sends and is not forwarded to app', function() { + const route = new mr.Route(routeId, 'pId', '0', null, false, '', null); + const message = 'msg'; + providerManager.registerAllProviders([mockProvider1]); + providerManager.onRouteAdded(mockProvider1, route); + providerManager.onInternalMessage(mockProvider1, routeId, message); + expect(providerManager.mrRouteMessageSender_.send).not.toHaveBeenCalled(); + }); + + it('start/stopObservingMediaRoutes is passed to provider', function() { + mockProvider1.getRoutes.and.returnValue([]); + mockProvider2.getRoutes.and.returnValue([]); + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + + expect(mockProvider1.startObservingMediaRoutes.calls.count()).toBe(0); + expect(mockProvider2.startObservingMediaRoutes.calls.count()).toBe(0); + expect(mockProvider1.stopObservingMediaRoutes.calls.count()).toBe(0); + expect(mockProvider2.stopObservingMediaRoutes.calls.count()).toBe(0); + + providerManager.startObservingMediaRoutes(sourceUrn); + expect(mockProvider1.startObservingMediaRoutes.calls.count()).toBe(1); + expect(mockProvider2.startObservingMediaRoutes.calls.count()).toBe(1); + expect(mockProvider1.stopObservingMediaRoutes.calls.count()).toBe(0); + expect(mockProvider2.stopObservingMediaRoutes.calls.count()).toBe(0); + + providerManager.stopObservingMediaRoutes(sourceUrn); + expect(mockProvider1.startObservingMediaRoutes.calls.count()).toBe(1); + expect(mockProvider2.startObservingMediaRoutes.calls.count()).toBe(1); + expect(mockProvider1.stopObservingMediaRoutes.calls.count()).toBe(1); + expect(mockProvider2.stopObservingMediaRoutes.calls.count()).toBe(1); + }); + + it('Routes query result is sent back to MR initially', function() { + providerManager.startObservingMediaRoutes(sourceUrn); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + providerManager.stopObservingMediaRoutes(sourceUrn); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + }); + + it('Routes query result is sent back to MR on events', function() { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + + const route = new mr.Route(routeId, 'pId', '0', null, false, '', null); + + providerManager.startObservingMediaRoutes(sourceUrn); + // Send routes right away + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + + // Call to MR is throttled. + mockClock.tick(mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ / 2); + providerManager.onRouteAdded(mockProvider1, route); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + providerManager.onRouteRemoved(mockProvider1, route); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + providerManager.onRouteAdded(mockProvider1, route); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + mockClock.tick(mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ + 1); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(2); + }); + + it('Routes query result is not sent back to MR if stopped', function() { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + + const route = new mr.Route(routeId, 'pId', '0', null, false, '', null); + + providerManager.startObservingMediaRoutes(sourceUrn); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + + mockClock.tick(mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ / 2); + providerManager.onRouteAdded(mockProvider1, route); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + providerManager.onRouteRemoved(mockProvider1, route); + providerManager.onRouteAdded(mockProvider1, route); + providerManager.stopObservingMediaRoutes(sourceUrn); + // Query was stopped to MR is not called. + mockClock.tick(mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ / 2 + 1); + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + + }); + + it('Multiple route queries do not cause duplicate routes', function() { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + + const route = new mr.Route(routeId, 'pId', '0', null, false, '', null); + const sourceUrn2 = ''; + // Only one route will be returned. + mockProvider1.getRoutes.and.returnValue([route]); + mockProvider1.canJoin.and.returnValue(false); + providerManager.registerAllProviders([mockProvider1]); + + // Multiple route queries means the one route should be returned for + // multiple route queries. + providerManager.startObservingMediaRoutes(sourceUrn); + providerManager.startObservingMediaRoutes(sourceUrn2); + // Call to MR is throttled - so this will only happen once. + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(1); + expect(mockMediaRouterService.onRoutesUpdated) + .toHaveBeenCalledWith([route], sourceUrn, []); + mockClock.tick(mr.ProviderManager.ALL_QUERIES_INTERVAL_MS_ + 1); + // This will fire twice because there are two route queries, bringing the + // total to 3 times. + expect(mockMediaRouterService.onRoutesUpdated.calls.count()).toBe(3); + expect(mockMediaRouterService.onRoutesUpdated) + .toHaveBeenCalledWith([route], sourceUrn, []); + expect(mockMediaRouterService.onRoutesUpdated) + .toHaveBeenCalledWith([route], sourceUrn2, []); + }); + }); + + describe('Test keep alive', function() { + it('Keep alive for 1 provider', function() { + providerManager.requestKeepAlive(mockProvider1, true); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(true); + providerManager.requestKeepAlive(mockProvider1, false); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(false); + }); + + it('Keep alive for more than 1 provider', function() { + providerManager.requestKeepAlive(mockProvider1, true); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(true); + providerManager.requestKeepAlive(mockProvider2, true); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(true); + providerManager.requestKeepAlive(mockProvider1, false); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(true); + providerManager.requestKeepAlive(mockProvider2, false); + expect(mockMediaRouterService.setKeepAlive).toHaveBeenCalledWith(false); + }); + }); + + describe('Test createRoute', function() { + let sinkId; + let localRoute; + + beforeEach(function() { + sourceUrn = 'urn:dial-multiscreen-org:dial:application:YouTube'; + sinkId = 'sink1'; + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + }); + + it('No provider can handle it', function(done) { + mockProvider1.canRoute.and.returnValue(false); + mockProvider2.canRoute.and.returnValue(false); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .catch(e => { + expect(e instanceof mr.RouteRequestError).toBe(true); + expect(e.errorCode) + .toEqual(mr.RouteRequestResultCode.NO_SUPPORTED_PROVIDER); + expect(mockProvider1.canRoute) + .toHaveBeenCalledWith(sourceUrn, sinkId); + expect(mockProvider2.canRoute) + .toHaveBeenCalledWith(sourceUrn, sinkId); + + expect(mockMediaRouterService.setKeepAlive.calls.count()).toBe(0); + + done(); + }); + }); + + it('One provider can handle, but fail to create session', function(done) { + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + mockProvider1.createRoute.and.returnValue( + mr.CancellablePromise.reject(Error('err'))); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .catch(e => { + expect(e instanceof mr.RouteRequestError).toBe(true); + expect(e.errorCode) + .toEqual(mr.RouteRequestResultCode.UNKNOWN_ERROR); + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, undefined, + undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + + expect(mockMediaRouterService.setKeepAlive.calls.argsFor(0)) + .toEqual([true]); + expect(mockMediaRouterService.setKeepAlive.calls.argsFor(1)) + .toEqual([false]); + + done(); + }); + }); + + it('One provider can handle it, and created session', function(done) { + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(mockProvider1, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + providerManager.createRoute(sourceUrn, sinkId, presentationId).then(r => { + expect(r).toEqual(localRoute); + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, undefined, + undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + + done(); + }); + }); + + it('createRoute with custom timeout', function(done) { + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(mockProvider1, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + const timeoutMillis = 12345; + providerManager + .createRoute( + sourceUrn, sinkId, presentationId, undefined, undefined, + timeoutMillis) + .then(r => { + expect(r).toEqual(localRoute); + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, timeoutMillis, + undefined, undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + + done(); + }); + }); + + it('a mirror session is created', function(done) { + sourceUrn = mr.MediaSourceUtils.DESKTOP_MIRROR_URN; + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + const mirrorSettings = {}; + mockProvider1.getMirrorSettings.and.returnValue(mirrorSettings); + mockProvider1.getMirrorServiceName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(this, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + mockCastMirrorService.startMirroring.and.returnValue( + mr.CancellablePromise.resolve(localRoute)); + expect(providerManager.getLastUsedMirrorService()).toBeNull(); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .then(route => { + providerManager.startMirroring(mockProvider1, route, presentationId) + .promise.then(r => { + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, undefined, + undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + expect(mockCastMirrorService.startMirroring.calls.count()) + .toBe(1); + expect(mockCastMirrorService.startMirroring) + .toHaveBeenCalledWith( + localRoute, sourceUrn, mirrorSettings, presentationId, + undefined); + expect(providerManager.routeIdToProvider_.has(localRoute.id)) + .toBe(true); + expect(providerManager.routeIdToMirrorServiceName_.has( + localRoute.id)) + .toBe(true); + expect(providerManager.getLastUsedMirrorService()) + .toBe(mr.mirror.ServiceName.CAST_STREAMING); + done(); + }); + }); + }); + + it('a mirror session is not created', function(done) { + sourceUrn = mr.MediaSourceUtils.DESKTOP_MIRROR_URN; + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + const mirrorSettings = {}; + mockProvider1.getMirrorSettings.and.returnValue(mirrorSettings); + mockProvider1.getMirrorServiceName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(mockProvider1, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + const error = Error('failed to mirror'); + mockCastMirrorService.startMirroring.and.callFake( + () => mr.CancellablePromise.reject(error)); + + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .then(route => { + providerManager.startMirroring(mockProvider1, route, presentationId) + .promise.catch(err => { + expect(err).toEqual(error); + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, undefined, + undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + expect(mockCastMirrorService.startMirroring.calls.count()) + .toBe(1); + expect(mockCastMirrorService.startMirroring) + .toHaveBeenCalledWith( + localRoute, sourceUrn, mirrorSettings, presentationId, + undefined); + // Call onMirrorSessionEnded as the mirror service would. + + providerManager.onMirrorSessionEnded(route.id); + // Call onRouteRemoved as the provider would. + + providerManager.onRouteRemoved(mockProvider1, route); + expect(providerManager.routeIdToProvider_.has(localRoute.id)) + .toBe(false); + expect(providerManager.routeIdToMirrorServiceName_.has( + localRoute.id)) + .toBe(false); + + done(); + }); + }); + }); + + it('an mr.RouteRequestError is returned for user cancellation', (done) => { + mockProvider1.canRoute.and.returnValue(true); + mockProvider1.getMirrorSettings.and.returnValue({}); + mockProvider1.getMirrorServiceName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + const error = new mr.mirror.Error( + '', + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL); + mockCastMirrorService.startMirroring.and.callFake( + () => mr.CancellablePromise.reject(error)); + + providerManager + .startMirroring( + mockProvider1, + new mr.Route( + 'r2', 'pId', sinkId, mr.MediaSourceUtils.DESKTOP_MIRROR_URN, + true, '', null), + presentationId) + .promise.catch(err => { + expect(err instanceof mr.RouteRequestError).toBe(true); + expect(err.errorCode).toBe(mr.RouteRequestResultCode.CANCELLED); + done(); + }); + }); + + it('createRoute with default timeout', function(done) { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + // The provider will never resolve the promise. Provider manager will + // reject it on timeout. + mockProvider1.createRoute.and.returnValue( + new mr.CancellablePromise(() => {})); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .then( + r => { + fail('Route unexpectedly created'); + }, + e => { + expect(e instanceof mr.RouteRequestError).toBe(true); + expect(e.errorCode) + .toEqual(mr.RouteRequestResultCode.TIMED_OUT); + expect(e.message).toMatch('timeout'); + done(); + }); + mockClock.tick(mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS + 1); + }); + + it('createRoute with custom timeout', function(done) { + mockClock = mr.UnitTestUtils.useMockClockAndPromises(); + + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + // The provider will never resolve the promise. Provider manager will + // reject it on timeout. + mockProvider1.createRoute.and.returnValue( + new mr.CancellablePromise(() => {})); + const timeoutMillis = 20 * 1000; + providerManager + .createRoute( + sourceUrn, sinkId, 'presentationId', 'origin', 0, timeoutMillis) + .then( + r => { + fail('Route unexpectedly created'); + }, + e => { + expect(e instanceof mr.RouteRequestError).toBe(true); + expect(e.errorCode) + .toEqual(mr.RouteRequestResultCode.TIMED_OUT); + expect(e.message).toMatch('timeout'); + done(); + }); + mockClock.tick(timeoutMillis + 1); + }); + + it('update mirror session', function(done) { + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + mockProvider1.canRoute.and.returnValue(true); + mockProvider2.canRoute.and.returnValue(false); + const mirrorSettings = {}; + mockProvider1.getMirrorSettings.and.returnValue(mirrorSettings); + mockProvider1.getMirrorServiceName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(this, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + mockCastMirrorService.startMirroring.and.returnValue( + mr.CancellablePromise.resolve(localRoute)); + mockCastMirrorService.updateMirroring.and.returnValue( + mr.CancellablePromise.resolve(localRoute)); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .then(route => { + providerManager.startMirroring(mockProvider1, route, presentationId) + .promise.then(r => { + expect(mockProvider1.createRoute.calls.count()).toBe(1); + expect(mockProvider1.createRoute) + .toHaveBeenCalledWith( + sourceUrn, sinkId, presentationId, false, + mr.ProviderManager.CREATE_ROUTE_TIMEOUT_MS, undefined, + undefined); + expect(mockProvider2.createRoute.calls.count()).toBe(0); + expect(mockCastMirrorService.startMirroring.calls.count()) + .toBe(1); + expect(mockCastMirrorService.startMirroring) + .toHaveBeenCalledWith( + localRoute, sourceUrn, mirrorSettings, presentationId, + undefined); + expect(providerManager.routeIdToProvider_.has(localRoute.id)) + .toBe(true); + expect(providerManager.routeIdToMirrorServiceName_.has( + localRoute.id)) + .toBe(true); + + const newSourceUrn = mr.MediaSourceUtils.DESKTOP_MIRROR_URN; + const callback = function() {}; + providerManager + .updateMirroring( + mockProvider1, r, newSourceUrn, presentationId, + undefined, callback) + .promise.then(updatedRoute => { + expect( + mockCastMirrorService.updateMirroring.calls.count()) + .toBe(1); + expect(mockCastMirrorService.updateMirroring) + .toHaveBeenCalledWith( + r, newSourceUrn, mirrorSettings, presentationId, + undefined, callback); + done(); + }); + }); + }); + }); + + it('updateMirroring before startMirroring fails', function(done) { + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + const callback = function() {}; + + providerManager + .updateMirroring( + mockProvider1, localRoute, sourceUrn, undefined, callback) + .promise.catch(done); + }); + }); + + describe('Test PersistentData', function() { + it('ProviderManager is restored properly as PersistentData', function() { + providerManager.registerAllProviders([mockProvider1]); + providerManager.sinkQueries_.add(sourceUrn); + providerManager.routeQueries_.add(sourceUrn); + providerManager.routeIdToProvider_.set(routeId, mockProvider1); + providerManager.sinkAvailabilityMap_.set( + mockProvider1Name, mr.SinkAvailability.AVAILABLE); + + const expectedSinkQueries = new Set(providerManager.sinkQueries_); + const expectedRouteQueries = new Set(providerManager.routeQueries_); + + const data = providerManager.getData(); + expect(data.length).toEqual(1); + const tempData = data[0]; + expect(tempData.providerNames).toEqual([mockProvider1Name]); + expect(tempData.sinkQueries).toEqual([sourceUrn]); + expect(tempData.routeQueries).toEqual([sourceUrn]); + expect(tempData.routeIdToProviderName).toEqual([ + [routeId, mockProvider1Name] + ]); + expect(tempData.sinkAvailabilityMap).toEqual([ + [mockProvider1Name, mr.SinkAvailability.AVAILABLE] + ]); + + // Data is saved to localStorage. + mr.PersistentDataManager.onSuspend_(); + + // Make PersistentDataManager forget providerManager, so it can be + // registered again. + mr.PersistentDataManager.dataInstances_.clear(); + + providerManager.routeIdToProvider_.clear(); + providerManager.sinkQueries_.clear(); + providerManager.routeQueries_.clear(); + providerManager.sinkAvailabilityMap_.clear(); + + // Load data back to providerManager. + mockProvider1.getAvailableSinks.and.returnValue(mr.SinkList.EMPTY); + mr.PersistentDataManager.register(providerManager); + expect(providerManager.sinkQueries_).toEqual(expectedSinkQueries); + expect(providerManager.routeQueries_).toEqual(expectedRouteQueries); + expect(providerManager.routeIdToProvider_.get(routeId)) + .toEqual(mockProvider1); + expect(providerManager.sinkAvailabilityMap_.get(mockProvider1Name)) + .toEqual(mr.SinkAvailability.AVAILABLE); + }); + + it('ProviderManager calls mDNS callbacks if mDNS enabled', function() { + let callbackRuns = 0; + const callback = function() { + ++callbackRuns; + }; + providerManager.mdnsEnabled_ = true; + + // Data is saved to localStorage. + mr.PersistentDataManager.onSuspend_(); + + // Prevent immediate callback so we are sure it runs in loadSavedData + providerManager.mdnsEnabled_ = false; + providerManager.registerMdnsDiscoveryEnabledCallback(callback); + expect(callbackRuns).toBe(0); + + // Make PersistentDataManager forget providerManager, so it can be + // registered again. + mr.PersistentDataManager.dataInstances_.clear(); + + // Load data back to providerManager. + mockProvider1.getAvailableSinks.and.returnValue(mr.SinkList.EMPTY); + mr.PersistentDataManager.register(providerManager); + expect(callbackRuns).toBe(1); + }); + }); + + describe('Test onRouteRemoved', function() { + let sourceUrn; + let sinkId; + let localRoute; + + beforeEach(function() { + sourceUrn = 'urn:dial-multiscreen-org:dial:application:YouTube'; + sinkId = 'sink1'; + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + providerManager.registerAllProviders([mockProvider1]); + spyOn(providerManager.mrRouteMessageSender_, 'onRouteRemoved') + .and.callThrough(); + sourceUrn = mr.MediaSourceUtils.DESKTOP_MIRROR_URN; + localRoute = new mr.Route('r2', 'pId', sinkId, sourceUrn, true, '', null); + mockProvider1.canRoute.and.returnValue(true); + const mirrorSettings = {}; + mockProvider1.getMirrorSettings.and.returnValue(mirrorSettings); + mockProvider1.getMirrorServiceName.and.returnValue( + mr.mirror.ServiceName.CAST_STREAMING); + mockProvider1.createRoute.and.callFake(() => { + providerManager.onRouteAdded(mockProvider1, localRoute); + return mr.CancellablePromise.resolve(localRoute); + }); + mockCastMirrorService.startMirroring.and.returnValue( + mr.CancellablePromise.resolve(localRoute)); + }); + + it('On a mirror session stopped', function(done) { + let count = 0; + const checkDone = () => { + if (++count == 2) { + expect(providerManager.routeIdToProvider_.has(localRoute.id)) + .toBe(false); + expect(providerManager.routeIdToMirrorServiceName_.has(localRoute.id)) + .toBe(false); + expect(mockCastMirrorService.stopCurrentMirroring).toHaveBeenCalled(); + expect(providerManager.mrRouteMessageSender_.onRouteRemoved) + .toHaveBeenCalledWith(localRoute.id); + done(); + } + }; + mockCastMirrorService.stopCurrentMirroring.and.callFake(checkDone); + providerManager.mrRouteMessageSender_.onRouteRemoved.and.callFake( + checkDone); + providerManager.createRoute(sourceUrn, sinkId, presentationId) + .then(route => { + providerManager.startMirroring(mockProvider1, route) + .promise.then(r => { + expect(route.id).toEqual(localRoute.id); + expect(providerManager.routeIdToProvider_.has(localRoute.id)) + .toBe(true); + expect(providerManager.routeIdToMirrorServiceName_.has( + localRoute.id)) + .toBe(true); + providerManager.onRouteRemoved(mockProvider1, localRoute); + }); + }); + }); + }); + + it('Test add/remove MediaSinksQuery', function() { + const sourceUrn = 'urn:x-org.chromium.media:source:desktop'; + const sink1 = new mr.Sink('s1', 'sink1'); + const sink2 = new mr.Sink('s2', 'sink2'); + const origins = ['https://www.google.com', 'https://youtube.com']; + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + mockProvider1.getAvailableSinks.and.returnValue( + new mr.SinkList([sink1, sink2], origins)); + mockProvider2.getAvailableSinks.and.returnValue(mr.SinkList.EMPTY); + providerManager.startObservingMediaSinks(sourceUrn); + expect(mockProvider1.startObservingMediaSinks) + .toHaveBeenCalledWith(sourceUrn); + expect(mockProvider2.startObservingMediaSinks) + .toHaveBeenCalledWith(sourceUrn); + expect(mockMediaRouterService.onSinksReceived) + .toHaveBeenCalledWith(sourceUrn, [sink1, sink2], jasmine.any(Object)); + const originList = + mockMediaRouterService.onSinksReceived.calls.argsFor(0)[2]; + expect(originList.length).toBe(2); + expect(originList[0].scheme).toBe('https'); + expect(originList[0].host).toBe('www.google.com'); + expect(originList[1].scheme).toBe('https'); + expect(originList[1].host).toBe('youtube.com'); + // add again + providerManager.startObservingMediaSinks(sourceUrn); + expect(mockProvider1.startObservingMediaSinks.calls.count()).toBe(1); + expect(mockProvider2.startObservingMediaSinks.calls.count()).toBe(1); + expect(mockMediaRouterService.onSinksReceived.calls.count()).toBe(2); + // remove + providerManager.stopObservingMediaSinks(sourceUrn); + expect(mockProvider1.stopObservingMediaSinks) + .toHaveBeenCalledWith(sourceUrn); + expect(mockProvider2.stopObservingMediaSinks) + .toHaveBeenCalledWith(sourceUrn); + // add again + providerManager.startObservingMediaSinks(sourceUrn); + expect(mockProvider1.startObservingMediaSinks.calls.count()).toBe(2); + expect(mockProvider2.startObservingMediaSinks.calls.count()).toBe(2); + expect(mockMediaRouterService.onSinksReceived.calls.count()).toBe(3); + }); + + it('Test onSinkAvailabilityUpdated', function() { + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + + providerManager.onSinkAvailabilityUpdated( + mockProvider1, mr.SinkAvailability.UNAVAILABLE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated) + .not.toHaveBeenCalled(); + + providerManager.onSinkAvailabilityUpdated( + mockProvider1, mr.SinkAvailability.PER_SOURCE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated) + .toHaveBeenCalledWith(mr.SinkAvailability.PER_SOURCE); + + providerManager.onSinkAvailabilityUpdated( + mockProvider2, mr.SinkAvailability.PER_SOURCE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated.calls.count()) + .toBe(1); + + providerManager.onSinkAvailabilityUpdated( + mockProvider2, mr.SinkAvailability.AVAILABLE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated) + .toHaveBeenCalledWith(mr.SinkAvailability.AVAILABLE); + + providerManager.onSinkAvailabilityUpdated( + mockProvider1, mr.SinkAvailability.UNAVAILABLE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated.calls.count()) + .toBe(2); + + providerManager.onSinkAvailabilityUpdated( + mockProvider2, mr.SinkAvailability.UNAVAILABLE); + expect(mockMediaRouterService.onSinkAvailabilityUpdated) + .toHaveBeenCalledWith(mr.SinkAvailability.UNAVAILABLE); + }); + + it('Test mDNS callbacks wait until mDNS is enabled', function() { + providerManager.mdnsEnabled_ = true; + let callbackRuns = 0; + const callback = function() { + ++callbackRuns; + }; + providerManager.registerMdnsDiscoveryEnabledCallback(callback); + // Execute immediately when mDNS discovery is enabled. + expect(callbackRuns).toBe(1); + + callbackRuns = 0; + providerManager.mdnsEnabled_ = false; + providerManager.registerMdnsDiscoveryEnabledCallback(callback); + // Defer execution until mDNS discovery is enabled. + expect(callbackRuns).toBe(0); + providerManager.enableMdnsDiscovery(); + expect(callbackRuns).toBe(1); + }); + + it('Test mDNS callback registration disallows duplicates', function() { + providerManager.mdnsEnabled_ = true; + let callbackRuns = 0; + const callback = function() { + ++callbackRuns; + }; + + providerManager.mdnsEnabled_ = false; + providerManager.registerMdnsDiscoveryEnabledCallback(callback); + providerManager.registerMdnsDiscoveryEnabledCallback(callback); + // Defer execution until mDNS discovery is enabled and ensure only called + // once. + expect(callbackRuns).toBe(0); + providerManager.enableMdnsDiscovery(); + expect(callbackRuns).toBe(1); + }); + + describe('searchSinks', function() { + const getSearchCriteria = function(input) { + return {'input': input, 'domain': 'google.com'}; + }; + let pseudoId; + + beforeEach(function() { + sourceUrn = 'urn:x-org.chromium.media:source:desktop'; + pseudoId = 'pseudo:' + mockProvider1Name; + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + }); + + it('returns the sink id if the provider returns a sink', done => { + const sinkId = 'sinkId1'; + const sinkName = 'sink name'; + const foundSink = new mr.Sink(sinkId, sinkName); + mockProvider1.searchSinks.and.returnValue(Promise.resolve(foundSink)); + + providerManager + .searchSinks(pseudoId, sourceUrn, getSearchCriteria(sinkName)) + .then((resolvedSinkId) => { + expect(resolvedSinkId).toBe(sinkId); + done(); + }); + }); + + it('returns nothing if the provider returns nothing', done => { + mockProvider1.searchSinks.and.returnValue( + Promise.resolve(new mr.Sink('', ''))); + providerManager + .searchSinks(pseudoId, sourceUrn, getSearchCriteria('sink name')) + .then((resolvedSinkId) => expect(resolvedSinkId).toBe('')) + .then(done, done.fail); + }); + + it('returns nothing when no provider owns the pseudo sink', done => { + pending( + 'This may never have worked, because the test ' + + 'wasn\'t checking what it was supposed to check.'); + pseudoId = 'pseudo:bad'; + providerManager + .searchSinks(pseudoId, sourceUrn, getSearchCriteria('sink name')) + .then((resolvedSinkId) => expect(resolvedSinkId).toBe('')) + .then(done, done.fail); + }); + }); + + describe('updateMediaSinks', function() { + const sink1 = new mr.Sink('s1', 'sink1'); + const sink2 = new mr.Sink('s2', 'sink2'); + + beforeEach(function() { + sourceUrn = 'urn:x-org.chromium.media:source:desktop'; + providerManager.registerAllProviders([mockProvider1, mockProvider2]); + mockProvider1.getAvailableSinks.and.returnValue( + new mr.SinkList([sink1, sink2], ['https://www.google.com'])); + mockProvider2.getAvailableSinks.and.returnValue(mr.SinkList.EMPTY); + }); + + it('queries providers with desktop source', function() { + providerManager.updateMediaSinks(sourceUrn); + expect(mockProvider1.getAvailableSinks).toHaveBeenCalledWith(sourceUrn); + expect(mockProvider2.getAvailableSinks).toHaveBeenCalledWith(sourceUrn); + expect(mockMediaRouterService.onSinksReceived) + .toHaveBeenCalledWith( + sourceUrn, [sink1, sink2], [jasmine.any(Object)]); + const originList = + mockMediaRouterService.onSinksReceived.calls.argsFor(0)[2]; + expect(originList.length).toBe(1); + expect(originList[0].scheme).toBe('https'); + expect(originList[0].host).toBe('www.google.com'); + }); + + it('returns immediately if query already exists', function() { + providerManager.sinkQueries_.add(sourceUrn); + // updateMediaSinks should return and not touch providers + providerManager.updateMediaSinks(sourceUrn); + expect(mockProvider1.getAvailableSinks).not.toHaveBeenCalled(); + expect(mockProvider2.getAvailableSinks).not.toHaveBeenCalled(); + expect(mockMediaRouterService.onSinksReceived).not.toHaveBeenCalled(); + }); + }); + + describe('calls terminateRoute', function() { + const routeIdToTerminate = 'deadbeef'; + + beforeEach(function() { + providerManager.registerAllProviders([mockProvider1]); + providerManager.routeIdToProvider_.set(routeIdToTerminate, mockProvider1); + }); + + it('with correct routeId', function(done) { + mockProvider1.terminateRoute.and.callFake(routeId => { + expect(routeId).toBe(routeIdToTerminate); + done(); + }); + providerManager.terminateRoute(routeIdToTerminate); + }); + + it('and returns a rejected promise when it fails', function(done) { + mockProvider1.terminateRoute.and.callFake(routeId => { + expect(routeId).toBe(routeIdToTerminate); + return Promise.reject(new Error('Some error')); + }); + providerManager.terminateRoute(routeIdToTerminate).then(done.fail, done); + }); + + it('and terminates a mirroring route', function(done) { + providerManager.routeIdToMirrorServiceName_.set( + routeIdToTerminate, mr.mirror.ServiceName.CAST_STREAMING); + providerManager.terminateRoute(routeIdToTerminate).then(() => { + expect(mockProvider1.terminateRoute) + .toHaveBeenCalledWith(routeIdToTerminate); + expect(mockCastMirrorService.stopCurrentMirroring).toHaveBeenCalled(); + expect( + providerManager.routeIdToMirrorServiceName_.has(routeIdToTerminate)) + .toBe(false); + done(); + }); + }); + }); + + describe('createMediaRouteController tests', () => { + const routeId = 'deadbeef'; + let controller; + let observer; + + beforeEach(() => { + controller = mr.UnitTestUtils.createMojoRequestSpyObj(); + observer = mr.UnitTestUtils.createMojoMediaStatusObserverSpyObj(); + providerManager.registerAllProviders([mockProvider1]); + providerManager.routeIdToProvider_.set(routeId, mockProvider1); + }); + + it('succeeds', done => { + mockProvider1.createMediaRouteController.and.callFake( + () => Promise.resolve()); + providerManager.createMediaRouteController(routeId, controller, observer) + .then(() => { + expect(mockProvider1.createMediaRouteController).toHaveBeenCalled(); + expect(controller.close).not.toHaveBeenCalled(); + expect(observer.ptr.reset).not.toHaveBeenCalled(); + done(); + }, fail); + }); + + it('fails if route does not exist', done => { + providerManager + .createMediaRouteController( + 'nonexistentRouteId', controller, observer) + .then(fail, () => { + expect(controller.close).toHaveBeenCalled(); + expect(observer.ptr.reset).toHaveBeenCalled(); + done(); + }); + }); + + it('fails if provider fails', done => { + mockProvider1.createMediaRouteController.and.callFake( + () => Promise.reject('Foo')); + providerManager.createMediaRouteController(routeId, controller, observer) + .then(fail, () => { + expect(mockProvider1.createMediaRouteController).toHaveBeenCalled(); + expect(controller.close).toHaveBeenCalled(); + expect(observer.ptr.reset).toHaveBeenCalled(); + done(); + }); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id.js new file mode 100644 index 00000000000..a5d9f37a9d0 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id.js @@ -0,0 +1,114 @@ +// 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 routeId includes presentation ID, provider name, + * sink ID and media source. See mr.RouteId.getRouteId method for its + * format (note the different formatting of routeId for 2UA cases). + */ + +goog.provide('mr.RouteId'); + +goog.require('mr.MediaSourceUtils'); + +/** + * @final + */ +mr.RouteId = class { + constructor() { + /** @private {string} */ + this.routeId_; + + /** @private {string} */ + this.presentationId_; + + /** @private {string} */ + this.providerName_; + + /** @private {string} */ + this.sinkId_; + + /** @private {string} */ + this.source_; + } + + /** + * @param {string} routeId + * @return {?mr.RouteId} A route ID object if the input string is valid; + * null otherwise. + */ + static create(routeId) { + if (!routeId.startsWith(mr.RouteId.PREFIX_)) { + return null; + } + const suffix = routeId.substring(mr.RouteId.PREFIX_.length); + if (!suffix) { + return null; + } + const match = suffix.match(/([^/]*)\/([^-/]*)-([^/]*)\/(.*)/); + if (!match) { + return null; + } + const obj = new mr.RouteId(); + obj.routeId_ = routeId; + [, obj.presentationId_, obj.providerName_, obj.sinkId_, obj.source_] = + match; + return obj; + } + + /** + * @param {string} presentationId + * @param {string} providerName + * @param {string} sinkId + * @param {?string} source + * @return {string} + */ + static getRouteId(presentationId, providerName, sinkId, source) { + // In a 2UA mode, the routeId should be the presentationId. + if (source && mr.MediaSourceUtils.isPresentationSource(source)) { + return presentationId; + } + return mr.RouteId.PREFIX_ + presentationId + '/' + providerName + '-' + + sinkId + '/' + source; + } + + /** + * @return {string} + */ + getRouteId() { + return this.routeId_; + } + + /** + * @return {string} + */ + getPresentationId() { + return this.presentationId_; + } + + /** + * @return {string} + */ + getProviderName() { + return this.providerName_; + } + + /** + * @return {string} + */ + getSinkId() { + return this.sinkId_; + } + + /** + * @return {string} + */ + getSource() { + return this.source_; + } +}; + + +/** @private @const {string} */ +mr.RouteId.PREFIX_ = 'urn:x-org.chromium:media:route:'; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id_test.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id_test.js new file mode 100644 index 00000000000..daff81e818f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_id_test.js @@ -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. + +goog.require('mr.RouteId'); + +describe('Tests RouteId', function() { + it('Test getRouteId', function() { + expect(mr.RouteId.getRouteId('123', 'cast', 'sink1', 'some-source')) + .toEqual(mr.RouteId.PREFIX_ + '123/cast-sink1/some-source'); + }); + + it('Test getRouteId in 2UA mode', function() { + expect(mr.RouteId.getRouteId('123', 'cast', 'sink1', 'http://mysite.com')) + .toEqual('123'); + }); + + it('Test getRouteId with valid input', function() { + let routeId = mr.RouteId.PREFIX_ + '123/cast-sink1/some-source'; + let obj = mr.RouteId.create(routeId); + expect(obj.getRouteId()).toEqual(routeId); + expect(obj.getPresentationId()).toEqual('123'); + expect(obj.getProviderName()).toEqual('cast'); + expect(obj.getSinkId()).toEqual('sink1'); + expect(obj.getSource()).toEqual('some-source'); + + routeId = mr.RouteId.PREFIX_ + '/cast-sink1/some-source'; + obj = mr.RouteId.create(routeId); + expect(obj.getRouteId()).toEqual(routeId); + expect(obj.getPresentationId()).toEqual(''); + expect(obj.getProviderName()).toEqual('cast'); + expect(obj.getSinkId()).toEqual('sink1'); + expect(obj.getSource()).toEqual('some-source'); + }); + + it('Test getRouteId with invalid input 1', function() { + expect(mr.RouteId.create('123')).toBe(null); + expect(mr.RouteId.create(mr.RouteId.PREFIX_ + '123/cast-sink1')).toBe(null); + expect(mr.RouteId.create(mr.RouteId.PREFIX_ + '123/cast-')).toBe(null); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port.js new file mode 100644 index 00000000000..d6cae814fe9 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port.js @@ -0,0 +1,74 @@ +// 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 API for posting messages to a route and getting messages from + * the route. + */ + +goog.provide('mr.MessagePort'); +goog.provide('mr.MessagePortService'); + + + +/** + * @record + */ +mr.MessagePort = class { + /** + * Called to post a message to the sink via a media route. + * @param {!Object|string} message The message to post. Object will be + * serialized to a JSON string. + * @param {Object=} opt_extraInfo Extra info about how to send a message. + * @return {!Promise} Fulfilled when the message is posted, or rejected + * if there an error. + */ + sendMessage(message, opt_extraInfo) {} + + /** + * Releases any resources used by this object. + */ + dispose() {} +}; + + +/** + * The message handler to invoke when messages associated + * with the route arrives. + * @type {function((!Object|string))} + */ +mr.MessagePort.prototype.onMessage; + + + +/** + * @record + */ +mr.MessagePortService = class { + /** + * Returns the MessagePort for internal communication with the provider. + * @param {string} routeId + * @return {!mr.MessagePort} + */ + getInternalMessenger(routeId) {} + /** + * @return {!mr.MessagePortService} + */ + static getService() { + return mr.MessagePortService.service_; + } + + /** + * @param {!mr.MessagePortService} service + */ + static setService(service) { + if (!mr.MessagePortService.service_) { + mr.MessagePortService.service_ = service; + } + } +}; + + +/** @private {!mr.MessagePortService} */ +mr.MessagePortService.service_; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port_impl.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port_impl.js new file mode 100644 index 00000000000..78efd2d018c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/route_message_port_impl.js @@ -0,0 +1,107 @@ +// 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. + + + +goog.provide('mr.MessagePortServiceImpl'); + +goog.require('mr.EventTarget'); +goog.require('mr.MessagePort'); +goog.require('mr.MessagePortService'); +goog.require('mr.ProviderEventType'); + + +/** + * @template T + * @implements {mr.MessagePort} + * @private + */ +mr.MessagePortImpl_ = class { + /** + * @param {string} routeId + * @param {function(string, (!Object|string), Object=): !Promise} + * sendMessage + * @param {!mr.EventTarget} messageEventTarget + */ + constructor(routeId, sendMessage, messageEventTarget) { + /** @private {string} */ + this.routeId_ = routeId; + + /** @private {function(string, (!Object|string), Object=): !Promise} */ + this.sendMessage_ = sendMessage; + + /** @private {!mr.EventTarget} */ + this.messageEventTarget_ = messageEventTarget; + + /** @type {function((!Object|string))} */ + this.onMessage = () => {}; + + messageEventTarget.listen( + mr.ProviderEventType.INTERNAL_MESSAGE, this.onRouteMessage_, this); + } + + /** + * @override + */ + sendMessage(message, opt_extraInfo) { + return this.sendMessage_(this.routeId_, message, opt_extraInfo); + } + + /** + * Called when a message is received. + * + * @param {mr.InternalMessageEvent.<!Object|string>} event + * @private + */ + onRouteMessage_(event) { + if (event.routeId != this.routeId_) { + return; + } + this.onMessage(event.message); + } + + /** + * @override + */ + dispose() { + + this.onMessage = () => {}; + this.messageEventTarget_.unlisten( + mr.ProviderEventType.INTERNAL_MESSAGE, this.onRouteMessage_, this); + } +}; + + +/** @private @const {!mr.MessagePort} */ +mr.MessagePortImpl_.NULL_INSTANCE_ = + new mr.MessagePortImpl_('', () => Promise.resolve(), new mr.EventTarget()); + + +/** + * @implements {mr.MessagePortService} + */ +mr.MessagePortServiceImpl = class { + /** + * @param {!mr.ProviderManagerCallbacks} providerManagerCallbacks + */ + constructor(providerManagerCallbacks) { + /** + * @private {!mr.ProviderManagerCallbacks} + */ + this.providerManagerCallbacks_ = providerManagerCallbacks; + } + + /** + * @override + */ + getInternalMessenger(routeId) { + const provider = + this.providerManagerCallbacks_.getProviderFromRouteId(routeId); + return !provider ? + mr.MessagePortImpl_.NULL_INSTANCE_ : + new mr.MessagePortImpl_( + routeId, provider.sendRouteMessage.bind(provider), + this.providerManagerCallbacks_.getRouteMessageEventTarget()); + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/manager/sink_availability.js b/chromium/chrome/browser/resources/media_router/extension/src/manager/sink_availability.js new file mode 100644 index 00000000000..5c3c1ce0862 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/manager/sink_availability.js @@ -0,0 +1,21 @@ +// 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 Enum for per-media-route-provider sink availability. + */ + +goog.provide('mr.SinkAvailability'); + + +/** + * Per-provider sink availability. + * Keep in sync with MediaRouter.SinkAvailability in media_router.mojom. + * @enum {number} + */ +mr.SinkAvailability = { + UNAVAILABLE: 0, + PER_SOURCE: 1, + AVAILABLE: 2 +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity.js new file mode 100644 index 00000000000..df658d67a66 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity.js @@ -0,0 +1,150 @@ +// 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 Mirroring activities for display in the UI. + */ + +goog.module('mr.mirror.Activity'); +goog.module.declareLegacyNamespace(); + +const Assertions = goog.require('mr.Assertions'); +const MediaSourceUtils = goog.require('mr.MediaSourceUtils'); + + +/** + * Possible mirroring activities. + * @enum {string} + */ +const Type = { + MIRROR_TAB: 'mirror_tab', + MIRROR_DESKTOP: 'mirror_desktop', + MIRROR_FILE: 'mirror_file', + MEDIA_REMOTING: 'media_remoting', + PRESENTATION: 'presentation' +}; + + +/** + * Describes a mirroring activity for display in the UI. + */ +const Activity = class { + /** + * Constructs a new Activity describing a mirroring route. origin is + * required, unless the activityType is MIRROR_DESKTOP. Both contentTitle and + * origin may change over the course of a mirroring activity from tab + * navigation. + * + * @param {!Type} type + * @param {boolean} incognito + * @param {?string=} origin The top level origin of the tab being cast. + */ + constructor(type, incognito, origin = null) { + /** @private {!Type} */ + this.type_ = type; + + /** @private {boolean} */ + this.incognito_ = incognito; + + /** @private {?string} */ + this.origin_ = origin; + + /** @private {?string} */ + this.contentTitle_ = null; + } + + /** + * @param {!mr.Route} route A mirroring route + * @return {!Activity} Intital activity object for route + */ + static createFromRoute(route) { + let type; + let origin = null; + const source = /** @type {string} */ (Assertions.assert(route.mediaSource)); + if (MediaSourceUtils.isPresentationSource(source)) { + type = Type.PRESENTATION; + origin = new URL(source).origin; + } else if (MediaSourceUtils.isTabMirrorSource(source)) { + // Tab mirroring routes may switch to MEDIA_REMOTING or MIRROR_FILE after + // they have begun. + type = Type.MIRROR_TAB; + } else if (MediaSourceUtils.isDesktopMirrorSource(source)) { + type = Type.MIRROR_DESKTOP; + } + Assertions.assert(type, `Unexpected mediaSource ${source}`); + return new Activity( + /** @type {Type} */ (type), route.offTheRecord, origin); + } + + /** @param {Type} type Sets the activity type. */ + setType(type) { + this.type_ = type; + } + + /** @param {?string} origin Sets the current origin of the activity. */ + setOrigin(origin) { + this.origin_ = origin; + } + + /** @param {?string} contentTitle Sets the content title of the activity. */ + setContentTitle(contentTitle) { + this.contentTitle_ = contentTitle; + } + + /** @param {boolean} incognito Sets the incognito status of the activity. */ + setIncognito(incognito) { + this.incognito_ = incognito; + } + + /** @return {string} The string to use for the route's description. */ + getRouteDescription() { + switch (this.type_) { + case Type.MIRROR_TAB: + return this.origin_ ? `Casting tab (${this.origin_})` : 'Casting tab'; + case Type.MIRROR_DESKTOP: + return 'Casting desktop'; + case Type.MIRROR_FILE: + return 'Casting local content'; + case Type.MEDIA_REMOTING: + return this.origin_ ? `Casting media (${this.origin_})` : + `Casting media`; + case Type.PRESENTATION: + return `Casting ${this.origin_ || 'site'}`; + default: + Assertions.assert(false, 'Unexpected type ' + this.type_); + return ''; + } + } + + /** @return {string} The string to use for the route's media status. */ + getRouteMediaStatus() { + if (this.type_ == Type.MIRROR_DESKTOP) { + return ''; + } + return this.contentTitle_ || ''; + } + + /** @return {string} The string to broadcast to other Cast senders. */ + getCastRemoteTitle() { + if (this.incognito_) return 'Casting active'; + switch (this.type_) { + case Type.MIRROR_TAB: + return 'Casting tab'; + case Type.MIRROR_DESKTOP: + return 'Casting desktop'; + case Type.MIRROR_FILE: + return 'Casting local content'; + case Type.MEDIA_REMOTING: + return 'Casting media'; + case Type.PRESENTATION: + return 'Casting site'; + default: + Assertions.assert(false, 'Unexpected type ' + this.type_); + return ''; + } + } +}; + +exports = Activity; +exports.Type = Type; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity_test.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity_test.js new file mode 100644 index 00000000000..e431bd7dd14 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_activity_test.js @@ -0,0 +1,93 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.Route'); +goog.require('mr.mirror.Activity'); + +describe('Tests mr.mirror.Activity', () => { + let tabMirrorRoute; + let desktopMirrorRoute; + let presentationRoute; + + beforeEach(() => { + tabMirrorRoute = new mr.Route( + 'routeId', 'presentationId', 'sinkId', + 'urn:x-org.chromium.media:source:tab:47', true, null); + desktopMirrorRoute = new mr.Route( + 'routeId2', 'presentationId2', 'sinkId2', + 'urn:x-org.chromium.media:source:desktop', true, null); + presentationRoute = new mr.Route( + 'routeId3', 'presentationId3', 'sinkId3', 'https://www.example.com', + true, null); + }); + + it('creates activity from a tab mirroring route', () => { + let activity = mr.mirror.Activity.createFromRoute(tabMirrorRoute); + expect(activity.getRouteDescription()).toBe('Casting tab'); + expect(activity.getRouteMediaStatus()).toBe(''); + expect(activity.getCastRemoteTitle()).toBe('Casting tab'); + }); + + it('creates activity from a desktop mirroring route', () => { + let activity = mr.mirror.Activity.createFromRoute(desktopMirrorRoute); + expect(activity.getRouteDescription()).toBe('Casting desktop'); + expect(activity.getRouteMediaStatus()).toBe(''); + expect(activity.getCastRemoteTitle()).toBe('Casting desktop'); + }); + + it('creates activity from a presentation route', () => { + let activity = mr.mirror.Activity.createFromRoute(presentationRoute); + expect(activity.getRouteDescription()) + .toBe('Casting https://www.example.com'); + expect(activity.getRouteMediaStatus()).toBe(''); + expect(activity.getCastRemoteTitle()).toBe('Casting site'); + }); + + it('uses the tab origin and page title for tab mirroring', () => { + let activity = mr.mirror.Activity.createFromRoute(tabMirrorRoute); + activity.setOrigin('news.google.com'); + activity.setContentTitle('Google News'); + expect(activity.getRouteDescription()) + .toBe('Casting tab (news.google.com)'); + expect(activity.getRouteMediaStatus()).toBe('Google News'); + expect(activity.getCastRemoteTitle()).toBe('Casting tab'); + }); + + it('uses the tab origin and page title for presentation', () => { + let activity = mr.mirror.Activity.createFromRoute(presentationRoute); + activity.setOrigin('www.example.com'); + activity.setContentTitle('Some Presentation'); + expect(activity.getRouteDescription()).toBe('Casting www.example.com'); + expect(activity.getRouteMediaStatus()).toBe('Some Presentation'); + expect(activity.getCastRemoteTitle()).toBe('Casting site'); + }); + + it('updates the remote title for incognito', () => { + let activity = mr.mirror.Activity.createFromRoute(tabMirrorRoute); + activity.setIncognito(true); + expect(activity.getCastRemoteTitle()).toBe('Casting active'); + }); + + it('updates the activity for remoting', () => { + let activity = mr.mirror.Activity.createFromRoute(tabMirrorRoute); + activity.setOrigin('www.vimeo.com'); + activity.setContentTitle('Vimeo'); + activity.setType(mr.mirror.Activity.Type.MEDIA_REMOTING); + expect(activity.getRouteDescription()) + .toBe('Casting media (www.vimeo.com)'); + expect(activity.getRouteMediaStatus()).toBe('Vimeo'); + expect(activity.getCastRemoteTitle()).toBe('Casting media'); + }); + + it('updates the activity for mirroring local media', () => { + let activity = mr.mirror.Activity.createFromRoute(tabMirrorRoute); + activity.setOrigin(''); + activity.setContentTitle('some_file.mp4'); + activity.setType(mr.mirror.Activity.Type.MIRROR_FILE); + expect(activity.getRouteDescription()).toBe('Casting local content'); + expect(activity.getRouteMediaStatus()).toBe('some_file.mp4'); + expect(activity.getCastRemoteTitle()).toBe('Casting local content'); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics.js new file mode 100644 index 00000000000..20ea156371e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics.js @@ -0,0 +1,74 @@ +// 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 Defines UMA analytics specific to Mirroring. + */ + +goog.provide('mr.MirrorAnalytics'); +goog.provide('mr.mirror.Error'); + +goog.require('mr.Analytics'); + + +/** + * Contains all common analytics logic for Mirroring. + * @const {*} + */ +mr.MirrorAnalytics = {}; + + +/** + * Histogram name for mirroring start failure. + * @private @const {string} + */ +mr.MirrorAnalytics.CAPTURING_FAILURE_HISTOGRAM_ = + 'MediaRouter.Mirror.Capturing.Failure'; + + +/** + * Possible values for the start failure analytics. + * @enum {number} + */ +mr.MirrorAnalytics.CapturingFailure = { + CAPTURE_TAB_FAIL_EMPTY_STREAM: 0, + CAPTURE_DESKTOP_FAIL_ERROR_TIMEOUT: 1, + CAPTURE_TAB_TIMEOUT: 2, + CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL: 3, + ANSWER_NOT_RECEIVED: 4, + CAPTURE_TAB_FAIL_ERROR_TIMEOUT: 5, + ICE_CONNECTION_CLOSED: 6, + TAB_FAIL: 7, + DESKTOP_FAIL: 8, + UNKNOWN: 9, +}; + + +/** + * Records a mirroring start failure. + * @param {mr.MirrorAnalytics.CapturingFailure} reason The type of failure. + * @param {string} name The name of the histogram. + */ +mr.MirrorAnalytics.recordCapturingFailureWithName = function(reason, name) { + mr.Analytics.recordEnum(name, reason, mr.MirrorAnalytics.CapturingFailure); +}; + + +/** + * Error thrown when initiating a mirroring session. + */ +mr.mirror.Error = class extends Error { + /** + * @param {string} message The error message. + * @param {mr.MirrorAnalytics.CapturingFailure=} reason The failure reason. + */ + constructor(message, reason = undefined) { + super(message); + /** {!mr.MirrorAnalytics.CapturingFailure} The failure reason. */ + this.reason = + (reason >= 0 && reason <= mr.MirrorAnalytics.CapturingFailure.UNKNOWN) ? + reason : + mr.MirrorAnalytics.CapturingFailure.UNKNOWN; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics_test.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics_test.js new file mode 100644 index 00000000000..5bc5596eb43 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_analytics_test.js @@ -0,0 +1,111 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.MirrorAnalytics'); + +describe('Tests Analytics', function() { + const metricName = 'MediaRouter.Fake.Start.Failure'; + + beforeEach(function() { + chrome.metricsPrivate = { + recordTime: jasmine.createSpy('recordTime'), + recordMediumTime: jasmine.createSpy('recordMediumTime'), + recordLongTime: jasmine.createSpy('recordLongTime'), + recordUserAction: jasmine.createSpy('recordUserAction'), + recordValue: jasmine.createSpy('recordValue'), + }; + }); + + describe('Test Mirror Analytics', function() { + describe('Test recordCapturingFailure', function() { + const testConfig = { + 'metricName': metricName, + 'type': 'histogram-linear', + 'min': 1, + 'max': 10, + 'buckets': 11 + }; + it('Should record an empty stream error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_FAIL_EMPTY_STREAM, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 0); + }); + it('Should record a desktop timeout error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_TIMEOUT, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 1); + }); + it('Should record a tab timeout error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_TIMEOUT, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 2); + }); + it('Should record an user cancel error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 3); + }); + it('Should record an answer not received error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.ANSWER_NOT_RECEIVED, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 4); + }); + it('Should record a tab fail error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_FAIL_ERROR_TIMEOUT, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 5); + }); + it('Should record an ice connection closed error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.ICE_CONNECTION_CLOSED, + metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 6); + }); + it('Should record a tab failure', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.TAB_FAIL, metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 7); + }); + it('Should record a desktop failure', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.DESKTOP_FAIL, metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 8); + }); + it('Should record an unknown error', function() { + mr.MirrorAnalytics.recordCapturingFailureWithName( + mr.MirrorAnalytics.CapturingFailure.UNKNOWN, metricName); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 9); + }); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_config.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_config.js new file mode 100644 index 00000000000..fe079b2053b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_config.js @@ -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. + +/** + * @fileoverview Mirroring configs. + */ + +goog.provide('mr.mirror.Config'); +goog.require('mr.PlatformUtils'); + + +/** + * True if TDLS is supported by the browser and platform. + * @const {boolean} + */ +mr.mirror.Config.isTDLSSupportedByPlatform = Boolean( + typeof chrome != 'undefined' && chrome.networkingPrivate && + chrome.networkingPrivate.setWifiTDLSEnabledState && + mr.PlatformUtils.getCurrentOS() == mr.PlatformUtils.OS.CHROMEOS); + + +/** + * True if desktop audio capture is available. + * @const {boolean} + */ +mr.mirror.Config.isDesktopAudioCaptureAvailable = + // Audio capture is supported on ChromeOS with Chrome version + // 30.0.1584.0 and up, and Chrome 31 on Windows. + [mr.PlatformUtils.OS.CHROMEOS, mr.PlatformUtils.OS.WINDOWS].includes( + mr.PlatformUtils.getCurrentOS()); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service.js new file mode 100644 index 00000000000..c1bc326c9cf --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service.js @@ -0,0 +1,522 @@ +// 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 Service supporting tab and screen mirroring. + * + * This object is a singleton that controls all capture MediaStreams and all + * instances of MirrorSession. + * + + */ + +goog.provide('mr.mirror.Service'); + +goog.require('mr.Assertions'); +goog.require('mr.CancellablePromise'); +goog.require('mr.EventAnalytics'); +goog.require('mr.Issue'); +goog.require('mr.IssueSeverity'); +goog.require('mr.Logger'); +goog.require('mr.MediaSourceUtils'); +goog.require('mr.MirrorAnalytics'); +goog.require('mr.Module'); +goog.require('mr.mirror.CaptureParameters'); +goog.require('mr.mirror.CaptureSurfaceType'); +goog.require('mr.mirror.Error'); +goog.require('mr.mirror.Messages'); +goog.require('mr.mirror.MirrorMediaStream'); + +mr.mirror.Service = class extends mr.Module { + /** + * @param {!mr.mirror.ServiceName} serviceName + * @param {mr.ProviderManagerMirrorServiceCallbacks=} mirrorServiceCallbacks + */ + constructor(serviceName, mirrorServiceCallbacks) { + super(); + + /** @private @const {!mr.mirror.ServiceName} */ + this.serviceName_ = serviceName; + + /** @private {?mr.ProviderManagerMirrorServiceCallbacks} */ + this.mirrorServiceCallbacks_ = mirrorServiceCallbacks || null; + + /** @protected {?mr.mirror.Session} */ + this.currentSession = null; + + /** @private {?mr.mirror.MirrorMediaStream} */ + this.currentMediaStream_ = null; + + /** @protected @const */ + this.logger = mr.Logger.getInstance('mr.mirror.Service.' + serviceName); + + /** @private @const */ + this.onTabUpdated_ = this.handleTabUpdate_.bind(this); + + /** @private {boolean} */ + this.initialized_ = false; + } + + /** + * Initializes the service. Sets callbacks to provider manager. No-ops if + * already initialized. + * @param {!mr.ProviderManagerMirrorServiceCallbacks} mirrorServiceCallbacks + * Callbacks to provider manager. + */ + initialize(mirrorServiceCallbacks) { + if (this.initialized_) { + return; + } + this.mirrorServiceCallbacks_ = mirrorServiceCallbacks; + this.initialized_ = true; + this.doInitialize(); + } + + /** + * Called during initialization to perform service-specific initialization. + * @protected + */ + doInitialize() {} + + /** + * @return {mr.mirror.ServiceName} + */ + getName() { + return this.serviceName_; + } + + /** + * @param {!mr.Route} route + * @param {string} sourceUrn + * @param {!mr.mirror.Settings} mirrorSettings + * @param {string=} opt_presentationId + * @param {(function(!mr.Route): !mr.CancellablePromise)=} + * opt_streamStartedCallback Callback to invoke after stream capture + * succeeded and before the mirror session is created. The callback + * may update the route. + * @return {!mr.CancellablePromise<!mr.Route>} A promise fulfilled + * when mirroring has started successfully. + */ + startMirroring( + route, sourceUrn, mirrorSettings, opt_presentationId, + opt_streamStartedCallback) { + this.logger.info('Start mirroring on route ' + route.id); + if (!this.initialized_) { + return mr.CancellablePromise.reject(Error('Not initialized')); + } + const promise = new Promise((resolve, reject) => { + this.stopCurrentMirroring() + .then(() => { + const captureParams = mr.mirror.Service.createCaptureParameters_( + sourceUrn, mirrorSettings, opt_presentationId); + return new mr.mirror.MirrorMediaStream(captureParams).start(); + }) + .then(stream => { + if (this.currentMediaStream_) { + stream.stop(); + throw new mr.mirror.Error('Cannot start multiple streams'); + } + this.currentMediaStream_ = stream; + this.currentMediaStream_.setOnStreamEnded(this.cleanup_.bind(this)); + if (opt_streamStartedCallback) { + // Yuck. Converting a CancellablePromise to a plain Promise + // prevents cancellation from propagating correctly. + return opt_streamStartedCallback(route).promise; + } + return route; + }) + .then(updatedRoute => { + if (this.currentSession) { + throw new mr.mirror.Error('Cannot start multiple sessions'); + } + if (!this.currentMediaStream_) { + throw new mr.mirror.Error( + 'Media stream ended before session could start.'); + } + this.currentSession = + this.createMirrorSession(mirrorSettings, updatedRoute); + updatedRoute.mirrorInitData.activity = + this.currentSession.getActivity(); + this.currentSession.setOnActivityUpdate( + this.mirrorServiceCallbacks_.handleMirrorActivityUpdate.bind( + this.mirrorServiceCallbacks_)); + return this.currentSession.start(/** @type {!MediaStream} */ ( + this.currentMediaStream_.getMediaStream())); + }) + .then(() => { + if (mr.MediaSourceUtils.isTabMirrorSource(sourceUrn) && + !chrome.tabs.onUpdated.hasListener(this.onTabUpdated_)) { + chrome.tabs.onUpdated.addListener(this.onTabUpdated_); + } + return this.postProcessMirroring_(route, sourceUrn, mirrorSettings); + }) + .then(() => { + resolve(route); + }) + .catch(err => { + + this.onStartError_(/** @type {!Error} */ (err)); + return this.cleanup_().then(() => { + reject(err); + }); + }); + }); + return mr.CancellablePromise.forPromise(promise); + } + + /** + * @param {!mr.Route} route + * @param {string} sourceUrn + * @param {!mr.mirror.Settings} mirrorSettings + * @param {string=} opt_presentationId + * @param {number=} opt_tabId + * @param {(function(!mr.Route): !mr.CancellablePromise<!mr.Route>)=} + * opt_streamStartedCallback Callback to invoke after stream capture + * succeeded and before the mirror session is created. The callback may + * update the route. + * @return {!mr.CancellablePromise<!mr.Route>} A promise fulfilled + * when mirroring has started successfully. + */ + updateMirroring( + route, sourceUrn, mirrorSettings, opt_presentationId, opt_tabId, + opt_streamStartedCallback) { + this.logger.info('Update mirroring on route ' + route.id); + if (!this.initialized_) { + return mr.CancellablePromise.reject(Error('Not initialized')); + } + return mr.CancellablePromise.forPromise(this.doUpdateMirroring_( + route, sourceUrn, mirrorSettings, opt_presentationId, + opt_streamStartedCallback)); + } + + /** + * A helper method for updateMirroring. + * @param {!mr.Route} route + * @param {string} sourceUrn + * @param {!mr.mirror.Settings} mirrorSettings + * @param {string=} opt_presentationId + * @param {(function(!mr.Route): !mr.CancellablePromise<!mr.Route>)=} + * opt_streamStartedCallback Callback to invoke after stream capture + * succeeded and before the mirror session is created. The callback may + * update the route. + * @return {!Promise<!mr.Route>} A promise fulfilled + * when mirroring has started successfully. + * @private + */ + doUpdateMirroring_( + route, sourceUrn, mirrorSettings, opt_presentationId, + opt_streamStartedCallback) { + if (!this.currentSession) { + return Promise.reject(new mr.mirror.Error( + 'No session to update streams on', + mr.MirrorAnalytics.CapturingFailure.TAB_FAIL)); + } + if (!this.currentSession.supportsUpdateStream()) { + return Promise.reject(new mr.mirror.Error( + 'Session does not support updating stream', + mr.MirrorAnalytics.CapturingFailure.TAB_FAIL)); + } + + let streamSwapped = false; + return new Promise((resolve, reject) => { + const captureParams = mr.mirror.Service.createCaptureParameters_( + sourceUrn, mirrorSettings, opt_presentationId); + new mr.mirror.MirrorMediaStream(captureParams) + .start() + .then(stream => { + if (this.currentMediaStream_) { + this.currentMediaStream_.setOnStreamEnded(null); + this.currentMediaStream_.stop(); + this.recordStreamEnded(); + } + this.currentMediaStream_ = stream; + this.currentMediaStream_.setOnStreamEnded(this.cleanup_.bind(this)); + streamSwapped = true; + + if (opt_streamStartedCallback) { + return opt_streamStartedCallback(route).promise; + } + return route; + }) + .then(_ => { + if (!this.currentSession) { + throw new mr.mirror.Error('Session ended while updating stream'); + } + if (!this.currentMediaStream_) { + throw new mr.mirror.Error( + 'Media stream ended before session could be updated.'); + } + return this.currentSession.updateStream( + /** @type {!MediaStream} */ ( + this.currentMediaStream_.getMediaStream())); + }) + .then(this.postProcessMirroring_.bind( + this, route, sourceUrn, mirrorSettings)) + .then(() => resolve(route)) + .catch(err => { + this.onStartError_(/** @type {!Error} */ (err)); + if (streamSwapped) { + return this.cleanup_().then(() => { + reject(err); + }); + } else { + reject(err); + } + }); + }); + } + + /** + * @param {!mr.Route} route + * @param {string} sourceUrn + * @param {!mr.mirror.Settings} mirrorSettings + * @return {!Promise<void>} Resolves when done. + * @private + */ + postProcessMirroring_(route, sourceUrn, mirrorSettings) { + return new Promise((resolve, reject) => { + if (!this.currentSession) { + reject(new mr.mirror.Error( + 'Session gone before executing post-startup steps', + mr.MirrorAnalytics.CapturingFailure.TAB_FAIL)); + return; + } + if (mr.MediaSourceUtils.isTabMirrorSource(sourceUrn)) { + this.currentSession.setTabId( + /** @type {number} */ (route.mirrorInitData.tabId)); + this.recordTabMirrorStartSuccess(); + } else if (mr.MediaSourceUtils.isPresentationSource(sourceUrn)) { + this.currentSession.setTabId( + /** @type {number} */ (route.mirrorInitData.tabId)); + this.recordOffscreenTabMirrorStartSuccess(); + } else { + this.recordDesktopMirrorStartSuccess(); + } + this.checkCaptureIssues_( + mirrorSettings, + /** @type {!mr.mirror.MirrorMediaStream} */ + (this.currentMediaStream_)); + this.currentSession.onActivityUpdated(); + resolve(); + }); + } + + /** + * @param {!Error|!mr.mirror.Error} error The error that occurred when + * starting mirroring. + * @private + */ + onStartError_(error) { + error.reason = (error.reason != null) ? + error.reason : + mr.MirrorAnalytics.CapturingFailure.UNKNOWN; + + this.logger.error( + `Failed to start mirroring: ${error.message}` + + `, reason = ${error.reason}: ${error.stack}`); + this.recordMirrorStartFailure(error.reason); + } + + /** + * @return {!Promise<boolean>} Fulfilled with true if there was a session + * and it was stopped, and with false if there is no session to stop. + */ + stopCurrentMirroring() { + if (!this.initialized_) { + return Promise.reject('Not initialized'); + } + return this.cleanup_().then(hadSession => { + if (hadSession) this.recordMirrorSessionEnded(); + + return hadSession; + }); + } + + /** + * @return {!Promise<boolean>} Fulfilled with true if there was a session + * and it was stopped. This promise never rejects. + * @private + */ + cleanup_() { + // No-op if the listener was already removed. + chrome.tabs.onUpdated.removeListener(this.onTabUpdated_); + + const streamToCleanUp = this.currentMediaStream_; + this.currentMediaStream_ = null; + if (streamToCleanUp) { + // Clear the "on ended" callback to prevent recursive calls to this method + // while the session and MediaStream are being torn down (below). + streamToCleanUp.setOnStreamEnded(null); + } + + const sessionToCleanUp = this.currentSession; + this.currentSession = null; + + // Create a promise chain to execute the stopping of the session, invoke the + // before/after stop callbacks, and thereafter stop the MediaStream. The + // MediaStream is stopped after the session to avoid any extra churn in the + // session shutdown process. All of this is conditional on whether a session + // and/or MediaStream was ever started since cleanup_() is also used as an + // all-purpose failed-start recovery handler. + let cleanupPromise; + if (sessionToCleanUp) { + cleanupPromise = + this.beforeCleanUpSession(sessionToCleanUp) + .catch( + err => + this.logger.error('Error in before-cleanup steps', err)) + .then(() => sessionToCleanUp.stop()) + .catch(err => this.logger.error('Error stopping session', err)) + .then(() => { + this.mirrorServiceCallbacks_.onMirrorSessionEnded( + sessionToCleanUp.getRoute().id); + }) + .catch(err => this.logger.error('Error in ended callbacks', err)) + .then(() => true); + } else { + cleanupPromise = Promise.resolve(false); + } + if (streamToCleanUp) { + cleanupPromise = cleanupPromise.then(hadSession => { + streamToCleanUp.stop(); + this.recordStreamEnded(); + return hadSession; + }); + } + + return cleanupPromise; + } + + /** + * @param {!mr.mirror.Session} session the session about to be cleaned up. + * @return {!Promise<void>} Fulfilled when session has been cleaned up. + * @protected + */ + beforeCleanUpSession(session) { + return Promise.resolve(); + } + + /** + * Handles tab updated event. + * + * @param {number} tabId the ID of the tab. + * @param {!TabChangeInfo} changeInfo The changes to the state of the tab. + * @param {!Tab} tab The tab. + * @private + */ + handleTabUpdate_(tabId, changeInfo, tab) { + mr.EventAnalytics.recordEvent(mr.EventAnalytics.Event.TABS_ON_UPDATED); + this.currentSession && + this.currentSession.onTabUpdated(tabId, changeInfo, tab); + } + + /** + * Creates a new mr.mirror.CaptureParameters from the given inputs. + * + * @param {string} sourceUrn + * @param {!mr.mirror.Settings} mirrorSettings + * @param {string=} opt_presentationId + * @return {!mr.mirror.CaptureParameters} + * @private + */ + static createCaptureParameters_( + sourceUrn, mirrorSettings, opt_presentationId) { + if (mr.MediaSourceUtils.isTabMirrorSource(sourceUrn)) { + return new mr.mirror.CaptureParameters( + mr.mirror.CaptureSurfaceType.TAB, mirrorSettings); + } else if (mr.MediaSourceUtils.isDesktopMirrorSource(sourceUrn)) { + return new mr.mirror.CaptureParameters( + mr.mirror.CaptureSurfaceType.DESKTOP, mirrorSettings); + } else if (mr.MediaSourceUtils.isPresentationSource(sourceUrn)) { + if (!opt_presentationId) { + throw new mr.mirror.Error('Missing offscreen tab presentation id'); + } + return new mr.mirror.CaptureParameters( + mr.mirror.CaptureSurfaceType.OFFSCREEN_TAB, mirrorSettings, sourceUrn, + /** @type {!string} */ (opt_presentationId)); + } else { + throw new mr.mirror.Error( + 'Source URN does not suggest a known capture type.'); + } + } + + /** + * Checks for any capture issues after mirroring has started successfully. + * + * @param {!mr.mirror.Settings} settings The requested settings. + * @param {!mr.mirror.MirrorMediaStream} mediaStream The captured stream. + * @private + */ + checkCaptureIssues_(settings, mediaStream) { + if (settings.shouldCaptureAudio && mediaStream.getMediaStream() && + !mediaStream.getMediaStream().getAudioTracks().length) { + this.mirrorServiceCallbacks_.sendIssue(new mr.Issue( + mr.mirror.Messages.MSG_MR_MIRROR_NO_AUDIO_CAPTURED, + mr.IssueSeverity.NOTIFICATION)); + } + } + + /** + * @return {?mr.mirror.Session} + * @deprecated Use of this getter is dangerous, since mr.mirror.Service could + * be in the middle of a sequence of asynchronous steps to start up or + * shut down the current session. + */ + getCurrentSession() { + return this.currentSession; + } + + /** + * @param {!mr.mirror.Settings} mirrorSettings + * @param {!mr.Route} route + * @return {!mr.mirror.Session} + */ + createMirrorSession(mirrorSettings, route) {} + /** + * Records that Tab Mirroring successfully started. + */ + recordTabMirrorStartSuccess() {} + /** + * Records that Desktop Mirroring successfully started. + */ + recordDesktopMirrorStartSuccess() {} + /** + * Records that Offscreen Tab (1UA mode) Mirroring successfully started. + */ + recordOffscreenTabMirrorStartSuccess() {} + /** + * Records that the session has ended. + */ + recordMirrorSessionEnded() {} + /** + * Records that a Mirroring session failed to start. + * @param {mr.MirrorAnalytics.CapturingFailure} reason + * The reason for the failure. + */ + recordMirrorStartFailure(reason) {} + /** + * Records that a Mirroring stream ended. + */ + recordStreamEnded() {} + /** + * Asynchronously uploads logs for the most recent mirroring session. + * @param {string} feedbackId ID of feedback requesting log upload. + * @return {!Promise<string|undefined>} Resolved with an identifier (e.g., + * URL) of the log that will be uploaded (which will be undefined if there + * is no such identifier), or rejected if upload failed. + */ + requestLogUpload(feedbackId) { + return mr.Assertions.rejectNotImplemented(); + } + /** + * See documentation in interface_data/mojo.js. + * @param {string} routeId + * @param {!mojo.InterfaceRequest} controllerRequest + * @param {!mojo.MediaStatusObserverPtr} observer + * @return {!Promise<void>} + */ + createMediaRouteController(routeId, controllerRequest, observer) { + return mr.Assertions.rejectNotImplemented(); + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_loader.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_loader.js new file mode 100644 index 00000000000..4d0eefeb307 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_loader.js @@ -0,0 +1,47 @@ +// 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. + +goog.provide('mr.mirror.DefaultServiceLoader'); +goog.provide('mr.mirror.ServiceLoader'); + + + +/** + * Loads a mirror.Service. Note that this loader does not need to handle event + * page suspending and waking up (the event page is running when there is a + * local mirroring route). + * @record + */ +mr.mirror.ServiceLoader = class { + /** + * Loads and returns the service. + * @return {!Promise<!mr.mirror.Service>} + */ + loadService() {} +}; + + +/** + * A lightweight implementation of ServiceLoader which just returns the instance + * provided to the constructor. + * @implements {mr.mirror.ServiceLoader} + */ +mr.mirror.DefaultServiceLoader = class { + /** + * @param {!mr.mirror.Service} serviceInstance + */ + constructor(serviceInstance) { + /** + * @private {!mr.mirror.Service} + */ + this.serviceInstance_ = serviceInstance; + } + + /** + * @override + */ + loadService() { + return Promise.resolve(this.serviceInstance_); + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_name.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_name.js new file mode 100644 index 00000000000..0a588727732 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_service_name.js @@ -0,0 +1,21 @@ +// 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 Service name uniquely identifying each service. + */ + + +goog.provide('mr.mirror.ServiceName'); + + +/** + * @enum {string} + */ +mr.mirror.ServiceName = { + CAST_STREAMING: 'cast_streaming', + HANGOUTS: 'hangouts', + MEETINGS: 'meetings', + WEBRTC: 'webrtc' +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session.js new file mode 100644 index 00000000000..7c3a0a3442c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session.js @@ -0,0 +1,179 @@ +// 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 Interface to a mirroring session. + + */ + +goog.provide('mr.mirror.Session'); + +goog.require('mr.TabUtils'); +goog.require('mr.mirror.Activity'); + + +/** + * Creates a new MirrorSession. Do not call, as this is an abstract base class. + */ +mr.mirror.Session = class { + /** + * @param {!mr.Route} route + * @param {?function(!mr.Route, !mr.mirror.Activity)=} onActivityUpdate + */ + constructor(route, onActivityUpdate = null) { + /** + * The media route associated with this mirror session. + * @protected @const {!mr.Route} + */ + this.route = route; + + /** + * Called when this.activity_ has changed. + * @private {?function(!mr.Route, !mr.mirror.Activity)} + */ + this.onActivityUpdate_ = onActivityUpdate; + + /** + * The activity description for keeping UI strings up to date. + * @protected {!mr.mirror.Activity} + */ + this.activity = mr.mirror.Activity.createFromRoute(route); + + /** @type {?number} */ + this.tabId = null; + + /** @type {?Tab} */ + this.tab = null; + + /** @type {boolean} */ + this.isRemoting = false; + } + + /** + * Sets the callback for handling activity updates. + * @param {!function(!mr.Route, !mr.mirror.Activity)} onActivityUpdate + */ + setOnActivityUpdate(onActivityUpdate) { + this.onActivityUpdate_ = onActivityUpdate; + } + + /** + * @param {number} tabId The id of the tab being captured, if any. + * @return {!Promise<void>} + */ + setTabId(tabId) { + if (this.tabId != tabId) { + this.tabId = tabId; + return mr.TabUtils.getTab(tabId).then((tab) => { + this.tab = tab; + this.onActivityUpdated(); + }); + } else { + return Promise.resolve(); + } + } + + /** + * Handles tab updated event. + * + * @param {number} tabId the ID of the tab. + * @param {!TabChangeInfo} changeInfo The changes to the state of the tab. + * @param {!Tab} tab The tab. + */ + onTabUpdated(tabId, changeInfo, tab) { + if (tabId != this.tabId) return; + if (changeInfo.status == 'complete' || + (!!changeInfo.favIconUrl && tab.status == 'complete')) { + this.tab = tab; + this.onActivityUpdated(); + } + } + + /** + * Called when the activity object for the mirror session needs to be updated. + * This happens when e.g. the tab being mirrored navigates, changes title, or + * switches in or out of media remoting. + */ + onActivityUpdated() { + if (this.tab) { + this.activity.setContentTitle(this.tab.title); + this.activity.setIncognito(this.tab.incognito); + const url = new URL(this.tab.url); + if (url.protocol == 'file:') { + this.activity.setType(mr.mirror.Activity.Type.MIRROR_FILE); + this.activity.setOrigin(null); + } else { + this.activity.setType( + this.isRemoting ? mr.mirror.Activity.Type.MEDIA_REMOTING : + mr.mirror.Activity.Type.MIRROR_TAB); + if (url.protocol == 'https:') { + // OK to drop the protocol for secure origins. + this.activity.setOrigin(url.origin.substr(8)); + } else { + this.activity.setOrigin(url.origin); + } + } + } + this.route.description = this.activity.getRouteDescription(); + this.onActivityUpdate_ && this.onActivityUpdate_(this.route, this.activity); + this.sendActivityToSink(); + } + + /** + * @return {!mr.Route} + */ + getRoute() { + return this.route; + } + + /** + * @return {!mr.mirror.Activity} + */ + getActivity() { + return this.activity; + } + + /** + * Starts the mirroring session. The |mediaStream| must provide one audio + * and/or one video track. It is illegal to call start() more than once on the + * same session, even after stop() has been called. See updateStream(), or + * else create a new instance to re-start a session. + * @param {!MediaStream} mediaStream The media stream that has the audio + * and/or video track. + * @return {!Promise<mr.mirror.Session>} Fulfilled when the + * session has been created and transports have started for the streams. + */ + start(mediaStream) {} + + /** + * @return {boolean} Whether the session supports updating its media stream + * while it is active. + */ + supportsUpdateStream() { + return false; + } + + /** + * Updates the media stream that the session is using. + * @param {!MediaStream} mediaStream + * @return {!Promise} + */ + updateStream(mediaStream) {} + + /** + * Stops the mirroring session. The underlying streams and transports are + * stopped and destroyed. Sessions cannot be re-started. Instead, either use + * updateStream() or create a new instance to re-start a session. + * @return {!Promise<void>} Fulfilled with once the session has been stopped. + * The promise is rejected on error. + */ + stop() { + return Promise.resolve(); + } + + /** + * Sends updated activity info directly to the sink. + */ + sendActivityToSink() {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session_test.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session_test.js new file mode 100644 index 00000000000..52f0536c11a --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_session_test.js @@ -0,0 +1,118 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.Route'); +goog.require('mr.TabUtils'); +goog.require('mr.mirror.Session'); + +describe('Tests mr.mirror.Session', () => { + let mirrorRoute; + let session; + let onActivityUpdated; + + function expectNormalSession(s) { + expect(s.tabId).toBe(47); + expect(s.tab).not.toBe(null); + expect(s.isRemoting).toBe(false); + expect(s.activity.getRouteDescription()) + .toBe('Casting tab (news.google.com)'); + expect(s.activity.getRouteMediaStatus()).toBe('Google News'); + expect(s.activity.getCastRemoteTitle()).toBe('Casting tab'); + } + + function expectIncognitoSession(s) { + expect(s.tabId).toBe(47); + expect(s.tab).not.toBe(null); + expect(s.isRemoting).toBe(false); + expect(s.activity.getRouteDescription()) + .toBe('Casting tab (news.google.com)'); + expect(s.activity.getRouteMediaStatus()).toBe('Google News'); + expect(s.activity.getCastRemoteTitle()).toBe('Casting active'); + } + + beforeEach(() => { + window['mojo'] = null; // Workaround to allow tests to run in Jasmine + // without mojo bindings + mirrorRoute = new mr.Route( + 'routeId', 'presentationId', 'sinkId', + 'urn:x-org.chromium.media:source:tab:47', true, null); + onActivityUpdated = jasmine.createSpy(); + session = new mr.mirror.Session(mirrorRoute, onActivityUpdated); + session.tabId = 47; + spyOn(session, 'sendActivityToSink'); + }); + + it('has default data for a tab mirroring route', () => { + expect(session.route).toBe(mirrorRoute); + expect(session.tabId).toBe(47); + expect(session.tab).toBe(null); + expect(session.isRemoting).toBe(false); + expect(session.activity.getRouteDescription()).toBe('Casting tab'); + expect(session.activity.getRouteMediaStatus()).toBe(''); + expect(session.activity.getCastRemoteTitle()).toBe('Casting tab'); + }); + + describe('onTabUpdated', () => { + it('sets all fields with normal tab', () => { + session.onTabUpdated(47, {'status': 'complete'}, { + 'title': 'Google News', + 'url': 'https://news.google.com', + 'incognito': false + }); + expect(session.sendActivityToSink).toHaveBeenCalled(); + expect(onActivityUpdated).toHaveBeenCalled(); + expectNormalSession(session); + }); + it('sets some fields with incognito tab', () => { + session.onTabUpdated(47, {'status': 'complete'}, { + 'title': 'Google News', + 'url': 'https://news.google.com', + 'incognito': true + }); + expect(session.sendActivityToSink).toHaveBeenCalled(); + expect(onActivityUpdated).toHaveBeenCalled(); + expectIncognitoSession(session); + }); + it('sets some fields with OTR route', () => { + mirrorRoute.offTheRecord = true; + otrSession = new mr.mirror.Session(mirrorRoute, onActivityUpdated); + otrSession.tabId = 47; + spyOn(otrSession, 'sendActivityToSink'); + otrSession.onTabUpdated(47, {'status': 'complete'}, { + 'title': 'Google News', + 'url': 'https://news.google.com', + 'incognito': true + }); + expect(otrSession.sendActivityToSink).toHaveBeenCalled(); + expect(onActivityUpdated).toHaveBeenCalled(); + expectIncognitoSession(otrSession); + }); + }); + + describe('setTabId', () => { + beforeEach((done) => { + spyOn(mr.TabUtils, 'getTab').and.returnValue(Promise.resolve({ + 'title': 'CNN', + 'url': 'https://www.cnn.com', + 'incognito': false + })); + session.setTabId(48); + done(); + }); + + it('updates the tab', (done) => { + expect(session.sendActivityToSink).toHaveBeenCalled(); + expect(onActivityUpdated).toHaveBeenCalled(); + expect(session.tabId).toBe(48); + expect(session.tab).not.toBe(null); + expect(session.isRemoting).toBe(false); + expect(session.activity.getRouteDescription()) + .toBe('Casting tab (www.cnn.com)'); + expect(session.activity.getRouteMediaStatus()).toBe('CNN'); + expect(session.activity.getCastRemoteTitle()).toBe('Casting tab'); + done(); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings.js new file mode 100644 index 00000000000..74499bbacbc --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings.js @@ -0,0 +1,400 @@ +// 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 Tab-mirroring settings, including video bitrate, video + * resolution, and audio bitrate. + * + + */ + +goog.provide('mr.mirror.Settings'); +goog.provide('mr.mirror.VideoCodec'); + +goog.require('mr.Logger'); +goog.require('mr.PlatformUtils'); + + +/** + * @enum {string} + */ +mr.mirror.VideoCodec = { + VP8: 'VP8', + // This is the internal codename for hardware-encoded H264. for V1 mirroring. + CAST1: 'CAST1', + H264: 'H264', + + // This is a fake codec for retransmissions used in WebRTC. + RTX: 'rtx' +}; + + + +/** + * Settings that affect capture and transport (via Cast Streaming or WebRTC). + * Generally, these should all be left unchanged from their defaults. + * Overriding them is only meant for development or user experiments. + * + * WARNING: If making any changes to defaults here, it is on *you* to confirm + * that all downstream consumers of these settings will behave correctly with + * the new values. If not, see usage notes below for per-sink adjustments. + * + * Usage: Generally, this class should be instantiated by mr.Provider + * implementations only. The provider creates a new instance, and then must + * adjust any properties based on both the sender's and sink's capabilities. + * It should also, as a final step, freeze the settings object to prevent any + * downstream code from making changes. Example provider code: + * + * getMirrorSettings(sinkId) { + * // Constrain default settings to a sink that is only capable of standard + * // definition (lower resolution and no high frame rate support). + * const settings = new mr.mirror.Settings(); + * settings.maxWidth = Math.min(settings.maxWidth, 640); + * settings.maxHeight = Math.min(settings.maxWidth, 360); + * settings.maxFrameRate = Math.min(settings.maxFrameRate, 30); + * + * // Override: Some sinks might require the sender to do the letterboxing. + * settings.senderSideLetterboxing = + * !this.canSinkHandleLetterboxing_(sinkId); + * + * settings.makeFinalAdjustmentsAndFreeze(); + * return settings; + * } + * + */ +mr.mirror.Settings = class { + constructor() { + /** + * Maximum video width in pixels. + * + * @export {number} + */ + this.maxWidth = 1920; + + /** + * Maximum video height in pixels. + * + * @export {number} + */ + this.maxHeight = 1080; + + /** + * Minimum video width in pixels. + * + * @export {number} + */ + this.minWidth = 180; + + /** + * Minimum video height in pixels. + * + * @export {number} + */ + this.minHeight = 180; + + /** + * Whether the screen capture must handle letterboxing/pillarboxing. If + * false (more desired), the receiver will handle it. When setting this to + * true, please see comments for getMinDimensionsToMatchAspectRatio(). + * + * @export {boolean} + */ + this.senderSideLetterboxing = false; + + /** + * The minimum frame rate for captures. Well-behaved clients can handle a + * minimum frame rate of zero, which prevents wasting system resources + * sender-side. Unfortunately, not all clients are well-behaved... + * + * @export {number} + */ + this.minFrameRate = 0; + + /** + * The maximum frame rate for captures. + * + * @export {number} + */ + this.maxFrameRate = 30; + + /** + * Minimum video bitrate in kbps. + * + * @export {number} + */ + this.minVideoBitrate = 300; + + /** + * Maximum video bitrate in kbps. + * + * @export {number} + */ + this.maxVideoBitrate = 5000; + + /** + * Target audio bitrate in kbps (0 means automatic). + * + * @export {number} + */ + this.audioBitrate = 0; + + /** + * Maximum end-to-end latency (in milliseconds). + * + * @export {number} + */ + this.maxLatencyMillis = 800; + + /** + * Minimum end-to-end latency (in milliseconds). This allows cast streaming + * to adaptively lower latency in interactive streaming scenarios. + * This setting currently applies to cast streaming only. + * + + * + * @export {number} + */ + this.minLatencyMillis = 400; + + /** + * Starting end-to-end latency for animated content (in milliseconds). + * + * @export {number} + */ + this.animatedLatencyMillis = 400; + + /** + * Enable DSCP? + * This setting currently applies to cast streaming only. + * + * @export {boolean} + */ + this.dscpEnabled = + [ + mr.PlatformUtils.OS.MAC, mr.PlatformUtils.OS.LINUX, + mr.PlatformUtils.OS.CHROMEOS + ].includes(mr.PlatformUtils.getCurrentOS()) || + mr.PlatformUtils.isWindows8OrNewer(); + + /** + * Whether to enable network transport logging. + * + * @export {boolean} + */ + this.enableLogging = true; + + /** + * Whether an attempt should be made to use TDLS. + * This setting currently applies to cast streaming only. + * + * @export {boolean} + */ + this.useTdls = false; + + + /** + * Whether video should be captured. This could be influenced by the + * application and/or the sink's capabilities. + * @export {boolean} + */ + this.shouldCaptureVideo = true; + + /** + * Whether audio should be captured. This could be influenced by the + * application and/or the sink's capabilities. + * @export {boolean} + */ + this.shouldCaptureAudio = true; + + // For development, debugging, or integration testing use only! + const overrides = window.localStorage ? + window.localStorage.getItem(mr.mirror.Settings.OverridesKey) : + null; + if (overrides) { + try { + const parsedOverrides = JSON.parse(String(overrides)); + if (parsedOverrides instanceof Object) { + this.update_(/** @type {!Object} */ (parsedOverrides)); + mr.Logger.getInstance('mr.mirror.Settings') + .warning( + () => 'Initial mr.mirror.Settings overridden to: ' + + this.toJsonString()); + } else { + throw Error( + `localStorage[${mr.mirror.Settings.OverridesKey}] ` + + `does not parse as an Object: ${overrides}`); + } + } catch (exception) { + mr.Logger.getInstance('mr.mirror.Settings') + .error( + mr.mirror.Settings.OverridesKey + ' must be of the form ' + + '\'{"maxWidth":640, "maxHeight":360}\'.', + exception); + // Prevent mirroring from starting if overrides are present and not + // parseable. + throw new Error('Overrides not parseable. See ERROR log for details.'); + } + } + } + + /** + * @return {!mr.mirror.Settings} + */ + clone() { + const settings = new mr.mirror.Settings(); + settings.update_(this); + return settings; + } + + /** + * Returns the properties of this Settings object as a JSON-formatted string. + * @return {!string} + */ + toJsonString() { + return JSON.stringify(this, (key, value) => { + // Only public fields are included in the stringified output. + if (key.length == 0 || !key.endsWith('_')) { + return value; + } + return undefined; + }); + } + + /** + * Update this object to have the same settings as another object. + * @param {!Object} settings The properties to apply, which may be any subset + * of all the public fields of this class. + * @private + */ + update_(settings) { + // Override Closure-compiler "access on a struct" error. + const self = /** @type {!Object} */ (this); + for (const key of Object.keys(settings)) { + if (key.endsWith('_') || (typeof settings[key] !== typeof self[key])) { + continue; + } + self[key] = settings[key]; + } + } + + /** + * Make mandatory system-wide bounds adjustments and then freeze this Settings + * object. See example usage in class-level comments. + */ + makeFinalAdjustmentsAndFreeze() { + this.clampMaxDimensionsToScreenSize_(); + Object.freeze(this); + } + + /** + * Adjusts the maxWidth/maxHeight to within the size of the user's screen, and + * rounds down to a standard 16:9 resolution (i.e., width is 0 modulo 160 and + * height is 0 modulo 90). This prevents performance problems due to: + * 1. The pre-capture fullscreen size being something way larger than the + * system was designed for (e.g., 1080p on a Daisy Chromebook). + * 2. The receiver dealing with scaling from an oddball resolution to a + * standard resolution (e.g., 1366x768 --> 1280x720). + * @private + */ + clampMaxDimensionsToScreenSize_() { + const widthStep = 160; + const heightStep = 90; + const screenWidth = mr.mirror.Settings.getScreenWidth(); + const screenHeight = mr.mirror.Settings.getScreenHeight(); + const x = this.maxWidth * screenHeight; + const y = this.maxHeight * screenWidth; + let clampedWidth = 0; + let clampedHeight = 0; + if (y < x) { + clampedWidth = Math.min(this.maxWidth, screenWidth); + clampedWidth = clampedWidth - (clampedWidth % widthStep); + clampedHeight = clampedWidth * heightStep / widthStep; + } else { + clampedHeight = Math.min(this.maxHeight, screenHeight); + clampedHeight = clampedHeight - (clampedHeight % heightStep); + clampedWidth = clampedHeight * widthStep / heightStep; + } + if (clampedWidth < Math.max(widthStep, this.minWidth) || + clampedHeight < Math.max(heightStep, this.minHeight)) { + clampedWidth = Math.max(widthStep, this.minWidth); + clampedHeight = Math.max(heightStep, this.minHeight); + } + + this.maxWidth = clampedWidth; + this.maxHeight = clampedHeight; + } + + /** + * Returns alternate |minWidth| and |minHeight| values that match the aspect + * ratio of |maxWidth| and |maxHeight| to the nearest integer. Some of the + * capture APIs will then interpret the matching aspect ratios to mean that + * the sender should letterbox/pillarbox the video, rather than allowing the + * receiver to handle it. This method does NOT modify any properties of this + * Settings object. + * @return {!{width: number, height: number}} New minimum width/height values, + * as described. + */ + getMinDimensionsToMatchAspectRatio() { + if (this.minAndMaxAspectRatiosAreSimilar()) { + return {width: this.minWidth, height: this.minHeight}; + } + + let a = this.maxWidth; + let b = this.maxHeight; + while (b != 0) { + const remainder = a % b; + a = b; + b = remainder; + } + // Note: |a| now contains the greatest common divisor. + let width = this.maxWidth / a; + let height = this.maxHeight / a; + if (width < this.minWidth || height < this.minHeight) { + // Increase to respect the current min width/Height setting. + const upFactor = Math.max(this.minWidth / width, this.minHeight / height); + width *= upFactor; + height *= upFactor; + // ...and make sure the increase does not now exceed the max width/height. + if (width > this.maxWidth || height > this.maxHeight) { + width = this.maxWidth; + height = this.maxHeight; + } + } + width = Math.round(width); + height = Math.round(height); + return {width, height}; + } + + /** + * @return {boolean} Returns true if the aspect ratios of the min and max + * dimensions are within one part in one hundred of each other. + */ + minAndMaxAspectRatiosAreSimilar() { + if (this.minHeight == 0 || this.maxHeight == 0) { + return false; + } + // Source: content/renderer/media/media_stream_video_capturer_source.cc (in + // Chromium project, circa 2016). + const ratioOfMinSize = Math.floor(100.0 * this.minWidth / this.minHeight); + const ratioOfMaxSize = Math.floor(100.0 * this.maxWidth / this.maxHeight); + return ratioOfMinSize == ratioOfMaxSize; + } + + /** @return {number} */ + static getScreenWidth() { + return screen.width; + } + + /** @return {number} */ + static getScreenHeight() { + return screen.height; + } +}; + + +/** + * The key for retrieving settings overrides from localStorage. + * @type {string} + */ +mr.mirror.Settings.OverridesKey = 'mr.mirror.Settings.Overrides'; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings_test.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings_test.js new file mode 100644 index 00000000000..066e92f3198 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/mirror_settings_test.js @@ -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. + +goog.require('mr.mirror.Settings'); + +describe('Tests Mirror Settings', function() { + it('produces JSON strings containing public fields', function() { + // Emit Settings as a JSON-format string. + const settings = new mr.mirror.Settings(); + const jsonString = settings.toJsonString(); + + // The string should be parseable as JSON and contain only public fields. + const parsed = JSON.parse(jsonString); + expect(typeof parsed['maxWidth']).toBe('number'); + expect(typeof parsed['enableLogging']).toBe('boolean'); + expect(Object.keys(parsed).sort()) + .toEqual(Object.keys(settings).filter(x => !x.endsWith('_')).sort()); + }); + + it('only updates public fields from localStorage overrides', function() { + const settings = new mr.mirror.Settings(); + + // Normal case: A public field is updated with a new value of the same type. + const originalMaxWidth = settings.maxWidth; + settings.update_({'maxWidth': originalMaxWidth / 2}); + expect(settings.maxWidth).toBe(originalMaxWidth / 2); + + // Bad case: A public field being set to a value of a different type. + const jsonBefore = settings.toJsonString(); + settings.update_({'maxWidth': true}); + expect(settings.toJsonString()).toEqual(jsonBefore); + + // Bad case: A non-existant field. + settings.update_({'fooey': true}); + expect(settings.toJsonString()).toEqual(jsonBefore); + + // Bad case: Attempt to set private field. + const loggerBefore = settings.logger_; + const injectionAttackFunc = function() { + return 'MUAHAHAHAHA!'; + }; + settings.update_({'logger_': injectionAttackFunc}); + expect(settings.logger_).not.toBe(injectionAttackFunc); + expect(settings.logger_).toBe(loggerBefore); + expect(settings.toJsonString()).toEqual(jsonBefore); + }); + + it('clamps max dimensions to 1920x1080 screen size', function() { + spyOn(mr.mirror.Settings, 'getScreenWidth').and.returnValue(1920); + spyOn(mr.mirror.Settings, 'getScreenHeight').and.returnValue(1080); + const settings = new mr.mirror.Settings(); + settings.makeFinalAdjustmentsAndFreeze(); + expect(settings.maxWidth).toBe(1920); + expect(settings.maxHeight).toBe(1080); + }); + + it('clamps max dimensions to 1366x768 screen size', function() { + spyOn(mr.mirror.Settings, 'getScreenWidth').and.returnValue(1366); + spyOn(mr.mirror.Settings, 'getScreenHeight').and.returnValue(768); + const settings = new mr.mirror.Settings(); + settings.makeFinalAdjustmentsAndFreeze(); + expect(settings.maxWidth).toBe(1280); + expect(settings.maxHeight).toBe(720); + }); + + it('returns min size matching aspect ratio of max size', function() { + const settings = new mr.mirror.Settings(); + settings.maxWidth = 1920; + settings.maxHeight = 1080; + settings.minWidth = 320; + settings.minHeight = 180; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(true); + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 320, height: 180}); + + settings.minWidth = 0; + settings.minHeight = 0; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(false); + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 16, height: 9}); + settings.minWidth = 1; + settings.minHeight = 1; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(false); + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 16, height: 9}); + settings.minWidth = 16; + settings.minHeight = 9; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(true); + + settings.minWidth = 320; + settings.minHeight = 240; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(false); + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 427, height: 240}); + + settings.maxWidth = 1000; + settings.maxHeight = 1000; + settings.minWidth = 48; + settings.minHeight = 27; + expect(settings.minAndMaxAspectRatiosAreSimilar()).toBe(false); + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 48, height: 48}); + + settings.maxWidth = 1001; + settings.maxHeight = 999; + settings.minWidth = 0; + settings.minHeight = 0; + expect(settings.getMinDimensionsToMatchAspectRatio()) + .toEqual({width: 1001, height: 999}); + }); + + it('is frozen after final adjustments are made', function() { + 'use strict'; + const settings = new mr.mirror.Settings(); + expect(Object.isFrozen(settings)).toBe(false); + settings.makeFinalAdjustmentsAndFreeze(); + expect(Object.isFrozen(settings)).toBe(true); + expect(() => { + settings.maxWidth = 999; + }).toThrow(); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/capture_parameters.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/capture_parameters.js new file mode 100644 index 00000000000..dbf1d77fa8c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/capture_parameters.js @@ -0,0 +1,208 @@ +// 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 Stream capture parameters. + */ + +goog.provide('mr.mirror.CaptureParameters'); +goog.provide('mr.mirror.CaptureSurfaceType'); + +goog.require('mr.Assertions'); +goog.require('mr.mirror.Config'); + + +/** + * Parameters that configure and control local media capture. + */ +mr.mirror.CaptureParameters = class { + /** + * @param {!mr.mirror.CaptureSurfaceType} captureSurface + * @param {!mr.mirror.Settings} mirrorSettings + * @param {string=} opt_offscreenTabUrl + * @param {string=} opt_presentationId + */ + constructor( + captureSurface, mirrorSettings, opt_offscreenTabUrl, opt_presentationId) { + if (opt_offscreenTabUrl) { + mr.Assertions.assert( + captureSurface == mr.mirror.CaptureSurfaceType.OFFSCREEN_TAB); + mr.Assertions.assert(opt_presentationId); + } + + /** @type {!mr.mirror.CaptureSurfaceType} */ + this.captureSurface = captureSurface; + + /** @type {!mr.mirror.Settings} */ + this.mirrorSettings = mirrorSettings; + + /** @type {?string} */ + this.offscreenTabUrl = opt_offscreenTabUrl || null; + + /** @type {?string} */ + this.presentationId = opt_presentationId || null; + } + + /** + * @return {boolean} True if this is for tab capture + */ + isTab() { + return this.captureSurface == mr.mirror.CaptureSurfaceType.TAB; + } + + /** + * @return {boolean} True if this is for desktop capture + */ + isDesktop() { + return this.captureSurface == mr.mirror.CaptureSurfaceType.DESKTOP; + } + + /** + * @return {boolean} True if this is for offscreen tab capture + */ + isOffscreenTab() { + return this.captureSurface == mr.mirror.CaptureSurfaceType.OFFSCREEN_TAB; + } + + /** + * @param {string=} sourceId The source ID of the desktop media. + * @return {!MediaStreamConstraints|!Object} Media constraints for use with + * platform capture APIs. + */ + toMediaConstraints(sourceId = undefined) { + if (this.isTab()) { + return this.toTabMediaConstraints_(); + } else if (this.isOffscreenTab()) { + return this.toOffscreenTabMediaConstraints_(); + } else { + return this.toDesktopMediaConstraints_(sourceId); + } + } + + /** + * @return {!MediaConstraints} Media constraints for use with platform + * capture APIs. + * @private + */ + toTabMediaConstraints_() { + + // + // Also the tabCapture API shape doesn't match getUserMedia, wich combines + // audio and video constraints into a single dictionary. + // + // Both of these issues make it hard to type toMediaConstraints() properly. + const constraints = /** @type {!MediaConstraints} */ + ({ + 'audio': this.mirrorSettings.shouldCaptureAudio, + 'video': this.mirrorSettings.shouldCaptureVideo + }); + + if (this.mirrorSettings.shouldCaptureVideo) { + constraints['videoConstraints'] = { + 'mandatory': {'enableAutoThrottling': true} + }; + this.setCommonVideoConstraints_( + constraints['videoConstraints']['mandatory']); + } + return constraints; + } + + /** + * @return {!MediaConstraints} Media constraints for use with offscreen + * capture APIs. + * @private + */ + toOffscreenTabMediaConstraints_() { + mr.Assertions.assert( + this.presentationId, 'Missing offscreen capture presentation id'); + const constraints = this.toTabMediaConstraints_(); + constraints['presentationId'] = this.presentationId; + return constraints; + }; + + /** + * @param {string=} sourceId The source id of the desktop media. Only required + * if capturing video. + * @return {!MediaStreamConstraints} Media constraints for use with platform + * capture APIs. + * @private + */ + toDesktopMediaConstraints_(sourceId = undefined) { + const constraints = /** @type {!MediaStreamConstraints} */ + ({'audio': false, 'video': false}); + + if (this.mirrorSettings.shouldCaptureVideo) { + mr.Assertions.assert(sourceId); + constraints['video'] = { + 'mandatory': { + 'chromeMediaSource': 'desktop', + 'chromeMediaSourceId': sourceId, + } + }; + this.setCommonVideoConstraints_(constraints['video']['mandatory']); + + if (mr.mirror.Config.isDesktopAudioCaptureAvailable && + this.mirrorSettings.shouldCaptureAudio) { + // NOTE(mfoltz): Nothing in Chrome seems to consume the audio sourceId; + // however, continuing to pass it in case something changes in the + // future. + constraints['audio'] = { + 'mandatory': { + 'chromeMediaSource': 'system', + 'chromeMediaSourceId': sourceId, + } + }; + } + } else if (this.mirrorSettings.shouldCaptureAudio) { + mr.Assertions.assert(!sourceId); + mr.Assertions.assert(mr.mirror.Config.isDesktopAudioCaptureAvailable); + constraints['audio'] = { + 'mandatory': { + 'chromeMediaSource': 'system', + } + }; + } + + return constraints; + } + + /** + * Helper to populate common video constraints fields for both capture APIs. + * @param {!MediaStreamConstraints|!MediaConstraints} constraints + * @private + */ + setCommonVideoConstraints_(constraints) { + // If sender-side letterboxing is being used, pass altered min dimensions to + // the capture API which match the aspect ratio of the max dimensions. The + // capture API will interpret this to mean that it must perform + // letterboxing/pillarboxing. + let minWidth = this.mirrorSettings.minWidth; + let minHeight = this.mirrorSettings.minHeight; + if (this.mirrorSettings.senderSideLetterboxing) { + const altMin = this.mirrorSettings.getMinDimensionsToMatchAspectRatio(); + minWidth = altMin.width; + minHeight = altMin.height; + } + + Object.assign(constraints, { + 'minWidth': minWidth, + 'minHeight': minHeight, + 'maxWidth': this.mirrorSettings.maxWidth, + 'maxHeight': this.mirrorSettings.maxHeight, + 'minFrameRate': this.mirrorSettings.minFrameRate, + 'maxFrameRate': this.mirrorSettings.maxFrameRate, + }); + } +}; + + +/** + * Possible capture modes. + * @enum {string} + */ +mr.mirror.CaptureSurfaceType = { + TAB: 'tab', + DESKTOP: 'desktop', + OFFSCREEN_TAB: 'offscreen_tab' +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream.js new file mode 100644 index 00000000000..370b91fcb15 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream.js @@ -0,0 +1,340 @@ +// 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 Media stream for use with tab and desktop capture. + * + * A media stream has a video and/or audio track. Each capture source (i.e., + * tab) should use its own MediaStream object and call start() to initiate + * capture. + */ + +goog.provide('mr.mirror.MirrorMediaStream'); + +goog.require('mr.Assertions'); +goog.require('mr.Logger'); +goog.require('mr.MirrorAnalytics'); +goog.require('mr.PlatformUtils'); +goog.require('mr.mirror.Config'); +goog.require('mr.mirror.Error'); + +/** + * Constructs a new MediaStream that will capture media according to + * captureParams. + */ +mr.mirror.MirrorMediaStream = class { + /** + * @param {!mr.mirror.CaptureParameters} captureParams + */ + constructor(captureParams) { + /** @private {!mr.mirror.CaptureParameters} */ + this.captureParams_ = captureParams; + + /** @private {?function()} */ + this.onStreamEnded_ = null; + + /** @private {?MediaStream} */ + this.mediaStream_ = null; + + /** @private {mr.Logger} */ + this.logger_ = mr.Logger.getInstance('mr.mirror.MirrorMediaStream'); + } + + /** + * @param {?function()} onStreamEnded Invoked when the stream fires an + * onended event. + */ + setOnStreamEnded(onStreamEnded) { + this.onStreamEnded_ = onStreamEnded; + } + + /** + * @return {!mr.mirror.CaptureParameters} + */ + getCaptureParams() { + return this.captureParams_; + } + + /** + * @return {?MediaStream} + */ + getMediaStream() { + return this.mediaStream_; + } + + /** + * Starts capturing media and sets audioTrack and videoTrack. + * @return {!Promise<!mr.mirror.MirrorMediaStream>} Fulfilled when capture + * has started. + */ + start() { + if (this.captureParams_.isTab()) { + return this.startTabCapturing_(); + } else if (this.captureParams_.isOffscreenTab()) { + return this.startOffscreenTabCapturing_(); + } else { + return this.startDesktopCapturing_(); + } + } + + /** + * @return {!Promise<!mr.mirror.MirrorMediaStream>} Fulfilled when capture + * has started. + * @private + */ + startTabCapturing_() { + const constraints = this.captureParams_.toMediaConstraints(); + this.logger_.info( + 'Starting tab capture with constraints ' + JSON.stringify(constraints)); + + return new Promise((resolve, reject) => { + // Note: There's a subtle reason to NOT pass |resolve| as the + // callback function to the chrome.tabCapture.capture() call here. + // Because this is an extension API, when an error occurs, the value + // in chrome.runtime.lastError is only available during the call to + // the callback function. However, the Promise.then() method runs + // its function at a later time, when chrome.runtime.lastError is no + // longer set. + chrome.tabCapture.capture(constraints, stream => { + if (stream) { + this.setStream_(stream); + resolve(this); + } else { + reject(this.createTabCaptureError_()); + } + }); + + // Set a timer to reject the promise after a delay. + // + + window.setTimeout(() => { + // In normal usage, this will be a no-op because this promise will + // already have been resolved by the call to + // chrome.tabCapture.capture above. + reject(new mr.mirror.Error( + 'chrome.tabCapture.capture failed to call its callback', + mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_TIMEOUT)); + }, mr.mirror.MirrorMediaStream.TAB_CAPTURE_TIMEOUT_); + }); + } + + /** + * @param {?Event} event The ended event. + * @private + */ + handleTrackEnded_(event) { + if (event) { + this.logger_.info( + () => 'Track ' + JSON.stringify(event.target) + ' ended'); + } + this.stop(); + } + + /** + * Returns an Error corresponding to a chrome.tabCapture error. + * @return {!mr.mirror.Error} The corresponding error. + * @private + */ + createTabCaptureError_() { + // As of Chrome 51, when |stream| is null, chrome.runtime.lastError.message + // should always be set to a non-empty string. If it is not, fall back to + // the default error message so everyone can yell at miu@. + if (chrome.runtime.lastError && chrome.runtime.lastError.message) { + return new mr.mirror.Error( + chrome.runtime.lastError.message, + mr.MirrorAnalytics.CapturingFailure.TAB_FAIL); + } else { + return new mr.mirror.Error( + mr.mirror.MirrorMediaStream.EMPTY_STREAM_, + mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_FAIL_EMPTY_STREAM); + } + } + + /** + * Requests a screen capture source from the user via a native dialog and + * returns the source ID, or rejects if a timeout is reached or the user + * cancels. + * @param {number=} timeoutMillis The timeout in milliseconds. + * @return {!Promise<string>} Fulfilled with the source ID. + * @private + */ + requestScreenCaptureSourceId_( + timeoutMillis = mr.mirror.MirrorMediaStream.WINDOW_PICKER_TIMEOUT_) { + return new Promise((resolve, reject) => { + const desktopChooserConfig = ['screen', 'audio']; + if (mr.PlatformUtils.getCurrentOS() == mr.PlatformUtils.OS.LINUX) { + desktopChooserConfig.push('window'); + } + let requestId; + // Wait 60 seconds and then cancel the picker and reject the + // promise. + + const timeoutId = window.setTimeout(() => { + if (requestId) { + chrome.desktopCapture.cancelChooseDesktopMedia(requestId); + } + reject(new mr.mirror.Error( + 'timeout', + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_TIMEOUT)); + }, timeoutMillis); + // https://developer.chrome.com/extensions/desktopCapture#method-chooseDesktopMedia + requestId = chrome.desktopCapture.chooseDesktopMedia( + desktopChooserConfig, sourceId => { + window.clearTimeout(timeoutId); + if (!sourceId) { + // User cancelled the desktop media selector prompt. + reject(new mr.mirror.Error( + 'User cancelled capture dialog', + mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL)); + } else { + resolve(sourceId); + } + }); + }); + } + + /** + * Generates a screen capture MediaStream from the given + * MediaStreamConstraints. + * @param {!MediaStreamConstraints} constraints The constraints object to use. + * @return {!Promise<MediaStream>} Fulfilled with the MediaStream from + * capture. + * @private + */ + generateScreenCaptureStream_(constraints) { + return new Promise((resolve, reject) => { + this.logger_.info( + () => 'Starting desktop capture with constraints ' + + JSON.stringify(constraints)); + navigator.mediaDevices.getUserMedia(constraints) + .then( + stream => { + if (!stream) { + // NOTE(miu): This implies that getUserMedia is broken, and it + // may also be breaking chrome.tabCapture. + reject(new mr.mirror.Error( + mr.mirror.MirrorMediaStream.EMPTY_STREAM_, + mr.MirrorAnalytics.CapturingFailure.DESKTOP_FAIL)); + } + this.setStream_(stream); + resolve(stream); + }, + error => { + let errorReason = + mr.MirrorAnalytics.CapturingFailure.DESKTOP_FAIL; + // Certain errors indicate the user cancelled the request. + // https://www.w3.org/TR/mediacapture-streams/#methods-5 + if (error.name == 'NotAllowedError') { + errorReason = mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL; + } + reject(new mr.mirror.Error( + `${error.name} ${error.constraintName}: ${error.message}`, + errorReason)); + }); + }); + } + + /** + * @return {!Promise<!mr.mirror.MirrorMediaStream>} Fulfilled when capture + * has started. + * @private + */ + startDesktopCapturing_() { + if (mr.mirror.Config.isDesktopAudioCaptureAvailable && + this.captureParams_.mirrorSettings.shouldCaptureAudio && + !this.captureParams_.mirrorSettings.shouldCaptureVideo) { + return this + .generateScreenCaptureStream_( + this.captureParams_.toMediaConstraints()) + .then(_ => this); + } + + // Video capture requires asking the user to pick which screen to capture. + + return this.requestScreenCaptureSourceId_().then(sourceId => { + const constraints = this.captureParams_.toMediaConstraints(sourceId); + return this.generateScreenCaptureStream_(constraints).then(_ => this); + }); + } + + /** + * @return {!Promise<!mr.mirror.MirrorMediaStream>} Fulfilled when capture + * has started. + * @private + */ + startOffscreenTabCapturing_() { + mr.Assertions.assert(!!this.captureParams_.offscreenTabUrl); + const constraints = this.captureParams_.toMediaConstraints(); + this.logger_.info( + () => 'Starting offscreen tab capture with constraints ' + + JSON.stringify(constraints)); + return new Promise((resolve, reject) => { + chrome.tabCapture.captureOffscreenTab( + this.captureParams_.offscreenTabUrl.toString(), constraints, + stream => { + if (stream) { + this.setStream_(stream); + resolve(this); + } else { + reject(this.createTabCaptureError_()); + } + }); + }); + } + + /** + * @param {!MediaStream} stream + * @private + */ + setStream_(stream) { + this.mediaStream_ = stream; + mr.Assertions.assert( + stream.getAudioTracks().length || stream.getVideoTracks().length, + 'Expecting at least one audio or video track.'); + // For desktop capturing, users may stop capturing via desktop capturing's + // own stop button, which triggers onended event. + stream.getTracks().forEach(track => { + track.onended = this.handleTrackEnded_.bind(this); + }); + } + + /** + * Stops captured streams. + */ + stop() { + if (!this.mediaStream_) return; + this.mediaStream_.getTracks().forEach(track => { + track.onended = null; + track.stop(); + }); + this.mediaStream_ = null; + if (this.onStreamEnded_) { + this.onStreamEnded_(); + } + } +}; + + +/** + * The number of milliseconds to wait after for the browser to call the callback + * function after calling chrome.tabCapture.capture. + * @private @const {number} + */ +mr.mirror.MirrorMediaStream.TAB_CAPTURE_TIMEOUT_ = 5000; + + +/** + * @private @const {number} + */ +mr.mirror.MirrorMediaStream.WINDOW_PICKER_TIMEOUT_ = 60000; + + +/** + * Error messaging for reporting of empty streams. + * @private @const {string} + */ +mr.mirror.MirrorMediaStream.EMPTY_STREAM_ = 'empty_stream'; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream_test.js b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream_test.js new file mode 100644 index 00000000000..45746847799 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mirror_services/stream_capture/mirror_media_stream_test.js @@ -0,0 +1,407 @@ +// 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. + +goog.setTestOnly(); + +goog.require('mr.PlatformUtils'); +goog.require('mr.mirror.CaptureParameters'); +goog.require('mr.mirror.CaptureSurfaceType'); +goog.require('mr.mirror.Config'); +goog.require('mr.mirror.Error'); +goog.require('mr.mirror.MirrorMediaStream'); +goog.require('mr.mirror.Settings'); + +describe('mr.mirror.MirrorMediaStream', () => { + let captureParams; + let instance; + let mediaStream; + let mirrorSettings; + + beforeEach(() => { + chrome.runtime.lastError = null; + chrome.tabCapture = + jasmine.createSpyObj('tabCapture', ['capture', 'captureOffscreenTab']); + chrome.desktopCapture = jasmine.createSpyObj( + 'desktopCapture', ['chooseDesktopMedia', 'cancelChooseDesktopMedia']); + spyOn(navigator.mediaDevices, 'getUserMedia'); + spyOn(mr.PlatformUtils, 'getCurrentOS'); + + mediaStream = jasmine.createSpyObj( + 'mediaStream', ['getAudioTracks', 'getVideoTracks', 'getTracks']); + mirrorSettings = new mr.mirror.Settings(); + captureParams = new mr.mirror.CaptureParameters( + mr.mirror.CaptureSurfaceType.DESKTOP, mirrorSettings); + instance = new mr.mirror.MirrorMediaStream(captureParams); + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('accessor for capture params returns initial value', () => { + expect(instance.getCaptureParams()).toBe(captureParams); + }); + + describe('when capturing a tab', () => { + beforeEach(() => { + captureParams.captureSurface = mr.mirror.CaptureSurfaceType.TAB; + }); + + it('stores the stream upon successful capture', (done) => { + mediaStream.getVideoTracks.and.returnValue([{}]); + mediaStream.getAudioTracks.and.returnValue([{}]); + mediaStream.getTracks.and.returnValue([{}]); + chrome.tabCapture.capture.and.callFake((constraints, callback) => { + callback(mediaStream); + }); + + instance.start() + .then(() => { + expect(chrome.tabCapture.capture).toHaveBeenCalled(); + expect(instance.getMediaStream()).toBe(mediaStream); + done(); + }) + .catch(fail); + }); + + it('rejects with an error upon empty stream with error message', (done) => { + chrome.runtime.lastError = {message: 'expected-message'}; + chrome.tabCapture.capture.and.callFake((constraints, callback) => { + callback(); + }); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason).toBe(mr.MirrorAnalytics.CapturingFailure.TAB_FAIL); + expect(err.message).toBe('expected-message'); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an error upon empty stream with empty message', (done) => { + chrome.tabCapture.capture.and.callFake((constraints, callback) => { + callback(); + }); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure + .CAPTURE_TAB_FAIL_EMPTY_STREAM); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an error upon timeout', (done) => { + instance.start().catch((err) => { + expect(window.chrome.tabCapture.capture).toHaveBeenCalled(); + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure.CAPTURE_TAB_TIMEOUT); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + jasmine.clock().tick(5001); + }); + }); + + describe('when capturing an off-screen tab', () => { + beforeEach(() => { + captureParams.offscreenTabUrl = 'offscreen-tab-url'; + captureParams.presentationId = 'offscreen-tab-presentation-id'; + captureParams.captureSurface = mr.mirror.CaptureSurfaceType.OFFSCREEN_TAB; + }); + + it('stores the stream upon successful capture', (done) => { + mediaStream.getVideoTracks.and.returnValue([{}]); + mediaStream.getAudioTracks.and.returnValue([{}]); + mediaStream.getTracks.and.returnValue([{}]); + chrome.tabCapture.captureOffscreenTab.and.callFake( + (tabUrl, constraints, callback) => { + expect(tabUrl).toBe('offscreen-tab-url'); + callback(mediaStream); + }); + + instance.start() + .then(() => { + expect(window.chrome.tabCapture.captureOffscreenTab) + .toHaveBeenCalled(); + expect(instance.getMediaStream()).toBe(mediaStream); + done(); + }) + .catch(fail); + }); + + it('rejects with an error upon empty stream with error message', (done) => { + chrome.runtime.lastError = {message: 'expected-message'}; + chrome.tabCapture.captureOffscreenTab.and.callFake( + (tabUrl, constraints, callback) => callback()); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason).toBe(mr.MirrorAnalytics.CapturingFailure.TAB_FAIL); + expect(err.message).toBe('expected-message'); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an error upon empty stream with empty message', (done) => { + chrome.tabCapture.captureOffscreenTab.and.callFake( + (tabUrl, constraints, callback) => callback()); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure + .CAPTURE_TAB_FAIL_EMPTY_STREAM); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + }); + + describe('when capturing the desktop', () => { + const supportsAudioCapture = + mr.mirror.Config.isDesktopAudioCaptureAvailable; + + beforeEach(() => { + captureParams.captureSurface = mr.mirror.CaptureSurfaceType.DESKTOP; + }); + + afterEach(() => { + mr.mirror.Config.isDesktopAudioCaptureAvailable = supportsAudioCapture; + }); + + it('stores the audio and video streams upon successful capture', (done) => { + mediaStream.getVideoTracks.and.returnValue([{}]); + mediaStream.getAudioTracks.and.returnValue([{}]); + mediaStream.getTracks.and.returnValue([{}]); + + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + callback('source-id'); + }); + navigator.mediaDevices.getUserMedia.and.callFake( + constraints => Promise.resolve(mediaStream)); + + instance.start() + .then(() => { + expect(chrome.desktopCapture.chooseDesktopMedia).toHaveBeenCalled(); + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalled(); + expect(instance.getMediaStream()).toBe(mediaStream); + done(); + }) + .catch(fail); + }); + + it('stores the audio stream upon successful capture', (done) => { + mr.mirror.Config.isDesktopAudioCaptureAvailable = true; + const audioOnlyMirrorSettings = new mr.mirror.Settings(); + audioOnlyMirrorSettings.shouldCaptureVideo = false; + const audioOnlyCaptureParams = new mr.mirror.CaptureParameters( + mr.mirror.CaptureSurfaceType.DESKTOP, audioOnlyMirrorSettings); + const audioOnlyInstance = + new mr.mirror.MirrorMediaStream(audioOnlyCaptureParams); + + mediaStream.getVideoTracks.and.returnValue([{}]); + mediaStream.getAudioTracks.and.returnValue([{}]); + mediaStream.getTracks.and.returnValue([{}]); + + navigator.mediaDevices.getUserMedia.and.callFake( + constraints => Promise.resolve(mediaStream)); + + audioOnlyInstance.start() + .then(() => { + expect(chrome.desktopCapture.chooseDesktopMedia) + .not.toHaveBeenCalled(); + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalled(); + expect(audioOnlyInstance.getMediaStream()).toBe(mediaStream); + done(); + }) + .catch(fail); + }); + + it('allows choosing only screen, audio for non-linux platforms', (done) => { + mr.PlatformUtils.getCurrentOS.and.returnValue( + mr.PlatformUtils.OS.WINDOWS); + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + expect(config).toContain('screen'); + expect(config).toContain('audio'); + expect(config).not.toContain('window'); + done(); + }); + instance.start(); + }); + + it('allows choosing screen, audio, window for linux platforms', (done) => { + mr.PlatformUtils.getCurrentOS.and.returnValue(mr.PlatformUtils.OS.LINUX); + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + expect(config).toContain('screen'); + expect(config).toContain('audio'); + expect(config).toContain('window'); + done(); + }); + instance.start(); + }); + + it('rejects with an error upon timeout in desktop chooser', (done) => { + chrome.desktopCapture.chooseDesktopMedia.and.returnValue('expected-id'); + + instance.start().catch((err) => { + expect(chrome.desktopCapture.chooseDesktopMedia).toHaveBeenCalled(); + expect(chrome.desktopCapture.cancelChooseDesktopMedia) + .toHaveBeenCalledWith('expected-id'); + expect(navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled(); + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_TIMEOUT); + expect(err.message).toBe('timeout'); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + + jasmine.clock().tick(60001); + }); + + it('rejects with an error when user cancels desktop picker', (done) => { + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + callback(/* no source id */); + }); + + instance.start().catch((err) => { + expect(chrome.desktopCapture.chooseDesktopMedia).toHaveBeenCalled(); + expect(navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled(); + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL); + expect(err.message).toMatch(/cancelled/i); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an error upon getUserMedia error', (done) => { + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + callback('source-id'); + }); + + navigator.mediaDevices.getUserMedia.and.callFake( + constraints => Promise.reject( + new DOMException('expected-message', 'SecurityError'))); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure.DESKTOP_FAIL); + expect(err.message).toMatch(/\bSecurityError\b/); + expect(err.message).toMatch(/\bexpected-message\b/); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an cancelled error upon NotAllowedError', (done) => { + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + callback('source-id'); + }); + navigator.mediaDevices.getUserMedia.and.callFake( + constraints => Promise.reject( + new DOMException('expected-message', 'NotAllowedError'))); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure + .CAPTURE_DESKTOP_FAIL_ERROR_USER_CANCEL); + expect(err.message).toMatch(/\bNotAllowedError\b/); + expect(err.message).toMatch(/\bexpected-message\b/); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + + it('rejects with an error upon empty stream object', (done) => { + chrome.desktopCapture.chooseDesktopMedia.and.callFake( + (config, callback) => { + callback('source-id'); + }); + navigator.mediaDevices.getUserMedia.and.callFake( + constraints => Promise.resolve(null)); + + instance.start().catch((err) => { + expect(err instanceof mr.mirror.Error).toBe(true); + expect(err.reason) + .toBe(mr.MirrorAnalytics.CapturingFailure.DESKTOP_FAIL); + expect(instance.getMediaStream()).toBe(null); + done(); + }); + }); + }); + + describe('when stopping a stream', () => { + let startPromise; + let track; + + beforeEach(() => { + track = jasmine.createSpyObj('track', ['stop']); + mediaStream.getVideoTracks.and.returnValue([{}]); + mediaStream.getAudioTracks.and.returnValue([{}]); + mediaStream.getTracks.and.returnValue([track]); + captureParams.captureSurface = mr.mirror.CaptureSurfaceType.TAB; + chrome.tabCapture.capture.and.callFake((constraints, callback) => { + callback(mediaStream); + }); + + startPromise = instance.start(); + }); + + it('calls stop() on the tracks', (done) => { + startPromise + .then(() => { + instance.stop(); + expect(track.stop).toHaveBeenCalled(); + expect(track.onended).toBe(null); + expect(instance.getMediaStream()).toBe(null); + done(); + }) + .catch(fail); + }); + + it('automatically stops when track ends', (done) => { + startPromise + .then(() => { + track.onended(); + expect(track.stop).toHaveBeenCalled(); + expect(track.onended).toBe(null); + expect(instance.getMediaStream()).toBe(null); + done(); + }) + .catch(fail); + }); + + it('calls the onStreamEnded callback if it exists', (done) => { + const onStreamEndedSpy = jasmine.createSpy('onStreamEnded'); + instance.setOnStreamEnded(onStreamEndedSpy); + + startPromise + .then(() => { + instance.stop(); + expect(onStreamEndedSpy).toHaveBeenCalled(); + done(); + }) + .catch(fail); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/module.js b/chromium/chrome/browser/resources/media_router/extension/src/module.js new file mode 100644 index 00000000000..248c6cb6097 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/module.js @@ -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. + +/** + * @fileoverview Contains definition for modules and the module loader. + * The Media Router extension is logically separated into modules. Each module + * and their corresponding bundle path are registered at startup + * time. When a module is required, they will be loaded on-demand. + */ + +goog.provide('mr.Module'); +goog.provide('mr.ModuleId'); + +goog.require('mr.Logger'); +goog.require('mr.PromiseResolver'); + + +/** + * Identifier for each module. + * @enum {string} + */ +mr.ModuleId = { + CAST_CHANNEL_SERVICE: 'mr.cast.ChannelService', + CAST_SINK_DISCOVERY_SERVICE: 'mr.cast.SinkDiscoveryService', + CAST_STREAMING_SERVICE: 'mr.mirror.cast.Service', + SLARTI_SINK_DISCOVERY_SERVICE: 'mr.cloud.slarti.SinkDiscoveryService', + WEAVE_SINK_DISCOVERY_SERVICE: 'mr.cloud.discovery.WeaveSinkDiscoveryService', + HANGOUTS_SERVICE: 'mr.mirror.hangouts.HangoutsService', + MEETINGS_SERVICE: 'mr.mirror.hangouts.MeetingsService', + PROVIDER_MANAGER: 'mr.ProviderManager', + WEBRTC_STREAMING_SERVICE: 'mr.mirror.webrtc.WebRtcService' +}; + + +/** + * Identifier for bundles. A bundle is a js file containing a collection of + * modules. + + * @enum {string} + */ +mr.Bundle = { + MAIN: 'background_script.js', + MIRRORING_CAST_STREAMING: 'mirroring_cast_streaming.js', + MIRRORING_HANGOUTS: 'mirroring_hangouts.js', + MIRRORING_WEBRTC: 'mirroring_webrtc.js' +}; + + +/** + * Base class for a module. When a module is loaded and initialized, it should + * call mr.Module.onModuleLoaded to inform its dependencies that it is ready. + */ +mr.Module = class { + /** + * Returns the module with the given ID if it is already initialized, null + * otherwise. + * @param {mr.ModuleId} moduleId + * @return {?mr.Module} + */ + static get(moduleId) { + return mr.Module.moduleById_.get(moduleId) || null; + } + + /** + * Loads the module with the given ID. If the module is already loaded, the + * Promise is resolved immediately. + * @param {mr.ModuleId} moduleId + * @return {!Promise<!mr.Module>} Resolved with the module when + * it is loaded. + */ + static load(moduleId) { + const module = mr.Module.get(moduleId); + if (module) { + return Promise.resolve(module); + } + let resolver = mr.Module.resolverByModuleId_.get(moduleId); + if (!resolver) { + resolver = new mr.PromiseResolver(); + mr.Module.resolverByModuleId_.set(moduleId, resolver); + mr.Module.loadBundleForModule_(moduleId, resolver); + } + + return resolver.promise; + } + + /** + * Loads the bundle corresponding to the given module. Called the first time + * a module is requested. + * @param {mr.ModuleId} moduleId + * @param {!mr.PromiseResolver<!mr.Module>} resolver Rejected if the bundle + * associated with the module won't be loaded due to permanent error. + * @private + */ + static loadBundleForModule_(moduleId, resolver) { + const bundle = mr.Module.getBundle_(moduleId); + if (!bundle) { + resolver.reject(new Error(`No corresponding bundle for ${moduleId}`)); + return; + } + + if (mr.Module.DEFAULT_LOAD_BUNDLES_.has(bundle)) { + return; + } + + // Check if the bundle has been not requested previously. + let bundlePromise = mr.Module.bundlePromises_.get(bundle); + if (!bundlePromise) { + + mr.Module.logger_.info(`Loading bundle ${bundle} for module ${moduleId}`); + bundlePromise = mr.Module.doLoadBundle_(bundle); + mr.Module.bundlePromises_.set(bundle, bundlePromise); + } + + // Add an error handler to reject the module load request. + bundlePromise.catch(e => { + resolver.reject(e); + }); + } + + /** + * Returns the bundle associated with a module. + * @param {mr.ModuleId} moduleId + * @return {?mr.Bundle} + * @private + */ + static getBundle_(moduleId) { + return mr.Module.MODULE_TO_BUNDLE_MAPPING_.get(moduleId) || null; + } + + /** + * Called when a bundle needs to be loaded. + * @param {mr.Bundle} bundle Name of bundle to load. + * @return {!Promise<void>} Resolves when the bundle is loaded or rejected if + * it failed to load. + * @private + */ + static doLoadBundle_(bundle) { + let resolver = new mr.PromiseResolver(); + resolver.promise.then( + () => { + mr.Module.logger_.info(`Bundle ${bundle} loaded`); + }, + e => { + mr.Module.logger_.error(`Failed to load bundle ${bundle}`); + throw e; + }); + let script = document.createElement('script'); + script.src = chrome.extension.getURL(bundle); + script.setAttribute('type', 'text/javascript'); + script.async = true; + + script.onload = () => resolver.resolve(undefined); + script.onerror = () => + resolver.reject(new Error(`Failed to load bundle ${bundle}`)); + document.head.appendChild(script); + return resolver.promise; + } + + /** + * Called by a module when it is done loading and initializing. Registers the + * module and resolves the outstanding promise returned by |load(moduleId)|. + * @param {mr.ModuleId} moduleId Identifier of the module. No two modules can + * have the same identifier. + * @param {!mr.Module} module The module that is ready. + */ + static onModuleLoaded(moduleId, module) { + if (mr.Module.moduleById_.has(moduleId)) { + throw new Error('Duplicate module ' + moduleId); + } + mr.Module.moduleById_.set(moduleId, module); + const resolver = mr.Module.resolverByModuleId_.get(moduleId); + if (resolver) { + resolver.resolve(module); + } + } + + /** + * Used for testing only. + */ + static clearForTest() { + mr.Module.moduleById_.clear(); + mr.Module.resolverByModuleId_.clear(); + mr.Module.bundlePromises_.clear(); + } + + /** + * Subclasses should override this if a mr.EventListener designated this + * module to forward the events to. + * @param {*} event The event delivered to the handler. It is the handler's + * responsibility to verify that it can handle the event. + * @param {...*} args Arguments for the event. + */ + handleEvent(event, ...args) { + throw new Error('Not implemented'); + } +}; + + +/** + * Maps a module ID to a bundle ID. Used for loading the bundle that contains + * a required module. + * @private @const {!Map<mr.ModuleId, mr.Bundle>} + */ +mr.Module.MODULE_TO_BUNDLE_MAPPING_ = new Map([ + [mr.ModuleId.CAST_CHANNEL_SERVICE, mr.Bundle.MAIN], + [mr.ModuleId.CAST_SINK_DISCOVERY_SERVICE, mr.Bundle.MAIN], + [mr.ModuleId.CAST_STREAMING_SERVICE, mr.Bundle.MIRRORING_CAST_STREAMING], + [mr.ModuleId.SLARTI_SINK_DISCOVERY_SERVICE, mr.Bundle.MAIN], + [mr.ModuleId.WEAVE_SINK_DISCOVERY_SERVICE, mr.Bundle.MAIN], + [mr.ModuleId.HANGOUTS_SERVICE, mr.Bundle.MIRRORING_HANGOUTS], + [mr.ModuleId.MEETINGS_SERVICE, mr.Bundle.MIRRORING_HANGOUTS], + [mr.ModuleId.PROVIDER_MANAGER, mr.Bundle.MAIN], + [mr.ModuleId.WEBRTC_STREAMING_SERVICE, mr.Bundle.MIRRORING_WEBRTC] +]); + + +/** + * Set of bundles that are loaded by default. + * @private @const {!Set<mr.Bundle>} + */ +mr.Module.DEFAULT_LOAD_BUNDLES_ = new Set([mr.Bundle.MAIN]); + + +/** @private {mr.Logger} */ +mr.Module.logger_ = mr.Logger.getInstance('mr.Module'); + +/** + * The set of modules currently loaded and initialized in the extension, keyed + * by their IDs. + * @private {!Map<mr.ModuleId, !mr.Module>} + */ +mr.Module.moduleById_ = new Map(); + + +/** + * Holds the outstanding promise while a module is being loaded. + * @private {!Map<mr.ModuleId, !mr.PromiseResolver<!mr.Module>>} + */ +mr.Module.resolverByModuleId_ = new Map(); + + +/** + * Holds the outstanding promise while a bundle is being loaded. + * @private {!Map<mr.Bundle, !Promise<void>>} + */ +mr.Module.bundlePromises_ = new Map(); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/module_test.js b/chromium/chrome/browser/resources/media_router/extension/src/module_test.js new file mode 100644 index 00000000000..1d2fed8c118 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/module_test.js @@ -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. + +goog.setTestOnly('module_test'); + +goog.require('mr.Module'); +goog.require('mr.PromiseUtils'); + + + +describe('Tests modules', function() { + let mockModule; + + beforeEach(function() { + mockModule = {'id': 1}; + }); + + afterEach(function() { + mr.Module.clearForTest(); + }); + + it('gets a module before and after it is loaded', function() { + let module = mr.Module.get('SomeModule'); + expect(module).toBeNull(); + + const mockModule2 = {'id': 2}; + mr.Module.onModuleLoaded('AnotherModule', mockModule2); + + module = mr.Module.get('SomeModule'); + expect(module).toBeNull(); + + mr.Module.onModuleLoaded('SomeModule', mockModule); + module = mr.Module.get('SomeModule'); + expect(module).toBe(mockModule); + + module = mr.Module.get('AnotherModule'); + expect(module).toBe(mockModule2); + }); + + it('loads a module which loads a bundle', function(done) { + spyOn(mr.Module, 'getBundle_').and.returnValue('SomeBundle'); + spyOn(mr.Module, 'doLoadBundle_').and.returnValue(Promise.resolve()); + + const promise = mr.Module.load('SomeModule'); + const promise2 = mr.Module.load('SomeModule'); + + expect(mr.Module.getBundle_).toHaveBeenCalledWith('SomeModule'); + expect(mr.Module.doLoadBundle_).toHaveBeenCalledWith('SomeBundle'); + expect(mr.Module.doLoadBundle_.calls.count()).toBe(1); + + mr.Module.onModuleLoaded('SomeModule', mockModule); + + const promise3 = mr.Module.load('SomeModule'); + Promise.all([promise, promise2, promise3]).then(modules => { + for (let module of modules) { + expect(module).toBe(mockModule); + } + done(); + }); + }); + + it('load rejects if failed to load a bundle', function(done) { + spyOn(mr.Module, 'getBundle_').and.returnValue('SomeBundle'); + spyOn(mr.Module, 'doLoadBundle_') + .and.returnValue(Promise.reject(new Error('failed to load bundle'))); + + const promise = mr.Module.load('SomeModule'); + const promise2 = mr.Module.load('SomeModule'); + + expect(mr.Module.getBundle_).toHaveBeenCalledWith('SomeModule'); + expect(mr.Module.doLoadBundle_).toHaveBeenCalledWith('SomeBundle'); + expect(mr.Module.doLoadBundle_.calls.count()).toBe(1); + + const promise3 = mr.Module.load('SomeModule'); + mr.PromiseUtils.allSettled([promise, promise2, promise3]).then(results => { + for (let result of results) { + expect(result.fulfilled).toBe(false); + } + done(); + }); + }); + + it('each module is mapped to a bundle', () => { + for (const key in mr.ModuleId) { + expect(mr.Module.getBundle_(mr.ModuleId[key])) + .not.toBeNull(`${mr.ModuleId[key]} does not map to a bundle`); + } + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/mojo_externs.js b/chromium/chrome/browser/resources/media_router/extension/src/mojo_externs.js new file mode 100644 index 00000000000..93f4805ccde --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/mojo_externs.js @@ -0,0 +1,522 @@ +// 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 Closure definitions of Mojo service objects. Note that these + * definitions are bound only after chrome.mojoPrivate.requireAsync resolves + * in mojo.js. + */ + + +var mojo = {}; + + + +// Core Mojo. + +/** + * @param {!Object} interfaceType + * @param {!Object} impl + * @param {mojo.InterfaceRequest=} request + * @constructor + */ +mojo.Binding = function(interfaceType, impl, request) {}; + + +/** + * Closes the message pipe. The bound object will no longer receive messages. + */ +mojo.Binding.prototype.close = function() {}; + + +/** + * Binds to the given request. + * @param {!mojo.InterfaceRequest} request + */ +mojo.Binding.prototype.bind = function(request) {}; + + +/** @param {function()} callback */ +mojo.Binding.prototype.setConnectionErrorHandler = function(callback) {}; + + +/** + * Creates an interface ptr and bind it to this instance. + * @return {?} The interface ptr. + */ +mojo.Binding.prototype.createInterfacePtrAndBind = function() {}; + + + +/** @constructor */ +mojo.InterfaceRequest = function() {}; + + +/** + * Closes the message pipe. The object can no longer be bound to an + * implementation. + */ +mojo.InterfaceRequest.prototype.close = function() {}; + + + +/** @constructor */ +mojo.InterfacePtrController = function() {}; + + +/** + * Closes the message pipe. Messages can no longer be sent with this object. + */ +mojo.InterfacePtrController.prototype.reset = function() {}; + + +/** @param {function()} callback */ +mojo.InterfacePtrController.prototype.setConnectionErrorHandler = function( + callback) {}; + + + +/** + * @param {!Object} interfacePtr + */ +mojo.makeRequest = function(interfacePtr) {}; + + +// Mojom structs. + +/** + * @constructor + * @struct + */ +mojo.IPAddress = function() {}; + + + +/** @type {!Array<number>} */ +mojo.IPAddress.prototype.address; + + +/** @type {!Array<number>} */ +mojo.IPAddress.prototype.address_bytes; + + + +/** + * @constructor + * @struct + */ +mojo.IPEndPoint = function() {}; + + +/** @type {mojo.IPAddress} */ +mojo.IPEndPoint.prototype.address; + + +/** @type {number} */ +mojo.IPEndPoint.prototype.port; + + + +/** @constructor */ +mojo.Url = function() {}; + + +/** @type {string} */ +mojo.Url.prototype.url; + + + +/** + * @constructor + * @struct + */ +mojo.DialMediaSink = function() {}; + + +/** @type {mojo.IPAddress} */ +mojo.DialMediaSink.prototype.ip_address; + + +/** @type {string} */ +mojo.DialMediaSink.prototype.model_name; + + +/** @type {mojo.Url} */ +mojo.DialMediaSink.prototype.app_url; + + + +/** + * @constructor + * @struct + */ +mojo.CastMediaSink = function() {}; + + + +/** @type {mojo.IPAddress} */ +mojo.CastMediaSink.prototype.ip_address; + + +/** @type {mojo.IPEndPoint} */ +mojo.CastMediaSink.prototype.ip_endpoint; + + +/** @type {string} */ +mojo.CastMediaSink.prototype.model_name; + + +/** @type {number} */ +mojo.CastMediaSink.prototype.capabilities; + + +/** @type {number} */ +mojo.CastMediaSink.prototype.cast_channel_id; + + + +/** + * @constructor + * @struct + */ +mojo.SinkExtraData = function() {}; + + +/** @type {mojo.DialMediaSink} */ +mojo.SinkExtraData.prototype.dial_media_sink; + + +/** @type {mojo.CastMediaSink} */ +mojo.SinkExtraData.prototype.cast_media_sink; + + + +/** + * @constructor + * @struct + */ +mojo.Sink = function() {}; + + +/** @constructor */ +mojo.SinkIconType = function() {}; +mojo.SinkIconType.CAST = 0; +mojo.SinkIconType.CAST_AUDIO_GROUP = 1; +mojo.SinkIconType.CAST_AUDIO = 2; +mojo.SinkIconType.MEETING = 3; +mojo.SinkIconType.HANGOUT = 4; +mojo.SinkIconType.EDUCATION = 5; +mojo.SinkIconType.GENERIC = 6; + + +/** @type {string} */ +mojo.Sink.prototype.sink_id; + + +/** @type {string} */ +mojo.Sink.prototype.name; + + +/** @type {?string} */ +mojo.Sink.prototype.description; + + +/** @type {?string} */ +mojo.Sink.prototype.domain; + + +/** @type {?mojo.SinkIconType} */ +mojo.Sink.prototype.icon_type; + + +/** @type {?mojo.SinkExtraData} */ +mojo.Sink.prototype.extra_data; + + + +/** + * @param {!Object} values An object mapping from property names to values. + * @constructor + * @struct + */ +mojo.TimeDelta = function(values) {}; + + +/** @type {number} */ +mojo.TimeDelta.prototype.microseconds; + + + +/** + * @param {!Object} values An object mapping from property names to values. + * @constructor + * @struct + */ +mojo.MediaStatus = function(values) {}; + + +/** @constructor */ +mojo.MediaStatus.PlayState = function() {}; +/** @type {!mojo.MediaStatus.PlayState} */ +mojo.MediaStatus.PlayState.PLAYING; +/** @type {!mojo.MediaStatus.PlayState} */ +mojo.MediaStatus.PlayState.PAUSED; +/** @type {!mojo.MediaStatus.PlayState} */ +mojo.MediaStatus.PlayState.BUFFERING; + + +/** @type {?string} */ +mojo.MediaStatus.prototype.title; + + +/** @type {?string} */ +mojo.MediaStatus.prototype.description; + + +/** @type {boolean} */ +mojo.MediaStatus.prototype.can_play_pause; + + +/** @type {boolean} */ +mojo.MediaStatus.prototype.can_mute; + + +/** @type {boolean} */ +mojo.MediaStatus.prototype.can_set_volume; + + +/** @type {boolean} */ +mojo.MediaStatus.prototype.can_seek; + + +/** @type {?mojo.MediaStatus.PlayState} */ +mojo.MediaStatus.prototype.play_state; + + +/** @type {boolean} */ +mojo.MediaStatus.prototype.is_muted; + + +/** @type {number} */ +mojo.MediaStatus.prototype.volume; + + +/** @type {?mojo.TimeDelta} */ +mojo.MediaStatus.prototype.duration; + + +/** @type {?mojo.TimeDelta} */ +mojo.MediaStatus.prototype.current_time; + + +/** @type {mojo.HangoutsMediaStatusExtraData} */ +mojo.MediaStatus.prototype.hangouts_extra_data; + + + +/** + * @param {!Object} values + * @constructor + * @struct + */ +mojo.HangoutsMediaStatusExtraData = function(values) {}; + + +/** @type {boolean} */ +mojo.HangoutsMediaStatusExtraData.prototype.local_present; + + + +/** + * @param {!Object} values An object mapping from property names to values. + * @constructor + * @struct + */ +mojo.Origin = function(values) {}; + + +/** @type {string} */ +mojo.Origin.prototype.scheme; + + +/** @type {string} */ +mojo.Origin.prototype.host; + + +/** @type {number} */ +mojo.Origin.prototype.port; + + +/** @type {string} */ +mojo.Origin.prototype.suborigin; + + +/** @type {boolean} */ +mojo.Origin.prototype.unique; + + + +/** @constructor */ +mojo.RouteControllerType = function() {}; +/** @type {mojo.RouteControllerType} */ +mojo.RouteControllerType.kNone; +/** @type {mojo.RouteControllerType} */ +mojo.RouteControllerType.kGeneric; +/** @type {mojo.RouteControllerType} */ +mojo.RouteControllerType.kHangouts; +/** @type {mojo.RouteControllerType} */ +mojo.RouteControllerType.kMirroring; + + +// Mojom interfaces. + +/** + * @constructor + * @struct + */ +mojo.MediaStatusObserverPtr = function() {}; + + +/** @param {!mojo.MediaStatus} status */ +mojo.MediaStatusObserverPtr.prototype.onMediaStatusUpdated = function(status) { +}; + + +/** @type {!mojo.InterfacePtrController} */ +mojo.MediaStatusObserverPtr.prototype.ptr; + + + +/** @type {!Object} */ +mojo.MediaController; + + +/** @type {!Object} */ +mojo.HangoutsMediaRouteController; + + +/** @constructor */ +mojo.MediaRouteProviderConfig = function() {}; + + +/** @type {boolean} */ +mojo.MediaRouteProviderConfig.prototype.enable_dial_discovery; + + +/** @type {boolean} */ +mojo.MediaRouteProviderConfig.prototype.enable_dial_sink_query; + + +/** @type {boolean} */ +mojo.MediaRouteProviderConfig.prototype.enable_cast_discovery; + + + +/** @constructor */ +mojo.RemotingStopReason = function() {}; +/** @type {!mojo.RemotingStopReason} */ +mojo.RemotingStopReason.ROUTE_TERMINATED; +/** @type {!mojo.RemotingStopReason} */ +mojo.RemotingStopReason.USER_DISABLED; + + + +/** @constructor */ +mojo.RemotingSinkFeature = function() {}; + + + +/** @constructor */ +mojo.RemotingSinkAudioCapability = function() {}; +/** @type {!mojo.RemotingSinkAudioCapability} */ +mojo.RemotingSinkAudioCapability.CODEC_BASELINE_SET; +/** @type {!mojo.RemotingSinkAudioCapability} */ +mojo.RemotingSinkAudioCapability.CODEC_AAC; +/** @type {!mojo.RemotingSinkAudioCapability} */ +mojo.RemotingSinkAudioCapability.CODEC_OPUS; + + + +/** @constructor */ +mojo.RemotingSinkVideoCapability = function() {}; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.SUPPORT_4K; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.CODEC_BASELINE_SET; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.CODEC_H264; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.CODEC_VP8; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.CODEC_VP9; +/** @type {!mojo.RemotingSinkVideoCapability} */ +mojo.RemotingSinkVideoCapability.CODEC_HEVC; + + + +/** + * @constructor + * @struct + */ +mojo.RemotingSinkMetadata = function() {}; + + +/** @type {!Array<mojo.RemotingSinkFeature>} */ +mojo.RemotingSinkMetadata.prototype.features; + + +/** @type {!Array<mojo.RemotingSinkAudioCapability>} */ +mojo.RemotingSinkMetadata.prototype.audio_capabilities; + + +/** @type {!Array<mojo.RemotingSinkVideoCapability>} */ +mojo.RemotingSinkMetadata.prototype.video_capabilities; + + +/** @type {!string} */ +mojo.RemotingSinkMetadata.prototype.friendly_name; + + + +/** @type {!Object} */ +mojo.MirrorServiceRemoter; + + + +/** + * @constructor + */ +mojo.MirrorServiceRemoterPtr = function() {}; + + +/** @type {!mojo.InterfacePtrController} */ +mojo.MirrorServiceRemoterPtr.prototype.ptr; + + + +/** + * @constructor + */ +mojo.MirrorServiceRemotingSourcePtr = function() {}; + + +/** @param {!mojo.RemotingSinkMetadata} capabilities */ +mojo.MirrorServiceRemotingSourcePtr.prototype.onSinkAvailable = function( + capabilities) {}; + +/** @param {!Uint8Array} message */ +mojo.MirrorServiceRemotingSourcePtr.prototype.onMessageFromSink = function( + message) {}; + + +/** @param {!mojo.RemotingStopReason} reason */ +mojo.MirrorServiceRemotingSourcePtr.prototype.onStopped = function(reason) {}; + + +/** Notifies the remoting source when streaming error occurs. */ +mojo.MirrorServiceRemotingSourcePtr.prototype.onError = function() {}; + + +/** @type {!mojo.InterfacePtrController} */ +mojo.MirrorServiceRemotingSourcePtr.prototype.ptr; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/persistent_data.js b/chromium/chrome/browser/resources/media_router/extension/src/persistent_data.js new file mode 100644 index 00000000000..a7490f357e6 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/persistent_data.js @@ -0,0 +1,463 @@ +// 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 API for classes to save data and cleanup before the event page + * is shut down. + * + * Temporary data is removed once the extension version is changed, and thus + * objects should never write anything to temporary data that needs to survive + * an extension update. + * + * Persistent data is retained across extension versions and browser restarts; + * it can be removed when the user clears local storage from their profile. Do + * not store any sensitive or per-route data persistently. + */ + +goog.provide('mr.PersistentData'); +goog.provide('mr.PersistentDataManager'); +goog.require('mr.Logger'); + + + +/** + * @interface + */ +mr.PersistentData = class {}; + + +/** + * Alias for localStorage to deal with broken Storage interface. + * @const @private {!Object} + */ +mr.PersistentData.storageObj_ = /** @type {!Object} */ (window.localStorage); + + +/** + * Get the unique name of the object instance that has data to be saved. + * The name is used to isolate data from different objects. + * @return {string} + */ +mr.PersistentData.prototype.getStorageKey; + + +/** + * Invoked by persistent data manager when an object registers itself with the + * manager. The object should load its saved data in this method. + */ +mr.PersistentData.prototype.loadSavedData; + + +/** + * Implements this method to cleanup and return data that needs to be saved + * before the event page is shut down. The method should normally return a one- + * or two-element array of temporary and optional persistent data to save. + * + * Temporary data is cleared on browser restart or extension version change. + * Persistent data is retained until local storage is cleared by the browser + * profile. + * + * Any Objects must be serializable with JSON.stringify. + * + * The implementation of getData should not make any asynchronous + * calls; otherwise, there is no guarantee that asynchronous calls can finish. + * + * @return {!Array<!Object>} A one- or two-element array of data that needs to + * be saved. The first element is temporary data and the second element is + * persistent data. Return an empty array if there is no data to save. If + * only persistent data needs to be persisted, pass in undefined for the + * first element in the two-element array. + */ +mr.PersistentData.prototype.getData; + + +/** + * The total number of characters that can be stored in localStorage, + * approximately. + * @const {number} + */ +mr.PersistentDataManager.QUOTA_CHARS = 5200000; + + +/** + * The total number of characters used by persistent data. Note that writes that + * access localStorage directly may not be counted here. + + * @private {number} + */ +mr.PersistentDataManager.charsUsed_ = 0; + + +/** + * @param {!mr.PersistentData} obj The object that may have temporary data. + * @return {T} The data saved before. Null if no data is saved. + * @template T + */ +mr.PersistentDataManager.getTemporaryData = function(obj) { + const data = window.localStorage.getItem( + mr.PersistentDataManager.getStorageKey_(obj, false)); + return data ? JSON.parse(data) : null; +}; + + +/** + * @param {!mr.PersistentData} obj The object that may have peristent data. + * @return {T} The data saved before. Null if no data is saved. + * @template T + */ +mr.PersistentDataManager.getPersistentData = function(obj) { + const data = window.localStorage.getItem( + mr.PersistentDataManager.getStorageKey_(obj, true)); + return data ? JSON.parse(data) : null; +}; + + +/** + * Registers an object so that it gets informed about onSuspend events. + * + * @param {!mr.PersistentData} obj An object that has data to save. + */ +mr.PersistentDataManager.register = function(obj) { + if (mr.PersistentDataManager.dataInstances_.has(obj.getStorageKey())) { + throw Error('Duplicate instance name ' + obj.getStorageKey()); + } + mr.PersistentDataManager.dataInstances_.set(obj.getStorageKey(), obj); + obj.loadSavedData(); +}; + + +/** + * Un-Registers an object from being informed of onSuspend/Resume events. + * + * @param {!mr.PersistentData} obj An object to remove from tracking. + */ +mr.PersistentDataManager.unregister = function(obj) { + mr.PersistentDataManager.dataInstances_.delete(obj.getStorageKey()); +}; + + +/** + * @param {string} mrInstanceId The media router instance ID, which stays the + * same till Chrome restarts. + */ +mr.PersistentDataManager.initialize = function(mrInstanceId) { + let otherChars = 0; + for (let key of Object.keys(mr.PersistentData.storageObj_)) { + const itemSize = key.length + window.localStorage.getItem(key).length; + if (key.startsWith(mr.PersistentDataManager.KEY_PREFIX_)) { + mr.PersistentDataManager.charsUsed_ += itemSize; + } else { + otherChars += itemSize; + } + } + mr.PersistentDataManager.mrInstanceId_ = mrInstanceId; + if (mr.PersistentDataManager.isVersionChanged_() || + mr.PersistentDataManager.isChromeReloaded(mrInstanceId)) { + mr.PersistentDataManager.removeTemporary_(); + } + mr.PersistentDataManager.logger_.info( + 'initialize: ' + mr.PersistentDataManager.charsUsed_ + ' chars used, ' + + otherChars + ' other chars'); + chrome.runtime.onSuspend.addListener(mr.PersistentDataManager.onSuspend_); +}; + + +/** + * @private {?string} + */ +mr.PersistentDataManager.mrInstanceId_ = null; + + +/** + * @const {mr.Logger} + * @private + */ +mr.PersistentDataManager.logger_ = + mr.Logger.getInstance('mr.PersistentDataManager'); + + +/** + * A map from object's instance name to the object. + * @private @const {!Map<string, !mr.PersistentData>} + */ +mr.PersistentDataManager.dataInstances_ = new Map(); + + +/** @private @const {string} */ +mr.PersistentDataManager.KEY_PREFIX_ = 'mr.'; + + +/** + * @param {!mr.PersistentData} obj + * @param {boolean} persistent + * @return {string} + * @private + */ +mr.PersistentDataManager.getStorageKey_ = function(obj, persistent) { + return mr.PersistentDataManager.KEY_PREFIX_ + + (persistent ? 'persistent.' : 'temp.') + obj.getStorageKey(); +}; + + +/** + * Checks if the extension version has changed. + * @return {boolean} True if current extension has a different version as + * the saved version. + * @private + */ +mr.PersistentDataManager.isVersionChanged_ = function() { + return !!window.localStorage.getItem('version') && + window.localStorage.getItem('version') !== + chrome.runtime.getManifest().version; +}; + + +/** + * Checks if the chrome has been reloaded since the last time the extension is + * loaded. + * @param {string} mrInstanceId The media router instance ID, which stays the + * same till Chrome restarts. + * @return {boolean} True if Chrome was reloaded. + */ +mr.PersistentDataManager.isChromeReloaded = function(mrInstanceId) { + return !!window.localStorage.getItem('mrInstanceId') && + window.localStorage.getItem('mrInstanceId') !== mrInstanceId; +}; + + +/** + * Handles onSuspend event. + * @private + */ +mr.PersistentDataManager.onSuspend_ = function() { + mr.PersistentDataManager.logger_.info('onSuspend'); + + mr.PersistentDataManager.write( + 'version', chrome.runtime.getManifest().version); + if (mr.PersistentDataManager.mrInstanceId_) { + mr.PersistentDataManager.write( + 'mrInstanceId', mr.PersistentDataManager.mrInstanceId_); + } + const logManager = mr.PersistentDataManager.dataInstances_.get('LogManager'); + for (const [key, obj] of mr.PersistentDataManager.dataInstances_) { + if (obj != logManager) { + mr.PersistentDataManager.saveData( + /** @type {!mr.PersistentData} */ (obj)); + } + } + // Save the data for LogManager last, so that we save the logs generated + // during saveData() calls. + if (logManager) { + mr.PersistentDataManager.saveData(logManager); + } +}; + + +/** + * Save PersistentData object to local storage. + * @param {!mr.PersistentData} obj + */ +mr.PersistentDataManager.saveData = function(obj) { + try { + const data = obj.getData(); + if (data && data[0] != undefined) { + mr.PersistentDataManager.write( + mr.PersistentDataManager.getStorageKey_(obj, false), + JSON.stringify(data[0])); + } + if (data && data[1] != undefined) { + mr.PersistentDataManager.write( + mr.PersistentDataManager.getStorageKey_(obj, true), + JSON.stringify(data[1])); + } + } catch (e) { + mr.PersistentDataManager.logger_.error( + `Error while saving data for ${obj.getStorageKey()}: ${e.message}`); + } +}; + + +/** + * Writes value to localStorage under key. If the value is too large to fit + * into the remaining localStorage quota, temporary data is first removed. If + * the value still won't fit, an exception is thrown. + + * @param {string} key The localStorage key. + * @param {string} value The value to write. + */ +mr.PersistentDataManager.write = function(key, value) { + const dm = mr.PersistentDataManager; + let sizeDelta = 0; + const currentValue = window.localStorage.getItem(key); + if (currentValue != null) { + sizeDelta = value.length - currentValue.length; + } else { + sizeDelta = key.length + value.length; + } + + if (dm.charsUsed_ + sizeDelta > dm.QUOTA_CHARS) { + mr.PersistentDataManager.logger_.warning( + 'Unable to write ' + sizeDelta + ' chars'); + dm.removeTemporary_(); + } + + if (dm.charsUsed_ + sizeDelta > dm.QUOTA_CHARS) { + dm.logger_.error( + 'Unable to write ' + sizeDelta + ' chars after clearing temporary'); + throw Error( + `Setting the value of '${key}' would exceed the quota, ` + + 'according to accounting.'); + } + + try { + window.localStorage.setItem(key, value); + } catch (error) { + throw Error( + `Setting the value of '${key}' would exceed the quota, ` + + 'according to the browser.'); + } + // Adjusting dm.charsUsed_ only after the call to setItem() has succeeded. + dm.charsUsed_ += sizeDelta; +}; + + +/** + * Writes a Blob to localStorage under the given key, making space-efficient use + * of localStorage by encoding two of the Blob's bytes into each DOMString + * character. Use readBlob() to read the value back. + * @param {string} key The localStorage key. + * @param {!Blob} value The value to write. + * @return {!Promise<void>} Resolves once the Blob has been written; or rejects + * if it won't fit. + */ +mr.PersistentDataManager.writeBlob = function(key, value) { + // The byte size needs to be a multiple of two, since each string character + // code is a 16-bit unsigned integer. Thus, append padding byte(s) to the end + // of the Blob. These will also be used to indicate whether the original Blob + // was of even or odd length when reading back later. + if (value.size % 2 == 0) { + value = new Blob([value, new Uint8Array([0, 0])]); + } else { + value = new Blob([value, new Uint8Array([1])]); + } + + return new Promise((resolve, reject) => { + // Use FileReader to gain access to the Blob content via an ArrayBuffer. + const reader = new FileReader(); + reader.onloadend = () => { + if (reader.error) { + reject(reader.error); + return; + } + + try { + const buffer = /** @type {!ArrayBuffer} */ (reader.result); + + // Convert the buffer bytes into a string, storing two bytes in each of + // the string's characters for space efficiency. This is done in batches + // to avoid smashing the call stack when calling String.fromCharCode(). + const batchSize = 8192; + const pieces = []; + for (let pos = 0, end = buffer.byteLength; pos < end; + pos += batchSize) { + // Note: The byteLength will always be an even number since all input + // values to the following expression must be even numbers: + const byteLengthOfChunk = Math.min(end - pos, batchSize); + pieces.push(String.fromCharCode.apply( + null, new Uint16Array(buffer, pos, byteLengthOfChunk / 2))); + } + + // Finally, join the pieces into a single string and attempt to store + // the string using the quota management heuristics in write(). + mr.PersistentDataManager.write(key, pieces.join('')); + + resolve(); + } catch (error) { + reject(error); + } + }; + reader.readAsArrayBuffer(value); + }); +}; + + +/** + * Reads a Blob from localStorage under the given key. Returns null if it does + * not exist. + * @param {string} key The localStorage key. + * @param {Object=} blobOptions The options for the reconstituted Blob (e.g., + * {'type': 'application/gzip'}). + * @return {?Blob} + */ +mr.PersistentDataManager.readBlob = function(key, blobOptions) { + const asString = window.localStorage.getItem(key); + if (asString == null || asString.length < 1) { + return null; + } + const charCodes = new Uint16Array(asString.length); + for (let i = 0; i < asString.length; ++i) { + charCodes[i] = asString.charCodeAt(i); + } + // Determine, from the last byte value, whether the original Blob was of even + // or odd length (see writeBlob()). Create a Blob from a view of all but the + // padding byte(s) at the end of the buffer. + const buffer = charCodes.buffer; + const flagByte = (new Uint8Array(buffer, buffer.byteLength - 1, 1))[0]; + const viewOfPayload = + new Uint8Array(buffer, 0, buffer.byteLength - ((flagByte == 0) ? 2 : 1)); + return new Blob([viewOfPayload], blobOptions); +}; + + +/** + * Removes temporary data. + * @private + */ +mr.PersistentDataManager.removeTemporary_ = function() { + for (let key of Object.keys(mr.PersistentData.storageObj_)) { + if (key.startsWith(mr.PersistentDataManager.KEY_PREFIX_ + 'temp.')) { + mr.PersistentDataManager.charsUsed_ -= + (key.length + window.localStorage.getItem(key).length); + delete window.localStorage[key]; + } + } + mr.PersistentDataManager.logger_.info( + 'removeTemporary_: ' + mr.PersistentDataManager.charsUsed_ + + ' chars used'); +}; + + +/** + * Removes all data. + * @private + */ +mr.PersistentDataManager.removeAll_ = function() { + for (let key of Object.keys(mr.PersistentData.storageObj_)) { + if (key.startsWith(mr.PersistentDataManager.KEY_PREFIX_)) + window.localStorage.removeItem(key); + } + mr.PersistentDataManager.charsUsed_ = 0; +}; + + +/** + * Clears internal state and remove all saved data. + * Utility method for unit test. + + */ +mr.PersistentDataManager.clear = function() { + mr.PersistentDataManager.removeAll_(); + mr.PersistentDataManager.dataInstances_.clear(); +}; + + +/** + * Simulates suspend. + * Utility method for unit test. + + */ +mr.PersistentDataManager.suspendForTest = function() { + mr.PersistentDataManager.onSuspend_(); + mr.PersistentDataManager.dataInstances_.clear(); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/persistent_data_test.js b/chromium/chrome/browser/resources/media_router/extension/src/persistent_data_test.js new file mode 100644 index 00000000000..6708e9fdb4f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/persistent_data_test.js @@ -0,0 +1,361 @@ +// 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. + +goog.setTestOnly('persistent_data_test'); + +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); +goog.require('mr.UnitTestUtils'); + + +/** + * @implements {mr.PersistentData} + * @private + */ +DummyData_ = class { + /** + * @param {string} id + */ + constructor(id) { + /** @private {string} */ + this.id_ = id; + } + + /** + * @override + */ + getData() { + return []; + } + + /** + * @override + */ + getStorageKey() { + return 'dummy-data' + this.id_; + } + + /** + * @override + */ + loadSavedData() {} +}; + + +describe('Tests PersistentDataManager', () => { + let dataInstance1; + let dataInstance2; + let onSuspendListener; + let version; + let mrInstanceId; + const originalQuota = mr.PersistentDataManager.QUOTA_CHARS; + + beforeEach(() => { + window.localStorage.clear(); + mr.PersistentDataManager.charsUsed_ = 0; + dataInstance1 = new DummyData_('1'); + dataInstance2 = new DummyData_('2'); + version = '1.0'; + mrInstanceId = '123'; + mr.UnitTestUtils.mockChromeApi(); + chrome.runtime.onSuspend = { + addListener: l => { + onSuspendListener = l; + } + }; + chrome.runtime.getManifest = () => ({'version': version}); + mr.PersistentDataManager.initialize(mrInstanceId); + dataInstance1.loadSavedData = jasmine.createSpy('loadSavedData'); + dataInstance2.loadSavedData = jasmine.createSpy('loadSavedData'); + mr.PersistentDataManager.register(dataInstance1); + mr.PersistentDataManager.register(dataInstance2); + expect(dataInstance1.loadSavedData).toHaveBeenCalled(); + expect(dataInstance2.loadSavedData).toHaveBeenCalled(); + }); + + afterEach(() => { + mr.PersistentDataManager.clear(); + mr.PersistentDataManager.QUOTA_CHARS = originalQuota; + mr.UnitTestUtils.restoreChromeApi(); + }); + + it('returns null with no saved data', () => { + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toBe(null); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)).toBe(null); + }); + + describe('handles onSuspend', () => { + it('with one instance with data', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + dataInstance2.getData = () => null; + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toEqual({ + 'd': 1 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)) + .toEqual({'e': 1}); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)) + .toBe(null); + expect(mr.PersistentDataManager.charsUsed_).toBe(83); + }); + + it('with two instances with data', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + dataInstance2.getData = () => [{'d': 2}, {'e': 2}]; + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toEqual({ + 'd': 1 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)) + .toEqual({'e': 1}); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)).toEqual({ + 'd': 2 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance2)) + .toEqual({'e': 2}); + expect(mr.PersistentDataManager.charsUsed_).toBe(141); + }); + + it('with no instance with data', () => { + dataInstance1.getData = () => null; + dataInstance2.getData = () => null; + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)) + .toBe(null); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)) + .toBe(null); + expect(mr.PersistentDataManager.charsUsed_).toBe(25); + }); + + it('with an instance with a circular dependency', () => { + const data1 = {}; + data1.data = data1; + dataInstance1.getData = () => [{'d': data1}, {'e': data1}]; + dataInstance2.getData = () => [{'d': 2}, {'e': 2}]; + onSuspendListener(); + + // If there is a circular dependency of objects, that data cannot be + // converted into JSON or saved. However other data should still be saved. + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)).toEqual({ + 'd': 2 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance2)) + .toEqual({'e': 2}); + }); + }); + + it('Test saveData', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + dataInstance2.getData = () => [{'d': 2}, {'e': 2}]; + const dataInstance3 = new DummyData_('3'); + dataInstance3.getData = () => [false, {'e': 3}]; + const dataInstance4 = new DummyData_('4'); + dataInstance4.getData = () => [undefined, 0]; + + // Note that dataInstance2 is not saved. + mr.PersistentDataManager.saveData(dataInstance1); + mr.PersistentDataManager.saveData(dataInstance3); + mr.PersistentDataManager.saveData(dataInstance4); + + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toEqual({ + 'd': 1 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)).toEqual({ + 'e': 1 + }); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance2)).toBeNull(); + expect(mr.PersistentDataManager.getPersistentData(dataInstance2)) + .toBeNull(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance3)) + .toEqual(false); + expect(mr.PersistentDataManager.getPersistentData(dataInstance3)).toEqual({ + 'e': 3 + }); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance4)).toBeNull(); + expect(mr.PersistentDataManager.getPersistentData(dataInstance4)) + .toEqual(0); + }); + + it('Test unregister', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + dataInstance2.getData = () => [{'d': 2}, {'e': 2}]; + const dataInstance3 = new DummyData_('3'); + dataInstance3.getData = () => [{'d': 3}, {'e': 3}]; + mr.PersistentDataManager.register(dataInstance3); + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance3)).toEqual({ + 'd': 3 + }); + mr.PersistentDataManager.unregister(dataInstance3); + // Get data should not be called again. + dataInstance3.getData = () => { + fail(); + }; + onSuspendListener(); + }); + + it('handles version change', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toEqual({ + 'd': 1 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)).toEqual({ + 'e': 1 + }); + version = '1.1'; + expect(mr.PersistentDataManager.isChromeReloaded(mrInstanceId)).toBe(false); + mr.PersistentDataManager.initialize(mrInstanceId); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toBe(null); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)).toEqual({ + 'e': 1 + }); + }); + + it('handles mrInstanceId change', () => { + dataInstance1.getData = () => [{'d': 1}, {'e': 1}]; + onSuspendListener(); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toEqual({ + 'd': 1 + }); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)).toEqual({ + 'e': 1 + }); + mrInstanceId = '321'; + expect(mr.PersistentDataManager.isChromeReloaded(mrInstanceId)).toBe(true); + mr.PersistentDataManager.initialize(mrInstanceId); + expect(mr.PersistentDataManager.getTemporaryData(dataInstance1)).toBe(null); + expect(mr.PersistentDataManager.getPersistentData(dataInstance1)).toEqual({ + 'e': 1 + }); + }); + + describe('allows writes', () => { + it('of small values', () => { + mr.PersistentDataManager.write('mr.temp.Buckaroo', 'Bonzai'); + expect(window.localStorage.getItem('mr.temp.Buckaroo')).toBe('Bonzai'); + expect(mr.PersistentDataManager.charsUsed_).toBe(22); + }); + + it('of large values over quota', () => { + mr.PersistentDataManager.QUOTA_CHARS = 100; + [1, 2, 3, 4].forEach(index => { + mr.PersistentDataManager.write( + 'mr.temp.' + index, 'Only the dead have seen the end of war.'); + }); + expect(mr.PersistentDataManager.charsUsed_).toBe(96); + // Normally this would go over QUOTA_CHARS, but clearing temporary + // values allows it to succeed. + mr.PersistentDataManager.write( + 'mr.persistent.5', 'Courage is knowing what not to fear.'); + expect(mr.PersistentDataManager.charsUsed_).toBe(51); + [1, 2, 3, 4].forEach(index => { + expect(window.localStorage.getItem('mr.temp.' + index)).toBeNull(); + }); + expect(window.localStorage.getItem('mr.persistent.5')) + .toBe('Courage is knowing what not to fear.'); + }); + }); + + describe('stores and reads Blobs', () => { + const dm = mr.PersistentDataManager; + const testKey = 'test'; + + const allUint16Values = []; + for (let i = 0; i < (1 << 16); ++i) { + allUint16Values.push(i); + } + + // Takes an array of byte values, creates a Blob, stores and retrieves it + // back, and then maps the bytes of the Blob back to an array of byte + // values. + function writeThenReadByteValues(values) { + return new Promise((resolve, reject) => { + dm.writeBlob(testKey, new Blob([new Uint8Array(values)])).then(() => { + const reader = new FileReader(); + reader.onloadend = () => { + if (reader.error) { + reject(reader.error); + } else { + resolve(Array.from(new Uint8Array(reader.result))); + } + }; + reader.readAsArrayBuffer(dm.readBlob(testKey)); + }, reject); + }); + } + + // Same as writeThenReadByteValues(), except operate on an array of uint16. + function writeThenReadShortValues(values) { + return new Promise((resolve, reject) => { + dm.writeBlob(testKey, new Blob([new Uint16Array(values)])).then(() => { + const reader = new FileReader(); + reader.onloadend = () => { + if (reader.error) { + reject(reader.error); + } else { + resolve(Array.from(new Uint16Array(reader.result))); + } + }; + reader.readAsArrayBuffer(dm.readBlob(testKey)); + }, reject); + }); + } + + it('of zero size', done => { + writeThenReadByteValues([]).then(values => { + expect(values).toEqual([]); + done(); + }, fail); + }); + + it('of even size', done => { + writeThenReadByteValues([42, 1]).then(values => { + expect(values).toEqual([42, 1]); + done(); + }, fail); + }); + + it('of odd size', done => { + writeThenReadByteValues([42, 1, 0]).then(values => { + expect(values).toEqual([42, 1, 0]); + done(); + }, fail); + }); + + it('containing all possible uint16 values', done => { + const original = allUint16Values.concat(allUint16Values.reverse()); + writeThenReadShortValues(original).then(values => { + expect(values).toEqual(original); + done(); + }, fail); + }); + + it('that are just within quota', done => { + // Note on quota space needed: The string length of the key plus value + // must be available. A blob of 7 uint16 values will have a byte size of + // 14. The impl will add two padding bytes to the end, making the total 16 + // bytes. Thus, 16/2 = 8 chars of quota must be available for the value. + // In total, 4 + 8 (key + value) chars must be available. + mr.PersistentDataManager.QUOTA_CHARS = + mr.PersistentDataManager.charsUsed_ + testKey.length + 8; + const original = allUint16Values.slice(0, 7); + writeThenReadShortValues(original).then(values => { + expect(values).toEqual(original); + done(); + }, fail); + }); + + it('that exceed quota', done => { + // See note above on how quota accounting works. + mr.PersistentDataManager.QUOTA_CHARS = + mr.PersistentDataManager.charsUsed_ + testKey.length + 8; + const original = allUint16Values.slice(0, 8); + writeThenReadShortValues(original).then(fail, error => { + expect(dm.readBlob(testKey)).toBeNull(); + done(); + }); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/presentation.js b/chromium/chrome/browser/resources/media_router/extension/src/presentation.js new file mode 100644 index 00000000000..07ed021276b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/presentation.js @@ -0,0 +1,191 @@ +// 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 Presentation API. + * @externs + * @see http://w3c.github.io/presentation-api + * @see https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/presentation/ + */ + + + +/** + * @interface + * @see http://w3c.github.io/presentation-api/#idl-def-presentationconnection + */ +function PresentationConnection() {} + + +/** + * @type {string} + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-id + */ +PresentationConnection.prototype.id; + + +/** + * @type {string} + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-url + */ +PresentationConnection.prototype.url; + + +/** + * @type {string} + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-state + */ +PresentationConnection.prototype.state; + + +/** + * @type {?function(!MessageEvent)} + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-onmessage + */ +PresentationConnection.prototype.onmessage; + + +/** + * @type {?function(!PresentationConnectionCloseEvent)} + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-onclose + */ +PresentationConnection.prototype.onclose; + + +/** + * @type {?function()} + * @see https://www.w3.org/TR/presentation-api/#dom-presentationconnection-onterminate + */ +PresentationConnection.prototype.onterminate; + + +/** + * @param {string} message + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-send + */ +PresentationConnection.prototype.send = function(message) {}; + +/** + * @see https://w3c.github.io/presentation-api/#dom-presentationconnection-terminate + */ +PresentationConnection.prototype.terminate = function() {}; + +/** + * @see http://w3c.github.io/presentation-api/#dom-presentationconnection-close + */ +PresentationConnection.prototype.close = function() {}; + + + +/** + * @constructor + * @extends {Event} + */ +function PresentationConnectionCloseEvent() {} + + +/** @type {string} */ +PresentationConnectionCloseEvent.prototype.reason; + + +/** @type {string} */ +PresentationConnectionCloseEvent.prototype.message; + + + +/** + * @constructor + * @extends {Event} + * @see http://w3c.github.io/presentation-api/#presentationavailability + */ +function PresentationAvailability() {} + + +/** @type {boolean} */ +PresentationAvailability.prototype.value; + + +/** @type {?function()} */ +PresentationAvailability.prototype.onchange; + + + +/** + * @constructor + * @extends {Event} + * @see http://w3c.github.io/presentation-api/#availablechangeevent + */ +function AvailableChangeEvent() {} + + +/** + * @type {boolean} + */ +AvailableChangeEvent.prototype.available; + + + +/** + * @constructor + * @extends {Event} + */ +function PresentationConnectionAvailableEvent() {} + + +/** + * @type {!PresentationConnection} + */ +PresentationConnectionAvailableEvent.prototype.connection; + + + +/** + * @param {string|!Array<string>} url + * @constructor + */ +function PresentationRequest(url) {} + + +/** + * @return {!Promise<!PresentationConnection>} + */ +PresentationRequest.prototype.start = function() {}; + + +/** + * @param {string} presentationId + * @return {!Promise<!PresentationConnection>} + */ +PresentationRequest.prototype.reconnect = function(presentationId) {}; + + +/** + * @return {!Promise<!PresentationAvailability>} + */ +PresentationRequest.prototype.getAvailability = function() {}; + + +/** + * @type {?function(!PresentationConnectionAvailableEvent)} + */ +PresentationRequest.prototype.onconnectionavailable; + + + +/** + * @interface + */ +function Presentation() {} + + +/** + * @type {PresentationRequest} + */ +Presentation.prototype.defaultRequest; + + +/** + * @type {!Presentation} + */ +Navigator.prototype.presentation; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/cloud_webrtc/webrtc_presentation_session.js b/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/cloud_webrtc/webrtc_presentation_session.js new file mode 100644 index 00000000000..907ac9b5aab --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/cloud_webrtc/webrtc_presentation_session.js @@ -0,0 +1,192 @@ +// 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 Implementation of PresentationSession that uses the WebRTC + * PeerConnection. + */ +goog.provide('mr.presentation.webrtc.CloudWebRtcSession'); + +goog.require('mr.MessagePortService'); +goog.require('mr.PromiseResolver'); +goog.require('mr.presentation.Session'); +goog.require('mr.webrtc.Message'); +goog.require('mr.webrtc.MessageType'); +goog.require('mr.webrtc.OfferMessageData'); +goog.require('mr.webrtc.PeerConnection'); + +goog.scope(function() { + + + + +/** + * Constructs a new WebRTC presentation session. + * @implements {mr.presentation.Session} + */ +mr.presentation.webrtc.CloudWebRtcSession = class { + /** + * @param {!mr.Route} route + * @param {!string} sourceUrn + */ + constructor(route, sourceUrn) { + /** @type {!mr.Route} */ + this.route = route; + + /** @type {!string} */ + this.sourceUrn = sourceUrn; + + /** @private {!mr.PromiseResolver<!mr.webrtc.PeerConnection>} */ + this.peerConnectionResolver_ = new mr.PromiseResolver(); + + /** @private {!Promise<!mr.webrtc.PeerConnection>} */ + this.peerConnection_ = this.peerConnectionResolver_.promise; + + /** @private {!mr.MessagePort} */ + this.messagePort_ = + mr.MessagePortService.getService().getInternalMessenger(route.id); + + /** @private {!mr.PromiseResolver<!mr.Route>} */ + this.startResolver_ = new mr.PromiseResolver(); + + /** @private {boolean} */ + this.started_ = false; + + this.setUpMessagePort_(); + this.setUpPeerConnection_(); + + // Send a request for TURN credentials, then expect a response message with + // type TURN_CREDENTIALS. + this.sendMessageToMrp_( + new mr.webrtc.Message(mr.webrtc.MessageType.GET_TURN_CREDENTIALS)); + } + + /** + * @override + */ + start() { + return this.peerConnection_.then(pc => { + if (pc.isStarted()) { + return Promise.reject(Error('Presentation already started')); + } + + pc.start(); + return this.startResolver_.promise; + }); + } + + /** + * @override + */ + stop() { + this.started_ = false; + return this.peerConnection_.then(pc => { + pc.stop(); + this.peerConnection_ = + Promise.reject(Error('Peer connection has already been stopped')); + }); + } + + /** + * Sets up the message port to receive incoming messages. + * @private + */ + setUpMessagePort_() { + this.messagePort_.onMessage = message => { + if (!message.type) { + // Wrap message and send it along as a presentation message. + this.peerConnection_.then(pc => { + pc.sendDataChannelMessage({ + type: mr.webrtc.MessageType.PRESENTATION_CONNECTION_MESSAGE, + data: message + }); + }); + return; + } + switch (message.type) { + case mr.webrtc.MessageType.TURN_CREDENTIALS: + // Response to a GET_TURN_CREDENTIALS message. This causes the + // PeerConnection to be created. + this.peerConnectionResolver_.resolve(new mr.webrtc.PeerConnection( + this.route.id, + /** @type {!Array<!mr.webrtc.TurnCredential>} */ + (message.data['credentials']))); + break; + case mr.webrtc.MessageType.ANSWER: + this.peerConnection_.then(pc => { + pc.setRemoteDescription(message.data); + }); + break; + case mr.webrtc.MessageType.STOP: + this.startResolver_.reject('Stop signal received'); + this.stop(); + break; + default: + throw Error('Unknown message type: ' + message.type); + } + }; + } + + /** + * Sets up the peer connection (with callbacks). + * @private + */ + setUpPeerConnection_() { + this.peerConnection_.then(pc => { + // Pass the description up the MessagePort. + pc.setOnOfferDescriptionReady(description => { + const offerData = new mr.webrtc.OfferMessageData( + description, + /* opt_settings_ */ null, + /* opt_mediaConstraints */ null, this.sourceUrn, this.route.id); + const message = + new mr.webrtc.Message(mr.webrtc.MessageType.OFFER, offerData); + this.sendMessageToMrp_(message); + }); + // Pass along the data channel message up the MessagePort. + pc.setOnDataChannelMessage(message => { + // Check if message is a STOP message and calls stop() before sending it + // through the message port to MRP. + const webRtcMessage = mr.webrtc.Message.fromString(message); + if (webRtcMessage.type == mr.webrtc.MessageType.STOP) { + this.stop(); + } + this.sendMessageToMrp_(webRtcMessage); + }); + + // Send the connection success/closed/failed events up the MessagePort, + // and + // also resolve or reject the start() promise. + pc.setOnConnectionSuccess(event => { + this.started_ = true; + this.sendMessageToMrp_( + new mr.webrtc.Message(mr.webrtc.MessageType.SESSION_START_SUCCESS)); + this.startResolver_.resolve(this.route); + }); + pc.setOnConnectionClosed(event => { + this.sendMessageToMrp_( + new mr.webrtc.Message(mr.webrtc.MessageType.SESSION_END)); + }); + pc.setOnConnectionFailure(error => { + // If we haven't started yet, reject the start promise. + if (!this.started_) { + this.startResolver_.reject(error); + } + this.sendMessageToMrp_( + new mr.webrtc.Message(mr.webrtc.MessageType.SESSION_FAILURE)); + }); + }); + } + + /** + * Sends the provided message to the MRP via the message port. + * @param {!Object} message + * @private + */ + sendMessageToMrp_(message) { + this.messagePort_.sendMessage(message); + } +}; + +}); // goog.scope diff --git a/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/presentation_session.js b/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/presentation_session.js new file mode 100644 index 00000000000..02b2ad5253b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/presentation_services/presentation_session.js @@ -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. + +/** + * @fileoverview Interface to a presentation session. + */ + +goog.provide('mr.presentation.Session'); + + + +/** + * Creates a new PresentationSession. + * @record + */ +mr.presentation.Session = class { + /** + * Starts the presentation session. + * @return {!Promise<!mr.Route>} Fulfilled when the + * session has been created and transports have started. + */ + start() {} + /** + * Stops the presentation session. The underlying streams and transports are + * stopped and destroyed. + * + * @return {!Promise} Promise that resolves when the session is stopped. + */ + stop() {} +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator.js new file mode 100644 index 00000000000..91096c736f8 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator.js @@ -0,0 +1,80 @@ +// 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 An ID generator that keeps its state across extension + * suspend/wakeup cycles. + * + + */ + +goog.provide('mr.IdGenerator'); + +goog.require('mr.PersistentData'); +goog.require('mr.PersistentDataManager'); + + +/** + * @implements {mr.PersistentData} + */ +mr.IdGenerator = class { + /** + * @param {!string} storageKey + */ + constructor(storageKey) { + /** @private @const {!string} */ + this.storageKey_ = storageKey; + + /** @private {!number} */ + this.nextId_ = Math.floor(Math.random() * 1e6) * 1000; + } + + /** + * Enables persistent + */ + enablePersistent() { + mr.PersistentDataManager.register(this); + } + + /** + * @return {!number} The next ID. 0 will never be returned. + */ + getNext() { + let nextId = this.nextId_++; + if (nextId == 0) { + // Skip 0, which is used by Cast receiver to + // indicate that the broadcast status message is not coming from a + // specific + // sender (it is an autonomous status change, not triggered by a command + // from any sender). Strange usage of 0 though; could be a null / optional + // field. + nextId = this.nextId_++; + } + return nextId; + } + + /** + * @override + */ + getStorageKey() { + return 'IdGenerator.' + this.storageKey_; + } + + /** + * @override + */ + getData() { + return [this.nextId_]; + } + + /** + * @override + */ + loadSavedData() { + const savedData = mr.PersistentDataManager.getTemporaryData(this); + if (savedData) { + this.nextId_ = savedData; + } + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator_test.js new file mode 100644 index 00000000000..1e9e37a220d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/id_generator_test.js @@ -0,0 +1,36 @@ +// 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. + +goog.require('mr.IdGenerator'); +goog.require('mr.PersistentDataManager'); + +describe('IdGenerator Tests', function() { + let generator; + + beforeEach(function() { + spyOn(Math, 'random').and.returnValue(0); + generator = new mr.IdGenerator('test'); + generator.enablePersistent(); + }); + + afterEach(function() { + mr.PersistentDataManager.clear(); + }); + + it('getNext', function() { + expect(generator.getNext()).toBe(1); + expect(generator.getNext()).toBe(2); + expect(generator.getNext()).toBe(3); + }); + + it('Persistent', function() { + expect(generator.getNext()).toBe(1); + expect(generator.getNext()).toBe(2); + mr.PersistentDataManager.suspendForTest(); + generator = new mr.IdGenerator('test'); + generator.loadSavedData(); + expect(generator.getNext()).toBe(3); + }); + +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils.js new file mode 100644 index 00000000000..fe52d1d5ce2 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils.js @@ -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. + +/** + * @fileoverview Utilites for handling network data. + + */ + +goog.module('mr.NetUtils'); +goog.module.declareLegacyNamespace(); + + + +/** @const @private {!RegExp} */ +exports.IPV4_REGEXP_ = + new RegExp('^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$'); + +/** @const @private {!Array<number>} */ +exports.IPV4_PRIVATE_ADDRESS_MASKS_ = [ + // 10.0.0.0/8 + 4278190080, + // 172.16.0.0/12 + 4293918720, + // 192.168.0.0/16 + 4294901760 +]; + +/** @const @private {!Array<number>} */ +exports.IPV4_PRIVATE_ADDRESS_SUBNETS_ = [ + // 10.0.0.0/8 + 167772160, + // 172.16.0.0/12 + 2886729728, + // 192.168.0.0/16 + 3232235520 +]; + +/** + * Parses an IPv4 dotted-quad address into an array of four numbers, or + * returns null if the argument cannot be parsed. + * + * @param {string} ipAddress + * @return {?Array<number>} Array of four integers 0-255 or null. + */ +exports.parseIPv4Address = function(ipAddress) { + const matches = ipAddress.match(exports.IPV4_REGEXP_); + if (!matches || matches.length != 5) return null; + const result = []; + for (let i = 0; i < 4; i++) { + result[i] = Number.parseInt(matches[i + 1], 10); + if (result[i] < 0 || result[i] > 255) return null; + } + return result; +}; + +/** + * Returns true if ipAddress can be parsed into a valid IPv4 private network + * address. + * + * @param {string} ipAddress + * @return {boolean} True if ipAddress is a valid IPv4 private network address. + */ +exports.isPrivateIPv4Address = function(ipAddress) { + const parsedAddress = exports.parseIPv4Address(ipAddress); + if (!parsedAddress) return false; + // >>> 0 converts a signed integer to unsigned, so bitwise operations are + // sensible. + const addressValue = (parsedAddress[0] << 24 | parsedAddress[1] << 16 | + parsedAddress[2] << 8 | parsedAddress[0]) >>> + 0; + for (let i = 0; i < 3; i++) { + if ((addressValue & exports.IPV4_PRIVATE_ADDRESS_MASKS_[i]) >>> 0 == + exports.IPV4_PRIVATE_ADDRESS_SUBNETS_[i]) + return true; + } + return false; +}; + +/** + * @param {string} url A url. + * @return {!HTMLAnchorElement} The result of parsing url. + */ +exports.parseUrl = function(url) { + const a = document.createElement('a'); + a.href = url; + return /** @type {!HTMLAnchorElement} */ (a); +}; + +/** + * Non-exhaustive list of HTTP status codes. Add new codes as needed here. + * @enum {number} + */ +const HttpStatus = { + NOT_FOUND: 404, +}; + +exports.HttpStatus = HttpStatus; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils_test.js new file mode 100644 index 00000000000..4143201990b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/net_utils_test.js @@ -0,0 +1,61 @@ +// 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 Unit tests for mr.NetUtils. + + */ + +goog.module('mr.NetUtilsTest'); +goog.setTestOnly('mr.NetUtilsTest'); + +const n = goog.require('mr.NetUtils'); + +describe('mr.NetUtils', () => { + + it('parses a valid IPv4 address', () => { + expect(n.parseIPv4Address('128.164.100.104')).toEqual([128, 164, 100, 104]); + expect(n.parseIPv4Address('0.0.0.0')).toEqual([0, 0, 0, 0]); + expect(n.parseIPv4Address('255.255.255.255')).toEqual([255, 255, 255, 255]); + }); + + it('does not parse an invalid IPv4 address', () => { + expect(n.parseIPv4Address('')).toBeNull(); + expect(n.parseIPv4Address('deadbeef')).toBeNull(); + expect(n.parseIPv4Address('128.164.100')).toBeNull(); + expect(n.parseIPv4Address('128.164.100.104.333')).toBeNull(); + expect(n.parseIPv4Address('256.164.100.104')).toBeNull(); + expect(n.parseIPv4Address('-1.164.100.104')).toBeNull(); + }); + + it('validates an IPv4 private network address', () => { + expect(n.isPrivateIPv4Address('10.0.0.0')).toBe(true); + expect(n.isPrivateIPv4Address('10.255.255.255')).toBe(true); + expect(n.isPrivateIPv4Address('172.16.0.0')).toBe(true); + expect(n.isPrivateIPv4Address('172.31.255.255')).toBe(true); + expect(n.isPrivateIPv4Address('192.168.0.0')).toBe(true); + expect(n.isPrivateIPv4Address('192.168.255.255')).toBe(true); + }); + + it('does not validate an IPv4 public network address', () => { + expect(n.isPrivateIPv4Address('9.255.255.255')).toBe(false); + expect(n.isPrivateIPv4Address('11.0.0.0')).toBe(false); + expect(n.isPrivateIPv4Address('172.15.255.255')).toBe(false); + expect(n.isPrivateIPv4Address('172.32.0.0')).toBe(false); + expect(n.isPrivateIPv4Address('193.167.255.255')).toBe(false); + expect(n.isPrivateIPv4Address('193.169.0.0')).toBe(false); + }); + + it('parses a URL', () => { + const url = + n.parseUrl('https://www.example.com:8080/a/path?a_query#a_fragment'); + expect(url.protocol).toBe('https:'); + expect(url.hostname).toBe('www.example.com'); + expect(url.port).toBe('8080'); + expect(url.pathname).toBe('/a/path'); + expect(url.search).toBe('?a_query'); + expect(url.hash).toBe('#a_fragment'); + expect(url.host).toBe('www.example.com:8080'); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/retry.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/retry.js new file mode 100644 index 00000000000..326dbfa1d54 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/retry.js @@ -0,0 +1,198 @@ +// 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 class for attempting some unreliable operation until it succeeds. Sort of + * an asynchronous while loop. + * + * (new mr.Retry(attempt, 500, 10)).start().then(onSuccess, onFailure); + * + * The next attempt is driven by the failure of current attempt. Thus, there is + * at most one attempt invocation at any time. + */ + +goog.provide('mr.Retry'); + +goog.require('mr.Assertions'); +goog.require('mr.PromiseResolver'); + + +/** + * An object that attempts some operation until it succeeds. + * + * @template R + */ +mr.Retry = class { + /** + * @param {function():!Promise<R>} onAttempt An + * idempotent function to call repeatedly until it succeeds. + * @param {number} retryDelay The number of milliseconds to wait between the + * start of one attempt and the start of the next. It must be positive. + * More precisely, this is the amount of time to wait between the failure + * of one call to onAttempt(). + * @param {number} maxAttempts The maximum number of attempts. + * It must be positive. + */ + constructor(onAttempt, retryDelay, maxAttempts) { + /** + * @private {!function():!Promise<R>} + */ + this.onAttempt_ = onAttempt; + + /** + * @private {number} + */ + this.retryDelay_ = retryDelay > 0 ? retryDelay : 10; + + /** + * @private {number} + */ + this.maxAttempts_ = maxAttempts > 0 ? maxAttempts : 1; + + /** + * @private {number} + */ + this.maxRetryDelay_ = 0; + + /** + * @private {number} + */ + this.backoffFactor_ = 1; + + /** + * The number of times `onAttempt_` has been called. + * @private {number} + */ + this.numAttemptsStarted_ = 0; + + /** + * @private {boolean} + */ + this.isFinished_ = false; + + /** + * The ID of the most recently created timer. + * @private {?number} + */ + this.timerId_ = null; + + /** @private {mr.PromiseResolver} */ + this.resolver_ = null; + } + + /** + * Starts running this object. + * + * This method starts an asynchronous process that repeatedly calls + * `onAttempt`. + * + * For each attempt, `onAttempt` is called. When `onAttempt` + * resolves, the returned promise is resolved with the same result. + * The returned promise rejects if `abort` is called on this object, + * or the number of attempts specified by `setMaxAttempts` is reached. + * + * @return {!Promise<R>} + * @template R + */ + start() { + if (this.resolver_ != null) { + return Promise.reject(Error('Cannot call Retry.start more than once.')); + } + this.resolver_ = new mr.PromiseResolver(); + this.retryOnce_(); + return this.resolver_.promise; + } + + /** + * Makes the next call to `onAttempt_`. + * @private + */ + retryOnce_() { + this.timerId_ = null; + if (this.isFinished_) { + // The abort method has been called, don't start a new attempt. + return; + } + + this.numAttemptsStarted_++; + this.onAttempt_().then( + result => { + this.cleanup_(); + this.resolver_.resolve(result); + }, + error => { + if (this.numAttemptsStarted_ >= this.maxAttempts_) { + // Maximum number of attempts has been reached, do not try again. + this.cleanup_(); + this.resolver_.reject(Error('Max attempts reached')); + } else { + this.timerId_ = + setTimeout(this.retryOnce_.bind(this), this.retryDelay_); + this.updateRetryDelay_(); + } + }); + } + + /** + * Implement exponential backoff. + * @private + */ + updateRetryDelay_() { + let newRetryDelay = this.retryDelay_ * this.backoffFactor_; + if (this.maxRetryDelay_ > 0) { + newRetryDelay = Math.min(newRetryDelay, this.maxRetryDelay_); + } + this.retryDelay_ = newRetryDelay; + } + + /** + * Sets the backoff factor. After each attempt, the retry delay is + * multiplied by the backoff factor, which must be at least 1. + * + * @param {number} backoffFactor The factor by which the retry delay should be + * increased after each attempt. + * @return {!mr.Retry} this + */ + setBackoffFactor(backoffFactor) { + mr.Assertions.assert(backoffFactor >= 1); + this.backoffFactor_ = backoffFactor; + return this; + } + + /** + * Sets the maximum retry delay that can be used as a result of the backoff + * factor. If 0, there is no maximum. + * @param {number} maxRetryDelay The maximum number of milliseconds to wait + * before retrying. + * @return {!mr.Retry} this + */ + setMaxRetryDelay(maxRetryDelay) { + mr.Assertions.assert(maxRetryDelay >= 0); + this.maxRetryDelay_ = maxRetryDelay; + return this; + } + + /** + * Causes this object to stop making attempts and puts it in a + * finished state. May be called any time after `start`. + */ + abort() { + this.cleanup_(); + this.resolver_.reject(Error('abort')); + } + + /** + * @private + */ + cleanup_() { + if (this.timerId_ != null) { + // Clean up any timer, because it will be a no-op when it fires. + clearTimeout(this.timerId_); + this.timerId_ = null; + } + + this.isFinished_ = true; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/runtime_error_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/runtime_error_utils.js new file mode 100644 index 00000000000..f3eab537456 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/runtime_error_utils.js @@ -0,0 +1,38 @@ +// 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 Utilities for handling extension API errors. + */ + +goog.module('mr.RunTimeErrorUtils'); +goog.module.declareLegacyNamespace(); + +const Logger = goog.require('mr.Logger'); + + +/** + * Converts chrome.runtime.lastError into an Error object. Should only be + * called from an extension function callback that returns null or undefined. + * By accessing chrome.runtime.lastError, this method has a side effect of + * "handling" the error by preventing it from turning into an unchecked error. + + * + * @param {string} functionName The name of the extension API function. + * @param {!Logger=} logger If there is an error, logs a FINE error message + * with the logger, if provided. + * @return {?Error} An Error object with chrome.runtime.lastError.message, if + * any. + */ +exports.getError = function(functionName, logger = undefined) { + if (!chrome.runtime.lastError) { + return null; + } + const message = functionName + ' failed, chrome.runtime.lastError: ' + + (chrome.runtime.lastError.message || 'Unknown error'); + if (logger) { + logger.fine(message); + } + return new Error(message); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/sink_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/sink_utils.js new file mode 100644 index 00000000000..830ecbe5895 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/sink_utils.js @@ -0,0 +1,187 @@ +// 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 Utilites and settings for media sinks. + + */ + +goog.module('mr.SinkUtils'); +goog.module.declareLegacyNamespace(); + +const PersistentData = goog.require('mr.PersistentData'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const Sha1 = goog.require('mr.Sha1'); +const StringUtils = goog.require('mr.StringUtils'); +const base64 = goog.require('mr.base64'); + + +/** + * @implements {PersistentData} + */ +class SinkUtils { + constructor() { + /** + * Used to generate per-profile media sink ids. Persistent data. + * @private {string} + */ + this.receiverIdToken_ = StringUtils.getRandomString(); + + /** + * The model name of the device that was most recently used to start a media + * route. Temporary data. + * @type {!SinkUtils.DeviceData} + */ + this.recentLaunchedDevice = new SinkUtils.DeviceData(null, null); + + /** + * The model name of the device that was most recently discovered. Temporary + * data. + * @type {!SinkUtils.DeviceData} + */ + this.recentDiscoveredDevice = new SinkUtils.DeviceData(null, null); + + /** + * List of IP addresses of devices that are assumed to exist (and not + * discvered on the LAN), for debugging purposes. Persistent data. + * @type {!Array<string>} + */ + this.fixedIpList = []; + + /** + * Persistent data. + * @type {number} + */ + this.castControlPort = 0; + + /** + * The last time cloud sinks were checked. + * @type {number} + */ + this.lastCloudSinkCheckTimeMillis = 0; + + PersistentDataManager.register(this); + } + + /** + * @return {!SinkUtils} + */ + static getInstance() { + if (!SinkUtils.instance_) { + SinkUtils.instance_ = new SinkUtils(); + } + return SinkUtils.instance_; + } + + /** + * Generates ID from the receiver UUID and a per-profile token saved in + * localStorage. + * + * Both DIAL and mDNS use this to generate receiver ID so that it is + * consistent and can be used to deduplicate receivers. For a given token, the + * ID is the same for the same device no matter when it is discovered. + * + * @param {string} uniqueId + * @return {string} receiver ID. + */ + generateId(uniqueId) { + uniqueId = uniqueId.toLowerCase(); + const sha1 = new Sha1(); + sha1.update(uniqueId); + sha1.update(this.receiverIdToken_); + return 'r' + base64.encodeArray(sha1.digest(), true); + } + + /** + * @return {!SinkUtils.DeviceData} Most recent device launched or + * discovered. + */ + getRecentDevice() { + if (this.recentLaunchedDevice.model) return this.recentLaunchedDevice; + + if (this.recentDiscoveredDevice.model) return this.recentDiscoveredDevice; + + return new SinkUtils.DeviceData(null, null); + } + + /** + * @override + */ + getStorageKey() { + return 'SinkUtils'; + } + + /** + * @override + */ + getData() { + return [ + { + 'recentLaunchedDevice': this.recentLaunchedDevice, + 'recentDiscoveredDevice': this.recentDiscoveredDevice + }, + { + 'receiverIdToken': this.receiverIdToken_, + 'fixedIpList': this.fixedIpList.join(','), + 'castControlPort': this.castControlPort, + 'lastCloudSinkCheckTimeMillis': this.lastCloudSinkCheckTimeMillis + } + ]; + } + + /** + * @override + */ + loadSavedData() { + const tempData = PersistentDataManager.getTemporaryData(this); + if (tempData) { + this.recentLaunchedDevice = tempData['recentLaunchedDevice'] || + new SinkUtils.DeviceData(null, null); + this.recentDiscoveredDevice = tempData['recentDiscoveredDevice'] || + new SinkUtils.DeviceData(null, null); + } + + const persistentData = PersistentDataManager.getPersistentData(this); + if (persistentData) { + this.receiverIdToken_ = + persistentData['receiverIdToken'] || StringUtils.getRandomString(); + this.fixedIpList = (persistentData['fixedIpList'] && + persistentData['fixedIpList'].split(',')) || + []; + this.castControlPort = persistentData['castControlPort'] || 0; + this.lastCloudSinkCheckTimeMillis = + persistentData['lastCloudSinkCheckTimeMillis'] || 0; + } + } +} + + +/** @private {SinkUtils} */ +SinkUtils.instance_ = null; + + +/** + * The device data to keep track of. + */ +SinkUtils.DeviceData = class { + /** + * @param {?string} modelName + * @param {?string} ipAddress + */ + constructor(modelName, ipAddress) { + /** + * @type {?string} + * @export + */ + this.model = modelName; + + /** + * @type {?string} + * @export + */ + this.ip = ipAddress; + } +}; + +exports = SinkUtils; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/common/xhr_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/xhr_utils.js new file mode 100644 index 00000000000..0ce839efb46 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/common/xhr_utils.js @@ -0,0 +1,72 @@ +// 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 Utilities for dealing with XmlHttpRequests. + */ + +goog.provide('mr.XhrUtils'); + +goog.require('mr.Logger'); + + +/** + * Logs the outcome of a XmlHttpRequest. + * @param {mr.Logger} logger Where to log. + * @param {string} action The operation that created the Fetch. + * @param {string} method The HTTP method. + * @param {!XMLHttpRequest} xhr The response from Fetch. + */ +mr.XhrUtils.logRawXhr = function(logger, action, method, xhr) { + const logString = mr.XhrUtils.getStatusString_( + action, method, xhr.responseURL, xhr.status, xhr.statusText); + if (mr.XhrUtils.isSuccess(xhr)) { + logger.info(logString); + } else { + logger.fine(logString); + } +}; + + +/** + * Returns true if the given XMLHttpRequest represents a successful response. + * @param {!XMLHttpRequest} xhr + * @return {boolean} + */ +mr.XhrUtils.isSuccess = function(xhr) { + return xhr.status >= 200 && xhr.status <= 299; +}; + + +/** + * Returns a loggable string with the given parameters. + * @param {string} action + * @param {string} method + * @param {string} url + * @param {number} status + * @param {string} statusText + * @return {string} + * @private + */ +mr.XhrUtils.getStatusString_ = function( + action, method, url, status, statusText) { + return `[${action}]: ${method} ${url} => ${status} (${statusText})`; +}; + + +/** + * Parses xmlText into an XML document. + * @param {string} xmlText A serialized XML document. + * @return {?Document} An XML document if the parse was successful, null + * otherwise. + */ +mr.XhrUtils.parseXml = function(xmlText) { + const xml = new DOMParser().parseFromString(xmlText, 'text/xml'); + // A failed parse returns an HTML document with a <parsererror> element. + return xml.getElementsByTagNameNS( + 'http://www.w3.org/1999/xhtml', 'parsererror') + .length ? + null : + xml; +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity.js new file mode 100644 index 00000000000..264e20928c6 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity.js @@ -0,0 +1,22 @@ +// 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 DIAL activity (locally launched or discovered). + */ + +goog.provide('mr.dial.Activity'); + +mr.dial.Activity = class { + /** + * @param {!mr.Route} route + * @param {!string} appName + */ + constructor(route, appName) { + /** @type {!mr.Route} */ + this.route = route; + /** @type {string} */ + this.appName = appName; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records.js new file mode 100644 index 00000000000..112ec31bd15 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records.js @@ -0,0 +1,156 @@ +// 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. + +goog.module('mr.dial.ActivityRecords'); + +const ActivityCallbacks = goog.require('mr.dial.ActivityCallbacks'); +const PersistentData = goog.require('mr.PersistentData'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); + + +/** + * Keeps track of DIAL activities. + * @implements {PersistentData} + */ +const ActivityRecords = class { + /** + * @param {!ActivityCallbacks} activityCallbacks + */ + constructor(activityCallbacks) { + /** + * Maps route ID to the corresponding Activity. + * @private {!Map<string, !mr.dial.Activity>} + */ + this.routeIdToAppInfo_ = new Map(); + + /** @private @const {!ActivityCallbacks} */ + this.activityCallbacks_ = activityCallbacks; + } + + /** + * Restores routeIdToAppInfo_ from saved data. + */ + init() { + PersistentDataManager.register(this); + } + + /** + * Removes all activities. + */ + clear() { + this.routeIdToAppInfo_.clear(); + } + + /** + * Adds a new activity. If the activity already exists, this is no-op. + * @param {!mr.dial.Activity} activity + */ + add(activity) { + if (this.getByRouteId(activity.route.id)) { + return; + } + this.routeIdToAppInfo_.set(activity.route.id, activity); + this.activityCallbacks_.onActivityAdded(activity); + } + + /** + * Returns the activity corresponding to the given route ID, or null if there + * is none. + * @param {string} routeId + * @return {?mr.dial.Activity} + */ + getByRouteId(routeId) { + return this.routeIdToAppInfo_.get(routeId) || null; + } + + /** + * Returns the activity corresponding to the given sink ID, or null if there + * is none. + * @param {string} sinkId + * @return {?mr.dial.Activity} + */ + getBySinkId(sinkId) { + for (let [routeId, activity] of this.routeIdToAppInfo_) { + if (activity.route.sinkId == sinkId) { + return /** @type {!mr.dial.Activity} */ (activity); + } + } + return null; + } + + /** + * Removes the activity associated with the given sink ID. + * @param {string} sinkId + */ + removeBySinkId(sinkId) { + const activity = this.getBySinkId(sinkId); + if (activity) { + this.routeIdToAppInfo_.delete(activity.route.id); + this.activityCallbacks_.onActivityRemoved(activity); + } + } + + /** + * Removes the activity associated with the given route ID. + * @param {string} routeId + */ + removeByRouteId(routeId) { + const activity = this.routeIdToAppInfo_.get(routeId); + if (activity) { + this.routeIdToAppInfo_.delete(routeId); + this.activityCallbacks_.onActivityRemoved(activity); + } + } + + /** + * Returns the list of routes associated with the current list of activities. + * @return {!Array<!mr.Route>} + */ + getRoutes() { + return Array.from( + this.routeIdToAppInfo_.values(), activity => activity.route); + } + + /** + * Returns the current list of acitvities. + * @return {!Array<!mr.dial.Activity>} + */ + getActivities() { + return Array.from(this.routeIdToAppInfo_.values()); + } + + /** + * Returns the number of activities. + * @return {number} + */ + getActivityCount() { + return this.routeIdToAppInfo_.size; + } + + /** + * @override + */ + getStorageKey() { + return 'dial.ActivityRecords'; + } + + /** + * @override + */ + getData() { + return [Array.from(this.routeIdToAppInfo_)]; + } + + /** + * @override + */ + loadSavedData() { + const savedData = PersistentDataManager.getTemporaryData(this); + if (savedData) { + this.routeIdToAppInfo_ = new Map(savedData); + } + } +}; + +exports = ActivityRecords; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records_test.js new file mode 100644 index 00000000000..38ff262fba0 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_activity_records_test.js @@ -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. + +goog.module('mr.dial.ActivityRecordsTest'); +goog.setTestOnly('mr.dial.ActivityRecordsTest'); + +const Activity = goog.require('mr.dial.Activity'); +const ActivityRecords = goog.require('mr.dial.ActivityRecords'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const Route = goog.require('mr.Route'); +const UnitTestUtils = goog.require('mr.UnitTestUtils'); + +describe('DIAL ActivityRecords Tests', function() { + let records; + let mockCallbacks; + let activity1; + let activity2; + + beforeEach(function() { + UnitTestUtils.mockMojoApi(); + UnitTestUtils.mockChromeApi(); + let route = new Route( + 'routeId1', 'presentationId1', 'sinkId1', null, false, 'description1', + 'imageUrl1'); + activity1 = new Activity(route, 'app1'); + route = new Route( + 'routeId2', 'presentationId2', 'sinkId2', null, true, 'description2', + 'imageUrl2'); + activity2 = new Activity(route, 'app2'); + mockCallbacks = jasmine.createSpyObj( + 'ActivityCallbacks', + ['onActivityAdded', 'onActivityRemoved', 'onActivityUpdated']); + records = new ActivityRecords(mockCallbacks); + records.init(); + }); + + afterEach(function() { + PersistentDataManager.clear(); + UnitTestUtils.restoreChromeApi(); + }); + + it('Add activity', function() { + records.add(activity1); + expect(records.getByRouteId(activity1.route.id)).toEqual(activity1); + expect(mockCallbacks.onActivityAdded.calls.count()).toBe(1); + expect(mockCallbacks.onActivityAdded).toHaveBeenCalledWith(activity1); + records.add(activity1); + expect(mockCallbacks.onActivityAdded.calls.count()).toBe(1); + }); + + it('Get activity and route', function() { + records.add(activity1); + expect(records.getByRouteId(activity1.route.id)).toEqual(activity1); + expect(records.getBySinkId(activity1.route.sinkId)).toEqual(activity1); + expect(records.getRoutes()).toEqual([activity1.route]); + records.add(activity2); + expect(records.getByRouteId(activity2.route.id)).toEqual(activity2); + expect(records.getBySinkId(activity2.route.sinkId)).toEqual(activity2); + expect(records.getRoutes()).toEqual([activity1.route, activity2.route]); + }); + + it('Remove activity', function() { + records.add(activity1); + records.add(activity2); + records.removeByRouteId(activity2.route.id); + expect(mockCallbacks.onActivityRemoved.calls.count()).toBe(1); + expect(mockCallbacks.onActivityRemoved).toHaveBeenCalledWith(activity2); + records.removeByRouteId(activity2.route.id); + expect(mockCallbacks.onActivityRemoved.calls.count()).toBe(1); + expect(records.getByRouteId(activity2.route.id)).toEqual(null); + expect(records.getBySinkId(activity2.route.sinkId)).toEqual(null); + expect(records.getRoutes()).toEqual([activity1.route]); + + records.removeBySinkId(activity1.route.sinkId); + expect(mockCallbacks.onActivityRemoved.calls.count()).toBe(2); + expect(mockCallbacks.onActivityRemoved).toHaveBeenCalledWith(activity1); + expect(records.getRoutes()).toEqual([]); + }); + + it('Save without any data', function() { + expect(records.getRoutes()).toEqual([]); + PersistentDataManager.suspendForTest(); + records = new ActivityRecords(mockCallbacks); + records.loadSavedData(); + expect(records.getRoutes()).toEqual([]); + }); + + it('Save with data', function() { + records.add(activity1); + records.add(activity2); + PersistentDataManager.suspendForTest(); + records = new ActivityRecords(mockCallbacks); + records.loadSavedData(); + expect(JSON.stringify(records.getRoutes())).toEqual(JSON.stringify([ + activity1.route, activity2.route + ])); + }); + +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics.js new file mode 100644 index 00000000000..9abe0f835a5 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics.js @@ -0,0 +1,119 @@ +// 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 Defines UMA analytics specific to the DIAL provider. + */ + +goog.provide('mr.DialAnalytics'); + +goog.require('mr.Analytics'); + + +/** + * Contains all analytics logic for the DIAL provider. + * @const {*} + */ +mr.DialAnalytics = {}; + + +/** @enum {string} */ +mr.DialAnalytics.Metric = { + DEVICE_DESCRIPTION_FAILURE: 'MediaRouter.Dial.Device.Description.Failure', + DEVICE_DESCRIPTION_FROM_CACHE: 'MediaRouter.Dial.Device.Description.Cached', + DIAL_CREATE_ROUTE: 'MediaRouter.Dial.Create.Route', + NON_CAST_DISCOVERY: 'MediaRouter.Dial.Sink.Discovered.NonCast' +}; + + +/** + * Possible values for the route creation analytics. + * @enum {number} + */ +mr.DialAnalytics.DialRouteCreation = { + FAILED_NO_SINK: 0, + ROUTE_CREATED: 1, + NO_APP_INFO: 2, + FAILED_LAUNCH_APP: 3 +}; + + +/** + * Possible values for device description failures. + * @enum {number} + */ +mr.DialAnalytics.DeviceDescriptionFailures = { + ERROR: 0, + PARSE: 1, + EMPTY: 2 +}; + + +/** + * Records analytics around route creation. + * @param {mr.DialAnalytics.DialRouteCreation} value + */ +mr.DialAnalytics.recordCreateRoute = function(value) { + mr.Analytics.recordEnum( + mr.DialAnalytics.Metric.DIAL_CREATE_ROUTE, value, + mr.DialAnalytics.DialRouteCreation); +}; + + +/** + * Records a failure with the device description. + * @param {!mr.DialAnalytics.DeviceDescriptionFailures} value The failure + * reason. + */ +mr.DialAnalytics.recordDeviceDescriptionFailure = function(value) { + mr.Analytics.recordEnum( + mr.DialAnalytics.Metric.DEVICE_DESCRIPTION_FAILURE, value, + mr.DialAnalytics.DeviceDescriptionFailures); +}; + + +/** + * Records that device description was retreived from the cache. + */ +mr.DialAnalytics.recordDeviceDescriptionFromCache = function() { + mr.Analytics.recordEvent( + mr.DialAnalytics.Metric.DEVICE_DESCRIPTION_FROM_CACHE); +}; + + +/** + * Records that a device was discovered by DIAL that didn't support the cast + * protocol. + */ +mr.DialAnalytics.recordNonCastDiscovery = function() { + mr.Analytics.recordEvent(mr.DialAnalytics.Metric.NON_CAST_DISCOVERY); +}; + + +/** + * Histogram name for available DIAL devices count. + * @private @const {string} + */ +mr.DialAnalytics.AVAILABLE_DEVICES_COUNT_ = + 'MediaRouter.Dial.AvailableDevicesCount'; + + +/** + * Histogram name for known DIAL devices count. + * @private @const {string} + */ +mr.DialAnalytics.KNOWN_DEVICES_COUNT_ = 'MediaRouter.Dial.KnownDevicesCount'; + + +/** + * Records device counts. + * @param {!mr.DeviceCounts} deviceCounts + */ +mr.DialAnalytics.recordDeviceCounts = function(deviceCounts) { + mr.Analytics.recordSmallCount( + mr.DialAnalytics.AVAILABLE_DEVICES_COUNT_, + deviceCounts.availableDeviceCount); + mr.Analytics.recordSmallCount( + mr.DialAnalytics.KNOWN_DEVICES_COUNT_, deviceCounts.knownDeviceCount); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics_test.js new file mode 100644 index 00000000000..37a762b4bf4 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_analytics_test.js @@ -0,0 +1,88 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.DialAnalytics'); + +describe('Dial Analytics', function() { + + beforeEach(function() { + chrome.metricsPrivate = jasmine.createSpyObj( + ['recordSmallCount', 'recordUserAction', 'recordValue']); + }); + + it('should record a Dial.Create.Route result', function() { + const testConfig = { + 'metricName': 'MediaRouter.Dial.Create.Route', + 'type': 'histogram-linear', + 'min': 1, + 'max': 4, + 'buckets': 5 + }; + let numCalls = 0; + for (key in mr.DialAnalytics.DialRouteCreation) { + const value = mr.DialAnalytics.DialRouteCreation[key]; + mr.DialAnalytics.recordCreateRoute(value); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(++numCalls); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, value); + } + }); + + it('should not record an unknown Dial.Create.Route result', function() { + mr.DialAnalytics.recordCreateRoute('test'); + expect(chrome.metricsPrivate.recordValue).not.toHaveBeenCalled(); + }); + + it('should record a Dial.Device.Description.Failure result', function() { + const testConfig = { + 'metricName': 'MediaRouter.Dial.Device.Description.Failure', + 'type': 'histogram-linear', + 'min': 1, + 'max': 3, + 'buckets': 4 + }; + let numCalls = 0; + for (key in mr.DialAnalytics.DeviceDescriptionFailures) { + const value = mr.DialAnalytics.DeviceDescriptionFailures[key]; + mr.DialAnalytics.recordDeviceDescriptionFailure(value); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(++numCalls); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, value); + } + }); + + it('should not record an unknown Dial.Device.Description.Failure', + function() { + mr.DialAnalytics.recordDeviceDescriptionFailure('test'); + expect(chrome.metricsPrivate.recordValue).not.toHaveBeenCalled(); + }); + + it('should record a Device Description From Cache action', function() { + mr.DialAnalytics.recordDeviceDescriptionFromCache(); + expect(chrome.metricsPrivate.recordUserAction.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordUserAction) + .toHaveBeenCalledWith('MediaRouter.Dial.Device.Description.Cached'); + }); + + it('should record a non-Cast Sink Discovery action', function() { + mr.DialAnalytics.recordNonCastDiscovery(); + expect(chrome.metricsPrivate.recordUserAction.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordUserAction) + .toHaveBeenCalledWith('MediaRouter.Dial.Sink.Discovered.NonCast'); + }); + + it('DeviceCounts is recorded with recordSmallCount', () => { + const deviceCounts = {availableDeviceCount: 5, knownDeviceCount: 8}; + mr.DialAnalytics.recordDeviceCounts(deviceCounts); + expect(chrome.metricsPrivate.recordSmallCount.calls.count()).toBe(2); + let args = chrome.metricsPrivate.recordSmallCount.calls.argsFor(0); + expect(args[0]).toBe(mr.DialAnalytics.AVAILABLE_DEVICES_COUNT_); + expect(args[1]).toBe(deviceCounts.availableDeviceCount); + + args = chrome.metricsPrivate.recordSmallCount.calls.argsFor(1); + expect(args[0]).toBe(mr.DialAnalytics.KNOWN_DEVICES_COUNT_); + expect(args[1]).toBe(deviceCounts.knownDeviceCount); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service.js new file mode 100644 index 00000000000..d612fa90702 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service.js @@ -0,0 +1,480 @@ +// 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. + +goog.module('mr.dial.AppDiscoveryService'); + +const ActivityRecords = goog.require('mr.dial.ActivityRecords'); +const DialClient = goog.require('mr.dial.Client'); +const DialSink = goog.require('mr.dial.Sink'); +const DialSinkAppStatus = goog.require('mr.dial.SinkAppStatus'); +const Logger = goog.require('mr.Logger'); +const PersistentData = goog.require('mr.PersistentData'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const PromiseUtils = goog.require('mr.PromiseUtils'); +const SinkDiscoveryService = goog.require('mr.dial.SinkDiscoveryService'); + + +/** + * Service for determining whether a sink supports a given DIAL app. + * @implements {PersistentData} + */ +const AppDiscoveryService = class { + /** + * @param {!SinkDiscoveryService} discoveryService + * @param {!ActivityRecords} activityRecords + */ + constructor(discoveryService, activityRecords) { + /** @private @const {?Logger} */ + this.logger_ = Logger.getInstance('mr.dial.AppDiscoveryService'); + + /** + * Names of applications that are actively being queried for. + * @private {!Set<string>} + */ + this.registeredApps_ = new Set(); + + /** + * @private @const {!SinkDiscoveryService} + */ + this.discoveryService_ = discoveryService; + + /** + * @private @const {!ActivityRecords} + */ + this.activityRecords_ = activityRecords; + + /** + * Keeps track of pending requests to avoid duplication. Values are + * Promises returning strings of the form <sink.getId()>:<appName>. + * @private @const {!Map<string, !Promise<!DialClient.AppInfo>>} + */ + this.pendingRequests_ = new Map(); + + /** + * DIAL clients used by this service. Keys are sink ids. + * @private @const {!Map<string, !DialClient.Client>} + */ + this.clientsBySinkId_ = new Map(); + + /** + * The timeout ID for the next scan. + * @private {?number} + */ + this.timeoutId_ = null; + + /** + * Whether the service is running. + * @private {boolean} + */ + this.running_ = false; + } + + init() { + PersistentDataManager.register(this); + } + + /** + * Starts discovery. + */ + start() { + this.logger_.info('Starting periodic scanning.'); + if (!this.running_) { + this.running_ = true; + this.doScan_(); + } + } + + /** + * Stops discovery. Also aborts all outstanding discovery requests. + */ + stop() { + this.logger_.info('Stopping periodic scanning.'); + if (!this.running_) return; + this.running_ = false; + if (this.timeoutId_) { + clearTimeout(this.timeoutId_); + this.timeoutId_ = null; + } + this.pendingRequests_.clear(); + } + + /** + * Registers an application name that should be queried. Starts periodic + * scanning if it hasn't already started. Otherwise, issues an out-of-band + * query for the app against all discovered sinks. + * @param {string} appName + */ + registerApp(appName) { + if (this.registeredApps_.has(appName) && + !this.anyUnknownAppStatus_(appName)) { + // No need to scan since status for appName is known for all sinks. + return; + } + + this.registeredApps_.add(appName); + + if (this.discoveryService_.getSinkCount() == 0) { + // No sinks, no need to scan. + return; + } + if (!this.running_) { + this.start(); + } else { + this.discoveryService_.getSinks().forEach( + this.doScanSinkForApp_.bind(this, appName)); + } + } + + /** + * Unregisters an application name. + * @param {string} appName + */ + unregisterApp(appName) { + this.registeredApps_.delete(appName); + } + + /** + * @return {!Array<string>} + */ + getRegisteredApps() { + return Array.from(this.registeredApps_); + } + + /** + * @return {number} + */ + getAppCount() { + return this.registeredApps_.size; + } + + /** + * Issues app info queries and updates sink app status and activities + * according to results. Queries will be made for the following: + * 1) All registered apps, against all discovered sinks. Note that a (app, + * sink) combination is only queried if its status is unknown, or if enough + * time has elapsed since its previous status was known. + * 2) Each activity's app against its sink. + * Once all queries have returned and all updates have been made, schedules a + * scan in the future. + * @private + */ + doScan_() { + this.logger_.info('Start app status scan.'); + const promises = []; + + // Scans all sinks against all registered apps. + this.discoveryService_.getSinks().forEach(sink => { + promises.push.apply(promises, this.scanSink(sink)); + }); + + // Scans all activities. + this.activityRecords_.getActivities().forEach(activity => { + promises.push(this.scanActivity_(activity)); + }); + + PromiseUtils.allSettled(promises).then(() => { + if (this.running_ && !this.timeoutId_) { + this.logger_.fine('Scan complete; scheduling for next scan.'); + // NOTE(imcheng): setTimeout with a large delay does not work well in + // event pages. Instead we should be using chrome.alarms API, but note + // that it is subject to a minmum delay of 1 minute. + this.timeoutId_ = setTimeout(() => { + this.doRescan_(); + }, AppDiscoveryService.CHECK_INTERVAL_MILLIS); + } + }); + } + + /** + * Clears the timer and starts a round of scanning. Only valid when called + * from a timer. + * @private + */ + doRescan_() { + this.logger_.fine('Start app status scan (timer-based)'); + this.timeoutId_ = null; + this.doScan_(); + } + + /** + * Asynchronously scans a sink against all registered apps and updates its app + * status map if needed. + * @param {!DialSink} sink + * @return {!Array<!Promise<void>>} If the sink does not support app + * the getting app info, returns an empty array. Otherwise, returns a list + * of Promises, each corresponding to a registered app, resolved when the + * sink's app status has been updated. + */ + scanSink(sink) { + if (!sink.supportsAppAvailability()) { + return []; + } + + const promises = []; + for (let appName of this.registeredApps_) { + promises.push(this.doScanSinkForApp_(appName, sink)); + } + return promises; + } + + /** + * Issues an app info query for the given app to the given sink, and updates + * the sink's app status map with the response. + * @param {string} appName + * @param {!DialSink} sink + * @return {!Promise<void>} Resolved when the sink's app status has been + * updated. + * @private + */ + doScanSinkForApp_(appName, sink) { + if (sink.getAppStatus(appName) != DialSinkAppStatus.UNKNOWN && + Date.now() - sink.getAppStatusTimeStamp(appName) < + AppDiscoveryService.CACHE_PERIOD_) { + // App status already known and not expired. + return Promise.resolve(); + } + if (!sink.supportsAppAvailability()) { + return Promise.resolve(); + } + this.logger_.fine( + 'Querying ' + sink.getId() + ' for ' + appName + + ' to update app status'); + return this.getAppInfo_(sink, appName) + .then( + appInfo => { + const newAppStatus = this.getAvailabilityFromAppInfo_(appInfo); + this.maybeUpdateAppStatus_(sink, appName, newAppStatus); + }, + e => { + // Some devices return NOT_FOUND for GetAppInfo to indicate the + // app is unavailable. + if (e instanceof DialClient.AppInfoNotFoundError) { + this.maybeUpdateAppStatus_( + sink, appName, DialSinkAppStatus.UNAVAILABLE); + return; + } + this.logger_.warning( + 'Failed to process app availability; ' + sink.getId() + + ' does not support app availability'); + sink.setSupportsAppAvailability(false); + }); + } + + /** + * Issues an app info query for the given activity's app to the activity's + * sink, and updates the activity records if the app is no longer running. + * @param {!mr.dial.Activity} activity + * @return {!Promise<void>} Resolved when the activity record has possibly + * been updated. + * @private + */ + scanActivity_(activity) { + const sink = this.discoveryService_.getSinkById(activity.route.sinkId); + if (!sink) { + this.logger_.warning( + 'Activity refers to nonexistent sink: ' + activity.route.id); + return Promise.resolve(); + } + if (!sink.supportsAppAvailability()) { + return Promise.resolve(); + } + const appName = activity.appName; + this.logger_.fine( + 'Querying ' + sink.getId() + ' for ' + appName + ' to update activity'); + return this.getAppInfo_(sink, appName) + .then( + appInfo => this.maybeUpdateActivityRecord_( + /** @type {!DialSink} */ (sink), appName, appInfo), + e => this.doRemoveActivityRecord_( + /** @type {!DialSink} */ (sink), appName)); + } + + /** + * Gets the DIAL client associated with the given sink, or creates one if it + * does not exist. + * @param {!DialSink} sink + * @return {!DialClient.Client} A client instance for the given sink. + * @private + */ + getDialClient_(sink) { + let client = this.clientsBySinkId_.get(sink.getId()); + if (!client) { + client = new DialClient.Client(sink); + this.logger_.fine('Created DIAL client for ' + sink.getId()); + this.clientsBySinkId_.set(sink.getId(), client); + } + return client; + } + + /** + * Issues an app info query with the given sink's DIAL client. A query will + * not be issued if there is already a same one pending. + * @param {!DialSink} sink + * @param {string} appName + * @return {!Promise<!DialClient.AppInfo>} Resolved with the query response, + * or rejected on error. + * @private + */ + getAppInfo_(sink, appName) { + const requestId = AppDiscoveryService.getRequestId_(sink, appName); + let promise = this.pendingRequests_.get(requestId); + if (promise) { + return promise; + } + + promise = this.getDialClient_(sink).getAppInfo(appName); + this.pendingRequests_.set(requestId, promise); + const cleanup = () => { + this.pendingRequests_.delete(requestId); + }; + promise.then(cleanup, cleanup); + return promise; + } + + /** + * Translates the given app info result into a DialSinkAppStatus value. + * @param {!DialClient.AppInfo} appInfo + * @return {DialSinkAppStatus} + * @private + */ + getAvailabilityFromAppInfo_(appInfo) { + if (appInfo.name == 'Netflix') { + return this.getAppStatusFromNetflixAppInfo_(appInfo); + } else { + switch (appInfo.state) { + case DialClient.DialAppState.RUNNING: + case DialClient.DialAppState.STOPPED: + return DialSinkAppStatus.AVAILABLE; + default: + return DialSinkAppStatus.UNAVAILABLE; + } + } + } + + /** + * Returns app status from a Netflix app info. + * @param {!DialClient.AppInfo} appInfo The app info from DIAL GET. + * @return {DialSinkAppStatus} + * @private + */ + getAppStatusFromNetflixAppInfo_(appInfo) { + const isNetflixWebsocket = + appInfo.extraData && appInfo.extraData['capabilities'] == 'websocket'; + if (isNetflixWebsocket && + (appInfo.state == DialClient.DialAppState.RUNNING || + appInfo.state == DialClient.DialAppState.STOPPED)) { + return DialSinkAppStatus.AVAILABLE; + } else { + return DialSinkAppStatus.UNAVAILABLE; + } + } + + /** + * Returns the request ID to use for an app info request. + * @param {!DialSink} sink + * @param {string} appName + * @return {string} + * @private + */ + static getRequestId_(sink, appName) { + return sink.getId() + ':' + appName; + } + + /** + * Checks whether the status of an app on a sink has changed, and if so + * notifies discovery service. + * @param {!DialSink} sink + * @param {string} appName + * @param {DialSinkAppStatus} newAppStatus + * @private + */ + maybeUpdateAppStatus_(sink, appName, newAppStatus) { + this.logger_.fine( + 'Got app status ' + newAppStatus + ' from ' + sink.getId() + ' for ' + + appName); + const oldAppStatus = sink.getAppStatus(appName); + sink.setAppStatus(appName, newAppStatus); + if (newAppStatus != oldAppStatus) { + this.discoveryService_.onAppStatusChanged(appName, sink); + } + } + + /** + * Checks whether the given app is no longer running on the given sink, and if + * so notifies activity records. + * @param {!DialSink} sink + * @param {string} appName + * @param {!DialClient.AppInfo} appInfo + * @private + */ + maybeUpdateActivityRecord_(sink, appName, appInfo) { + if (appInfo.state != DialClient.DialAppState.RUNNING) { + this.doRemoveActivityRecord_(sink, appName); + } + } + + /** + * Removes the activity record with the given sink and app name, if it exists. + * @param {!DialSink} sink + * @param {string} appName + * @private + */ + doRemoveActivityRecord_(sink, appName) { + const activity = this.activityRecords_.getBySinkId(sink.getId()); + if (activity && activity.appName == appName) { + this.activityRecords_.removeByRouteId(activity.route.id); + } + } + + /** + * Checks if there is a sink whose status of appName is unknown. + * @param {string} appName + * @return {boolean} + * @private + */ + anyUnknownAppStatus_(appName) { + return this.discoveryService_.getSinks().some( + s => s.getAppStatus(appName) == DialSinkAppStatus.UNKNOWN); + } + + /** + * @override + */ + getStorageKey() { + return 'dial.AppDiscoveryService'; + } + + /** + * @override + */ + getData() { + return [this.getRegisteredApps()]; + } + + /** + * @override + */ + loadSavedData() { + const savedData = PersistentDataManager.getTemporaryData(this); + this.registeredApps_ = new Set(savedData || []); + } +}; + + +/** + * The interval for periodic scanning. + * @package @const {number} + */ +AppDiscoveryService.CHECK_INTERVAL_MILLIS = 60 * 1000; + + +/** + * The amount of time an availability result for an app (both AVAILABLE and + * UNAVAILABLE) will be cached for. + * @private @const {number} + */ +AppDiscoveryService.CACHE_PERIOD_ = 60 * 60 * 1000; + + +exports = AppDiscoveryService; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service_test.js new file mode 100644 index 00000000000..cd7edeac16b --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_app_discovery_service_test.js @@ -0,0 +1,362 @@ +// 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. + +goog.module('mr.dial.AppDiscoveryServiceTest'); +goog.setTestOnly('mr.dial.AppDiscoveryServiceTest'); + +const Activity = goog.require('mr.dial.Activity'); +const ActivityRecords = goog.require('mr.dial.ActivityRecords'); +const AppDiscoveryService = goog.require('mr.dial.AppDiscoveryService'); +const DialClient = goog.require('mr.dial.Client'); +const DialSink = goog.require('mr.dial.Sink'); +const DialSinkAppStatus = goog.require('mr.dial.SinkAppStatus'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const Route = goog.require('mr.Route'); +const UnitTestUtils = goog.require('mr.UnitTestUtils'); + +describe('DIAL AppDiscoveryService Tests', function() { + let activityRecords; + let service; + let mockClock; + let mockDiscoveryService; + let mockActivityCallbacks; + let sink1; + let sink2; + let sink3; + let mockDialClient; + let stoppedAppInfo; + let runningAppInfo; + let installableAppInfo; + let stoppedAppInfoWithWebsocket; + + const setUpGetAppInfoResponse = function(appInfo) { + mockDialClient.getAppInfo.and.callFake(() => Promise.resolve(appInfo)); + }; + + const setUpGetAppInfoError = function() { + mockDialClient.getAppInfo.and.callFake( + () => Promise.reject(new Error('getAppInfo failed'))); + }; + + const setUpGetAppInfoNotFoundError = function() { + mockDialClient.getAppInfo.and.callFake( + () => Promise.reject(new DialClient.AppInfoNotFoundError())); + }; + + beforeEach(function() { + mockClock = UnitTestUtils.useMockClockAndPromises(); + + mockDiscoveryService = jasmine.createSpyObj( + 'discoveryService', + ['getSinks', 'getSinkById', 'getSinkCount', 'onAppStatusChanged']); + mockActivityCallbacks = jasmine.createSpyObj( + 'activityCallbacks', + ['onActivityAdded', 'onActivityRemoved', 'onActivityUpdated']); + activityRecords = new ActivityRecords(mockActivityCallbacks); + service = new AppDiscoveryService(mockDiscoveryService, activityRecords); + + sink1 = new DialSink('sink1', '1').setSupportsAppAvailability(true); + sink2 = new DialSink('sink2', '2').setSupportsAppAvailability(true); + sink3 = new DialSink('sink3', '3').setSupportsAppAvailability(false); + mockDialClient = jasmine.createSpyObj('dialClient', ['getAppInfo']); + spyOn(AppDiscoveryService.prototype, 'getDialClient_') + .and.returnValue(mockDialClient); + stoppedAppInfo = {'state': DialClient.DialAppState.STOPPED}; + runningAppInfo = {'state': DialClient.DialAppState.RUNNING}; + installableAppInfo = {'state': DialClient.DialAppState.INSTALLABLE}; + stoppedAppInfoWithWebsocket = { + 'name': 'Netflix', + 'state': DialClient.DialAppState.STOPPED, + 'extraData': {'capabilities': 'websocket'} + }; + + chrome.runtime = { + id: 'fakeId', + getManifest: function() { + return {version: 'fakeVersion'}; + } + }; + }); + + afterEach(function() { + service.stop(); + UnitTestUtils.restoreRealClockAndPromises(); + PersistentDataManager.clear(); + }); + + describe('Tests registerApp', function() { + beforeEach(function() { + mockDiscoveryService.getSinks.and.returnValue([sink1, sink2, sink3]); + mockDiscoveryService.getSinkCount.and.returnValue(3); + }); + + const expectAppStatus = function(expectedAppStatus, appName) { + service.init(); + + expect(sink1.getAppStatus(appName)).toEqual(DialSinkAppStatus.UNKNOWN); + expect(sink2.getAppStatus(appName)).toEqual(DialSinkAppStatus.UNKNOWN); + expect(sink3.getAppStatus(appName)).toEqual(DialSinkAppStatus.UNKNOWN); + + service.registerApp(appName); + + service.start(); + // Let internal promises to resolve or reject. + mockClock.tick(1); + + expect(sink1.getAppStatus(appName)).toEqual(expectedAppStatus); + expect(sink2.getAppStatus(appName)).toEqual(expectedAppStatus); + expect(sink3.getAppStatus(appName)).toEqual(DialSinkAppStatus.UNKNOWN); + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + }; + + it('Response indicates app was stopped', function() { + setUpGetAppInfoResponse(stoppedAppInfo); + expectAppStatus(DialSinkAppStatus.AVAILABLE, 'YouTube'); + }); + + it('Response indicates app was running', function() { + setUpGetAppInfoResponse(runningAppInfo); + expectAppStatus(DialSinkAppStatus.AVAILABLE, 'YouTube'); + }); + + it('Response indicates app was installable', function() { + setUpGetAppInfoResponse(installableAppInfo); + expectAppStatus(DialSinkAppStatus.UNAVAILABLE, 'YouTube'); + }); + + it('Response has invalid app info', function() { + setUpGetAppInfoError(); + expectAppStatus(DialSinkAppStatus.UNKNOWN, 'YouTube'); + expect(sink1.supportsAppAvailability()).toBe(false); + expect(sink2.supportsAppAvailability()).toBe(false); + }); + + it('Response indicates not found', function() { + setUpGetAppInfoNotFoundError(); + expectAppStatus(DialSinkAppStatus.UNAVAILABLE, 'YouTube'); + }); + + it('Netflix with special stopped info', function() { + setUpGetAppInfoResponse(stoppedAppInfoWithWebsocket); + expectAppStatus(DialSinkAppStatus.AVAILABLE, 'Netflix'); + }); + + it('Netflix with normal stopped info', function() { + stoppedAppInfo.name = 'Netflix'; + setUpGetAppInfoResponse(stoppedAppInfo); + expectAppStatus(DialSinkAppStatus.UNAVAILABLE, 'Netflix'); + }); + + it('No new query generated when registering existing app with known status', + function() { + setUpGetAppInfoResponse(stoppedAppInfo); + expectAppStatus(DialSinkAppStatus.AVAILABLE, 'YouTube'); + // register again + service.registerApp('YouTube'); + mockClock.tick(1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + // unregister does nothing because sink already had the app status. + service.unregisterApp('YouTube'); + service.registerApp('YouTube'); + mockClock.tick(1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + // unless clear app status first. + sink1.clearAppStatus(); + sink2.clearAppStatus(); + service.unregisterApp('YouTube'); + service.registerApp('YouTube'); + mockClock.tick(1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(4); + }); + + it('No new query generated when register existing app with unknown status', + function() { + setUpGetAppInfoError(); + expectAppStatus(DialSinkAppStatus.UNKNOWN, 'YouTube'); + // Make sink1 have known status. But sink2 has unknown status. + sink1.setAppStatus('YouTube', DialSinkAppStatus.UNAVAILABLE); + // register again + service.registerApp('YouTube'); + mockClock.tick(1); + // No re-query + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + }); + }); + + describe('Tests app status caching and onAppStatusChanged', function() { + beforeEach(function() { + mockDiscoveryService.getSinks.and.returnValue([sink1]); + mockDiscoveryService.getSinkCount.and.returnValue(1); + }); + + it('Known app status does not change during caching period', function() { + service.init(); + setUpGetAppInfoResponse(stoppedAppInfo); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + service.registerApp('YouTube'); + service.start(); + mockClock.tick(1); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(1); + + // Make app unavailable + setUpGetAppInfoNotFoundError(); + service.doScan_(); + mockClock.tick(1); + // No new query and app is still available since cache is not expired. + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(1); + }); + + it('Does not re-query app status when getAppInfo throws error', function() { + service.init(); + setUpGetAppInfoError(); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + service.registerApp('YouTube'); + service.start(); + mockClock.tick(1); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(0); + + service.doScan_(); + mockClock.tick(1); + // No new query + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(0); + }); + + it('Cache expires, re-query, different status', function() { + service.init(); + setUpGetAppInfoResponse(stoppedAppInfo); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + service.registerApp('YouTube'); + service.start(); + mockClock.tick(1); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(1); + + // Make app unavailable + setUpGetAppInfoNotFoundError(); + // Make cache expire + mockClock.tick(AppDiscoveryService.CACHE_PERIOD_); + service.doScan_(); + mockClock.tick(1); + // new query and app becomes unavailable + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.UNAVAILABLE); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(2); + }); + + it('Cache expires, re-query, same status', function() { + service.init(); + setUpGetAppInfoResponse(stoppedAppInfo); + expect(sink1.getAppStatus('YouTube')).toEqual(DialSinkAppStatus.UNKNOWN); + service.registerApp('YouTube'); + service.start(); + mockClock.tick(1); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(1); + + // Make cache expire + mockClock.tick(AppDiscoveryService.CACHE_PERIOD_); + service.doScan_(); + mockClock.tick(1); + // New query and app becomes unavailable + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + // Did not trigger app status changed event + expect(mockDiscoveryService.onAppStatusChanged.calls.count()).toBe(1); + }); + }); + + describe('Tests activity scanning', function() { + beforeEach(function() { + mockDiscoveryService.getSinks.and.returnValue([sink1, sink2, sink3]); + mockDiscoveryService.getSinkCount.and.returnValue(3); + mockDiscoveryService.getSinkById.and.returnValue(sink1); + + service.init(); + const route = Route.createRoute( + 'presentationId', 'providerName', sink1.getId(), 'source', true, + 'description', null); + const activity = new Activity(route, 'YouTube'); + activityRecords.add(activity); + expect(mockActivityCallbacks.onActivityAdded).toHaveBeenCalled(); + }); + + it('Activity is removed when app is no longer running', function() { + setUpGetAppInfoResponse(stoppedAppInfo); + service.start(); + mockClock.tick(1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockActivityCallbacks.onActivityRemoved.calls.count()).toBe(1); + expect(activityRecords.getActivityCount()).toBe(0); + }); + + it('Activity is not removed when app is still running', function() { + setUpGetAppInfoResponse(runningAppInfo); + service.start(); + mockClock.tick(1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(1); + expect(mockActivityCallbacks.onActivityRemoved).not.toHaveBeenCalled(); + expect(activityRecords.getActivityCount()).toBe(1); + + // Trigger periodic rescan + mockClock.tick(AppDiscoveryService.CHECK_INTERVAL_MILLIS + 1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + expect(mockActivityCallbacks.onActivityRemoved).not.toHaveBeenCalled(); + expect(activityRecords.getActivityCount()).toBe(1); + + // No more periodic rescan. + service.stop(); + mockClock.tick(AppDiscoveryService.CHECK_INTERVAL_MILLIS + 1); + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + }); + + it('Activity scanning not duplicated with app scanning', function() { + setUpGetAppInfoResponse(stoppedAppInfo); + service.registerApp('YouTube'); + mockClock.tick(1); + // One for sink1 and one for sink2. Activity scanning does not issue a + // duplicated request. Both app status and activity status can be updated + // with the same response. + expect(mockDialClient.getAppInfo.calls.count()).toBe(2); + expect(sink1.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(sink2.getAppStatus('YouTube')) + .toEqual(DialSinkAppStatus.AVAILABLE); + expect(mockActivityCallbacks.onActivityRemoved.calls.count()).toBe(1); + expect(activityRecords.getActivityCount()).toBe(0); + }); + }); + + it('Persistent with data', function() { + mockDiscoveryService.getSinks.and.returnValue([]); + mockDiscoveryService.getSinkCount.and.returnValue(0); + const mockServiceToSuspend = new AppDiscoveryService( + mockDiscoveryService, new ActivityRecords(mockActivityCallbacks)); + mockServiceToSuspend.init(); + mockServiceToSuspend.registerApp('app1'); + mockServiceToSuspend.registerApp('app2'); + PersistentDataManager.suspendForTest(); + + const mockServiceToLoad = new AppDiscoveryService( + mockDiscoveryService, new ActivityRecords(mockActivityCallbacks)); + mockServiceToLoad.loadSavedData(); + expect(mockServiceToLoad.getRegisteredApps()).toEqual(['app1', 'app2']); + expect(mockServiceToLoad.getAppCount()).toEqual(2); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client.js new file mode 100644 index 00000000000..52759a070fd --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client.js @@ -0,0 +1,325 @@ +// 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 Client wrapper for interacting with DIAL devices and data + * structures for responses. + */ + +goog.module('mr.dial.Client'); +goog.module.declareLegacyNamespace(); + +const Assertions = goog.require('mr.Assertions'); +const Logger = goog.require('mr.Logger'); +const NetUtils = goog.require('mr.NetUtils'); +const XhrManager = goog.require('mr.XhrManager'); +const XhrUtils = goog.require('mr.XhrUtils'); + +/** + * Possible states of a DIAL application. + * @enum {string} + */ +const DialAppState = { + /** The app is running. */ + RUNNING: 'running', + /** The app is not running. */ + STOPPED: 'stopped', + /** The app can be installed. */ + INSTALLABLE: 'installable', + /** + * An error was encountered getting the state. + + */ + ERROR: 'error' +}; + + +/** + * Holds data parsed from a DIAL GET response. + */ +const AppInfo = class { + constructor() { + /** + * The application name. Mandatory. + * @type {string} + */ + this.name = 'unknown'; + + /** + * The reported state of the application. + * @type {DialAppState} + */ + this.state = DialAppState.ERROR; + + /** + * If the application's state is INSTALLABLE, then the URL where the app + * can be installed. + * @type {?string} + */ + this.installUrl = null; + + /** + * Whether the DELETE operation is supported. + * @type {boolean} + */ + this.allowStop = true; + + /** + * If the applications's state is RUNNING, a resource identifier for the + * running application. + * @type {?string} + */ + this.resource = null; + + /** + * Application-specific data included with the GET response that is not part + * of the official specifciations. + * @type {!Object<string, string>} + */ + this.extraData = {}; + } +}; + + +/** + * Indicates that the DIAL sink returned NOT_FOUND in response to a GET request. + */ +const AppInfoNotFoundError = class extends Error { + constructor() { + super(); + } +}; + + +/** + * Wrapper for a DIAL sink used for communicating with it. + */ +const Client = class { + /** + * @param {!mr.dial.Sink} sink + * @param {!XhrManager=} xhrManager Manager for all requests. + */ + constructor(sink, xhrManager = Client.getXhrManager_()) { + // NOTE(mfoltz,haibinlu): We do not assert if the sink supports DIAL, + // since combined discovery uses DialClient to check app info to see if a + // mDNS-discovered sink is also a DIAL sink. + Assertions.assert( + sink.getDialAppUrl(), 'Receiver must have a DIAL app URL set.'); + + /** @private @const {!mr.dial.Sink} */ + this.sink_ = sink; + + /** @private @const {!XhrManager} */ + this.xhrManager_ = xhrManager; + + /** @private @const {?Logger} */ + this.logger_ = Logger.getInstance('mr.dial.Client'); + } + + /** + * @param {!mr.dial.Sink} sink + * @return {!Client} + */ + static create(sink) { + return new Client(sink); + } + + /** + * Returns the default XhrManager, creating it if necessary. + * @return {!XhrManager} + * @private + */ + static getXhrManager_() { + if (!Client.xhrManager_) { + Client.xhrManager_ = new XhrManager( + /* maxRequests */ 10, + /* defaultTimeoutMillis */ 2000, + /* defaultNumAttempts */ 1); + } + return Client.xhrManager_; + } + + /** + * @param {string} state A string representing a DIAL application state. + * @return {DialAppState} The corresponding state or ERROR if the + * state is invalid. + * @private + */ + static parseDialAppState_(state) { + switch (state) { + case 'running': + return DialAppState.RUNNING; + case 'stopped': + return DialAppState.STOPPED; + default: + return DialAppState.ERROR; + } + } + + /** + * Launches an application on the sink. + * @param {string} appName Name of the DIAL application to launch. + * @param {string} postData Data to include in the HTTP POST request. + * @return {!Promise<void>} Fulfilled when the operation completes + * successfully. Rejected otherwise. + */ + launchApp(appName, postData) { + return this.xhrManager_ + .send( + this.getAppUrl_(appName), 'POST', postData, {timeoutMillis: 15000}) + .then(xhr => this.handleResponse_('launchApp', 'POST', xhr)); + } + + /** + * Stops a running application on the sink. + * @param {string} appName Name of the DIAL application to stop. + * @return {!Promise<void>} Fulfilled when the operation completes + * successfully. Rejected otherwise. + */ + stopApp(appName) { + return this.xhrManager_.send(this.getAppUrl_(appName), 'DELETE') + .then(xhr => this.handleResponse_('stopApp', 'DELETE', xhr)); + } + + /** + * Gets information about a running application on the sink. + * @param {string} appName Name of the DIAL application to get info from. + * @return {!Promise<!AppInfo>} Fulfilled with AppInfo. Rejected if the + * operation did not complete successfully. In the case of the sink + * returning NOT_FOUND for the request, AppInfoNotFoundError will be + * thrown. + */ + getAppInfo(appName) { + return this.xhrManager_ + .send(this.getAppUrl_(appName), 'GET', undefined, {numAttempts: 3}) + .then(xhr => this.handleGetAppInfoResponse_(appName, xhr)); + } + + /** + * Parses the response from a Xhr GET request. + * @param {string} appName App nam used in the request. + * @param {!XMLHttpRequest} xhr + * @return {!AppInfo} + * @private + */ + handleGetAppInfoResponse_(appName, xhr) { + XhrUtils.logRawXhr(this.logger_, 'GetAppInfo', 'GET', xhr); + if (!XhrUtils.isSuccess(xhr)) { + if (xhr.status == NetUtils.HttpStatus.NOT_FOUND) { + throw new AppInfoNotFoundError(); + } else { + throw new Error(`Response error: ${xhr.status}`); + } + } + + const xml = XhrUtils.parseXml(xhr.responseText); + if (!xml) { + this.logger_.info('Invalid or empty response'); + throw new Error('Invalid or empty response'); + } + + const service = xml.getElementsByTagName('service'); + if (!service || service.length != 1) { + this.logger_.info('Invalid GET response (invalid service)'); + throw new Error('Invalid GET response (invalid service)'); + } + const appInfo = new AppInfo(); + for (var i = 0, l = service[0].childNodes.length; i < l; i++) { + const node = service[0].childNodes[i]; + if (node.nodeName == 'state') { + appInfo.state = Client.parseDialAppState_(node.textContent); + } else if (node.nodeName == 'name') { + appInfo.name = node.textContent; + } else if (node.nodeName == 'link') { + appInfo.resource = node.getAttribute('href'); + } else if (node.nodeName == 'options') { + // The default value for allowStop is true per DIAL spec. + appInfo.allowStop = (node.getAttribute('allowStop') != 'false'); + } else { + appInfo.extraData[node.nodeName] = node.innerHTML; + } + } + + // Validate mandatory fields (name, state). + if (appInfo.name == 'unknown') { + this.logger_.info('GET response missing name value'); + throw new Error('GET response missing name value'); + } + + if (appInfo.name != appName) { + this.logger_.info('GET app name mismatch'); + throw new Error('GET app name mismatch'); + } + + if (appInfo.state == DialAppState.ERROR) { + this.logger_.info('GET response missing state value'); + throw new Error('GET response missing state value'); + } + + // Parse state. + const installable = /installable=(.+)/.exec(appInfo.state); + if (installable && installable[1]) { + appInfo.state = DialAppState.INSTALLABLE; + appInfo.installUrl = installable[1]; + } else if ( + appInfo.state == DialAppState.RUNNING || + appInfo.state == DialAppState.STOPPED) { + // Valid state. Continue. + } else { + this.logger_.info('GET response has invalid state value'); + throw new Error('GET response has invalid state value'); + } + + // Success! + return appInfo; + } + + /** + * Returns the URL used to communicate with a given DIAL application. + * @param {string} appName The name of the DIAL application. + * @return {string} The URL for the activity. + * @private + */ + getAppUrl_(appName) { + let appUrl = this.sink_.getDialAppUrl(); + if (appUrl.charAt(appUrl.length - 1) != '/') { + appUrl += '/'; + } + return appUrl + appName; + } + + /** + * Logs the given response and returns a Promise that resolves if it indicates + * success. + * @param {string} action Name of the operation that created the request. + * @param {string} method The HTTP method. + * @param {!XMLHttpRequest} xhr + * @return {!Promise<void>} Resolves if the response indicates success, + * rejected otherwise. + * @private + */ + handleResponse_(action, method, xhr) { + return new Promise((resolve, reject) => { + XhrUtils.logRawXhr(this.logger_, action, method, xhr); + if (XhrUtils.isSuccess(xhr)) { + resolve(); + } else { + reject(Error(xhr.statusText)); + } + }); + } +}; + + +/** + * Lazily instantiated and shared between DialClient instances. + * @private {?XhrManager} + */ +Client.xhrManager_ = null; + + +exports.AppInfo = AppInfo; +exports.AppInfoNotFoundError = AppInfoNotFoundError; +exports.Client = Client; +exports.DialAppState = DialAppState; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client_test.js new file mode 100644 index 00000000000..0c1979d405f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_client_test.js @@ -0,0 +1,284 @@ +// 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. + +goog.module('mr.dial.ClientTest'); +goog.setTestOnly('mr.dial.ClientTest'); + +const DialClient = goog.require('mr.dial.Client'); +const DialSink = goog.require('mr.dial.Sink'); +const XhrUtils = goog.require('mr.XhrUtils'); + +describe('Dial Client Tests', function() { + let client; + let mockXhrManager; + let sink; + const appUrl = 'http://198.0.0.100/apps'; + + + beforeEach(function() { + sink = new DialSink('sink', 'sinkid'); + sink.setDialAppUrl(appUrl); + mockXhrManager = jasmine.createSpyObj('XhrManager', ['send']); + spyOn(XhrUtils, 'logRawXhr'); + client = new DialClient.Client(sink, mockXhrManager); + }); + + const setMockXhrResponse = function(xml) { + mockXhrManager.send.and.returnValue( + Promise.resolve({responseText: xml, status: 200})); + }; + + const setMockXhrErrorResponse = function() { + mockXhrManager.send.and.returnValue( + Promise.resolve({responseText: null, status: 403 /* Forbidden */})); + }; + + const setMockXhrNotFoundResponse = function() { + mockXhrManager.send.and.returnValue( + Promise.resolve({responseText: null, status: 404})); + }; + + const setMockXhrReject = function() { + mockXhrManager.send.and.returnValue( + Promise.reject(new Error('send failed'))); + }; + + // Suppress Jasmine warning about a spec with no expectations. + const noExpectations = function() { + expect(true).toBe(true); + }; + + describe('Tests launchApp', function() { + const expectLaunchAppFails = function(done) { + client.launchApp('YouTube', 'v=12345678').then(() => { + fail('launchApp unexpectedly succeeded.'); + }, done); + }; + + it('Resolves', done => { + setMockXhrResponse(''); + client.launchApp('YouTube', 'v=12345678').then(() => { + expect(mockXhrManager.send) + .toHaveBeenCalledWith( + appUrl + '/YouTube', 'POST', 'v=12345678', jasmine.any(Object)); + done(); + }); + }); + + it('Rejects on error response', done => { + setMockXhrErrorResponse(); + expectLaunchAppFails(done); + noExpectations(); + }); + + it('Rejects on send rejection', done => { + setMockXhrReject(); + expectLaunchAppFails(done); + noExpectations(); + }); + }); + + describe('Tests stopApp', function() { + const expectStopAppFails = function(done) { + client.stopApp('YouTube').then(() => { + fail('stopApp unexpectedly succeeded.'); + }, done); + }; + + it('Resolves', done => { + setMockXhrResponse(''); + client.stopApp('YouTube').then(() => { + expect(mockXhrManager.send) + .toHaveBeenCalledWith(appUrl + '/YouTube', 'DELETE'); + done(); + }); + }); + + it('Rejects on error response', done => { + setMockXhrErrorResponse(); + expectStopAppFails(done); + noExpectations(); + }); + + it('Rejects on send rejection', done => { + setMockXhrReject(); + expectStopAppFails(done); + noExpectations(); + }); + }); + + const expectMockSendGet = function() { + expect(mockXhrManager.send) + .toHaveBeenCalledWith( + 'http://198.0.0.100/apps/YouTube', 'GET', undefined, + jasmine.any(Object)); + }; + + const VALID_GET_RESPONSE_ = '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>YouTube</name>' + + '<options allowStop="false"/>' + + '<state>running</state>' + + '<link rel="run" href="run"/>' + + '</service>'; + + const VALID_GET_RESPONSE_EXTRA_DATA_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>YouTube</name>' + + '<state>running</state>' + + '<link rel="run" href="run"/>' + + '<port>8080</port>' + + '<additionalData>' + + '<screenId>e5n3112oskr42pg0td55b38nh4</screenId>' + + '<otherField>2</otherField>' + + '</additionalData>' + + '</service>'; + + const INVALID_GET_RESPONSE_NO_SERVICE_ = + '<?xml version="1.0" encoding="UTF-8"?>'; + + const INVALID_GET_RESPONSE_NO_STATE_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>YouTube</name>' + + '<options allowStop="true"/>' + + '<link rel="run" href="run"/>' + + '</service>'; + + const INVALID_GET_RESPONSE_INVALID_STATE_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>YouTube</name>' + + '<options allowStop="true"/>' + + '<state>xyzzy</state>' + + '<link rel="run" href="run"/>' + + '</service>'; + + const INVALID_GET_RESPONSE_INSTALLABLE_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>YouTube</name>' + + '<options allowStop="true"/>' + + '<state>installable=http://play.google.com/youtube</state>' + + '<link rel="run" href="run"/>' + + '</service>'; + + const INVALID_GET_RESPONSE_NO_NAME_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<options allowStop="true"/>' + + '<state>running</state>' + + '<link rel="run" href="run"/>' + + '</service>'; + + const INVALID_GET_RESPONSE_WRONG_APP_NAME_ = + '<?xml version="1.0" encoding="UTF-8"?>' + + '<service xmlns="urn:dial-multiscreen-org:schemas:dial">' + + '<name>WrongAppName</name>' + + '<options allowStop="true"/>' + + '<state>running</state>' + + '<link rel="run" href="run"/>' + + '</service>'; + + describe('Tests getAppInfo', function() { + it('Returns info from valid response', done => { + setMockXhrResponse(VALID_GET_RESPONSE_); + client.getAppInfo('YouTube').then(appInfo => { + expect(appInfo.name).toEqual('YouTube'); + expect(appInfo.state).toEqual('running'); + expect(appInfo.allowStop).toBe(false); + expect(appInfo.resource).toEqual('run'); + expectMockSendGet(); + done(); + }); + }); + + it('Returns info with extraData', done => { + setMockXhrResponse(VALID_GET_RESPONSE_EXTRA_DATA_); + client.getAppInfo('YouTube').then(appInfo => { + expect(appInfo.name).toEqual('YouTube'); + expect(appInfo.state).toEqual('running'); + expect(appInfo.allowStop).toBe(true); + expect(appInfo.resource).toEqual('run'); + expect(appInfo.extraData.port).toEqual('8080'); + expect(appInfo.extraData.additionalData) + .toEqual( + '<screenId xmlns="urn:dial-multiscreen-org:schemas:dial">' + + 'e5n3112oskr42pg0td55b38nh4</screenId>' + + '<otherField xmlns="urn:dial-multiscreen-org:schemas:dial">2' + + '</otherField>'); + expectMockSendGet(); + done(); + }); + }); + + const expectGetAppInfoFails = function(done) { + client.getAppInfo('YouTube').then( + _ => { + fail('getAppInfo unexpectedly succeeded.'); + }, + e => { + expectMockSendGet(); + done(); + }); + }; + + const testInvalidResponse = function(response, done) { + setMockXhrResponse(response); + expectGetAppInfoFails(done); + }; + + it('Rejects on invalid response 1', done => { + testInvalidResponse('blarg', done); + }); + + it('Rejects on invalid response 2', done => { + testInvalidResponse(INVALID_GET_RESPONSE_NO_SERVICE_, done); + }); + + it('Rejects on invalid response 3', done => { + testInvalidResponse(INVALID_GET_RESPONSE_NO_STATE_, done); + }); + + it('Rejects on invalid response 4', done => { + testInvalidResponse(INVALID_GET_RESPONSE_NO_NAME_, done); + }); + + it('Rejects on invalid response 5', done => { + testInvalidResponse(INVALID_GET_RESPONSE_INVALID_STATE_, done); + }); + + it('Rejects on invalid response 6', done => { + testInvalidResponse(INVALID_GET_RESPONSE_INSTALLABLE_, done); + }); + + it('Rejects on mismatched app name', done => { + testInvalidResponse(INVALID_GET_RESPONSE_WRONG_APP_NAME_, done); + }); + + it('Rejects on error response', done => { + setMockXhrErrorResponse(); + expectGetAppInfoFails(done); + }); + + it('Rejects on send rejection', done => { + setMockXhrReject(); + expectGetAppInfoFails(done); + }); + + it('Rejects with AppInfoNotFoundError', done => { + setMockXhrNotFoundResponse(); + client.getAppInfo('YouTube').then( + _ => { + fail('getAppInfo unexpectedly succeeded.'); + }, + e => { + expect(e instanceof DialClient.AppInfoNotFoundError).toBe(true); + expectMockSendGet(); + done(); + }); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider.js new file mode 100644 index 00000000000..f5c471a82a5 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider.js @@ -0,0 +1,474 @@ +// 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. + +goog.module('mr.DialProvider'); +goog.module.declareLegacyNamespace(); + +const Activity = goog.require('mr.dial.Activity'); +const ActivityRecords = goog.require('mr.dial.ActivityRecords'); +const AppDiscoveryService = goog.require('mr.dial.AppDiscoveryService'); +const Assertions = goog.require('mr.Assertions'); +const CancellablePromise = goog.require('mr.CancellablePromise'); +const DeviceCountsProvider = goog.require('mr.DeviceCountsProvider'); +const DialAnalytics = goog.require('mr.DialAnalytics'); +const DialClient = goog.require('mr.dial.Client'); +const DialPresentationUrl = goog.require('mr.dial.PresentationUrl'); +const DialSink = goog.require('mr.dial.Sink'); +const Logger = goog.require('mr.Logger'); +const MediaSourceUtils = goog.require('mr.MediaSourceUtils'); +const PresentationConnectionState = goog.require('mr.PresentationConnectionState'); +const Provider = goog.require('mr.Provider'); +const ProviderCallbacks = goog.require('mr.dial.ProviderCallbacks'); +const ProviderManagerCallbacks = goog.require('mr.ProviderManagerCallbacks'); +const ProviderName = goog.require('mr.ProviderName'); +const Route = goog.require('mr.Route'); +const RouteRequestError = goog.require('mr.RouteRequestError'); +const RouteRequestResultCode = goog.require('mr.RouteRequestResultCode'); +const SinkAppStatus = goog.require('mr.dial.SinkAppStatus'); +const SinkAvailability = goog.require('mr.SinkAvailability'); +const SinkDiscoveryService = goog.require('mr.dial.SinkDiscoveryService'); +const SinkList = goog.require('mr.SinkList'); +const SinkUtils = goog.require('mr.SinkUtils'); + +/** + * DIAL implementation of Media Route Provider. + * @implements {Provider} + * @implements {ProviderCallbacks} + * @implements {DeviceCountsProvider} + */ +const DialProvider = class { + /** + * @param {!ProviderManagerCallbacks} providerManagerCallbacks + * @param {!SinkDiscoveryService=} sinkDiscoveryService + * @param {!AppDiscoveryService=} appDiscoveryService + * @final + */ + constructor( + providerManagerCallbacks, sinkDiscoveryService = undefined, + appDiscoveryService = undefined) { + /** @private @const {!ProviderManagerCallbacks} */ + this.providerManagerCallbacks_ = providerManagerCallbacks; + + /** @private @const {!SinkDiscoveryService} */ + this.sinkDiscoveryService_ = + sinkDiscoveryService || new SinkDiscoveryService(this); + + /** @private @const {!ActivityRecords} */ + this.activityRecords_ = new ActivityRecords(this); + + /** @private {?AppDiscoveryService} */ + this.appDiscoveryService_ = appDiscoveryService || null; + + /** @private @const {?Logger} */ + this.logger_ = Logger.getInstance('mr.DialProvider'); + } + + /** + * @override + */ + getName() { + return ProviderName.DIAL; + } + + /** + * @override + */ + getDeviceCounts() { + return this.sinkDiscoveryService_.getDeviceCounts(); + } + + /** + * @override + */ + initialize(config) { + const sinkQueryEnabled = + (config && config.enable_dial_sink_query == false) ? false : true; + this.logger_.info('Dial sink query enabled: ' + sinkQueryEnabled + '...'); + + this.activityRecords_.init(); + this.sinkDiscoveryService_.init(); + + if (sinkQueryEnabled) { + this.appDiscoveryService_ = this.appDiscoveryService_ || + new AppDiscoveryService(this.sinkDiscoveryService_, + this.activityRecords_); + this.appDiscoveryService_.init(); + } else { + this.appDiscoveryService_ = null; + } + + this.maybeStartAppDiscovery_(); + } + + /** + * @override + */ + getAvailableSinks(sourceUrn) { + // Prevent SinkDiscoveryService to return cached available sinks. + if (!this.appDiscoveryService_) { + return SinkList.EMPTY; + } + + this.logger_.fine('GetAvailableSinks for ' + sourceUrn); + const dialMediaSource = DialPresentationUrl.create(sourceUrn); + return dialMediaSource ? + this.sinkDiscoveryService_.getSinksByAppName(dialMediaSource.appName) : + SinkList.EMPTY; + } + + /** + * @override + */ + startObservingMediaSinks(sourceUrn) { + if (!this.appDiscoveryService_) { + return; + } + + const dialMediaSource = DialPresentationUrl.create(sourceUrn); + if (dialMediaSource) { + this.appDiscoveryService_.registerApp(dialMediaSource.appName); + this.maybeStartAppDiscovery_(); + } + } + + /** + * @override + */ + stopObservingMediaSinks(sourceUrn) { + if (!this.appDiscoveryService_) { + return; + } + + const dialMediaSource = DialPresentationUrl.create(sourceUrn); + if (dialMediaSource) { + this.appDiscoveryService_.unregisterApp(dialMediaSource.appName); + this.maybeStopAppDiscovery_(); + } + } + + /** + * @override + */ + startObservingMediaRoutes(sourceUrn) { + this.maybeStartAppDiscovery_(); + } + + /** + * @override + */ + stopObservingMediaRoutes(sourceUrn) { + this.maybeStopAppDiscovery_(); + } + + /** + * @private + */ + maybeStopAppDiscovery_() { + if (!this.appDiscoveryService_) { + return; + } + + if (this.sinkDiscoveryService_.getSinkCount() == 0 || + (this.appDiscoveryService_.getAppCount() == 0 && + this.activityRecords_.getActivityCount() == 0)) { + this.appDiscoveryService_.stop(); + } + } + + /** + * @private + */ + maybeStartAppDiscovery_() { + if (!this.appDiscoveryService_) { + return; + } + + if (this.sinkDiscoveryService_.getSinkCount() > 0 && + (this.appDiscoveryService_.getAppCount() > 0 || + this.activityRecords_.getActivityCount() > 0)) { + this.appDiscoveryService_.start(); + } + } + + /** + * @override + */ + getSinkById(id) { + const dialSink = this.sinkDiscoveryService_.getSinkById(id); + return dialSink ? dialSink.getMrSink() : null; + } + + /** + * @override + */ + getRoutes() { + return this.activityRecords_.getRoutes(); + } + + /** + * @override + */ + createRoute( + sourceUrn, sinkId, presentationId, offTheRecord, timeoutMillis, + opt_origin, opt_tabId) { + const sink = this.sinkDiscoveryService_.getSinkById(sinkId); + if (!sink) { + DialAnalytics.recordCreateRoute( + DialAnalytics.DialRouteCreation.FAILED_NO_SINK); + return CancellablePromise.reject(Error('Unkown sink: ' + sinkId)); + } + SinkUtils.getInstance().recentLaunchedDevice = + new SinkUtils.DeviceData(sink.getModelName(), sink.getIpAddress()); + const dialMediaSource = DialPresentationUrl.create(sourceUrn); + if (!dialMediaSource) { + return CancellablePromise.reject(Error('No app name set.')); + } + const appName = dialMediaSource.appName; + const dialClient = this.newClient_(sink); + + return CancellablePromise.forPromise( + /** @type {!Promise<!Route>} */ + (dialClient.getAppInfo(appName) + .then(appInfo => { + if (appInfo.state == DialClient.DialAppState.RUNNING) { + return dialClient.stopApp(appName); + } + }) + .then(() => { + return dialClient.launchApp( + appName, dialMediaSource.launchParameter); + }) + .then(() => { + return this.addRoute( + sinkId, sourceUrn, true, appName, presentationId, + offTheRecord); + }) + .catch(err => { + DialAnalytics.recordCreateRoute( + DialAnalytics.DialRouteCreation.FAILED_LAUNCH_APP); + throw err; + }))); + } + + /** + * @override + */ + joinRoute( + sourceUrn, presentationId, offTheRecord, timeoutMillis, origin, tabId) { + return CancellablePromise.reject(Error('Not supported')); + } + + /** + * @override + */ + connectRouteByRouteId(sourceUrn, routeId, presentationId, origin, tabId) { + return CancellablePromise.reject(Error('Not supported')); + } + + /** + * @override + */ + detachRoute(routeId) {} + + /** + * @param {string} sinkId + * @param {?string} sourceUrn + * @param {boolean} isLocal + * @param {string} appName + * @param {string} presentationId + * @param {boolean} offTheRecord + * @return {!Route} The route that was just added. + */ + addRoute(sinkId, sourceUrn, isLocal, appName, presentationId, offTheRecord) { + DialAnalytics.recordCreateRoute( + DialAnalytics.DialRouteCreation.ROUTE_CREATED); + const route = Route.createRoute( + presentationId, this.getName(), sinkId, sourceUrn, isLocal, appName, + null); + route.offTheRecord = offTheRecord; + this.activityRecords_.add(new Activity(route, appName)); + return route; + } + + /** + * @override + */ + onSinkAdded(sink) { + this.providerManagerCallbacks_.onSinkAvailabilityUpdated( + this, SinkAvailability.PER_SOURCE); + if (this.appDiscoveryService_) { + this.maybeStartAppDiscovery_(); + this.appDiscoveryService_.scanSink(sink); + } + this.providerManagerCallbacks_.onSinksUpdated(); + SinkUtils.getInstance().recentDiscoveredDevice = + new SinkUtils.DeviceData(sink.getModelName(), sink.getIpAddress()); + } + + /** + * @override + */ + onSinksRemoved(sinks) { + if (this.sinkDiscoveryService_.getSinkCount() == 0) { + this.providerManagerCallbacks_.onSinkAvailabilityUpdated( + this, SinkAvailability.UNAVAILABLE); + } + this.maybeStopAppDiscovery_(); + sinks.forEach(sink => { + this.activityRecords_.removeBySinkId(sink.getId()); + }); + this.providerManagerCallbacks_.onSinksUpdated(); + } + + /** + * @override + */ + onSinkUpdated(sink) { + this.providerManagerCallbacks_.onSinksUpdated(); + } + + /** + * @override + */ + onActivityAdded(activity) { + this.maybeStartAppDiscovery_(); + this.providerManagerCallbacks_.onRouteAdded(this, activity.route); + } + + /** + * @override + */ + onActivityRemoved(activity) { + const route = activity.route; + if (route.isLocal) { + this.providerManagerCallbacks_.onPresentationConnectionStateChanged( + route.id, PresentationConnectionState.TERMINATED); + } + this.maybeStopAppDiscovery_(); + this.providerManagerCallbacks_.onRouteRemoved(this, route); + } + + /** + * @override + */ + onActivityUpdated(activity) { + this.providerManagerCallbacks_.onRouteUpdated(this, activity.route); + } + + /** + * @override + */ + terminateRoute(routeId) { + const activity = this.activityRecords_.getByRouteId(routeId); + if (!activity) { + return Promise.reject(new RouteRequestError( + RouteRequestResultCode.ROUTE_NOT_FOUND, + 'Route in DIAL provider not found for routeId ' + routeId)); + } + this.activityRecords_.removeByRouteId(routeId); + const sink = this.sinkDiscoveryService_.getSinkById(activity.route.sinkId); + if (!sink) { + return Promise.reject(new RouteRequestError( + RouteRequestResultCode.ROUTE_NOT_FOUND, + 'Sink in DIAL provider not found for sinkId ' + + activity.route.sinkId)); + } + return this.newClient_(sink).stopApp(activity.appName); + } + + /** + * @override + */ + getMirrorSettings(sinkId) { + throw new Error('Not implemented.'); + } + + /** + * @override + */ + getMirrorServiceName(sinkId) { + return null; + } + + /** + * @override + */ + onMirrorActivityUpdated(routeId) {} + + /** + * @override + */ + sendRouteMessage(routeId, message, opt_extraInfo) { + return Promise.reject(Error('DIAL sending messages is not supported')); + } + + /** + * @override + */ + sendRouteBinaryMessage(routeId, message) { + return Promise.reject(Error('DIAL sending messages is not supported')); + } + + /** + * @override + */ + canRoute(sourceUrn, sinkId) { + const sink = this.sinkDiscoveryService_.getSinkById(sinkId); + if (!sink) { + return false; + } + + if (MediaSourceUtils.isMirrorSource(sourceUrn)) { + return false; + } + + const dialMediaSource = DialPresentationUrl.create(sourceUrn); + if (!dialMediaSource) { + return false; + } + return sink.getAppStatus(dialMediaSource.appName) == + SinkAppStatus.AVAILABLE; + } + + /** + * @override + */ + canJoin(sourceUrn, presentationId, route) { + return false; + } + + /** + * @override + */ + searchSinks(sourceUrn, searchCriteria) { + // Not implemented. + return Assertions.rejectNotImplemented(); + } + + /** + * @override + */ + createMediaRouteController(routeId, controllerRequest, observer) { + // Not implemented. + return Assertions.rejectNotImplemented(); + } + + /** + * @override + */ + provideSinks(sinks) { + this.sinkDiscoveryService_.addSinks(sinks); + } + + /** + * @param {!DialSink} sink + * @return {!DialClient.Client} + * @private + */ + newClient_(sink) { + return new DialClient.Client(sink); + } +}; + +exports = DialProvider; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_callbacks.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_callbacks.js new file mode 100644 index 00000000000..2c7284922b0 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_callbacks.js @@ -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. + +/** + * @fileoverview DIAL provider callbacks used for other DIAL services to inform + * activity or sink updates. + */ + +goog.provide('mr.dial.ActivityCallbacks'); +goog.provide('mr.dial.ProviderCallbacks'); +goog.provide('mr.dial.SinkDiscoveryCallbacks'); + + + +/** + * @record + */ +mr.dial.ActivityCallbacks = class { + /** + * @param {!mr.dial.Activity } activity + */ + onActivityAdded(activity) {} + + /** + * @param {!mr.dial.Activity } activity + */ + onActivityRemoved(activity) {} + + /** + * @param {!mr.dial.Activity } activity + */ + onActivityUpdated(activity) {} +}; + + + +/** + * @record + */ +mr.dial.SinkDiscoveryCallbacks = class { + /** + * @param {!mr.dial.Sink} sink Sink that has been added. + */ + onSinkAdded(sink) {} + + /** + * @param {!Array.<!mr.dial.Sink>} sinks Sinks that have been removed. + */ + onSinksRemoved(sinks) {} + + /** + * @param {!mr.dial.Sink} sink Sink that has been updated. + */ + onSinkUpdated(sink) {} +}; + + + +/** + * @record + * @extends {mr.dial.ActivityCallbacks} + * @extends {mr.dial.SinkDiscoveryCallbacks} + */ +mr.dial.ProviderCallbacks = class {}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_test.js new file mode 100644 index 00000000000..c82f8b9931e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_provider_test.js @@ -0,0 +1,256 @@ +// 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. + +goog.module('mr.DialProviderTest'); +goog.setTestOnly('mr.DialProviderTest'); + +const Activity = goog.require('mr.dial.Activity'); +const DialClient = goog.require('mr.dial.Client'); +const DialProvider = goog.require('mr.DialProvider'); +const DialSink = goog.require('mr.dial.Sink'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const PresentationConnectionState = goog.require('mr.PresentationConnectionState'); +const Route = goog.require('mr.Route'); +const SinkAvailability = goog.require('mr.SinkAvailability'); + + +describe('DialProvider tests', function() { + let provider; + let mockPmCallbacks; + const pmCallbackMethods = [ + 'getRouteMessageEventTarget', 'getProviderFromRouteId', + 'onPresentationConnectionClosed', 'onPresentationConnectionStateChanged', + 'onRouteMessage', 'onRouteRemoved', 'onRouteAdded', 'onSinksUpdated', + 'onSinkAvailabilityUpdated' + ]; + let mockSinkDiscoveryService; + const sinkDiscoveryServiceMethods = + ['init', 'getSinkCount', 'init', 'getSinksByAppName', 'getSinkById']; + let mockAppDiscoveryService; + const appDiscoveryServiceMethods = [ + 'init', 'start', 'stop', 'registerApp', 'unregisterApp', 'getAppCount', + 'scanSink' + ]; + let mockDialClient; + + const appInfo = new DialClient.AppInfo(); + const youTubeUrl = 'dial:YouTube?postData=dj0xMjM='; + + beforeEach(function() { + mockPmCallbacks = + jasmine.createSpyObj('ProviderManagerCallbacks', pmCallbackMethods); + + mockSinkDiscoveryService = jasmine.createSpyObj( + 'SinkDiscoveryService', sinkDiscoveryServiceMethods); + mockAppDiscoveryService = + jasmine.createSpyObj('AppDiscoveryService', appDiscoveryServiceMethods); + + provider = new DialProvider( + mockPmCallbacks, mockSinkDiscoveryService, mockAppDiscoveryService); + provider.initialize({enable_dial_discovery: true}); + expect(mockAppDiscoveryService.init).toHaveBeenCalled(); + + const fakeDialSink = new DialSink('Fake DIAL sink', 'uniqueId'); + fakeDialSink.setDialAppUrl(youTubeUrl); + mockDialClient = jasmine.createSpyObj( + 'dialClient', ['getAppInfo', 'launchApp', 'stopApp']); + spyOn(DialProvider.prototype, 'newClient_').and.returnValue(mockDialClient); + mockSinkDiscoveryService.getSinkById.and.returnValue(fakeDialSink); + appInfo.name = 'YouTube'; + appInfo.state = DialClient.DialAppState.STOPPED; + mr.DialAnalytics.recordCreateRoute = jasmine.createSpy('recordCreateRoute'); + }); + + afterEach(function() { + PersistentDataManager.clear(); + }); + + describe('startObservingMediaSinks Test', function() { + it('Handles non-dial sink query', function() { + provider.startObservingMediaSinks('urn:not-dial:YouTube'); + expect(mockAppDiscoveryService.registerApp).not.toHaveBeenCalled(); + }); + + it('Handles valid dial sink query, no sinks', function() { + mockSinkDiscoveryService.getSinkCount.and.returnValue(0); + mockAppDiscoveryService.getAppCount.and.returnValue(1); + provider.startObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.registerApp) + .toHaveBeenCalledWith('YouTube'); + expect(mockAppDiscoveryService.start).not.toHaveBeenCalled(); + }); + + it('Handles valid dial sink query, at least one sink', function() { + mockSinkDiscoveryService.getSinkCount.and.returnValue(1); + mockAppDiscoveryService.getAppCount.and.returnValue(1); + provider.startObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.registerApp) + .toHaveBeenCalledWith('YouTube'); + expect(mockAppDiscoveryService.start).toHaveBeenCalled(); + }); + }); + + describe('stopObservingMediaSinks Test', function() { + it('Handles non-dial sink query', function() { + provider.stopObservingMediaSinks('urn:not-dial:YouTube'); + expect(mockAppDiscoveryService.unregisterApp).not.toHaveBeenCalled(); + }); + + it('Handles valid dial sink query', function() { + mockAppDiscoveryService.getAppCount.and.returnValue(0); + provider.stopObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.unregisterApp) + .toHaveBeenCalledWith('YouTube'); + }); + + it('Handles valid dial sink query, app query remains', function() { + mockAppDiscoveryService.getAppCount.and.returnValue(1); + provider.stopObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.unregisterApp) + .toHaveBeenCalledWith('YouTube'); + }); + }); + + describe('onPresentationConnectionStateChanged Test', function() { + it('Changes presentation state to terminated', function() { + const route = provider.addRoute( + 'sink1', youTubeUrl, true, 'app1', 'presentationId1'); + provider.onActivityRemoved(new Activity(route, 'app1')); + expect(mockPmCallbacks.onPresentationConnectionStateChanged) + .toHaveBeenCalledWith( + route.id, PresentationConnectionState.TERMINATED); + expect(mockPmCallbacks.onRouteRemoved).toHaveBeenCalled(); + }); + + it('Does not change state for non-local presentation', function() { + const route = provider.addRoute( + 'sink1', youTubeUrl, false, 'app1', 'presentationId1'); + provider.onActivityRemoved(new Activity(route, 'app1')); + expect(mockPmCallbacks.onPresentationConnectionStateChanged) + .not.toHaveBeenCalled(); + expect(mockPmCallbacks.onRouteRemoved).toHaveBeenCalled(); + }); + }); + + describe('createRoute Test', function() { + it('Creates a route', function(done) { + mockDialClient.getAppInfo.and.returnValue(Promise.resolve(appInfo)); + mockDialClient.launchApp.and.returnValue(Promise.resolve()); + provider.createRoute(youTubeUrl, 'sink1', 'presentationId1', false) + .promise.then( + route => { + expect(route.sinkId).toBe('sink1'); + expect(route.mediaSource).toBe(youTubeUrl); + expect(route.offTheRecord).toBe(false); + expect(mr.DialAnalytics.recordCreateRoute) + .toHaveBeenCalledWith( + mr.DialAnalytics.DialRouteCreation.ROUTE_CREATED); + done(); + }, + e => { + done.fail('Unexpected error: ' + e.message); + }); + }); + + it('Creates an off-the-record route', function(done) { + mockDialClient.getAppInfo.and.returnValue(Promise.resolve(appInfo)); + mockDialClient.launchApp.and.returnValue(Promise.resolve()); + provider.createRoute(youTubeUrl, 'sink1', 'presentationId1', true) + .promise.then( + route => { + expect(route.sinkId).toBe('sink1'); + expect(route.mediaSource).toBe(youTubeUrl); + expect(route.offTheRecord).toBe(true); + expect(mr.DialAnalytics.recordCreateRoute) + .toHaveBeenCalledWith( + mr.DialAnalytics.DialRouteCreation.ROUTE_CREATED); + done(); + }, + e => { + done.fail('Unexpected error: ' + e.message); + }); + }); + + it('Fails to create a route when get app info fails', function(done) { + mockDialClient.getAppInfo.and.returnValue(Promise.reject('fail')); + provider.createRoute(youTubeUrl, 'sink1', 'presentationId1', true) + .promise.then(done.fail, e => { + expect(mr.DialAnalytics.recordCreateRoute) + .toHaveBeenCalledWith( + mr.DialAnalytics.DialRouteCreation.FAILED_LAUNCH_APP); + done(); + }); + }); + + it('Fails to create a route when launch app fails', function(done) { + mockDialClient.getAppInfo.and.returnValue(Promise.resolve(appInfo)); + mockDialClient.launchApp.and.returnValue(Promise.reject('fail')); + provider.createRoute(youTubeUrl, 'sink1', 'presentationId1', true) + .promise.then(done.fail, e => { + expect(mr.DialAnalytics.recordCreateRoute) + .toHaveBeenCalledWith( + mr.DialAnalytics.DialRouteCreation.FAILED_LAUNCH_APP); + done(); + }); + }); + + it('Starts and stop app discovery', function() { + mockSinkDiscoveryService.getSinkCount.and.returnValue(1); + let appCount = 0; + mockAppDiscoveryService.getAppCount.and.callFake(() => appCount); + const route = Route.createRoute( + 'presentationId', 'providerName', 'sinkId', 'source', true, + 'description', null); + const activity = new Activity(route, 'YouTube'); + provider.activityRecords_.add(activity); + expect(mockAppDiscoveryService.start).toHaveBeenCalled(); + + appCount++; + provider.startObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.registerApp) + .toHaveBeenCalledWith('YouTube'); + + appCount--; + provider.stopObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.stop).not.toHaveBeenCalled(); + + provider.activityRecords_.removeByRouteId(route.id); + expect(mockAppDiscoveryService.stop).toHaveBeenCalled(); + }); + + it('Sets SinkAvailability to UNAVAILABLE if no more sinks', () => { + mockSinkDiscoveryService.getSinkCount.and.returnValue(0); + // Note: This should also work if an non-empty list if passed in. For + // simplicity, an empty list is used here. + provider.onSinksRemoved([]); + expect(mockPmCallbacks.onSinkAvailabilityUpdated) + .toHaveBeenCalledWith(provider, SinkAvailability.UNAVAILABLE); + }); + }); + + describe('Disables Dial sink query', function() { + beforeEach(function() { + PersistentDataManager.clear(); + provider.initialize({enable_dial_sink_query: false}); + expect(mockAppDiscoveryService.start).not.toHaveBeenCalled(); + }); + + it('Starting and stopping observing media sinks does nothing', function() { + provider.startObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.registerApp).not.toHaveBeenCalled(); + + provider.stopObservingMediaSinks(youTubeUrl); + expect(mockAppDiscoveryService.unregisterApp).not.toHaveBeenCalled(); + }); + + it('onSinkAdded does not start app discovery', function() { + const sink = new DialSink('s1', 'sink1'); + provider.onSinkAdded(sink); + expect(mockAppDiscoveryService.scanSink).not.toHaveBeenCalled(); + + const sinkList = provider.getAvailableSinks(); + expect(sinkList.sinks.length).toBe(0); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink.js new file mode 100644 index 00000000000..8cf0e1fe39d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink.js @@ -0,0 +1,309 @@ +// 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. + +goog.module('mr.dial.Sink'); +goog.module.declareLegacyNamespace(); + +const Sink = goog.require('mr.Sink'); +const SinkAppStatus = goog.require('mr.dial.SinkAppStatus'); + + +/** + * A wrapper for Sink containing DIAL specific data. + */ +const DialSink = class { + /** + * @param {string} friendlyName + * @param {string} uniqueId + * @final + */ + constructor(friendlyName, uniqueId) { + /** @private {?string} */ + this.ipAddress_ = null; + + /** @private {?number} */ + this.port_ = null; + + /** @private {?string} */ + this.dialAppUrl_ = null; + + /** @private {?string} */ + this.deviceDescriptionUrl_ = null; + + /** @private {?string} */ + this.modelName_ = null; + + /** @private @const {!Sink} */ + this.mrSink_ = new Sink(uniqueId, friendlyName); + + /** + * Holds the status of applications that may be available on the sink. + * Keys are application Names. + * @private {!Object<string, SinkAppStatus>} + */ + this.appStatusMap_ = {}; + + /** + * Holds the timestamp when the status of applications was set. + * @private {!Object<string, number>} + */ + this.appStatusTimeStamp_ = {}; + + /** @private {boolean} */ + this.supportsAppAvailability_ = false; + } + + /** + * @return {!Sink} + */ + getMrSink() { + return this.mrSink_; + } + + /** + * @return {string} A human readable name for the sink. + */ + getFriendlyName() { + return this.mrSink_.friendlyName; + } + + /** + * @param {string} friendlyName + * @return {!mr.dial.Sink} This sink. + */ + setFriendlyName(friendlyName) { + this.mrSink_.friendlyName = friendlyName; + return this; + } + + /** + * @return {?string} sink model name if known. + */ + getModelName() { + return this.modelName_; + } + + /** + * @param {?string} modelName + * @return {!mr.dial.Sink} This sink. + */ + setModelName(modelName) { + this.modelName_ = modelName; + return this; + } + + /** + * @return {string} An identifier for this sink. + */ + getId() { + return this.mrSink_.id; + } + + /** + * @param {string} id + * @return {!mr.dial.Sink} This sink. + */ + setId(id) { + this.mrSink_.id = id; + return this; + } + + /** + * @return {boolean} Whether this sink supports queries for DIAL app + * availability. + */ + supportsAppAvailability() { + return this.supportsAppAvailability_; + } + + /** + * Sets whether this sink supports DIAL app availability queries. + * @param {boolean} availability + * @return {!mr.dial.Sink} This sink. + */ + setSupportsAppAvailability(availability) { + this.supportsAppAvailability_ = availability; + return this; + } + + /** + * Updates sink properties. + * Fields that can be updated: friendlyName, dialAppUrl_, + * deviceDescriptionUrl_, ipAddress_, port_. + * @param {!mr.dial.Sink} sink + * @return {boolean} Whether the update resulted in changes to the sink. + */ + update(sink) { + if (this.getId() != sink.getId()) { + return false; + } + + let updated = false; + + if (this.mrSink_.friendlyName != sink.mrSink_.friendlyName) { + this.mrSink_.friendlyName = sink.mrSink_.friendlyName; + updated = true; + } + + if (this.dialAppUrl_ != sink.dialAppUrl_) { + this.dialAppUrl_ = sink.dialAppUrl_; + updated = true; + } + + if (this.deviceDescriptionUrl_ != sink.deviceDescriptionUrl_) { + this.deviceDescriptionUrl_ = sink.deviceDescriptionUrl_; + updated = true; + } + + if (this.ipAddress_ != sink.ipAddress_) { + this.ipAddress_ = sink.ipAddress_; + updated = true; + } + + if (this.port_ != sink.port_) { + this.port_ = sink.port_; + updated = true; + } + + return updated; + } + + /** + * @return {?string} The IP address of the sink, if any. + */ + getIpAddress() { + return this.ipAddress_; + } + + /** + * @param {?string} ipAddress The sink IP address. + * @return {!mr.dial.Sink} This sink. + */ + setIpAddress(ipAddress) { + this.ipAddress_ = ipAddress; + return this; + } + + /** + * @return {?number} The port number of the secure channel service. + */ + getPort() { + return this.port_; + } + + /** + * @param {?number} port + * @return {!mr.dial.Sink} This sink. + */ + setPort(port) { + this.port_ = port; + return this; + } + + /** + * @return {?string} The DIAL application URL, if any. + */ + getDialAppUrl() { + return this.dialAppUrl_; + } + + /** + * @param {string} url The DIAL app URL. + * @return {!mr.dial.Sink} This sink. + */ + setDialAppUrl(url) { + this.dialAppUrl_ = url; + return this; + } + + /** + * @return {?string} The DIAL device description URL, if any. + */ + getDeviceDescriptionUrl() { + return this.deviceDescriptionUrl_; + } + + /** + * @param {string} url The DIAL device description URL. + * @return {!mr.dial.Sink} This sink. + */ + setDeviceDescriptionUrl(url) { + this.deviceDescriptionUrl_ = url; + return this; + } + + /** + * Gets the availability of an application. + * @param {string} appName + * @return {SinkAppStatus} The status of the application, or null if it was + * not set. + */ + getAppStatus(appName) { + return this.appStatusMap_[appName] || SinkAppStatus.UNKNOWN; + } + + /** + * Gets the time stamp of the availability of an application was set. + * @param {string} appName + * @return {?number} the number of milliseconds between midnight, January 1, + * 1970 and the current time, or null if availability was not set. + */ + getAppStatusTimeStamp(appName) { + return this.appStatusTimeStamp_[appName] || null; + } + + /** + * Sets the availability of an application. + * @param {string} appName + * @param {SinkAppStatus} status + * @return {!mr.dial.Sink} This sink. + */ + setAppStatus(appName, status) { + this.appStatusMap_[appName] = status; + this.appStatusTimeStamp_[appName] = Date.now(); + return this; + } + + /** + * Clears all app status from the sink. + * @return {!mr.dial.Sink} This sink. + */ + clearAppStatus() { + this.appStatusMap_ = {}; + this.appStatusTimeStamp_ = {}; + return this; + } + + /** + * @return {string} String suitable for fine logging. + */ + toDebugString() { + return 'name = ' + this.mrSink_.friendlyName + + (this.ipAddress_ ? ', ip = ' + this.ipAddress_ : '') + + (this.modelName_ ? ', model = ' + this.modelName_ : '') + + ', apps = ' + JSON.stringify(this.appStatusMap_); + } + + /** + * Creates a new sink and copies the fields of the input sink to this. + * @param {!Object<string, *>} sink The object containing data fields. + * @return {!mr.dial.Sink} A newly created sink. + */ + static createFrom(sink) { + const newSink = new DialSink(sink.mrSink_.friendlyName, ''); + newSink.mrSink_.id = sink.mrSink_.id; // Override the sink ID. + newSink.ipAddress_ = sink.ipAddress_; + newSink.port_ = sink.port_; + newSink.dialAppUrl_ = sink.dialAppUrl_; + newSink.deviceDescriptionUrl_ = sink.deviceDescriptionUrl_; + newSink.modelName_ = sink.modelName_; + newSink.appStatusMap_ = sink.appStatusMap_; + newSink.appStatusTimeStamp_ = sink.appStatusTimeStamp_; + newSink.supportsAppAvailability_ = sink.supportsAppAvailability_; + return newSink; + } +}; + + +exports = DialSink; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service.js new file mode 100644 index 00000000000..93d062e3519 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service.js @@ -0,0 +1,334 @@ +// 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. + +goog.module('mr.dial.SinkDiscoveryService'); +goog.module.declareLegacyNamespace(); + +const DeviceCounts = goog.require('mr.DeviceCounts'); +const DeviceCountsProvider = goog.require('mr.DeviceCountsProvider'); +const DialAnalytics = goog.require('mr.DialAnalytics'); +const DialSink = goog.require('mr.dial.Sink'); +const Logger = goog.require('mr.Logger'); +const PersistentData = goog.require('mr.PersistentData'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const SinkAppStatus = goog.require('mr.dial.SinkAppStatus'); +const SinkDiscoveryCallbacks = goog.require('mr.dial.SinkDiscoveryCallbacks'); +const SinkList = goog.require('mr.SinkList'); + + +/** + * Implements local discovery using DIAL. + * DIAL specification: + * http://www.dial-multiscreen.org/dial-protocol-specification + * @implements {PersistentData} + * @implements {DeviceCountsProvider} + */ +class SinkDiscoveryService { + /** + * @param {!SinkDiscoveryCallbacks} sinkCallBacks + * @final + */ + constructor(sinkCallBacks) { + /** + * @private @const {!SinkDiscoveryCallbacks} + */ + this.sinkCallBacks_ = sinkCallBacks; + + /** + * @private @const {?Logger} + */ + this.logger_ = Logger.getInstance('mr.dial.SinkDiscoveryService'); + + /** + * The current set of *accessible* receivers, indexed by id. + * @private @const {!Map<string, !DialSink>} + */ + this.sinkMap_ = new Map(); + + /** + * The most recent snapshot of device counts. + * Updated when a DIAL onDeviceList or onError event is received. + * Part of PersistentData. + * @private {!DeviceCounts} + */ + this.deviceCounts_ = {availableDeviceCount: 0, knownDeviceCount: 0}; + + /** + * The last time device counts were recorded in DialAnalytics. + * Persistent data. + * @private {number} + */ + this.deviceCountMetricsRecordTime_ = 0; + } + + /** + * Initializes the service. Must be called before any other methods. + */ + init() { + PersistentDataManager.register(this); + } + + /** + * Add |sinks| to sink map. Remove outdated sinks that are in sink map but not + * in |sinks|. + * @param {!Array<!mojo.Sink>} sinks list of sinks discovered by Media Router. + */ + addSinks(sinks) { + this.logger_.info('addSinks returned ' + sinks.length + ' sinks'); + this.logger_.fine(() => '....the list is: ' + JSON.stringify(sinks)); + + const oldSinkIds = new Set(this.sinkMap_.keys()); + sinks.forEach(mojoSink => { + const dialSink = SinkDiscoveryService.convertSink_(mojoSink); + this.mayAddSink_(dialSink); + oldSinkIds.delete(dialSink.getId()); + }); + + let removedSinks = []; + oldSinkIds.forEach(sinkId => { + const sink = this.sinkMap_.get(sinkId); + removedSinks.push(sink); + this.sinkMap_.delete(sinkId); + }); + + if (removedSinks.length > 0) { + this.sinkCallBacks_.onSinksRemoved(removedSinks); + } + + // Record device count for feedback. + const sinkCount = this.getSinkCount(); + this.deviceCounts_ = { + availableDeviceCount: sinkCount, + knownDeviceCount: sinkCount + }; + } + + /** + * Updates deviceCounts_ with the given counts, and reports to analytics if + * applicable. + * @param {number} availableDeviceCount + * @param {number} knownDeviceCount + * @private + */ + recordDeviceCounts_(availableDeviceCount, knownDeviceCount) { + this.deviceCounts_ = { + availableDeviceCount: availableDeviceCount, + knownDeviceCount: knownDeviceCount + }; + if (Date.now() - this.deviceCountMetricsRecordTime_ < + SinkDiscoveryService.DEVICE_COUNT_METRIC_THRESHOLD_MS_) { + return; + } + DialAnalytics.recordDeviceCounts(this.deviceCounts_); + this.deviceCountMetricsRecordTime_ = Date.now(); + } + + /** + * Adds or updates an existing sink with the given sink. + * @param {!DialSink} sink The new or updated sink. + * @private + */ + mayAddSink_(sink) { + this.logger_.fine('mayAddSink, id = ' + sink.getId()); + const sinkToUpdate = this.sinkMap_.get(sink.getId()); + if (sinkToUpdate) { + if (sinkToUpdate.update(sink)) { + this.logger_.fine('Updated sink ' + sinkToUpdate.getId()); + this.sinkCallBacks_.onSinkUpdated(sinkToUpdate); + } + } else { + this.logger_.fine( + () => `Adding new sink ${sink.getId()}: ${sink.toDebugString()}`); + this.sinkMap_.set(sink.getId(), sink); + this.sinkCallBacks_.onSinkAdded(sink); + } + } + + /** + * Converts a mojo.Sink to a DialSink. + * @param {!mojo.Sink} mojoSink returned by Media Router at browser side. + * @return {!DialSink} DIAL sink. + * @private + */ + static convertSink_(mojoSink) { + + const uniqueId = mojoSink.sink_id; + const extraData = mojoSink.extra_data.dial_media_sink; + const isDiscoveryOnly = + SinkDiscoveryService.isDiscoveryOnly_(extraData.model_name); + + const ip_address = extraData.ip_address.address_bytes ? + extraData.ip_address.address_bytes.join('.') : + extraData.ip_address.address.join('.'); + return new DialSink(mojoSink.name, uniqueId) + .setIpAddress(ip_address) + .setDialAppUrl(extraData.app_url.url) + .setModelName(extraData.model_name) + .setSupportsAppAvailability(!isDiscoveryOnly); + } + + /** + * Returns true if DIAL (SSDP) was only used to discover this sink, and it is + * not expected to support other DIAL features (app discovery, activity + * discovery, etc.) + * @param {string} modelName + * @return {boolean} + * @private + */ + static isDiscoveryOnly_(modelName) { + return SinkDiscoveryService.DISCOVERY_ONLY_RE_.test(modelName); + } + + /** + * Returns the sink with the given ID, or null if not found. + * @param {string} sinkId + * @return {?DialSink} + */ + getSinkById(sinkId) { + return this.sinkMap_.get(sinkId) || null; + } + + /** + * Returns sinks that report availability of the given app name. + * @param {string} appName + * @return {!SinkList} + */ + getSinksByAppName(appName) { + const sinks = []; + this.sinkMap_.forEach(dialSink => { + if (dialSink.getAppStatus(appName) == SinkAppStatus.AVAILABLE) + sinks.push(dialSink.getMrSink()); + }); + return new SinkList( + sinks, SinkDiscoveryService.APP_ORIGIN_WHITELIST_[appName]); + } + + /** + * Returns current sinks. + * @return {!Array<!DialSink>} + */ + getSinks() { + return Array.from(this.sinkMap_.values()); + } + + /** + * @override + */ + getDeviceCounts() { + return this.deviceCounts_; + } + + /** + * @return {number} + */ + getSinkCount() { + return this.sinkMap_.size; + } + + /** + * Invoked when the app status of a sink changes. + * @param {string} appName + * @param {!DialSink} sink The sink whose status changed. + */ + onAppStatusChanged(appName, sink) { + this.sinkCallBacks_.onSinkUpdated(sink); + } + + /** + * @override + */ + getStorageKey() { + return 'dial.DialSinkDiscoveryService'; + } + + /** + * @override + */ + getData() { + return [ + new SinkDiscoveryService.PersistentData_( + Array.from(this.sinkMap_), this.deviceCounts_), + {'deviceCountMetricsRecordTime': this.deviceCountMetricsRecordTime_} + ]; + } + + /** + * @override + */ + loadSavedData() { + const tempData = + /** @type {?SinkDiscoveryService.PersistentData_} */ ( + PersistentDataManager.getTemporaryData(this)); + if (tempData) { + for (const entry of tempData.sinks) { + this.sinkMap_.set(entry[0], DialSink.createFrom(entry[1])); + } + this.deviceCounts_ = tempData.deviceCounts; + } + + const permanentData = PersistentDataManager.getPersistentData(this); + if (permanentData) { + this.deviceCountMetricsRecordTime_ = + permanentData['deviceCountMetricsRecordTime']; + } + } +} + + +/** + * @private @const {!Object<string, !Array<string>>} + */ +SinkDiscoveryService.APP_ORIGIN_WHITELIST_ = { + 'YouTube': [ + 'https://tv.youtube.com', 'https://tv-green-qa.youtube.com', + 'https://tv-release-qa.youtube.com', 'https://web-green-qa.youtube.com', + 'https://web-release-qa.youtube.com', 'https://www.youtube.com' + ], + 'Netflix': ['https://www.netflix.com'], + 'Pandora': ['https://www.pandora.com'], + 'Radio': ['https://www.pandora.com'], + 'Hulu': ['https://www.hulu.com'], + 'Vimeo': ['https://www.vimeo.com'], + 'Dailymotion': ['https://www.dailymotion.com'], + 'com.dailymotion': ['https://www.dailymotion.com'], +}; + + +/** + * Matches DIAL model names that only support discovery. + + * @private @const {!RegExp} + */ +SinkDiscoveryService.DISCOVERY_ONLY_RE_ = + new RegExp('Eureka Dongle|Chromecast Audio|Chromecast Ultra', 'i'); + +/** + * How long to wait between device counts metrics are recorded. Set to 1 hour. + * @private @const {number} + */ +SinkDiscoveryService.DEVICE_COUNT_METRIC_THRESHOLD_MS_ = 60 * 60 * 1000; + + +/** + * @private + */ +SinkDiscoveryService.PersistentData_ = class { + /** + * @param {!Array} sinks + * @param {!DeviceCounts} deviceCounts + */ + constructor(sinks, deviceCounts) { + /** + * @const {!Array} + */ + this.sinks = sinks; + + /** + * @const {!DeviceCounts} + */ + this.deviceCounts = deviceCounts; + } +}; + +exports = SinkDiscoveryService; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service_test.js new file mode 100644 index 00000000000..1aad198346c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_discovery_service_test.js @@ -0,0 +1,163 @@ +// 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. + +goog.module('mr.dial.SinkDiscoveryServiceTest'); +goog.setTestOnly('mr.dial.SinkDiscoveryServiceTest'); + +const DialAnalytics = goog.require('mr.DialAnalytics'); +const PersistentDataManager = goog.require('mr.PersistentDataManager'); +const SinkAppStatus = goog.require('mr.dial.SinkAppStatus'); +const SinkDiscoveryService = goog.require('mr.dial.SinkDiscoveryService'); +const UnitTestUtils = goog.require('mr.UnitTestUtils'); + +describe('DIAL SinkDiscoveryService Tests', function() { + let service; + let mockClock; + let mockSinkCallbacks; + + beforeEach(function() { + mockClock = UnitTestUtils.useMockClockAndPromises(); + mockSinkCallbacks = jasmine.createSpyObj( + 'SinkCallbacks', ['onSinkAdded', 'onSinksRemoved', 'onSinkUpdated']); + + chrome.metricsPrivate = { + recordTime: jasmine.createSpy('recordTime'), + recordMediumTime: jasmine.createSpy('recordMediumTime'), + recordLongTime: jasmine.createSpy('recordLongTime'), + recordUserAction: jasmine.createSpy('recordUserAction') + }; + + service = new SinkDiscoveryService(mockSinkCallbacks); + spyOn(DialAnalytics, 'recordDeviceCounts'); + }); + + afterEach(function() { + UnitTestUtils.restoreRealClockAndPromises(); + PersistentDataManager.clear(); + }); + + + /** + * Creates mojo sink instances. + * @param {number} numSinks The number of mojo sinks to create. + * @return {!Array<!mojo.Sink>} The mojo sinks. + */ + function createMojoSinks(numSinks) { + const mojoSinks = []; + for (var i = 1; i <= numSinks; i++) { + const dialMediaSink = { + ip_address: {address_bytes: [127, 0, 0, i]}, + model_name: 'Eureka Dongle', + app_url: {url: 'http://127.0.0.' + i + ':8008/apps'} + }; + + mojoSinks.push({ + sink_id: 'sinkId ' + i, + name: 'TV ' + i, + extra_data: {dial_media_sink: dialMediaSink} + }); + } + return mojoSinks; + } + + describe('addSinks tests', function() { + beforeEach(function() { + service.init(); + }); + + it('add mojo sinks to sink map', function() { + expect(service.getSinks().length).toBe(0); + const mojoSinks = createMojoSinks(1); + service.addSinks(mojoSinks); + + // sinks were added + const actualSinks = service.getSinks(); + expect(actualSinks.length).toBe(1); + + const actualSink = actualSinks[0]; + const mojoSink = mojoSinks[0]; + const extraData = mojoSink.extra_data.dial_media_sink; + expect(actualSink.getFriendlyName()).toEqual(mojoSink.name); + expect(actualSink.getIpAddress()) + .toEqual(extraData.ip_address.address_bytes.join('.')); + expect(actualSink.getDialAppUrl()).toEqual(extraData.app_url.url); + expect(actualSink.getModelName()).toEqual(extraData.model_name); + expect(actualSink.supportsAppAvailability()).toEqual(false); + + // add-sink-events were fired. + expect(mockSinkCallbacks.onSinkAdded.calls.count()).toBe(1); + expect(mockSinkCallbacks.onSinksRemoved.calls.count()).toBe(0); + }); + + it('remove outdated sinks', function() { + expect(service.getSinks().length).toBe(0); + const mojoSinks = createMojoSinks(3); + // First round discover sink 1, 2, 3 + service.addSinks(mojoSinks); + + // Second round discover sink 1 + const mojoSinks2 = createMojoSinks(1); + service.addSinks(mojoSinks2); + expect(mockSinkCallbacks.onSinkAdded.calls.count()).toBe(3); + + // 2 devices were removed + expect(mockSinkCallbacks.onSinksRemoved.calls.count()).toBe(1); + const sinks = mockSinkCallbacks.onSinksRemoved.calls.argsFor(0)[0]; + expect(sinks.length).toBe(2); + expect(sinks[0].getFriendlyName()).toEqual(mojoSinks[1].name); + expect(sinks[1].getFriendlyName()).toEqual(mojoSinks[2].name); + + expect(mockSinkCallbacks.onSinkUpdated.calls.count()).toBe(0); + }); + + it('Gets sinks by app name', function() { + const mojoSinks = createMojoSinks(3); + service.addSinks(mojoSinks); + service.getSinkById(mojoSinks[0].sink_id) + .setAppStatus('YouTube', SinkAppStatus.AVAILABLE); + service.getSinkById(mojoSinks[1].sink_id) + .setAppStatus('Netflix', SinkAppStatus.AVAILABLE); + service.getSinkById(mojoSinks[1].sink_id) + .setAppStatus('YouTube', SinkAppStatus.AVAILABLE); + service.getSinkById(mojoSinks[2].sink_id) + .setAppStatus('Pandora', SinkAppStatus.UNAVAILABLE); + expect(service.getSinksByAppName('YouTube').sinks.length).toBe(2); + expect(service.getSinksByAppName('Netflix').sinks.length).toBe(1); + expect(service.getSinksByAppName('Netflix').sinks[0].id) + .toEqual(mojoSinks[1].sink_id); + expect(service.getSinksByAppName('Pandora').sinks.length).toBe(0); + }); + + }); + + it('Saves PersistentData without any data', function() { + service.init(); + expect(service.getSinks()).toEqual([]); + PersistentDataManager.suspendForTest(); + service = new SinkDiscoveryService(mockSinkCallbacks); + service.loadSavedData(); + expect(service.getSinks()).toEqual([]); + }); + + it('Saves PersistentData with data', function() { + service.init(); + service.addSinks(createMojoSinks(3)); + mockClock.tick(1); + const sinks = service.getSinks(); + expect(sinks.length).toBe(3); + const expectedDeviceCounts = {availableDeviceCount: 3, knownDeviceCount: 3}; + expect(service.getDeviceCounts()).toEqual(expectedDeviceCounts); + + PersistentDataManager.suspendForTest(); + service = new SinkDiscoveryService(mockSinkCallbacks); + service.loadSavedData(); + const restoredSinks = service.getSinks(); + expect(restoredSinks.length).toBe(3); + expect(service.getDeviceCounts()).toEqual(expectedDeviceCounts); + for (let index = 0; index < 3; index++) { + expect(sinks[0].getId()).toEqual(restoredSinks[0].getId()); + } + }); + +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_test.js new file mode 100644 index 00000000000..03a467550bd --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/dial_sink_test.js @@ -0,0 +1,127 @@ +// 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. + +goog.module('mr.dial.SinkTest'); +goog.setTestOnly('mr.dial.SinkTest'); + +const DialSink = goog.require('mr.dial.Sink'); +const MockClock = goog.require('mr.MockClock'); +const Sink = goog.require('mr.Sink'); +const SinkAppStatus = goog.require('mr.dial.SinkAppStatus'); + +describe('DIAL Sink Tests', function() { + let mockClock; + + beforeEach(function() { + mockClock = new MockClock(true); + }); + + afterEach(function() { + mockClock.uninstall(); + }); + + it('Gets and sets fields', function() { + const sink = new DialSink('name', 'id1'); + expect(sink.getMrSink()).toEqual(new Sink('id1', 'name')); + + expect(sink.getId()).toEqual('id1'); + sink.setId('id2'); + expect(sink.getId()).toEqual('id2'); + + expect(sink.getIpAddress()).toBeNull(); + sink.setIpAddress('192.168.111.1'); + expect(sink.getIpAddress()).toEqual('192.168.111.1'); + + expect(sink.getDialAppUrl()).toBeNull(); + sink.setDialAppUrl('http://192.168.111.1/apps'); + expect(sink.getDialAppUrl()).toEqual('http://192.168.111.1/apps'); + + expect(sink.getDeviceDescriptionUrl()).toBeNull(); + sink.setDeviceDescriptionUrl('http://192.168.111.1/desc'); + expect(sink.getDeviceDescriptionUrl()).toEqual('http://192.168.111.1/desc'); + + expect(sink.getModelName()).toBeNull(); + sink.setModelName('chromecast'); + expect(sink.getModelName()).toEqual('chromecast'); + + expect(sink.getFriendlyName()).toEqual('name'); + sink.setFriendlyName('newname'); + expect(sink.getFriendlyName()).toEqual('newname'); + + expect(sink.getPort()).toEqual(null); + sink.setPort(8009); + expect(sink.getPort()).toEqual(8009); + }); + + it('Gets and sets sink app status', function() { + const sink = new DialSink('name', 'uniqueId'); + expect(sink.getAppStatus('youtube')).toBe(SinkAppStatus.UNKNOWN); + expect(sink.getAppStatusTimeStamp('youtube')).toBe(null); + + mockClock.tick(10); + const now1 = Date.now(); + sink.setAppStatus('youtube', SinkAppStatus.AVAILABLE); + mockClock.tick(10); + const now2 = Date.now(); + sink.setAppStatus('app2', SinkAppStatus.UNAVAILABLE); + expect(sink.getAppStatus('youtube')).toBe(SinkAppStatus.AVAILABLE); + expect(sink.getAppStatusTimeStamp('youtube')).toBe(now1); + expect(sink.getAppStatus('app2')).toBe(SinkAppStatus.UNAVAILABLE); + expect(sink.getAppStatusTimeStamp('app2')).toBe(now2); + + sink.clearAppStatus(); + expect(sink.getAppStatus('youtube')).toBe(SinkAppStatus.UNKNOWN); + expect(sink.getAppStatusTimeStamp('youtube')).toBe(null); + expect(sink.getAppStatus('app2')).toBe(SinkAppStatus.UNKNOWN); + expect(sink.getAppStatusTimeStamp('app2')).toBe(null); + }); + + it('Updates sink from another sink', function() { + const sink = new DialSink('name', 'uniqueId') + .setDialAppUrl('http://192.168.111.1/apps') + .setPort(8009) + .setDeviceDescriptionUrl('http://192.168.111.1/desc'); + + let updatedSink = new DialSink('name2', 'uniqueId'); + expect(sink.update(updatedSink)).toBe(true); + expect(sink.getFriendlyName()).toEqual('name2'); + + updatedSink = new DialSink('name', 'uniqueId') + .setDialAppUrl('http://192.168.111.1/apps/app2'); + expect(sink.update(updatedSink)).toBe(true); + expect(sink.getDialAppUrl()).toEqual('http://192.168.111.1/apps/app2'); + + updatedSink = new DialSink('name', 'uniqueId').setId('id2'); + expect(sink.update(updatedSink)).toBe(false); + }); + + it('Updates ip address', function() { + const sink = new DialSink('name', 'uniqueId').setIpAddress('192.168.111.1'); + const updatedSink = + new DialSink('name', 'uniqueId').setIpAddress('192.168.111.2'); + expect(sink.update(updatedSink)).toBe(true); + expect(sink.getIpAddress()).toEqual('192.168.111.2'); + }); + + it('Updates device description url', function() { + const sink = new DialSink('name', 'uniqueId') + .setDeviceDescriptionUrl('http://192.168.111.1/desc'); + const updatedSink = + new DialSink('name', 'uniqueId') + .setDeviceDescriptionUrl('http://192.168.111.2/desc'); + expect(sink.update(updatedSink)).toBe(true); + expect(sink.getDeviceDescriptionUrl()).toEqual('http://192.168.111.2/desc'); + }); + + it('Creates sink from an Object', function() { + const sink = new DialSink('name', 'uniqueId') + .setDialAppUrl('http://192.168.111.1/apps') + .setPort(8009) + .setDeviceDescriptionUrl('http://192.168.111.1/desc') + .setAppStatus('youtube', SinkAppStatus.AVAILABLE) + .setIpAddress('192.168.111.1') + .setModelName('chromecast'); + expect(DialSink.createFrom(sink)).toEqual(sink); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url.js new file mode 100644 index 00000000000..203b40e134f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url.js @@ -0,0 +1,146 @@ +// 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. + +goog.module('mr.dial.PresentationUrl'); + +const Logger = goog.require('mr.Logger'); +const base64 = goog.require('mr.base64'); + + +/** + * Represents a DIAL media source containing information specific to a DIAL + * launch. + */ +const PresentationUrl = class { + /** + * @param {string} appName The DIAL application name. + * @param {string=} launchParameter DIAL application launch parameter. + */ + constructor(appName, launchParameter = '') { + /** @const {string} */ + this.appName = appName; + /** @const {string} */ + this.launchParameter = launchParameter; + } + + /** + * Generates a DIAL Presentation URL using given parameters. + * @param {string} dialAppName Name of the DIAL app. + * @param {?string} dialPostData base-64 encoded string of the data for the + * DIAL launch. + * @return {string} + */ + static getPresentationUrlAsString(dialAppName, dialPostData) { + const url = new URL('dial:' + dialAppName); + if (dialPostData) { + url.searchParams.set('postData', dialPostData); + } + return url.toString(); + } + + /** + * Constructs a DIAL media source from a URL. The URL can take on the new + * format (with dial: protocol) or the old format (with https: protocol). + * @param {string} urlString The media source URL. + * @return {?PresentationUrl} A DIAL media source if the parse was + * successful, null otherwise. + */ + static create(urlString) { + let url; + try { + url = new URL(urlString); + } catch (err) { + PresentationUrl.logger_.info('Invalid URL: ' + urlString); + return null; + } + switch (url.protocol) { + case 'dial:': + return PresentationUrl.parseDialUrl_(url); + case 'https:': + + return PresentationUrl.parseLegacyUrl_(url); + default: + PresentationUrl.logger_.fine('Unhandled protocol: ' + url.protocol); + return null; + } + } + + /** + * Parses the given URL using the new DIAL URL format, which takes the form: + * dial:<App name>?postData=<base64-encoded launch parameters> + * @param {!URL} url + * @return {?PresentationUrl} + * @private + */ + static parseDialUrl_(url) { + const appName = url.pathname; + if (!appName.match(/^\w+$/)) { + PresentationUrl.logger_.warning('Invalid app name: ' + appName); + return null; + } + let postData = url.searchParams.get('postData') || undefined; + if (postData) { + try { + postData = base64.decodeString(postData); + } catch (err) { + PresentationUrl.logger_.warning( + 'Invalid base64 encoded postData:' + postData); + return null; + } + } + return new PresentationUrl(appName, postData); + } + + /** + * Parses the given URL using the legacy format specified in + * http://goo.gl/8qKAE7 + * Example: + * http://www.youtube.com/tv#__dialAppName__=YouTube/__dialPostData__=dj0xMjM= + * @param {!URL} url + * @return {?PresentationUrl} + * @private + */ + static parseLegacyUrl_(url) { + // Parse URI and get fragment. + const fragment = url.hash; + if (!fragment) return null; + let appName = PresentationUrl.APP_NAME_REGEX_.exec(fragment); + appName = appName ? appName[1] : null; + if (!appName) return null; + appName = decodeURIComponent(appName); + + let postData = PresentationUrl.LAUNCH_PARAM_REGEX_.exec(fragment); + postData = postData ? postData[1] : undefined; + if (postData) { + try { + postData = base64.decodeString(postData); + } catch (err) { + PresentationUrl.logger_.warning( + 'Invalid base64 encoded postData:' + postData); + return null; + } + } + return new PresentationUrl(appName, postData); + } +}; + + +/** @const @private {?Logger} */ +PresentationUrl.logger_ = Logger.getInstance('mr.dial.PresentationUrl'); + + +/** @const {string} */ +PresentationUrl.URN_PREFIX = 'urn:dial-multiscreen-org:dial:application:'; + + +/** @private @const {!RegExp} */ +PresentationUrl.APP_NAME_REGEX_ = + /__dialAppName__=([A-Za-z0-9-._~!$&'()*+,;=%]+)/; + + +/** @private @const {!RegExp} */ +PresentationUrl.LAUNCH_PARAM_REGEX_ = /__dialPostData__=([A-Za-z0-9]+={0,2})/; + + +exports = PresentationUrl; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url_test.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url_test.js new file mode 100644 index 00000000000..4477e5ee404 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/presentation_url_test.js @@ -0,0 +1,75 @@ +// 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. + +goog.module('PresentationUrlTest'); +goog.setTestOnly('PresentationUrlTest'); + +const PresentationUrl = goog.require('mr.dial.PresentationUrl'); + +describe('Tests PresentationUrl', function() { + it('Does not create from empty input', function() { + expect(PresentationUrl.create('')).toBeNull(); + }); + + it('Creates from a valid URL', function() { + expect(PresentationUrl.create( + 'https://www.youtube.com/tv#__dialAppName__=YouTube')) + .toEqual(new PresentationUrl('YouTube')); + }); + + it('Creates from a valid URL with launch parameters', function() { + expect(PresentationUrl.create( + 'https://www.youtube.com/tv#' + + '__dialAppName__=YouTube/__dialPostData__=dj0xMjM=')) + .toEqual(new PresentationUrl('YouTube', 'v=123')); + expect(PresentationUrl.create( + 'https://www.youtube.com/tv#' + + '__dialAppName__=YouTube/__dialPostData__=dj1NSnlKS3d6eEZwWQ==')) + .toEqual(new PresentationUrl('YouTube', 'v=MJyJKwzxFpY')); + }); + + it('Does not create from an invalid URL', function() { + expect(PresentationUrl.create( + 'https://www.youtube.com/tv#___emanPpaLiad__=YouTube')) + .toBeNull(); + }); + + it('Does not create from an invalid postData', function() { + expect(PresentationUrl.create( + 'https://www.youtube.com/tv#___emanPpaLiad__=YouTube' + + '/__dialPostData__=dj1=N')) + .toBeNull(); + }); + + it('Creates from DIAL URL', () => { + expect(PresentationUrl.create('dial:YouTube')) + .toEqual(new PresentationUrl('YouTube')); + expect(PresentationUrl.create('dial:YouTube?foo=bar')) + .toEqual(new PresentationUrl('YouTube')); + expect(PresentationUrl.create('dial:YouTube?foo=bar&postData=dj0xMjM=')) + .toEqual(new PresentationUrl('YouTube', 'v=123')); + expect(PresentationUrl.create('dial:YouTube?postData=dj0xMjM%3D')) + .toEqual(new PresentationUrl('YouTube', 'v=123')); + }); + + it('Does not create from invalid DIAL URL', () => { + expect(PresentationUrl.create('dial:')).toBeNull(); + expect(PresentationUrl.create('dial://')).toBeNull(); + expect(PresentationUrl.create('dial://YouTube')).toBeNull(); + expect( + PresentationUrl.create('dial:YouTube?postData=notEncodedProperly111')) + .toBeNull(); + }); + + it('Does not create from URL of unknown protocol', () => { + expect(PresentationUrl.create('unknown:YouTube')).toBeNull(); + }); + + it('getPresentationUrl returns DIAL presentation URLs', () => { + expect(PresentationUrl.getPresentationUrlAsString('YouTube', null)) + .toEqual('dial:YouTube'); + expect(PresentationUrl.getPresentationUrlAsString('YouTube', 'dj0xMjM=')) + .toEqual('dial:YouTube?postData=dj0xMjM%3D'); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/sink_app_status.js b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/sink_app_status.js new file mode 100644 index 00000000000..5acd9a9a8fa --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/providers/dial/sink_app_status.js @@ -0,0 +1,23 @@ +// 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 The availability of an app on a sink. + */ + +goog.provide('mr.dial.SinkAppStatus'); + + +/** + * Tracks the availability of an app on a sink. Apps start out in an + * UNKNOWN status and are changed to AVAILABLE or UNAVAILABLE once the status is + * known, i.e. after we query the sink for the app. + * + * @enum {string} + */ +mr.dial.SinkAppStatus = { + AVAILABLE: 'available', + UNAVAILABLE: 'unavailable', + UNKNOWN: 'unknown' +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics.js new file mode 100644 index 00000000000..8761cc404d7 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics.js @@ -0,0 +1,305 @@ +// 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 API for Analytics events. */ + +goog.provide('mr.Analytics'); +goog.provide('mr.LongTiming'); +goog.provide('mr.MediumTiming'); +goog.provide('mr.Timing'); + +goog.require('mr.Logger'); + + +/** + * Begins the timing period. + */ +mr.Timing = class { + /** + * @param {!string} name + */ + constructor(name) { + /** @private {!string} */ + this.name_ = name; + + /** @private {number} */ + this.startTime_ = Date.now(); + } + + /** + * Gets the full name with the suffix appended if provided. + * @param {string} name The name of the event or histogram. + * @param {string=} opt_suffix The optional suffix to add. + * @return {string} The full name with suffix added. + * @private + */ + getFullName_(name, opt_suffix) { + if (opt_suffix != null) { + name += '_' + opt_suffix; + } + return name; + } + + /** + * Sets the name for the timing object. + * @param {string} name The new name. + */ + setName(name) { + this.name_ = name; + } + + /** + * Ends the timing period and reports to UMA. + * @param {string=} opt_suffix An optional suffix. + */ + end(opt_suffix) { + const duration = Date.now() - this.startTime_; + const name = this.getFullName_(this.name_, opt_suffix); + mr.Timing.recordDuration(name, duration); + } + + /** + * Sends a short duration value (up to 10 seconds) for analytics collection. + * @param {string} name + * @param {number} duration Duration in milliseconds. + */ + static recordDuration(name, duration) { + if (duration < 0) { + mr.Timing.logger_.warning('Timing analytics event with negative time'); + duration = 0; + } + + if (duration > mr.Timing.TEN_SECONDS_) { + duration = mr.Timing.TEN_SECONDS_; + } + + try { + chrome.metricsPrivate.recordTime(name, duration); + } catch (e) { + mr.Timing.logger_.warning( + 'Failed to record time ' + duration + ' in ' + name); + } + } +}; + + +/** @private const */ +mr.Timing.logger_ = mr.Logger.getInstance('mr.Timing'); + + +/** + * Ten seconds in milliseconds. + * @const {number} + * @private + */ +mr.Timing.TEN_SECONDS_ = 10 * 1000; + + +/** + * Begins a medium timing period (should be measured in seconds). + */ +mr.MediumTiming = class extends mr.Timing { + /** + * @param {string} name The histogram name. + */ + constructor(name) { + super(name); + } + + /** + * @override + */ + end(opt_suffix) { + const duration = Date.now() - this.startTime_; + const name = this.getFullName_(this.name_, opt_suffix); + mr.MediumTiming.recordDuration(name, duration); + } + + /** + * Sends a medium duration value (up to 3 minutes) for analytics collection. + * @param {string} name + * @param {number} duration Duration in milliseconds. + */ + static recordDuration(name, duration) { + if (duration < 0) { + mr.MediumTiming.logger_.warning( + 'Timing analytics event with negative time'); + return; + } + + if (duration < mr.Timing.TEN_SECONDS_) { + duration = mr.Timing.TEN_SECONDS_; + } + + if (duration > mr.MediumTiming.THREE_MINUTES_) { + duration = mr.MediumTiming.THREE_MINUTES_; + } + + try { + chrome.metricsPrivate.recordMediumTime(name, duration); + } catch (e) { + mr.MediumTiming.logger_.warning( + 'Failed to record time ' + duration + ' in ' + name); + } + } +}; + + +/** @private @const */ +mr.MediumTiming.logger_ = mr.Logger.getInstance('mr.MediumTiming'); + + +/** + * Constant of 3 minutes (in milliseconds). + * @private @const {number} + **/ +mr.MediumTiming.THREE_MINUTES_ = 3 * 60 * 1000; + + +/** + * Begins a long timing period (up to 1 hour). + */ +mr.LongTiming = class extends mr.Timing { + /** + * @param {string} name The name of the histogram. + */ + constructor(name) { + super(name); + } + + /** + * @override + */ + end(opt_suffix) { + const duration = Date.now() - this.startTime_; + const name = this.getFullName_(this.name_, opt_suffix); + mr.LongTiming.recordDuration(name, duration); + } + + /** + * Sends a long duration value (up to 1 hour) for analytics collection. + * @param {string} name + * @param {number} duration Duration in milliseconds. + */ + static recordDuration(name, duration) { + if (duration < 0) { + mr.LongTiming.logger_.warning( + 'Timing analytics event with negative time'); + return; + } + + if (duration < mr.MediumTiming.THREE_MINUTES_) { + duration = mr.MediumTiming.THREE_MINUTES_; + } + + if (duration > mr.LongTiming.ONE_HOUR_) { + duration = mr.LongTiming.ONE_HOUR_; + } + + try { + chrome.metricsPrivate.recordLongTime(name, duration); + } catch (e) { + mr.LongTiming.logger_.warning( + 'Failed to record time ' + duration + ' in ' + name); + } + } +}; + + +/** @private @const */ +mr.LongTiming.logger_ = mr.Logger.getInstance('mr.LongTiming'); + + +/** + * Constant of 1 hour (in milliseconds). + * @private @const {number} + **/ +mr.LongTiming.ONE_HOUR_ = 60 * 60 * 1000; + + +/** @const {*} */ +mr.Analytics = {}; + + +/** + * @const {mr.Logger} + * @private + */ +mr.Analytics.logger_ = mr.Logger.getInstance('mr.Analytics'); + + +/** + * Sends a user action for analytics collection. + * @param {!string} name + */ +mr.Analytics.recordEvent = function(name) { + try { + chrome.metricsPrivate.recordUserAction(name); + } catch (e) { + mr.Analytics.logger_.warning('Failed to record event ' + name); + } +}; + + +/** + * Send a value for analytics collection. + * @param {!string} name + * @param {!number} value + * @param {!Object<string,number>} values + */ +mr.Analytics.recordEnum = function(name, value, values) { + let foundKey; + let size = 0; + for (let key in values) { + size++; + if (values[key] == value) { + foundKey = key; + } + } + if (!foundKey) { + mr.Analytics.logger_.error( + 'Unknown analytics value, ' + value + ' for histogram, ' + name, + Error() /* for stack trace */); + return; + } + + const config = { + 'metricName': name, + 'type': 'histogram-linear', + 'min': 1, + 'max': size, + // Add one for the underflow bucket. + 'buckets': size + 1 + }; + + try { + chrome.metricsPrivate.recordValue(config, value); + } catch (/** Error */ e) { + mr.Analytics.logger_.warning( + 'Failed to record enum value ' + foundKey + ' (' + value + ') in ' + + name, + e); + } +}; + + +/** + * Records a small count (0 to 100) for analytics collection. + * @param {string} name + * @param {number} count + */ +mr.Analytics.recordSmallCount = function(name, count) { + try { + if (count < 0) { + throw new Error(`Invalid count for ${name}: ${count}`); + } else if (count > 100) { + mr.Analytics.logger_.warning( + `Small count for ${name} exceeded limits: ${count}`, Error()); + } + chrome.metricsPrivate.recordSmallCount(name, count); + } catch (/** Error */ e) { + mr.Analytics.logger_.warning( + `Failed to record small count ${name} (${count})`, e); + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics_test.js new file mode 100644 index 00000000000..e9f6ee2274c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/analytics_test.js @@ -0,0 +1,246 @@ +// 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. + +goog.setTestOnly(); +goog.require('mr.Analytics'); +goog.require('mr.LongTiming'); +goog.require('mr.MediumTiming'); +goog.require('mr.MockClock'); +goog.require('mr.Timing'); + +describe('Tests Analytics', function() { + let mockClock; + + const TEN_SECONDS = 10 * 1000; + const THREE_MINUTES = 3 * 60 * 1000; + const ONE_HOUR = 60 * 60 * 1000; + + beforeEach(function() { + mockClock = new mr.MockClock(true); + chrome.metricsPrivate = { + recordTime: jasmine.createSpy('recordTime'), + recordMediumTime: jasmine.createSpy('recordMediumTime'), + recordLongTime: jasmine.createSpy('recordLongTime'), + recordUserAction: jasmine.createSpy('recordUserAction'), + recordValue: jasmine.createSpy('recordValue'), + recordSmallCount: jasmine.createSpy('recordSmallCount'), + }; + }); + + afterEach(function() { + mockClock.uninstall(); + }); + + describe('Test Timing Events', function() { + describe('Test mr.Timing', function() { + it('Should record the time passing', function() { + const histogramName = 'Test'; + const timeToPass = 34; + const timing = new mr.Timing(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordTime) + .toHaveBeenCalledWith(histogramName, timeToPass); + }); + it('Should record the time passing with a suffix', function() { + const histogramName = 'Test'; + const suffixName = 'Test'; + const expectedFinalName = histogramName + '_' + suffixName; + const timeToPass = 34; + const timing = new mr.Timing(histogramName); + mockClock.tick(timeToPass); + timing.end(suffixName); + expect(chrome.metricsPrivate.recordTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordTime) + .toHaveBeenCalledWith(expectedFinalName, timeToPass); + }); + it('Should record the max if duration exceeds ten seconds', function() { + const histogramName = 'Test'; + const timeToPass = TEN_SECONDS + 1; + const timing = new mr.Timing(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordTime) + .toHaveBeenCalledWith(histogramName, TEN_SECONDS); + }); + it('Should record the minimum if duration is negative', function() { + const histogramName = 'Test'; + const timeToPass = -1; + const timing = new mr.Timing(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordTime) + .toHaveBeenCalledWith(histogramName, 0); + }); + }); + describe('Test mr.MediumTiming', function() { + it('Should record the time passing', function() { + const histogramName = 'Test'; + const timeToPass = 34 * 1000; + const timing = new mr.MediumTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordMediumTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(histogramName, timeToPass); + }); + it('Should record the time passing with a suffix', function() { + const histogramName = 'Test'; + const suffixName = 'Test'; + const expectedFinalName = histogramName + '_' + suffixName; + const timeToPass = 34 * 1000; + const timing = new mr.MediumTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(suffixName); + expect(chrome.metricsPrivate.recordMediumTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(expectedFinalName, timeToPass); + }); + it('Should record ten seconds if duration is below ten seconds', + function() { + const histogramName = 'Test'; + const timeToPass = 34; + const timing = new mr.MediumTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordMediumTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(histogramName, TEN_SECONDS); + }); + it('Should record three minutes if duration exceeds three minutes', + function() { + const histogramName = 'Test'; + const timeToPass = THREE_MINUTES + 1; + const timing = new mr.MediumTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordMediumTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordMediumTime) + .toHaveBeenCalledWith(histogramName, THREE_MINUTES); + }); + }); + describe('Test mr.LongTiming', function() { + it('Should record the time passing', function() { + const histogramName = 'Test'; + const timeToPass = 34 * 60 * 1000; + const timing = new mr.LongTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordLongTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordLongTime) + .toHaveBeenCalledWith(histogramName, timeToPass); + }); + it('Should record the time passing with a suffix', function() { + const histogramName = 'Test'; + const suffixName = 'Test'; + const expectedFinalName = histogramName + '_' + suffixName; + const timeToPass = 34 * 60 * 1000; + const timing = new mr.LongTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(suffixName); + expect(chrome.metricsPrivate.recordLongTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordLongTime) + .toHaveBeenCalledWith(expectedFinalName, timeToPass); + }); + it('Should record three minutes if duration is below three minutes', + function() { + const histogramName = 'Test'; + const timeToPass = 2 * 60 * 1000; + const timing = new mr.LongTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordLongTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordLongTime) + .toHaveBeenCalledWith(histogramName, THREE_MINUTES); + }); + it('Should record one hour if duration exceeds one hour', function() { + const histogramName = 'Test'; + const timeToPass = ONE_HOUR + 1; + const timing = new mr.LongTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordLongTime.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordLongTime) + .toHaveBeenCalledWith(histogramName, ONE_HOUR); + }); + it('Should not record the time if it we went back in time', function() { + const histogramName = 'Test'; + const timeToPass = -1; + const timing = new mr.LongTiming(histogramName); + mockClock.tick(timeToPass); + timing.end(); + expect(chrome.metricsPrivate.recordLongTime.calls.count()).toBe(0); + }); + }); + }); + describe('Test recordEvent', function() { + it('Should record an event', function() { + const eventName = 'Test'; + mr.Analytics.recordEvent(eventName); + expect(chrome.metricsPrivate.recordUserAction.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordUserAction) + .toHaveBeenCalledWith(eventName); + }); + }); + describe('Test recordEnum', function() { + const testHistogram = 'Test'; + const testValues = {TEST1: 0, TEST2: 1, TEST3: 2}; + const testConfig = { + 'metricName': testHistogram, + 'type': 'histogram-linear', + 'min': 1, + 'max': 3, + 'buckets': 4 + }; + it('Should record an event with corrct index of 0', function() { + mr.Analytics.recordEnum(testHistogram, testValues.TEST1, testValues); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 0); + }); + it('Should record an event with corrct index of 1', function() { + mr.Analytics.recordEnum(testHistogram, testValues.TEST2, testValues); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 1); + }); + it('Should record an event with correct index of 2', function() { + mr.Analytics.recordEnum(testHistogram, testValues.TEST3, testValues); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordValue) + .toHaveBeenCalledWith(testConfig, 2); + }); + it('Should not record an event with an unknown value', function() { + mr.Analytics.recordEnum(testHistogram, 3, testValues); + expect(chrome.metricsPrivate.recordValue.calls.count()).toBe(0); + }); + }); + describe('Test recordSmallCount', () => { + it('Record 0 count succeeds', () => { + mr.Analytics.recordSmallCount('smallCount', 0); + expect(chrome.metricsPrivate.recordSmallCount.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordSmallCount) + .toHaveBeenCalledWith('smallCount', 0); + }); + it('Record negative count fails', () => { + mr.Analytics.recordSmallCount('smallCount', -1); + expect(chrome.metricsPrivate.recordSmallCount.calls.count()).toBe(0); + }); + it('Record large count succeeds', () => { + mr.Analytics.recordSmallCount('smallCount', 200); + expect(chrome.metricsPrivate.recordSmallCount.calls.count()).toBe(1); + expect(chrome.metricsPrivate.recordSmallCount) + .toHaveBeenCalledWith('smallCount', 200); + }); + it('Record regular count succeeds', () => { + mr.Analytics.recordSmallCount('smallCount', 1); + mr.Analytics.recordSmallCount('smallCount', 50); + mr.Analytics.recordSmallCount('smallCount', 100); + expect(chrome.metricsPrivate.recordSmallCount.calls.count()).toBe(3); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/assertions.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/assertions.js new file mode 100644 index 00000000000..93abbb7d8db --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/assertions.js @@ -0,0 +1,95 @@ +// 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 Various assert-like functions. + */ +goog.module('mr.Assertions'); +goog.module.declareLegacyNamespace(); + +const Config = goog.require('mr.Config'); + + +/** + * Given an unknown value, return it if it is an Error, or return a new Error + * otherwise. Note that unlike other methods in the module, it does not throw + * exceptions. + * + * @param {*} err The purported error + * @param {string=} opt_message The message used to construct an Error if |err| + * is not an error. + * @return {!Error} + */ +exports.toError = function(err, opt_message) { + if (err instanceof Error) { + return err; + } else { + return Error(opt_message || `Expected an Error value, got ${err}`); + } +}; + + +/** + * Represents an assertion failure. + */ +const AssertionError = class extends Error { + /** + * @param {string=} message Error message. + */ + constructor(message = '') { + super(); + this.name = 'AssertionError'; + this.message = message; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, AssertionError); + } else { + this.stack = new Error().stack; + } + } +}; + + +/** + * Checks if the condition evaluates to true if mr.Config.isDebugChannel is + * true. + * @template T + * @param {T} condition The condition to check. + * @param {string=} message Error message if condition evaluates to false. + * @throws {AssertionError} When the condition evaluates to false. + * @return {T} The condition. + */ +exports.assert = function(condition, message = undefined) { + if (Config.isDebugChannel && !condition) { + throw new AssertionError(message); + } + return condition; +}; + + +/** + * Checks that a value is a string if mr.Config.isDebugChannel is true. + * @param {*} value The value to check + * @param {string=} message The message + * @return {string} The value + * @throws {AssertionError} if the value is not a string + */ +exports.assertString = function(value, message = undefined) { + if (Config.isDebugChannel && typeof value !== 'string') { + throw new AssertionError(); + } + return /** @type {string} */ (value); +}; + + +/** + * Returns a Promise that rejects with 'Not implemented' as the error + * message. + * @template T + * @return {!Promise<T>} + */ +exports.rejectNotImplemented = function() { + return Promise.reject(new Error('Not implemented')); +}; + +exports.AssertionError = AssertionError; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/base64.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/base64.js new file mode 100644 index 00000000000..5d8b3d1ed08 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/base64.js @@ -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. + +goog.module('mr.base64'); + +/** + * @const {!Map<string, string>} + */ +const URL_SAFE_MAP = new Map().set('+', '-').set('/', '_').set('=', '.'); + + +/** + * @const {!Map<string, string>} + */ +const INVERSE_URL_SAFE_MAP = + new Map().set('-', '+').set('_', '/').set('.', '='); + + +/** + * Decodes a base64 string using either the normal or URL-safe alphabet. + * @param {string} encoded + * @return {string} + */ +function decodeString(encoded) { + return atob(encoded.replace(/[-_.]/g, c => INVERSE_URL_SAFE_MAP.get(c))); +} + + +/** + * Encodes an array of byte values in base64. + * @param {!Array<number>} data An array of byte values. + * @param {boolean} urlSafe If true, uses a URL-safe base64 alphabet. + * @return {string} The encoded data. + */ +function encodeArray(data, urlSafe) { + const encoded = btoa(String.fromCharCode(...data)); + return urlSafe ? encoded.replace(/[+/=]/g, c => URL_SAFE_MAP.get(c)) : + encoded; +} + + +exports = { + decodeString, + encodeArray, +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/base64_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/base64_test.js new file mode 100644 index 00000000000..07240a4f82d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/base64_test.js @@ -0,0 +1,72 @@ +// 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. + +goog.module('mr.base64.test'); +goog.setTestOnly(); + +const {encodeArray, decodeString} = goog.require('mr.base64'); + +/** + * Converts a ASCII string to an array of bytes. + * @param {string} s + * @return {!Array<number>} + */ +function strBytes(s) { + return s.split('').map(c => c.codePointAt(0)); +} + +describe('mr.base64.encodeArray', () => { + it('encodes well-known values correctly', () => { + expect(encodeArray(strBytes(''))).toBe(''); + expect(encodeArray(strBytes('f'))).toBe('Zg=='); + expect(encodeArray(strBytes('fo'))).toBe('Zm8='); + expect(encodeArray(strBytes('foo'))).toBe('Zm9v'); + expect(encodeArray(strBytes('foob'))).toBe('Zm9vYg=='); + expect(encodeArray(strBytes('fooba'))).toBe('Zm9vYmE='); + expect(encodeArray(strBytes('foobar'))).toBe('Zm9vYmFy'); + expect( + encodeArray(strBytes( + '\xe4\xb8\x80\xe4\xba\x8c\xe4\xb8\x89\xe5\x9b\x9b\xe4\xba\x94\xe5' + + '\x85\xad\xe4\xb8\x83\xe5\x85\xab\xe4\xb9\x9d\xe5\x8d\x81'))) + .toBe('5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Y2B'); + expect(encodeArray(strBytes('>>>???>>>???=/+'))) + .toBe('Pj4+Pz8/Pj4+Pz8/PS8r'); + }); + + it('handles the urlSafe parameter correctly', () => { + expect(encodeArray(strBytes('f'), true)).toBe('Zg..'); + expect(encodeArray(strBytes('fo'), true)).toBe('Zm8.'); + expect(encodeArray(strBytes('foo'), true)).toBe('Zm9v'); + expect(encodeArray(strBytes('foob'), true)).toBe('Zm9vYg..'); + expect(encodeArray(strBytes('fooba'), true)).toBe('Zm9vYmE.'); + expect(encodeArray(strBytes('foobar'), true)).toBe('Zm9vYmFy'); + expect(encodeArray(strBytes('>>>???>>>???=/+'), true)) + .toBe('Pj4-Pz8_Pj4-Pz8_PS8r'); + }); + + it('decodes correctly with the standard alphabet', () => { + expect(decodeString('')).toBe(''); + expect(decodeString('Zg==')).toBe('f'); + expect(decodeString('Zm8=')).toBe('fo'); + expect(decodeString('Zm9v')).toBe('foo'); + expect(decodeString('Zm9vYg==')).toBe('foob'); + expect(decodeString('Zm9vYmE=')).toBe('fooba'); + expect(decodeString('Zm9vYmFy')).toBe('foobar'); + expect(decodeString('5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Y2B')) + .toBe( + '\xe4\xb8\x80\xe4\xba\x8c\xe4\xb8\x89\xe5\x9b\x9b\xe4\xba\x94\xe5' + + '\x85\xad\xe4\xb8\x83\xe5\x85\xab\xe4\xb9\x9d\xe5\x8d\x81'); + expect(decodeString('Pj4+Pz8/Pj4+Pz8/PS8r')).toBe('>>>???>>>???=/+'); + }); + + it('decodes correctly with the URL-safe alphabet', () => { + expect(decodeString('Zg..')).toBe('f'); + expect(decodeString('Zm8.')).toBe('fo'); + expect(decodeString('Zm9v')).toBe('foo'); + expect(decodeString('Zm9vYg..')).toBe('foob'); + expect(decodeString('Zm9vYmE.')).toBe('fooba'); + expect(decodeString('Zm9vYmFy')).toBe('foobar'); + expect(decodeString('Pj4-Pz8_Pj4-Pz8_PS8r')).toBe('>>>???>>>???=/+'); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts.js new file mode 100644 index 00000000000..d8f7392dc69 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts.js @@ -0,0 +1,18 @@ +// 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. + +goog.module('mr.DeviceCounts'); +goog.module.declareLegacyNamespace(); + + +/** + * A struct to hold a snapshot of device counts for a sink discovery service. + * @typedef {{ + * availableDeviceCount: number, + * knownDeviceCount: number + * }} + */ +let DeviceCounts; + +exports = DeviceCounts; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts_provider.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts_provider.js new file mode 100644 index 00000000000..a76ecc07702 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/device_counts_provider.js @@ -0,0 +1,23 @@ +// 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. + +goog.module('mr.DeviceCountsProvider'); +goog.module.declareLegacyNamespace(); + +const DeviceCounts = goog.require('mr.DeviceCounts'); + +/** + * Implemented by services that are capable of providing counts of devices that + * they manage. + * @record + */ +const DeviceCountsProvider = class { + /** + * Returns the device counts currently known to the service. + * @return {!DeviceCounts} + */ + getDeviceCounts() {} +}; + +exports = DeviceCountsProvider; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics.js new file mode 100644 index 00000000000..f9fbde5e388 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics.js @@ -0,0 +1,57 @@ +// 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 Analytics for events. */ + +goog.provide('mr.EventAnalytics'); +goog.provide('mr.EventAnalytics.Event'); + +goog.require('mr.Analytics'); + +/** + * Possible event types that can wake the event page. Keep names in sync with + * extensions/browser/extension_event_histogram_value.h in Chromium and values + * in sync with MediaRouterWakeEventType in + * google3/analysis/uma/configs/chrome/histograms.xml. + * + * @enum {number} + */ +mr.EventAnalytics.Event = { + // Special value meaning the event page was woken by the Media Router and not + // a regular extension event. + MEDIA_ROUTER: 0, + CAST_CHANNEL_ON_ERROR: 1, + CAST_CHANNEL_ON_MESSAGE: 2, + DIAL_ON_DEVICE_LIST: 3, + DIAL_ON_ERROR: 4, + GCM_ON_MESSAGE: 5, + IDENTITY_ON_SIGN_IN_CHANGED: 6, + MDNS_ON_SERVICE_LIST: 7, + NETWORKING_PRIVATE_ON_NETWORKS_CHANGED: 8, + NETWORKING_PRIVATE_ON_NETWORK_LIST_CHANGED: 9, + PROCESSES_ON_UPDATED: 10, + RUNTIME_ON_MESSAGE: 11, + RUNTIME_ON_MESSAGE_EXTERNAL: 12, + SETTINGS_PRIVATE_ON_PREFS_CHANGED: 13, + TABS_ON_UPDATED: 14, +}; + +/** + * @private {mr.EventAnalytics.Event} The event that woke the event page. + */ +mr.EventAnalytics.firstEvent_; + +/** + * Records an event handler invocation in the event page. If it is the first + * event that woke the page, a histogram is recorded. Subsequent events are a + * no-op. + * + * @param {!mr.EventAnalytics.Event} eventType The event type. + */ +mr.EventAnalytics.recordEvent = function(eventType) { + if (mr.EventAnalytics.firstEvent_ != undefined) return; + mr.Analytics.recordEnum( + 'MediaRouter.Provider.WakeEvent', eventType, mr.EventAnalytics.Event); + mr.EventAnalytics.firstEvent_ = eventType; +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics_test.js new file mode 100644 index 00000000000..5be671d2ccb --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_analytics_test.js @@ -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. + +goog.setTestOnly(); +goog.require('mr.Analytics'); +goog.require('mr.EventAnalytics'); + +describe('Tests EventAnalytics', () => { + + beforeEach(() => { + mr.EventAnalytics.firstEvent_ = undefined; + }); + + describe('Test recordEvent', () => { + it('should record only the first event', () => { + spyOn(mr.Analytics, 'recordEnum'); + mr.EventAnalytics.recordEvent(mr.EventAnalytics.Event.DIAL_ON_ERROR); + mr.EventAnalytics.recordEvent(mr.EventAnalytics.Event.TABS_ON_UPDATED); + expect(mr.Analytics.recordEnum.calls.count()).toEqual(1); + expect(mr.Analytics.recordEnum) + .toHaveBeenCalledWith( + 'MediaRouter.Provider.WakeEvent', + mr.EventAnalytics.Event.DIAL_ON_ERROR, mr.EventAnalytics.Event); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/event_target.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_target.js new file mode 100644 index 00000000000..8fbae73ddf1 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/event_target.js @@ -0,0 +1,69 @@ +// 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. + +goog.module('mr.EventTarget'); +goog.module.declareLegacyNamespace(); + + +/** @final */ +class EventTarget { + constructor() { + /** @private @const {!Array<!Subscription>} */ + this.subscriptions_ = []; + } + + /** + * @param {string} type The event type id. + * @param {function(this:T, ?)} handler Callback + * @param {T=} target + * @template T + */ + listen(type, handler, target = undefined) { + this.subscriptions_.push({type, handler, target}); + } + + /** + * @param {string} type The event type id. + * @param {function(this:T, ?)} handler Callback + * @param {T=} target + * @template T + */ + unlisten(type, handler, target = undefined) { + const index = this.subscriptions_.findIndex( + sub => + sub.type == type && sub.handler == handler && sub.target == target); + if (index != -1) { + this.subscriptions_.splice(index, 1); + } + } + + /** + * @param {{type: string}} event + */ + dispatchEvent(event) { + this.subscriptions_.forEach(sub => { + if (sub.type == event.type) { + // Call handler asynchronously so exceptions don't show up at the source + // of the event. + Promise.resolve().then(() => sub.handler.call(sub.target, event)); + } + }); + } +} + + +/** @record */ +const Subscription = class {}; + +/** @type {string} */ +Subscription.prototype.type; + +/** @type {function(?)} */ +Subscription.prototype.handler; + +/** @type {Object|undefined} */ +Subscription.prototype.target; + + +exports = EventTarget; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue.js new file mode 100644 index 00000000000..7f8cb7c23a1 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue.js @@ -0,0 +1,126 @@ +// 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 FIFO with a hard limit on its size. + + */ + +goog.module('mr.FixedSizeQueue'); +goog.module.declareLegacyNamespace(); + + +/** + * A fixed-sized buffer with FIFO semantics. + * @template T + */ +class FixedSizeQueue { + /** + * @param {number} maxSize The size of the buffer. + */ + constructor(maxSize) { + if (maxSize <= 0) { + throw Error('invalid buffer size'); + } + + /** + * Items are popped from here. Elements are stored in reverse + * insertion order. + * @private @type {!Array<T>} + */ + this.head_ = []; + + /** + * Items are pushed here. Elements are stored in insertion order. + * @private @type {!Array<T>} + */ + this.tail_ = []; + + /** + * @private @const + */ + this.maxSize_ = maxSize; + } + + /** + * Adds an item to the buffer. Drops the last added item if the + * buffer is full. + * @param {T} item + */ + enqueue(item) { + if (this.getCount() >= this.maxSize_) { + this.dequeue(); + } + this.tail_.push(item); + } + + /** + * Removes the oldest item from the buffer, which must be non-empty. + * @return {T} The removed item. + */ + dequeue() { + if (this.isEmpty()) { + throw Error('Empty queue'); + } + if (this.head_.length == 0) { + this.head_ = this.tail_; + this.head_.reverse(); + this.tail_ = []; + } + return this.head_.pop(); + } + + /** + * Removes and returns all items in the buffer in insertion order. + * @return {!Array<T>} + */ + dequeueAll() { + const result = this.getValues(); + this.clear(); + return result; + } + + /** + * @return {number} The number of items in the buffer. + */ + getCount() { + return this.head_.length + this.tail_.length; + } + + /** + * @return {boolean} True if the buffer is full. + */ + isFull() { + return this.getCount() == this.maxSize_; + } + + /** + * @return {boolean} True if the buffer is empty. + */ + isEmpty() { + return this.getCount() == 0; + } + + /** + * Gets all the items in the buffer in insertion order. + * @return {!Array<T>} + */ + getValues() { + const result = this.head_.slice(); // clones array + result.reverse(); + result.push(...this.tail_); + return result; + } + + /** + * Makes the buffer empty. + */ + clear() { + this.head_ = []; + this.tail_ = []; + } +} + + +exports = FixedSizeQueue; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue_test.js new file mode 100644 index 00000000000..09f0bff6ae9 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/fixed_size_queue_test.js @@ -0,0 +1,74 @@ +// 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. + +goog.require('mr.FixedSizeQueue'); + +describe('mr.FixedSizeQueue', function() { + let queue; + + beforeEach(function() { + queue = new mr.FixedSizeQueue(3); + }); + + it('works', function() { + expect(queue.getCount()).toBe(0); + expect(queue.isFull()).toBe(false); + expect(queue.isEmpty()).toBe(true); + expect(queue.getValues()).toEqual([]); + + queue.enqueue(1); + expect(queue.getCount()).toBe(1); + expect(queue.isFull()).toBe(false); + expect(queue.isEmpty()).toBe(false); + expect(queue.getValues()).toEqual([1]); + + queue.enqueue(2); + queue.enqueue(3); + expect(queue.getCount()).toBe(3); + expect(queue.isFull()).toBe(true); + expect(queue.isEmpty()).toBe(false); + expect(queue.getValues()).toEqual([1, 2, 3]); + + queue.enqueue(4); + expect(queue.getCount()).toBe(3); + expect(queue.isFull()).toBe(true); + expect(queue.isEmpty()).toBe(false); + expect(queue.getValues()).toEqual([2, 3, 4]); + + queue.clear(); + expect(queue.getCount()).toBe(0); + expect(queue.isFull()).toBe(false); + expect(queue.isEmpty()).toBe(true); + expect(queue.getValues()).toEqual([]); + }); + + describe('when full', function() { + beforeEach(function() { + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + expect(queue.getCount()).toBe(3); + expect(queue.isFull()).toBe(true); + }); + + it('supports dequeue', function() { + expect(queue.dequeue()).toBe(1); + expect(queue.getCount()).toBe(2); + expect(queue.getValues()).toEqual([2, 3]); + expect(queue.dequeue()).toBe(2); + expect(queue.getCount()).toBe(1); + expect(queue.getValues()).toEqual([3]); + expect(queue.dequeue()).toBe(3); + expect(queue.getCount()).toBe(0); + expect(queue.getValues()).toEqual([]); + expect(queue.isFull()).toBe(false); + expect(queue.isEmpty()).toBe(true); + }); + + it('supports deqeueAll', function() { + expect(queue.dequeueAll()).toEqual([1, 2, 3]); + expect(queue.isEmpty()).toBe(true); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/logger.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/logger.js new file mode 100644 index 00000000000..49b214a4b90 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/logger.js @@ -0,0 +1,261 @@ +// 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. + +goog.provide('mr.Logger'); + +goog.require('mr.Assertions'); +goog.require('mr.Config'); + + +/** + * An object for recording logs. + */ +mr.Logger = class { + /** + * @param {string} name + */ + constructor(name) { + /** + * @private @const {string} + */ + this.name_ = name; + } + + /** + * @param {string} name + * @return {!mr.Logger} + */ + static getInstance(name) { + let instance = mr.Logger.instances_.get(name); + if (!instance) { + instance = new mr.Logger(name); + mr.Logger.instances_.set(name, instance); + } + return instance; + } + + /** + * @param {function(!mr.Logger.Record)} handler + */ + static addHandler(handler) { + mr.Logger.handlers_.push(handler); + } + + /** + * Logs a pre-built record if its level is high enough. + * @param {!mr.Logger.Record} record + */ + static logRecord(record) { + if (record.level >= mr.Logger.level) { + mr.Logger.handlers_.forEach(handler => handler(record)); + } + } + + /** + * Logs a message at the specified log level with an optional exception. + * + * @param {mr.Logger.Level} level + * @param {mr.Logger.Loggable} message + * @param {*=} exception An exception to associate with the log message. This + * should normally be an Error instance for best results, but any type is + * acceptable. + */ + log(level, message, exception = undefined) { + if (level < mr.Logger.level) { + return; + } + + // Logging will occur at the current logging level. If the message is a + // lazy-evaluated one, eval now. + if (typeof message == 'function') { + message = message(); + } + + // For non-debug builds, make an effort to programmatically scrub message + // text that potentially contains personally-identifying information. + // However, note that this only covers some of the more-obvious forms of + // PII, and no heuristic can ever hope to provide 100% safety. Also, some of + // the regular expressions may sometimes match more or less than what was + // intended. + mr.Assertions.assert( + typeof message == 'string', 'Expected message to be a string.'); + if (!mr.Config.isDebugChannel) { + message = message.replace(mr.Logger.URL_REGEXP_, '[Redacted URL]'); + message = message.replace( + mr.Logger.DOMAIN_OR_EMAIL_REGEXP_, '[Redacted domain/email]'); + message = message.replace(mr.Logger.SINK_ID_REGEXP_, (match, p1, p2) => { + return p1 + ':<' + p2.substr(-4) + '>'; + }); + } + + const record = { + logger: this.name_, + level: level, + time: Date.now(), + message: message, + exception: exception, + }; + mr.Logger.handlers_.forEach(handler => handler(record)); + } + + /** + * @param {mr.Logger.Loggable} message + * @param {*=} exception + */ + error(message, exception = undefined) { + this.log(mr.Logger.Level.SEVERE, message, exception); + } + + /** + * @param {mr.Logger.Loggable} message + * @param {*=} exception + */ + warning(message, exception = undefined) { + this.log(mr.Logger.Level.WARNING, message, exception); + } + + /** + * @param {mr.Logger.Loggable} message + * @param {*=} exception + */ + info(message, exception = undefined) { + this.log(mr.Logger.Level.INFO, message, exception); + } + + /** + * @param {mr.Logger.Loggable} message + * @param {*=} exception + */ + fine(message, exception = undefined) { + this.log(mr.Logger.Level.FINE, message, exception); + } + + /** + * @param {mr.Logger.Level} level + * @return {string} + */ + static levelToString(level) { + return mr.Logger.LEVEL_NAMES_[level]; + } + + /** + * @param {string} levelName + * @param {mr.Logger.Level} defaultLevel + * @return {mr.Logger.Level} + */ + static stringToLevel(levelName, defaultLevel) { + const index = mr.Logger.LEVEL_NAMES_.indexOf(levelName); + return index == -1 ? defaultLevel : /** @type {mr.Logger.Level} */ (index); + } + + /** + * Converts a numeric log level (as used in the Closure library) into a log + * level constant. + * @param {number} levelValue + * @return {mr.Logger.Level} + */ + static numberToLevel(levelValue) { + if (levelValue <= 600) { + return mr.Logger.Level.FINE; + } else if (levelValue <= 850) { + return mr.Logger.Level.INFO; + } else if (levelValue <= 950) { + return mr.Logger.Level.WARNING; + } else { + return mr.Logger.Level.SEVERE; + } + } +}; + + +/** + * @private @const {!Array<function(mr.Logger.Record)>} + */ +mr.Logger.handlers_ = []; + + +/** + * @private @const {!Map<string, !mr.Logger>} + */ +mr.Logger.instances_ = new Map(); + + +/** + * The available log levels. + * @enum {number} + */ +mr.Logger.Level = { + FINE: 0, + INFO: 1, + WARNING: 2, + SEVERE: 3, +}; + + +/** + * The canonical names of log levels in ascending order of severity. + * @private const {!Array<string>} + */ +mr.Logger.LEVEL_NAMES_ = ['FINE', 'INFO', 'WARNING', 'SEVERE']; + + +/** + * A regular expression that matches a very broad-range of text that looks like + * it could be a domain name or an e-mail address. + * @private const {!RegExp} + */ +mr.Logger.DOMAIN_OR_EMAIL_REGEXP_ = + /(([\w.+-]+@)|((www|m|mail|ftp)[.]))[\w.-]+[.][\w-]{2,4}/gi; + + +/** + * A regular expression that matches a very broad-range of text that looks like + * it could be an URL. + * @private const {!RegExp} + */ +mr.Logger.URL_REGEXP_ = /(data:|https?:\/\/)\S+/gi; + + +/** + * A regular expression that matches a very broad-range of text that looks like + * it could be a sink ID. + * @private const {!RegExp} + */ +mr.Logger.SINK_ID_REGEXP_ = /(dial|cast):<([a-zA-Z0-9]+)>/gi; + + +/** + * An abstract represenation of a log message. + * + * The `time` field should be in the format returned by `Date.now()`. The + * `exception` field will typically be an Error instance, but code that handles + * log records must be prepared to handle any type. + * + * @typedef {{ + * level: mr.Logger.Level, + * logger: string, + * time: number, + * message: string, + * exception: *, + * }} + */ +mr.Logger.Record; + + +/** + * @typedef {string|function():string} + */ +mr.Logger.Loggable; + + +/** + * @const + */ +mr.Logger.DEFAULT_LEVEL = mr.Logger.Level.INFO; + + +/** + * @type {mr.Logger.Level} + */ +mr.Logger.level = mr.Logger.DEFAULT_LEVEL; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/logger_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/logger_test.js new file mode 100644 index 00000000000..bb5048642ad --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/logger_test.js @@ -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. + +goog.module('mr.LoggerTest'); +goog.setTestOnly('mr.LoggerTest'); + +const Config = goog.require('mr.Config'); +const Logger = goog.require('mr.Logger'); + +describe('Test mr.Logger', function() { + let originalLevel; + let logger; + + beforeEach(() => { + originalLevel = Logger.level; + logger = new Logger('test'); + }); + + afterEach(() => { + Logger.level = originalLevel; + Logger.handlers_ = []; + }); + + it('logs string messages only at INFO and above', () => { + Logger.level = Logger.Level.WARNING; + const loggedMessages = []; + Logger.addHandler(record => { + expect(record.level).not.toBeLessThan(Logger.Level.WARNING); + expect(record.logger).toEqual('test'); + expect(typeof record.time).toBe('number'); + expect(typeof record.message).toBe('string'); + loggedMessages.push(record.message); + }); + + logger.fine('Should not log this message.'); + logger.info('Should not log this message either.'); + logger.warning('Should log this warning message.'); + logger.error('Should log this error message.'); + + expect(loggedMessages).toEqual([ + 'Should log this warning message.', 'Should log this error message.' + ]); + }); + + it('logs lazy-evaluated messages', () => { + Logger.level = Logger.Level.FINE; + const loggedMessages = []; + Logger.addHandler(record => { + expect(record.level).not.toBeLessThan(Logger.Level.FINE); + expect(record.logger).toEqual('test'); + expect(typeof record.time).toBe('number'); + expect(typeof record.message).toBe('string'); + loggedMessages.push(record.message); + }); + + logger.fine(() => 'Should log this fine message.'); + logger.info(() => 'Should log this info message.'); + logger.warning(() => 'Should log this warning message.'); + logger.error(() => 'Should log this error message.'); + + expect(loggedMessages).toEqual([ + 'Should log this fine message.', 'Should log this info message.', + 'Should log this warning message.', 'Should log this error message.' + ]); + }); + + describe('Personally-identifying info scrubbbing tests', () => { + let loggedMessages; + let isDebugChannelDefault = Config.isDebugChannel; + + beforeEach(() => { + Config.isDebugChannel = false; + loggedMessages = []; + Logger.level = Logger.Level.FINE; + Logger.addHandler(record => { + expect(typeof record.message).toBe('string'); + loggedMessages.push(record.message); + }); + }); + + afterEach(() => { + Config.isDebugChannel = isDebugChannelDefault; + }); + + it('does not scrub non-PII from messages', () => { + // Things that shouldn't be scrubbed. + logger.info(''); + logger.info('42'); + logger.info( + 'Found sink with id: ac6982d68e687faf6ebf8cc (Chromecast Ultra)'); + logger.info('The event occurred at 20:21:22 on 29 Mar 2017.'); + + expect(loggedMessages).toEqual([ + '', '42', + 'Found sink with id: ac6982d68e687faf6ebf8cc (Chromecast Ultra)', + 'The event occurred at 20:21:22 on 29 Mar 2017.' + ]); + }); + + it('scrubs domains', () => { + // Things that look like domain names. + logger.info('Visiting www.google.com...'); + logger.info('Tab favicon domain is: ftp.myfilez.net'); + // The following example shows the RegExp currently used does not match + // against all possible domains perfectly. + logger.info( + 'mail.personaldata.security.biz mapped to ' + + 'personaldata.security.biz.'); + + expect(loggedMessages).toEqual([ + 'Visiting [Redacted domain/email]...', + 'Tab favicon domain is: [Redacted domain/email]', + '[Redacted domain/email] mapped to personaldata.security.biz.' + ]); + }); + + it('scrubs email addresses', () => { + // Things that look like e-mail addresses. + logger.info('Reply to nobody@love-spam.net, and see what happens.'); + logger.info( + 'This CL was written by somebody@developers.chromium.org, ' + + 'or was it somebody@hooli.com?'); + + expect(loggedMessages).toEqual([ + 'Reply to [Redacted domain/email], and see what happens.', + 'This CL was written by [Redacted domain/email], or was it ' + + '[Redacted domain/email]?' + ]); + }); + + it('scrubs URLs', () => { + // Things that look like URLs. + logger.info( + 'Downloading from http://www.pictures.com/gifs/' + + 'kittens%20falling%20of%20furniture.png...'); + logger.info('Page navigation detected: https://youtube.com/profile'); + logger.info( + 'Relpacing content with: ' + + 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D'); + + expect(loggedMessages).toEqual([ + 'Downloading from [Redacted URL]', + 'Page navigation detected: [Redacted URL]', + 'Relpacing content with: [Redacted URL]' + ]); + }); + + it('scrubs sink IDs', () => { + logger.info( + 'Sink has pending connection' + + ' dial:<05f5e10100641000bc6f90f1aaa0bd90>'); + logger.info( + 'Adding new session: cast:<de51d94921f15f8af6dbf65592bb3610>, ' + + '5d85e5da-b773-4382-ba06-43c2a6dc6ba6'); + logger.info('Connecting to (id 1) rf72niQ3FPe8VpTz_tIEGNSkfGUo.'); + logger.info(''); + + expect(loggedMessages).toEqual([ + 'Sink has pending connection dial:<bd90>', + 'Adding new session: cast:<3610>, 5d85e5da-b773-4382-ba06-43c2a6dc6ba6', + 'Connecting to (id 1) rf72niQ3FPe8VpTz_tIEGNSkfGUo.', '' + ]); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils.js new file mode 100644 index 00000000000..e0392b46cf4 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils.js @@ -0,0 +1,127 @@ +// 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 The media source URN related utilities methods. + */ + +goog.provide('mr.MediaSourceUtils'); + +goog.require('mr.Config'); + + + + +/** + * @param {string} sourceUrn + * @return {boolean} True if it is a mirror URN. + */ +mr.MediaSourceUtils.isMirrorSource = function(sourceUrn) { + return mr.MediaSourceUtils.isTabMirrorSource(sourceUrn) || + mr.MediaSourceUtils.isDesktopMirrorSource(sourceUrn); +}; + + +/** + * @param {string} sourceUrn + * @return {boolean} True if it is a two UA mode presentation source. + * A presentation source has a sourceUrn that is a valid uri and + * is not a Cast custom receiver app. + */ +mr.MediaSourceUtils.isPresentationSource = function(sourceUrn) { + + if (!sourceUrn.startsWith('http:') && !sourceUrn.startsWith('https:')) { + return false; + } + // Use the DOM to parse sourceUrn. + const link = document.createElement('a'); + link.href = sourceUrn; + // Protocol must be http or https. + if (link.protocol != 'http:' && link.protocol != 'https:') { + return false; + } + + // Must not be a custom Cast receiver app. + return link.hash.indexOf(mr.MediaSourceUtils.CAST_APP_ID_) == -1; +}; + + + +/** @const {string} */ +mr.MediaSourceUtils.CAST_STREAMING_APP_ID = '0F5096E8'; + + +/** @const {string} */ +mr.MediaSourceUtils.TAB_MIRROR_URN_PREFIX = + 'urn:x-org.chromium.media:source:tab:'; + +/** @const {string} */ +mr.MediaSourceUtils.TAB_REMOTING_URN_PREFIX = + 'urn:x-org.chromium.media:source:tab_content_remoting:'; + +/** @const {string} */ +mr.MediaSourceUtils.DESKTOP_MIRROR_URN = + 'urn:x-org.chromium.media:source:desktop'; + + +/** @private @const {string} */ +mr.MediaSourceUtils.CAST_APP_ID_ = '__castAppId__'; + + +/** + * @private @const {!Array<string>} + */ +mr.MediaSourceUtils.MIRROR_APP_ID_ORIGIN_WHITELIST_ = [ + 'https://docs.google.com', // slides +]; + + +/** + * @param {string} sourceUrn + * @return {?Array<string>} array of origins whitelisted for the sourceUrn or + * |null| if any origin is allowed for the sourceUrn. + */ +mr.MediaSourceUtils.getWhitelistedOrigins = function(sourceUrn) { + if (mr.Config.isDebugChannel && + window.localStorage['debug.allowAllOrigins']) { + return null; + } + return sourceUrn.indexOf(mr.MediaSourceUtils.CAST_STREAMING_APP_ID) != -1 ? + mr.MediaSourceUtils.MIRROR_APP_ID_ORIGIN_WHITELIST_ : + null; +}; + + +/** + * @param {string} sourceUrn + * @return {boolean} True if it is a tab mirror URN. + */ +mr.MediaSourceUtils.isTabMirrorSource = function(sourceUrn) { + return sourceUrn.startsWith(mr.MediaSourceUtils.TAB_MIRROR_URN_PREFIX) || + sourceUrn.indexOf(mr.MediaSourceUtils.CAST_STREAMING_APP_ID) != -1; +}; + + +/** + * @param {string} sourceUrn + * @return {boolean} True if it is a desktop mirror URN. + */ +mr.MediaSourceUtils.isDesktopMirrorSource = function(sourceUrn) { + return sourceUrn == mr.MediaSourceUtils.DESKTOP_MIRROR_URN; +}; + + +/** + * Get the tab ID from |sourceUrn|. Returns null if the sourceUrn is not a tab + * mirror URN or if it doesn't contain a valid tab ID. + * @param {string} sourceUrn + * @return {?number} + */ +mr.MediaSourceUtils.getMirrorTabId = function(sourceUrn) { + const pos = sourceUrn.search(mr.MediaSourceUtils.TAB_MIRROR_URN_PREFIX); + if (pos == -1) return null; + const tabIdStr = + sourceUrn.substr(pos + mr.MediaSourceUtils.TAB_MIRROR_URN_PREFIX.length); + return parseInt(tabIdStr, 10) || null; +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils_test.js new file mode 100644 index 00000000000..9647921874f --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/media_source_utils_test.js @@ -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. + +goog.require('mr.MediaSourceUtils'); + +describe('Tests MediaSourceUtils', function() { + describe('Tests isTabMirrorSource', function() { + it('should return true for tab mirror source', function() { + expect(mr.MediaSourceUtils.isTabMirrorSource( + 'urn:x-org.chromium.media:source:tab:2')) + .toBe(true); + expect(mr.MediaSourceUtils.isTabMirrorSource( + 'urn:x-org.chromium.media:source:tab:666')) + .toBe(true); + }); + + it('should return false for non tab mirror source', function() { + expect(mr.MediaSourceUtils.isTabMirrorSource( + 'urn:x-org.chromium.media:source:desktop')) + .toBe(false); + }); + }); + + describe('Tests isPresentationSource', function() { + it('should return true for presentation source', function() { + expect(mr.MediaSourceUtils.isPresentationSource('http://www.google.com')) + .toBe(true); + expect(mr.MediaSourceUtils.isPresentationSource('https://www.google.com')) + .toBe(true); + }); + + it('should return false for non tab mirror source', function() { + expect( + mr.MediaSourceUtils.isPresentationSource('Invalid media source urn')) + .toBe(false); + }); + + it('should return false for cast receiver app', function() { + expect(mr.MediaSourceUtils.isPresentationSource( + 'http://www.google.com/cast#__castAppId__=deadbeef')) + .toBe(false); + }); + }); + + describe('Tests getMirrorTabId', function() { + it('should return null for non tab mirror source or invalid ID', + function() { + expect(mr.MediaSourceUtils.getMirrorTabId('http://www.google.com')) + .toBeNull(); + expect(mr.MediaSourceUtils.getMirrorTabId( + 'urn:x-org.chromium.media:source:tab:')) + .toBeNull(); + }); + + it('should return correct ID for correct tab mirror source', function() { + expect(mr.MediaSourceUtils.getMirrorTabId( + 'urn:x-org.chromium.media:source:tab:2')) + .toBe(2); + expect(mr.MediaSourceUtils.getMirrorTabId( + 'urn:x-org.chromium.media:source:tab:666')) + .toBe(666); + }); + }); + +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_clock.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_clock.js new file mode 100644 index 00000000000..dbb165cfb14 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_clock.js @@ -0,0 +1,391 @@ +// 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. + +goog.setTestOnly('mr.MockClock'); +goog.provide('mr.MockClock'); + +goog.require('mr.MockPromise'); + + +/** + * Class for unit testing code that uses setTimeout, clearTimeout, etc. + * @final + */ +mr.MockClock = class { + /** + * Installs the MockClock by overriding the global object's + * implementation of setTimeout, setInterval, clearTimeout and + * clearInterval. + */ + constructor() { + if (window.setTimeout !== mr.MockClock.REAL_SETTIMEOUT_) { + throw Error('MockClock already installed.'); + } + + /** + * List of times to fire, sorted in reverse order of when they + * will be executed. + * + * @type {!Array<mr.MockClock.Timeout_>} + * @private + */ + this.queue_ = []; + + /** + * The current simulated time in milliseconds. + * @type {number} + * @private + */ + this.nowMillis_ = 0; + + mr.MockClock.installedHere_ = Error('MockClock was installed here.'); + + window.setTimeout = this.setTimeout_.bind(this); + window.setInterval = this.setInterval_.bind(this); + window.setImmediate = this.setImmediate_.bind(this); + window.clearTimeout = this.clearTimeout_.bind(this); + window.clearInterval = this.clearTimeout_.bind(this); + Date.now = this.getCurrentTime_.bind(this); + } + + /** + * Removes the MockClock's hooks into the global object's functions + * and revert to their original values. + */ + uninstall() { + if (window.setTimeout === mr.MockClock.REAL_SETTIMEOUT_) { + throw Error('MockClock not installed.'); + } + + mr.MockClock.installedHere_ = null; + + window.setTimeout = mr.MockClock.REAL_SETTIMEOUT_; + window.setInterval = mr.MockClock.REAL_SETINTERVAL_; + window.setImmediate = mr.MockClock.REAL_SETIMMEDIATE_; + window.clearTimeout = mr.MockClock.REAL_CLEARTIMEOUT_; + window.clearInterval = mr.MockClock.REAL_CLEARINTERVAL_; + Date.now = mr.MockClock.REAL_DATENOW_; + } + + /** + * Restores this clock to the state it was in just after it was + * created. + */ + reset() { + this.queue_ = []; + this.nowMillis_ = 0; + } + + /** + * Increments the MockClock's time by a given number of + * milliseconds, running any functions that are now overdue. + * @param {number=} millis Number of milliseconds to increment the + * counter. If not specified, clock ticks 1 millisecond. + * @return {number} Current mock time in milliseconds. + */ + tick(millis = 1) { + const endTime = this.nowMillis_ + millis; + this.runFunctionsWithinRange_(endTime); + this.nowMillis_ = endTime; + return endTime; + } + + /** + * Ticks the clock until there are no more actions scheduled to run. + */ + flush() { + this.tick(Infinity); + } + + /** + * Takes a promise and then ticks the mock clock. If the promise + * successfully resolves, returns the value produced by the + * promise. If the promise is rejected, it throws the rejection as + * an exception. If the promise is not resolved at all, throws an + * exception. Also ticks the general clock by the specified amount. + * + * @param {!mr.MockPromise<T>} promise A promise that should be + * resolved after the mockClock is ticked for the given + * opt_millis. + * @param {number=} millis Number of milliseconds to increment the + * counter. If not specified, clock ticks 1 millisecond. + * @return {T} + * @template T + */ + tickPromise(promise, millis = 1) { + let value; + let error; + let resolved = false; + promise.then( + v => { + value = v; + resolved = true; + }, + e => { + error = e; + resolved = true; + }); + this.tick(millis); + if (!resolved) { + throw new Error( + 'Promise was expected to be resolved ' + + 'after mock clock tick.'); + } + if (error) { + throw error; + } + return value; + } + + /** + * Takes a promise and then ticks the mock clock. If the promise + * rejects, returns the error produced by the promise. If the + * promise is rejected, it throws the rejection as an exception. If + * the promise is not rejected at all, throws an exception. Also + * ticks the general clock by the specified amount. + * + * @param {!mr.MockPromise<T>} promise A promise that should be + * rejected after the mockClock is ticked for the given + * opt_millis. + * @param {number=} millis Number of milliseconds to increment the + * counter. If not specified, clock ticks 1 millisecond. + * @return {*} Error produced by the promise. + * @template T + */ + tickRejectingPromise(promise, millis = 1) { + let error; + let rejected = false; + promise.catch(e => { + error = e; + rejected = true; + }); + this.tick(millis); + if (!rejected) { + throw new Error( + 'Promise was expected to be rejected after mock clock tick.'); + } + return error; + } + + /** + * @return {number} The MockClock's current time in milliseconds. + * @private + */ + getCurrentTime_() { + return this.nowMillis_; + } + + /** + * Runs any function that is scheduled before a certain time. + * @param {number} endTime The latest time in the range, in + * milliseconds. + * @private + */ + runFunctionsWithinRange_(endTime) { + mr.MockPromise.callPendingHandlers(); + + // Repeatedly pop off the last item since the queue is always + // sorted. + while (this.queue_ && this.queue_.length && + this.queue_[this.queue_.length - 1].runAtMillis <= endTime) { + const timeout = this.queue_.pop(); + // Only move time forwards. + this.nowMillis_ = Math.max(this.nowMillis_, timeout.runAtMillis); + if (timeout.recurring) { + // Reschedule before calling the function so that if the + // function deletes the timeout, it's in the queue to be + // removed. + this.scheduleFunction_( + timeout.timeoutKey, timeout.funcToCall, timeout.millis, true); + } + timeout.funcToCall.call(undefined); + mr.MockPromise.callPendingHandlers(); + } + } + + /** + * Schedules a function to be run at a certain time. + * @param {number} timeoutKey The timeout key. + * @param {!Function} funcToCall The function to call. + * @param {number} millis The number of milliseconds to call it in. + * @param {boolean} recurring Whether to function call should recur. + * @private + */ + scheduleFunction_(timeoutKey, funcToCall, millis, recurring) { + const timeout = { + runAtMillis: this.nowMillis_ + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis, + }; + + // Insert a timer descriptor into a descending-order queue. + // + // Later-inserted duplicates appear at lower indices. For + // example, the asterisk in (5,4,*,3,2,1) would be the insertion + // point for 3. (The numbers here refer to timestamps.) + // + // Insertion of N items is quadratic, but unit tests are normally + // small, so scalability is not a primary issue. + // + // Since the queue is in reverse order (so we can pop rather than + // unshift), and later timers with the same time stamp should be + // executed later, we look for the element strictly greater than + // the one we are inserting. + let i; + for (i = this.queue_.length; i != 0; i--) { + if (this.queue_[i - 1].runAtMillis > timeout.runAtMillis) { + break; + } + this.queue_[i] = this.queue_[i - 1]; + } + this.queue_[i] = timeout; + } + + /** + * Schedules a function to be called after `millis` + * milliseconds. Mock implementation for setTimeout. + * @param {!Function} funcToCall The function to call. + * @param {number=} millis The number of milliseconds to call it + * after. + * @param {...*} args Arguments to pass to the function. + * @return {number} The number of timeouts created. + * @private + */ + setTimeout_(funcToCall, millis = 0, ...args) { + if (millis > mr.MockClock.MAX_INT_) { + throw Error(`Bad timeout value: ${millis}`); + } + this.scheduleFunction_( + mr.MockClock.nextId_, funcToCall.bind(undefined, ...args), millis, + false); + return mr.MockClock.nextId_++; + } + + /** + * Schedules a function to be called every `millis` milliseconds. + * Mock implementation for setInterval. + * @param {!Function} funcToCall The function to call. + * @param {number=} millis The number of milliseconds between calls. + * @param {...*} args Arguments to pass to the function. + * @return {number} The number of timeouts created. + * @private + */ + setInterval_(funcToCall, millis = 0, ...args) { + this.scheduleFunction_( + mr.MockClock.nextId_, funcToCall.bind(undefined, ...args), millis, + true); + return mr.MockClock.nextId_++; + } + + /** + * Schedules a function to be called immediately after the current JS + * execution. + * Mock implementation for setImmediate. + * @param {!Function} funcToCall The function to call. + * @param {...*} args Arguments to pass to the function. + * @return {number} The number of timeouts created. + * @private + */ + setImmediate_(funcToCall, ...args) { + return this.setTimeout_(funcToCall, 0, ...args); + } + + /** + * Clears a timeout. + * Mock implementation for clearTimeout and clearInterval. + * @param {number} timeoutKey The timeout key to clear. + * @private + */ + clearTimeout_(timeoutKey) { + const newQueue = + this.queue_.filter(timeout => timeout.timeoutKey != timeoutKey); + if (newQueue.length == this.queue_.length_) { + // The real versions of clearTimeout and clearInterval silently + // ignore invalid keys, but we hold ourselves to a higher + // standard :-) + throw Error('Invalid timeoutKey'); + } + this.queue_ = newQueue; + } +}; + + +/** + * ID to use for next timeout. Timeout IDs must never be reused, even + * across MockClock instances. + * @private {number} + */ +mr.MockClock.nextId_ = 0; + + +/** + * @private @const + */ +mr.MockClock.REAL_SETTIMEOUT_ = window.setTimeout; + + +/** + * @private @const + */ +mr.MockClock.REAL_SETINTERVAL_ = window.setInterval; + + +/** + * @private @const + */ +mr.MockClock.REAL_SETIMMEDIATE_ = window.setImmediate; + + +/** + * @private @const + */ +mr.MockClock.REAL_CLEARTIMEOUT_ = window.clearTimeout; + + +/** + * @private @const + */ +mr.MockClock.REAL_CLEARINTERVAL_ = window.clearInterval; + + +/** + * @private @const + */ +mr.MockClock.REAL_DATENOW_ = Date.now; + + +/** + * Maximum 32-bit signed integer. + * + * Timeouts over this time return immediately in many browsers, due to + * integer overflow. Such known browsers include Firefox, Chrome, and + * Safari, but not IE. + * + * @type {number} + * @private + */ +mr.MockClock.MAX_INT_ = 2147483647; + + +/** + * @typedef {{ + * runAtMillis: number, + * funcToCall: !Function, + * recurring: boolean, + * timeoutKey: number, + * millis: number, + * }} + * @private + */ +mr.MockClock.Timeout_; + + +/** + * Exception used to record where the current MockClock was created. Helpful + * for diagnosing unit tests that fail to uninstall their mock clocks. + * @private {Error} + */ +mr.MockClock.installedHere_ = null; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise.js new file mode 100644 index 00000000000..511ddbdec0a --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise.js @@ -0,0 +1,602 @@ +// 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. + +goog.provide('mr.MockPromise'); +goog.setTestOnly('mr.MockPromise'); + + +/** + * Why does this class exist? + * + * Most of our unit tests that involve promises are written in "synchronous + * style". In this style, everything happens in one iteration of the JS event + * loop. With native promises, this style isn't possible, because a call like + * p.then(f) won't run f until at least the next event loop iteration, even if p + * is already resolved. + * + * When the tests were originally written, they relied on a particular + * interaction between goog.testing.MockClock and goog.Promise, where calling + * mockClock.tick() would force any code scheduled by goog.Promise to be + * executed immediately. Since then, the non-test code has been changed to use + * native promises, and the test code has been changed to use this class and + * mr.MockClock, but the same principle applies. + * + * The long-term plan is to convert the tests to use asynchronous style, and get + * rid of this class and mr.MockClock entirely. + * + * @template TYPE + * @final + */ +mr.MockPromise = class { + /** + * @param {function( + * function((TYPE|mr.MockPromise<TYPE>)=), + * function(*=)): void} resolver + */ + constructor(resolver) { + /** + * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, + * or BLOCKED. + * @private {mr.MockPromise.State_} + */ + this.state_ = mr.MockPromise.State_.PENDING; + + /** + * The settled result of the Promise. Immutable once set with either a + * fulfillment value or rejection reason. + * @private {*} + */ + this.result_ = undefined; + + /** + * The linked list of `onFulfilled` and `onRejected` callbacks + * added to this Promise by calls to {@code then()}. + * @private {!Array<!mr.MockPromise.CallbackEntry_>} + */ + this.callbackEntries_ = []; + + /** + * Whether the Promise is in the queue of Promises to execute. + * @private {boolean} + */ + this.executing_ = false; + + /** + * A boolean that is set if the Promise is rejected, and reset to false if + * an `onRejected` callback is invoked for the Promise (or one of its + * descendants). If the rejection is not handled before the next timestep, + * the rejection reason is passed to the unhandled rejection handler. + * @private {boolean} + */ + this.hadUnhandledRejection_ = false; + + try { + resolver.call( + null, + value => { + this.resolve_(mr.MockPromise.State_.FULFILLED, value); + }, + reason => { + try { + // Promise was rejected. Step up one call frame to see why. + if (reason instanceof Error) { + throw reason; + } else { + throw new Error('Promise rejected.'); + } + } catch (e) { + // Only thrown so browser dev tools can catch rejections of + // promises when the option to break on caught exceptions is + // activated. + } + this.resolve_(mr.MockPromise.State_.REJECTED, reason); + }); + } catch (e) { + this.resolve_(mr.MockPromise.State_.REJECTED, e); + } + } + + /** + * Replaces the native Promise class with this class. + */ + static install() { + if (window.Promise !== mr.MockPromise.origPromise_) { + throw Error('Error installing mr.MockPromise'); + } + if (mr.MockPromise.pendingHandlers_.length) { + throw Error('Expected no pending handlers.'); + } + window.Promise = mr.MockPromise; + mr.MockPromise.ignoreUnhandledRejections = false; + } + + /** + * Undoes the effect of calling install(). + */ + static uninstall() { + if (window.Promise !== mr.MockPromise) { + throw Error('mr.MockPromise not installed'); + } + if (mr.MockPromise.pendingHandlers_.length) { + console.warn('Discarding pending handlers.'); + mr.MockPromise.pendingHandlers_.length = 0; + // throw Error('Expected no pending handlers.'); + } + window.Promise = mr.MockPromise.origPromise_; + } + + /** + * @return {void} + */ + static callPendingHandlers() { + while (mr.MockPromise.pendingHandlers_.length) { + const handler = mr.MockPromise.pendingHandlers_.shift(); + handler(); + } + } + + /** + * @param {Function} onFulfilled + * @param {Function} onRejected + * @return {!mr.MockPromise.CallbackEntry_} + * @private + */ + static getCallbackEntry_(onFulfilled, onRejected) { + const entry = new mr.MockPromise.CallbackEntry_(); + entry.onFulfilled = onFulfilled; + entry.onRejected = onRejected; + return entry; + } + + /** + * @param {*=} opt_value + * @return {!mr.MockPromise} A new Promise that is immediately resolved + * with the given value. If the input value is already a mr.MockPromise, + * it will be returned immediately without creating a new instance. + */ + static resolve(opt_value) { + if (opt_value instanceof mr.MockPromise) { + // Avoid creating a new object if we already have a promise object + // of the correct type. + return opt_value; + } + + return new mr.MockPromise((resolve, reject) => { + resolve(opt_value); + }); + } + + /** + * @param {*=} opt_reason + * @return {!mr.MockPromise} A new Promise that is immediately rejected with the + * given reason. + */ + static reject(opt_reason) { + return new mr.MockPromise((resolve, reject) => { + reject(opt_reason); + }); + } + + /** + * @param {!Array} promises + * @return {!mr.MockPromise} A Promise that receives the result of the first + * Promise input to settle immediately after it settles. + */ + static race(promises) { + return new mr.MockPromise((resolve, reject) => { + if (!promises.length) { + resolve(undefined); + } + for (let i = 0, promise; i < promises.length; i++) { + promise = promises[i]; + mr.MockPromise.resolve(promise).then(resolve, reject); + } + }); + } + + /** + * @param {!Array} promises + * @return {!mr.MockPromise<!Array>} A Promise that receives a list of + * every fulfilled value once every input Promise is successfully + * fulfilled, or is rejected with the first rejection reason immediately + * after it is rejected. + */ + static all(promises) { + return new mr.MockPromise((resolve, reject) => { + let toFulfill = promises.length; + const values = []; + + if (!toFulfill) { + resolve(values); + return; + } + + const onFulfill = (index, value) => { + toFulfill--; + values[index] = value; + if (toFulfill == 0) { + resolve(values); + } + }; + + const onReject = reason => { + reject(reason); + }; + + for (let i = 0, promise; i < promises.length; i++) { + promise = promises[i]; + mr.MockPromise.resolve(promise).then(onFulfill.bind(null, i), onReject); + } + }); + } + + /** + * Adds callbacks that will operate on the result of the Promise, returning a + * new child Promise. + * + * If the Promise is fulfilled, the `onFulfilled` callback will be + * invoked with the fulfillment value as argument, and the child Promise will + * be fulfilled with the return value of the callback. If the callback throws + * an exception, the child Promise will be rejected with the thrown value + * instead. + * + * If the Promise is rejected, the `onRejected` callback will be invoked + * with the rejection reason as argument, and the child Promise will be + * resolved with the return value or rejected with the thrown value of the + * callback. + * + * @param {?(function(TYPE):?)=} onFulfilled A + * function that will be invoked with the fulfillment value if the Promise + * is fulfilled. + * @param {?(function(*): *)=} onRejected A function that will + * be invoked with the rejection reason if the Promise is rejected. + * @return {!mr.MockPromise} A new Promise that will receive the result of the + * callback. + * @override + */ + then(onFulfilled = null, onRejected = null) { + if (onFulfilled != null && typeof onFulfilled != 'function') { + throw Error('onFulfilled should be a function.'); + } + if (onRejected != null && typeof onRejected != 'function') { + throw Error('onRejected should be a function.'); + } + + return this.addChildPromise_(onFulfilled, onRejected); + } + + /** + * Adds a callback that will be invoked only if the Promise is rejected. This + * is equivalent to {@code then(null, onRejected)}. + * + * @param {function(*): *} onRejected A function that will be + * invoked with the rejection reason if the Promise is rejected. + * @return {!mr.MockPromise} A new Promise that will receive the result of the + * callback. + */ + catch(onRejected) { + return this.addChildPromise_(null, onRejected); + } + + /** + * Adds a callback entry to the current Promise, and schedules callback + * execution if the Promise has already been settled. + * + * @param {mr.MockPromise.CallbackEntry_} callbackEntry Record containing + * { + * @private + */ + addCallbackEntry_(callbackEntry) { + if (!this.hasEntry_() && + (this.state_ == mr.MockPromise.State_.FULFILLED || + this.state_ == mr.MockPromise.State_.REJECTED)) { + this.scheduleCallbacks_(); + } + this.queueEntry_(callbackEntry); + } + + /** + * Creates a child Promise and adds it to the callback entry list. The result + * of the child Promise is determined by the result of the `onFulfilled` + * or `onRejected` callbacks as specified in the Promise resolution + * procedure. + * + * @param {?function(TYPE): + * (RESULT|mr.MockPromise<RESULT>)} onFulfilled A callback that + * will be invoked if the Promise is fulfilled, or null. + * @param {?function(*): *} onRejected A callback that will be + * invoked if the Promise is rejected, or null. + * @return {!mr.MockPromise} The child Promise. + * @template RESULT + * @private + */ + addChildPromise_(onFulfilled, onRejected) { + /** @type {mr.MockPromise.CallbackEntry_} */ + const callbackEntry = mr.MockPromise.getCallbackEntry_(null, null, null); + + callbackEntry.child = new mr.MockPromise((resolve, reject) => { + // Invoke onFulfilled, or resolve with the parent's value if absent. + callbackEntry.onFulfilled = onFulfilled ? value => { + try { + const result = onFulfilled.call(null, value); + resolve(result); + } catch (err) { + reject(err); + } + } : resolve; + + // Invoke onRejected, or reject with the parent's reason if absent. + callbackEntry.onRejected = onRejected ? reason => { + try { + resolve(onRejected.call(null, reason)); + } catch (err) { + reject(err); + } + } : reject; + }); + + this.addCallbackEntry_(callbackEntry); + return callbackEntry.child; + } + + /** + * Unblocks the Promise and fulfills it with the given value. + * + * @param {TYPE} value + * @private + */ + unblockAndFulfill_(value) { + if (this.state_ != mr.MockPromise.State_.BLOCKED) { + throw Error('Expected state to be BLOCKED.'); + } + this.state_ = mr.MockPromise.State_.PENDING; + this.resolve_(mr.MockPromise.State_.FULFILLED, value); + } + + /** + * Unblocks the Promise and rejects it with the given rejection reason. + * + * @param {*} reason + * @private + */ + unblockAndReject_(reason) { + if (this.state_ != mr.MockPromise.State_.BLOCKED) { + throw Error('Expected state to be BLOCKED.'); + } + this.state_ = mr.MockPromise.State_.PENDING; + this.resolve_(mr.MockPromise.State_.REJECTED, reason); + } + + /** + * Attempts to resolve a Promise with a given resolution state and value. This + * is a no-op if the given Promise has already been resolved. + * + * If the given result is a Promise, the Promise will be settled with the same + * state and result as the Thenable once it is itself settled. + * + * If the given result is not a Promise, the Promise will be settled + * (fulfilled or rejected) with that result based on the given state. + * + * @param {mr.MockPromise.State_} state + * @param {*} x The result to apply to the Promise. + * @private + */ + resolve_(state, x) { + if (this.state_ != mr.MockPromise.State_.PENDING) { + return; + } + + if (this === x) { + state = mr.MockPromise.State_.REJECTED; + x = new TypeError('Promise cannot resolve to itself'); + } + + this.state_ = mr.MockPromise.State_.BLOCKED; + + if (x instanceof mr.MockPromise) { + x.addCallbackEntry_(mr.MockPromise.getCallbackEntry_( + this.unblockAndFulfill_.bind(this), + this.unblockAndReject_.bind(this))); + return; + } + + this.result_ = x; + this.state_ = state; + this.scheduleCallbacks_(); + + if (state == mr.MockPromise.State_.REJECTED) { + mr.MockPromise.addUnhandledRejection_(this, x); + } + } + + /** + * Executes the pending callbacks of a settled Promise after a timeout. + * @private + */ + scheduleCallbacks_() { + if (!this.executing_) { + this.executing_ = true; + mr.MockPromise.pendingHandlers_.push(this.executeCallbacks_.bind(this)); + } + } + + /** + * @return {boolean} Whether there are any pending callbacks queued. + * @private + */ + hasEntry_() { + return this.callbackEntries_.size > 0; + } + + /** + * @param {mr.MockPromise.CallbackEntry_} entry + * @private + */ + queueEntry_(entry) { + if (entry.onFulfilled == null) { + throw Error('entry.onFulfilled == null'); + } + this.callbackEntries_.push(entry); + } + + /** + * @return {mr.MockPromise.CallbackEntry_} entry + * @private + */ + popEntry_() { + const entry = this.callbackEntries_.shift() || null; + if (entry && entry.onFulfilled == null) { + throw Error('entry.onFulfulled == null'); + } + return entry; + } + + /** + * Executes all pending callbacks for this Promise. + * + * @private + */ + executeCallbacks_() { + let entry = null; + while (entry = this.popEntry_()) { + this.executeCallback_(entry, this.state_, this.result_); + } + this.executing_ = false; + } + + /** + * Executes a pending callback for this Promise. Invokes an + * `onFulfilled` or `onRejected` callback based on the settled + * state of the Promise. + * + * @param {!mr.MockPromise.CallbackEntry_} callbackEntry An entry containing + * the onFulfilled and/or onRejected callbacks for this step. + * @param {mr.MockPromise.State_} state The resolution status of the Promise, + * either FULFILLED or REJECTED. + * @param {*} result The settled result of the Promise. + * @private + */ + executeCallback_(callbackEntry, state, result) { + // Cancel an unhandled rejection if the then call had an onRejected. + if (state == mr.MockPromise.State_.REJECTED && callbackEntry.onRejected) { + this.hadUnhandledRejection_ = false; + } + + if (callbackEntry.child) { + mr.MockPromise.invokeCallback_(callbackEntry, state, result); + } else { + try { + mr.MockPromise.invokeCallback_(callbackEntry, state, result); + } catch (err) { + mr.MockPromise.handleRejection_(err); + } + } + } + + /** + * Executes the onFulfilled or onRejected callback for a callbackEntry. + * + * @param {!mr.MockPromise.CallbackEntry_} callbackEntry + * @param {mr.MockPromise.State_} state + * @param {*} result + * @private + */ + static invokeCallback_(callbackEntry, state, result) { + if (state == mr.MockPromise.State_.FULFILLED) { + callbackEntry.onFulfilled.call(null, result); + } else if (callbackEntry.onRejected) { + callbackEntry.onRejected.call(null, result); + } + } + + /** + * Marks this rejected Promise as unhandled. If no `onRejected` callback + * is called for this Promise before the `UNHANDLED_REJECTION_DELAY` + * expires, the reason will be passed to the unhandled rejection handler. The + * handler typically rethrows the rejection reason so that it becomes visible + * in + * the developer console. + * + * @param {!mr.MockPromise} promise The rejected Promise. + * @param {*} reason The Promise rejection reason. + * @private + */ + static addUnhandledRejection_(promise, reason) { + promise.hadUnhandledRejection_ = true; + mr.MockPromise.pendingHandlers_.push(() => { + if (promise.hadUnhandledRejection_) { + mr.MockPromise.handleRejection_(reason); + } + }); + } + + /** + * @param {*} reason + * @private + */ + static handleRejection_(reason) { + if (!mr.MockPromise.ignoreUnhandledRejections) { + throw reason; + } + } +}; + + +/** + * @type {boolean} + */ +mr.MockPromise.ignoreUnhandledRejections = false; + + + +/** + * @private @const + */ +mr.MockPromise.origPromise_ = Promise; + + +/** + * The possible internal states for a Promise. These states are not directly + * observable to external callers. + * @enum {number} + * @private + */ +mr.MockPromise.State_ = { + /** The Promise is waiting for resolution. */ + PENDING: 0, + + /** The Promise is blocked waiting for the result of another Thenable. */ + BLOCKED: 1, + + /** The Promise has been resolved with a fulfillment value. */ + FULFILLED: 2, + + /** The Promise has been resolved with a rejection reason. */ + REJECTED: 3 +}; + + +/** + * @private @const {!Array<function()>} + */ +mr.MockPromise.pendingHandlers_ = []; + + +/** + * Entries in the callback chain. Each call to `then` or + * `catch` creates an entry containing the + * functions that may be invoked once the Promise is settled. + * + * @private @final + */ +mr.MockPromise.CallbackEntry_ = class { + constructor() { + /** @type {?mr.MockPromise} */ + this.child = null; + /** @type {Function} */ + this.onFulfilled = null; + /** @type {Function} */ + this.onRejected = null; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise_test.js new file mode 100644 index 00000000000..dc02a027eb3 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/mock_promise_test.js @@ -0,0 +1,850 @@ +// 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. + +goog.provide('mr.MockPromiseTest'); +goog.setTestOnly('mr.MockPromiseTest'); + +goog.require('mr.MockPromise'); + + +describe('mr.MockPromise', () => { + afterEach(() => { + mr.MockPromise.callPendingHandlers(); + }); + + // Simple shared objects used as test values. + const dummy = { + toString() { + return '[object dummy]'; + } + }; + const sentinel = { + toString() { + return '[object sentinel]'; + } + }; + + /** + * Dummy onfulfilled or onrejected function that should not be called. + * + * @param {*} result The result passed into the callback. + */ + function shouldNotCall(result) { + fail('This should not have been called (result: ' + String(result) + ')'); + } + + function resolveSoon(value) { + let resolveFunc; + const promise = new mr.MockPromise((resolve, reject) => { + resolveFunc = resolve.bind(null, value); + }); + return [promise, resolveFunc]; + } + + function rejectSoon(value) { + let rejectFunc; + const promise = new mr.MockPromise((resolve, reject) => { + rejectFunc = reject.bind(null, value); + }); + return [promise, rejectFunc]; + } + + // A trivial expectation test to suppress Jasmine warnings. + function noExpectations() { + expect(true).toBe(true); + } + + it('testThenIsFulfilled', () => { + let timesCalled = 0; + + const p = new mr.MockPromise((resolve, reject) => { + resolve(sentinel); + }); + p.then(value => { + timesCalled++; + expect(value).toBe(sentinel); + }); + + expect(timesCalled).toBe(0); + + mr.MockPromise.callPendingHandlers(); + expect(timesCalled).toBe(1); + }); + + it('testThenIsRejected', () => { + let timesCalled = 0; + + const p = mr.MockPromise.reject(sentinel); + p.then(shouldNotCall, value => { + timesCalled++; + expect(value).toBe(sentinel); + }); + + expect(timesCalled).toBe(0); + + mr.MockPromise.callPendingHandlers(); + expect(timesCalled).toBe(1); + }); + + it('testThenAsserts', () => { + const p = mr.MockPromise.resolve(); + + expect(() => { + p.then({}); + }).toThrowError(/onFulfilled should be a function./); + + expect(() => { + p.then(() => {}, {}); + }).toThrowError(/onRejected should be a function./); + }); + + it('testOptionalOnFulfilled', done => { + mr.MockPromise.resolve(sentinel) + .then(null, null) + .then(null, shouldNotCall) + .then(value => { + expect(value).toBe(sentinel); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testOptionalOnRejected', done => { + mr.MockPromise.reject(sentinel) + .then(null, null) + .then(shouldNotCall) + .then(null, reason => { + expect(reason).toBe(sentinel); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testMultipleResolves', () => { + let timesCalled = 0; + let resolvePromise; + + const p = new mr.MockPromise((resolve, reject) => { + resolvePromise = resolve; + resolve('foo'); + resolve('bar'); + }); + + p.then(value => { + timesCalled++; + expect(timesCalled).toBe(1); + }); + + mr.MockPromise.callPendingHandlers(); + + resolvePromise('baz'); + expect(timesCalled).toBe(1); + }); + + it('testMultipleRejects', () => { + let timesCalled = 0; + let rejectPromise; + + const p = new mr.MockPromise((resolve, reject) => { + rejectPromise = reject; + reject('foo'); + reject('bar'); + }); + + p.then(shouldNotCall, value => { + timesCalled++; + expect(timesCalled).toBe(1); + }); + + mr.MockPromise.callPendingHandlers(); + + rejectPromise('baz'); + expect(timesCalled).toBe(1); + }); + + it('testResolveWithPromise', () => { + let resolveBlocker; + let hasFulfilled = false; + const blocker = new mr.MockPromise((resolve, reject) => { + resolveBlocker = resolve; + }); + + const p = mr.MockPromise.resolve(blocker); + p.then(value => { + hasFulfilled = true; + expect(value).toBe(sentinel); + }, shouldNotCall); + + expect(hasFulfilled).toBe(false); + resolveBlocker(sentinel); + + mr.MockPromise.callPendingHandlers(); + expect(hasFulfilled).toBe(true); + }); + + it('testResolveWithRejectedPromise', () => { + let rejectBlocker; + let hasRejected = false; + const blocker = new mr.MockPromise((resolve, reject) => { + rejectBlocker = reject; + }); + + const p = mr.MockPromise.resolve(blocker); + const child = p.then(shouldNotCall, reason => { + hasRejected = true; + expect(reason).toBe(sentinel); + }); + + expect(hasRejected).toBe(false); + rejectBlocker(sentinel); + + mr.MockPromise.callPendingHandlers(); + expect(hasRejected).toBe(true); + }); + + it('testRejectWithPromise', () => { + let resolveBlocker; + let hasFulfilled = false; + const blocker = new mr.MockPromise((resolve, reject) => { + resolveBlocker = resolve; + }); + + const p = mr.MockPromise.reject(blocker); + const child = p.then(value => { + hasFulfilled = true; + expect(value).toBe(sentinel); + }, shouldNotCall); + + expect(hasFulfilled).toBe(false); + resolveBlocker(sentinel); + + mr.MockPromise.callPendingHandlers(); + expect(hasFulfilled).toBe(true); + }); + + it('testRejectWithRejectedPromise', () => { + let rejectBlocker; + let hasRejected = false; + const blocker = new mr.MockPromise((resolve, reject) => { + rejectBlocker = reject; + }); + + const p = mr.MockPromise.reject(blocker); + const child = p.then(shouldNotCall, reason => { + hasRejected = true; + expect(reason).toBe(sentinel); + }); + + expect(hasRejected).toBe(false); + rejectBlocker(sentinel); + + mr.MockPromise.callPendingHandlers(); + expect(hasRejected).toBe(true); + }); + + it('testResolveAndReject', () => { + let onFulfilledCalled = false; + let onRejectedCalled = false; + const p = new mr.MockPromise((resolve, reject) => { + resolve(); + reject(); + }); + + p.then( + () => { + onFulfilledCalled = true; + }, + () => { + onRejectedCalled = true; + }); + + mr.MockPromise.callPendingHandlers(); + expect(onFulfilledCalled).toBe(true); + expect(onRejectedCalled).toBe(false); + }); + + it('testResolveWithSelfRejects', done => { + let r; + const p = new mr.MockPromise(resolve => { + r = resolve; + }); + r(p); + p.then(shouldNotCall, e => { + expect(e.message).toBe('Promise cannot resolve to itself'); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testResolveWithObjectStringResolves', done => { + mr.MockPromise.resolve('[object Object]').then(v => { + expect(v).toBe('[object Object]'); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRejectAndResolve', done => { + noExpectations(); + new mr + .MockPromise((resolve, reject) => { + reject(); + resolve(); + }) + .then(shouldNotCall, done); + mr.MockPromise.callPendingHandlers(); + }); + + it('testThenReturnsBeforeCallbackWithFulfill', done => { + let thenHasReturned = false; + const p = mr.MockPromise.resolve(); + + p.then(() => { + expect(thenHasReturned).toBe(true); + done(); + }); + thenHasReturned = true; + + mr.MockPromise.callPendingHandlers(); + }); + + it('testThenReturnsBeforeCallbackWithReject', done => { + let thenHasReturned = false; + const p = mr.MockPromise.reject(); + + const child = p.then(shouldNotCall, () => { + expect(thenHasReturned).toBe(true); + done(); + }); + thenHasReturned = true; + + mr.MockPromise.callPendingHandlers(); + }); + + it('testResolutionOrder', () => { + const callbacks = []; + mr.MockPromise.resolve() + .then( + () => { + callbacks.push(1); + }, + shouldNotCall) + .then( + () => { + callbacks.push(2); + }, + shouldNotCall) + .then(() => { + callbacks.push(3); + }, shouldNotCall); + + mr.MockPromise.callPendingHandlers(); + expect(callbacks).toEqual([1, 2, 3]); + }); + + it('testResolutionOrderWithThrow', done => { + const callbacks = []; + const p = mr.MockPromise.resolve(); + + p.then(() => { + callbacks.push(1); + }, shouldNotCall); + const child = p.then(() => { + callbacks.push(2); + throw Error(); + }, shouldNotCall); + + child.then(shouldNotCall, () => { + // The parent callbacks should be evaluated before the child. + callbacks.push(4); + }); + + p.then(() => { + callbacks.push(3); + }, shouldNotCall); + + child.then(shouldNotCall, () => { + callbacks.push(5); + expect(callbacks).toEqual([1, 2, 3, 4, 5]); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testResolutionOrderWithNestedThen', done => { + const callbacks = []; + const promise = new mr.MockPromise((resolve, reject) => { + const p = mr.MockPromise.resolve(); + + p.then(() => { + callbacks.push(1); + p.then(() => { + callbacks.push(3); + resolve(); + }); + }); + p.then(() => { + callbacks.push(2); + }); + }); + + promise.then(() => { + expect(callbacks).toEqual([1, 2, 3]); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRejectionOrder', () => { + const callbacks = []; + const p = mr.MockPromise.reject(); + + p.then(shouldNotCall, () => { + callbacks.push(1); + }); + p.then(shouldNotCall, () => { + callbacks.push(2); + }); + p.then(shouldNotCall, () => { + callbacks.push(3); + }); + + mr.MockPromise.callPendingHandlers(); + expect(callbacks).toEqual([1, 2, 3]); + }); + + it('testRejectionOrderWithThrow', () => { + const callbacks = []; + const p = mr.MockPromise.reject(); + + p.then(shouldNotCall, () => { + callbacks.push(1); + }); + p.then(shouldNotCall, () => { + callbacks.push(2); + throw Error(); + }).catch(() => {}); + p.then(shouldNotCall, () => { + callbacks.push(3); + }); + + mr.MockPromise.callPendingHandlers(); + expect(callbacks).toEqual([1, 2, 3]); + }); + + it('testRejectionOrderWithNestedThen', done => { + const callbacks = []; + const promise = new mr.MockPromise((resolve, reject) => { + + const p = mr.MockPromise.reject(); + + p.then(shouldNotCall, () => { + callbacks.push(1); + p.then(shouldNotCall, () => { + callbacks.push(3); + resolve(); + }); + }); + p.then(shouldNotCall, () => { + callbacks.push(2); + }); + }); + + promise.then(() => { + expect(callbacks).toEqual([1, 2, 3]); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testBranching', () => { + const p = mr.MockPromise.resolve(2); + let branchesResolved = 0; + + const branch1 = p.then(value => { + expect(value).toBe(2); + return value / 2; + }).then(value => { + expect(value).toBe(1); + ++branchesResolved; + }); + + const branch2 = p.then(value => { + expect(value).toBe(2); + throw value + 1; + }).then(shouldNotCall, reason => { + expect(reason).toBe(3); + ++branchesResolved; + }); + + const branch3 = p.then(value => { + expect(value).toBe(2); + return value * 2; + }).then(value => { + expect(value).toBe(4); + ++branchesResolved; + }); + + mr.MockPromise.all([branch1, branch2, branch3]); + mr.MockPromise.callPendingHandlers(); + expect(branchesResolved).toBe(3); + }); + + it('testThenReturnsPromise', () => { + const parent = mr.MockPromise.resolve(); + const child = parent.then(); + + expect(child instanceof mr.MockPromise).toBe(true); + expect(child).not.toEqual(parent); + }); + + it('testBlockingPromise', () => { + const p = mr.MockPromise.resolve(); + let wasFulfilled = false; + let wasRejected = false; + + const p2 = p.then(() => new mr.MockPromise((resolve, reject) => {})); + + p2.then( + () => { + wasFulfilled = true; + }, + () => { + wasRejected = true; + }); + + mr.MockPromise.callPendingHandlers(); + expect(wasFulfilled).toBe(false); + expect(wasRejected).toBe(false); + }); + + it('testBlockingPromiseFulfilled', done => { + let resolveBlockingPromise; + const blockingPromise = new mr.MockPromise((resolve, reject) => { + resolveBlockingPromise = resolve; + }); + + const p = mr.MockPromise.resolve(dummy); + const p2 = p.then(value => blockingPromise); + + p2.then(value => { + expect(value).toBe(sentinel); + done(); + }); + resolveBlockingPromise(sentinel); + mr.MockPromise.callPendingHandlers(); + }); + + it('testBlockingPromiseRejected', done => { + let rejectBlockingPromise; + const blockingPromise = new mr.MockPromise((resolve, reject) => { + rejectBlockingPromise = reject; + }); + + const p = mr.MockPromise.resolve(blockingPromise); + + p.then(shouldNotCall, reason => { + expect(reason).toBe(sentinel); + done(); + }); + rejectBlockingPromise(sentinel); + mr.MockPromise.callPendingHandlers(); + }); + + it('testCatch', done => { + let catchCalled = false; + mr.MockPromise.reject() + .catch(reason => { + catchCalled = true; + return sentinel; + }) + .then(value => { + expect(catchCalled).toBe(true); + expect(value).toBe(sentinel); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithEmptyList', done => { + mr.MockPromise.race([]).then(value => { + expect(value).toBe(undefined); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithFulfill', done => { + const [a, resolveA] = resolveSoon('a'); + const [b, resolveB] = resolveSoon('b'); + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + + mr.MockPromise.race([a, b, c, d]) + .then(value => { + expect(value).toBe('c'); + // Return the slowest input promise to wait for it to complete. + return a; + }) + .then(value => { + expect(value).toBe('a'); + done(); + }); + resolveC(); + resolveD(); + resolveB(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithNonThenable', done => { + const [a, resolveA] = resolveSoon('a'); + const b = 'b'; + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + + mr.MockPromise.race([a, b, c, d]) + .then(value => { + expect(value).toBe('b'); + // Return the slowest input promise to wait for it to complete. + return a; + }) + .then(value => { + expect(value).toBe('a'); + done(); + }); + resolveC(); + resolveD(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithFalseyNonThenable', done => { + const [a, resolveA] = resolveSoon('a'); + const b = 0; + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + + mr.MockPromise.race([a, b, c, d]) + .then(value => { + expect(value).toBe(0); + // Return the slowest input promise to wait for it to complete. + return a; + }) + .then(value => { + expect(value).toBe('a'); + done(); + }); + resolveC(); + resolveD(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithFulfilledBeforeNonThenable', done => { + const [a, resolveA] = resolveSoon('a'); + const b = mr.MockPromise.resolve('b'); + const c = 'c'; + const [d, resolveD] = resolveSoon('d'); + + mr.MockPromise.race([a, b, c, d]) + .then(value => { + expect(value).toBe('b'); + // Return the slowest input promise to wait for it to complete. + return a; + }) + .then(value => { + expect(value).toBe('a'); + done(); + }); + resolveD(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testRaceWithReject', done => { + const [a, rejectA] = rejectSoon('rejected-a', 40); + const [b, rejectB] = rejectSoon('rejected-b', 30); + const [c, rejectC] = rejectSoon('rejected-c', 10); + const [d, rejectD] = rejectSoon('rejected-d', 20); + + mr.MockPromise.race([a, b, c, d]) + .then( + shouldNotCall, + value => { + expect(value).toBe('rejected-c'); + return a; + }) + .then(shouldNotCall, reason => { + expect(reason).toBe('rejected-a'); + done(); + }); + rejectC(); + rejectD(); + rejectB(); + rejectA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testAllWithEmptyList', done => { + mr.MockPromise.all([]).then(value => { + expect(value).toEqual([]); + done(); + }); + mr.MockPromise.callPendingHandlers(); + }); + + it('testAllWithFulfill', done => { + const [a, resolveA] = resolveSoon('a'); + const [b, resolveB] = resolveSoon('b'); + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + // Test a falsey value. + const [z, resolveZ] = resolveSoon(0); + + mr.MockPromise.all([a, b, c, d, z]).then(value => { + expect(value).toEqual(['a', 'b', 'c', 'd', 0]); + done(); + }); + resolveC(); + resolveD(); + resolveB(); + resolveZ(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testAllWithNonThenable', done => { + const [a, resolveA] = resolveSoon('a'); + const b = 'b'; + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + // Test a falsey value. + const z = 0; + + mr.MockPromise.all([a, b, c, d, z]).then(value => { + expect(value).toEqual(['a', 'b', 'c', 'd', 0]); + done(); + }); + resolveC(); + resolveD(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testAllWithReject', done => { + const [a, resolveA] = resolveSoon('a'); + const [b, rejectB] = rejectSoon('rejected-b'); + const [c, resolveC] = resolveSoon('c'); + const [d, resolveD] = resolveSoon('d'); + + mr.MockPromise.all([a, b, c, d]) + .then( + shouldNotCall, + reason => { + expect(reason).toBe('rejected-b'); + return a; + }) + .then(value => { + expect(value).toBe('a'); + done(); + }); + resolveC(); + resolveD(); + rejectB(); + resolveA(); + mr.MockPromise.callPendingHandlers(); + }); + + it('testMockClock', () => { + let resolveA; + let resolveB; + const calls = []; + + const p = new mr.MockPromise((resolve, reject) => { + resolveA = resolve; + }); + + p.then(value => { + expect(value).toBe(sentinel); + calls.push('then'); + }); + + const fulfilledChild = p.then(value => { + expect(value).toBe(sentinel); + return mr.MockPromise.resolve(1); + }).then(value => { + expect(value).toBe(1); + calls.push('fulfilledChild'); + + }); + + const rejectedChild = p.then(value => { + expect(value).toBe(sentinel); + return mr.MockPromise.reject(2); + }).then(shouldNotCall, reason => { + expect(reason).toBe(2); + calls.push('rejectedChild'); + }); + + const unresolvedChild = p.then(value => { + expect(value).toBe(sentinel); + return new mr.MockPromise(r => { + resolveB = r; + }); + }).then(value => { + expect(value).toBe(3); + calls.push('unresolvedChild'); + }); + + resolveA(sentinel); + expect(calls).toEqual([]); + + mr.MockPromise.callPendingHandlers(); + expect(calls).toEqual(['then', 'fulfilledChild', 'rejectedChild']); + + resolveB(3); + expect(calls).toEqual(['then', 'fulfilledChild', 'rejectedChild']); + + mr.MockPromise.callPendingHandlers(); + expect(calls).toEqual( + ['then', 'fulfilledChild', 'rejectedChild', 'unresolvedChild']); + }); + + it('testHandledRejection', () => { + noExpectations(); + mr.MockPromise.reject(sentinel).then(shouldNotCall, reason => {}); + mr.MockPromise.callPendingHandlers(); + }); + + it('testUnhandledRejection1', () => { + mr.MockPromise.reject(sentinel); + expect(mr.MockPromise.callPendingHandlers).toThrow(); + }); + + it('testUnhandledRejection2', () => { + mr.MockPromise.reject(sentinel).then(shouldNotCall); + expect(mr.MockPromise.callPendingHandlers).toThrow(); + }); + + it('testUnhandledThrow', () => { + mr.MockPromise.resolve().then(() => { + throw sentinel; + }); + expect(mr.MockPromise.callPendingHandlers).toThrow(); + }); + + it('testUnhandledBlockingRejection', () => { + const blocker = mr.MockPromise.reject(sentinel); + mr.MockPromise.resolve(blocker); + expect(mr.MockPromise.callPendingHandlers).toThrow(); + }); + + it('testHandledBlockingRejection', () => { + noExpectations(); + const blocker = mr.MockPromise.reject(sentinel); + mr.MockPromise.resolve(blocker).then(shouldNotCall, reason => {}); + mr.MockPromise.callPendingHandlers(); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/mojo_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/mojo_utils.js new file mode 100644 index 00000000000..f9d98798f8a --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/mojo_utils.js @@ -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. + +goog.module('mr.MojoUtils'); +goog.module.declareLegacyNamespace(); + + +/** + * Converts a mojo.Origin object to a string. + * @param {!mojo.Origin} origin + * @return {string} + */ +exports.mojoOriginToString = function(origin) { + if (origin.unique) { + return ''; + } else { + return `${origin.scheme}:\/\/${origin.host} + ${origin.port ? `:${origin.port}` : ''}/`; + } +}; + + +/** + * Converts an origin string to a mojo.Origin object. + * @param {string} origin + * @return {!mojo.Origin} + */ +exports.stringToMojoOrigin = function(origin) { + const url = new URL(origin); + const mojoOrigin = {}; + mojoOrigin.scheme = url.protocol.replace(':', ''); + mojoOrigin.host = url.hostname; + var port = url.port ? Number.parseInt(url.port, 10) : 0; + switch (mojoOrigin.scheme) { + case 'http': + mojoOrigin.port = port || 80; + break; + case 'https': + mojoOrigin.port = port || 443; + break; + default: + throw new Error('Scheme must be http or https'); + } + mojoOrigin.suborigin = ''; + return new mojo.Origin(mojoOrigin); +}; + +/** + * @param {?mojo.TimeDelta} timeDelta + * @return {number} + */ +exports.timeDeltaToSeconds = function(timeDelta) { + return timeDelta ? timeDelta.microseconds / 1000000 : 0; +}; + +/** + * @param {number} seconds + * @return {!mojo.TimeDelta} + */ +exports.secondsToTimeDelta = function(seconds) { + return new mojo.TimeDelta({microseconds: Math.floor(seconds * 1000000)}); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils.js new file mode 100644 index 00000000000..699c9f0be27 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils.js @@ -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. + +/** + * @fileoverview Utility methods for Objects. + */ + +goog.provide('mr.ObjectUtils'); + +mr.ObjectUtils = class { + /** + * Gets the value in an Object with the given list of keys by traversing the + * objects with them. + * @param {!Object} obj + * @param {...string} path + * @return {*} The value in the object with the given path, or undefined if + * it does not exist due to missing either one of the intermediate keys + * or the final key. + */ + static getPath(obj, ...path) { + let value = obj; + for (const key of path) { + if (value == undefined || typeof value != 'object') { + return undefined; + } + value = value[key]; + } + return value; + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils_test.js new file mode 100644 index 00000000000..253c6142f4e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/object_utils_test.js @@ -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. + +goog.setTestOnly('object_utils_test'); + +goog.require('mr.ObjectUtils'); + +describe('mr.ObjectUtils test', () => { + const thudObj = {thud: 2}; + const obj = {foo: {bar: {xyzzy: null, baz: {qux: 1, corge: thudObj}}}}; + + it('getPath returns undefined for nonexistent path', () => { + expect(mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'nonexistent')) + .toBeUndefined(); + expect( + mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'baz', 'qux', 'nonexistent')) + .toBeUndefined(); + expect(mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'xyzzy', 'nonexistent')) + .toBeUndefined(); + }); + + it('getPath returns value', () => { + expect(mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'baz', 'qux')).toBe(1); + expect(mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'baz', 'corge')) + .toBe(thudObj); + expect(mr.ObjectUtils.getPath(obj, 'foo', 'bar', 'xyzzy')).toBeNull(); + }); + + it('getPath returns itself if no path provided', () => { + expect(mr.ObjectUtils.getPath(obj)).toBe(obj); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/platform_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/platform_utils.js new file mode 100644 index 00000000000..26828b03848 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/platform_utils.js @@ -0,0 +1,62 @@ +// 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 Platform related utilities methods. + + */ + +goog.provide('mr.PlatformUtils'); + + +/** + * Returns true if the extension is running on Windows and Windows 8 or + * newer. + * + * @return {boolean} Whether the extension is running on Windows 8 or + * newer. + */ +mr.PlatformUtils.isWindows8OrNewer = function() { + // See MSDN for different windows versions: + // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx + const [, version] = navigator.userAgent.match(/Windows NT (\d+.\d+)/) || []; + + // Windows 8 has version number 6.2. + return parseFloat(version) >= 6.2; +}; + + +/** + * Enumeration of possible current operating systems. + * @enum {string} + */ +mr.PlatformUtils.OS = { + CHROMEOS: 'ChromeOS', + WINDOWS: 'Windows', + MAC: 'Mac', + LINUX: 'Linux', + OTHER: 'Other' +}; + + +/** + * Returns the current OS. + * @return {!mr.PlatformUtils.OS} + */ +mr.PlatformUtils.getCurrentOS = function() { + const userAgent = navigator.userAgent; + if (userAgent.includes('CrOS')) { + return mr.PlatformUtils.OS.CHROMEOS; + } + if (userAgent.includes('Windows')) { + return mr.PlatformUtils.OS.WINDOWS; + } + if (userAgent.includes('Macintosh')) { + return mr.PlatformUtils.OS.MAC; + } + if (userAgent.includes('Linux')) { + return mr.PlatformUtils.OS.LINUX; + } + return mr.PlatformUtils.OS.OTHER; +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_resolver.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_resolver.js new file mode 100644 index 00000000000..f7645339199 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_resolver.js @@ -0,0 +1,54 @@ +// 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. + +/** + + */ +goog.module('mr.PromiseResolver'); +goog.module.declareLegacyNamespace(); + + +/** + * Wrapper around a Promise that allows the Promise to be resolved by + * calling a method. + * + * @template T + */ +exports = class { + constructor() { + /** + * @private {function(T)} + */ + this.resolveFunc_; + + /** + * @private {function(*)} + */ + this.rejectFunc_; + + /** + * @const {!Promise<T>} + */ + this.promise = new Promise((resolve, reject) => { + this.resolveFunc_ = resolve; + this.rejectFunc_ = reject; + }); + } + + /** + * Resolves the promise. + * @param {T} value + */ + resolve(value) { + this.resolveFunc_(value); + } + + /** + * Rejects the promise. + * @param {*} reason + */ + reject(reason) { + this.rejectFunc_(reason); + } +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_utils.js new file mode 100644 index 00000000000..0dcd2e23b12 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/promise_utils.js @@ -0,0 +1,36 @@ +// 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 Utility functions for dealing with promises. + */ +goog.module('mr.PromiseUtils'); +goog.module.declareLegacyNamespace(); + + +/** + * Given a list of promises, waits for all promises to settle, and produces a + * list indicating whether each input promise was fulfilled (i.e. resolved) or + * rejected. + * + * Each object in the output contains two fields: the |fulfilled| field is true + * if the corresponding input promise was resolved, or false if it was rejected. + * When |fulfilled| is true, the |value| field contains the value with which the + * promise was resolved. Otherwise, the |reason| field contains the reason with + * which the promise was rejected. + * + * @param {!Array<!Promise<T>>} promises + * @return {!Promise<!Array<{ + * fulfilled: boolean, + * value: (T | undefined), + * reason: * + * }>>} + * @template T + */ +exports.allSettled = function(promises) { + return Promise.all(promises.map( + promise => promise.then( + value => ({fulfilled: true, value: value}), + reason => ({fulfilled: false, reason: reason})))); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1.js new file mode 100644 index 00000000000..8b537c9f02e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1.js @@ -0,0 +1,239 @@ +// 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. + +goog.module('mr.Sha1'); + +const BLOCK_SIZE = 512 / 8; + +/** + * SHA-1 cryptographic hash constructor. + * @final + */ +class Sha1 { + constructor() { + /** + * Holds the previous values of accumulated variables a-e in the compress_ + * function. + * @private @const {!Array<number>} + */ + this.chain_ = []; + + /** + * A buffer holding the partially computed hash result. + * @private @const {!Array<number>} + */ + this.buf_ = []; + + /** + * An array of 80 bytes, each a part of the message to be hashed. Referred + * to as the message schedule in the docs. + * @private @const {!Array<number>} + */ + this.W_ = []; + + /** + * Contains data needed to pad messages less than 64 bytes. + * @private @const {!Array<number>} + */ + this.pad_ = []; + + this.pad_[0] = 128; + for (let i = 1; i < BLOCK_SIZE; ++i) { + this.pad_[i] = 0; + } + + /** + * @private {number} + */ + this.inbuf_ = 0; + + /** + * @private {number} + */ + this.total_ = 0; + + this.reset(); + } + + /** + * Resets the internal accumulator. + */ + reset() { + this.chain_[0] = 0x67452301; + this.chain_[1] = 0xefcdab89; + this.chain_[2] = 0x98badcfe; + this.chain_[3] = 0x10325476; + this.chain_[4] = 0xc3d2e1f0; + + this.inbuf_ = 0; + this.total_ = 0; + } + + /** + * Internal compress helper function. + * @param {!Array<number>|string} buf Block to compress. + * @param {number=} offset Offset of the block in the buffer. + * @private + */ + compress_(buf, offset = 0) { + let W = this.W_; + + // get 16 big endian words + if (typeof buf === 'string') { + for (let i = 0; i < 16; i++) { + W[i] = (buf.charCodeAt(offset) << 24) | + (buf.charCodeAt(offset + 1) << 16) | + (buf.charCodeAt(offset + 2) << 8) | (buf.charCodeAt(offset + 3)); + offset += 4; + } + } else { + for (let i = 0; i < 16; i++) { + W[i] = (buf[offset] << 24) | (buf[offset + 1] << 16) | + (buf[offset + 2] << 8) | (buf[offset + 3]); + offset += 4; + } + } + + // expand to 80 words + for (let i = 16; i < 80; i++) { + let t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff; + } + + let a = this.chain_[0]; + let b = this.chain_[1]; + let c = this.chain_[2]; + let d = this.chain_[3]; + let e = this.chain_[4]; + let f, k; + + for (let i = 0; i < 80; i++) { + if (i < 40) { + if (i < 20) { + f = d ^ (b & (c ^ d)); + k = 0x5a827999; + } else { + f = b ^ c ^ d; + k = 0x6ed9eba1; + } + } else { + if (i < 60) { + f = (b & c) | (d & (b | c)); + k = 0x8f1bbcdc; + } else { + f = b ^ c ^ d; + k = 0xca62c1d6; + } + } + + let t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff; + e = d; + d = c; + c = ((b << 30) | (b >>> 2)) & 0xffffffff; + b = a; + a = t; + } + + this.chain_[0] = (this.chain_[0] + a) & 0xffffffff; + this.chain_[1] = (this.chain_[1] + b) & 0xffffffff; + this.chain_[2] = (this.chain_[2] + c) & 0xffffffff; + this.chain_[3] = (this.chain_[3] + d) & 0xffffffff; + this.chain_[4] = (this.chain_[4] + e) & 0xffffffff; + } + + /** + * Adds a string (must only contain 8-bit, i.e., Latin1 characters) + * to the internal accumulator. + * + * @param {string|!Array<number>} bytes Data used for the update. + * @param {number=} length + */ + update(bytes, length = bytes.length) { + let lengthMinusBlock = length - BLOCK_SIZE; + let n = 0; + // Using local instead of member variables gives ~5% speedup on Firefox 16. + let buf = this.buf_; + let inbuf = this.inbuf_; + + // The outer while loop should execute at most twice. + while (n < length) { + // When we have no data in the block to top up, we can directly process + // the input buffer (assuming it contains sufficient data). This gives + // ~25% speedup on Chrome 23 and ~15% speedup on Firefox 16, but requires + // that the data is provided in large chunks (or in multiples of 64 + // bytes). + if (inbuf == 0) { + while (n <= lengthMinusBlock) { + this.compress_(bytes, n); + n += BLOCK_SIZE; + } + } + + if (typeof bytes === 'string') { + while (n < length) { + buf[inbuf] = bytes.charCodeAt(n); + ++inbuf; + ++n; + if (inbuf == BLOCK_SIZE) { + this.compress_(buf); + inbuf = 0; + // Jump to the outer loop so we use the full-block optimization. + break; + } + } + } else { + while (n < length) { + buf[inbuf] = bytes[n]; + ++inbuf; + ++n; + if (inbuf == BLOCK_SIZE) { + this.compress_(buf); + inbuf = 0; + // Jump to the outer loop so we use the full-block optimization. + break; + } + } + } + } + + this.inbuf_ = inbuf; + this.total_ += length; + } + + /** + * @return {!Array<number>} The finalized hash computed + * from the internal accumulator. + */ + digest() { + let digest = []; + let totalBits = this.total_ * 8; + + // Add pad 0x80 0x00*. + if (this.inbuf_ < 56) { + this.update(this.pad_, 56 - this.inbuf_); + } else { + this.update(this.pad_, BLOCK_SIZE - (this.inbuf_ - 56)); + } + + // Add # bits. + for (let i = BLOCK_SIZE - 1; i >= 56; i--) { + this.buf_[i] = totalBits & 255; + totalBits /= 256; // Don't use bit-shifting here! + } + + this.compress_(this.buf_); + + let n = 0; + for (let i = 0; i < 5; i++) { + for (let j = 24; j >= 0; j -= 8) { + digest[n] = (this.chain_[i] >> j) & 255; + ++n; + } + } + + return digest; + } +} + +exports = Sha1; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1_test.js new file mode 100644 index 00000000000..1385d776a0d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/sha1_test.js @@ -0,0 +1,60 @@ +// 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. + +goog.module('mr.Sha1.test'); +goog.setTestOnly(); + +const Sha1 = goog.require('mr.Sha1'); + +/** + * Converts an array of byte values to a hexadecimal string. + * @param {!Array<number>} bytes + * @return {string} + */ +function toHex(bytes) { + return bytes.map(byte => byte.toString(16).padStart(2, '0')).join(''); +} + +describe('mr.Sha1', () => { + // Test vectors from: + // csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf + + let sha1; + + beforeEach(() => { + sha1 = new Sha1(); + }); + + it('hashes an empty stream correctly', () => { + expect(toHex(sha1.digest())) + .toBe('da39a3ee5e6b4b0d3255bfef95601890afd80709'); + }); + + it('hashes a one-block message correctly', () => { + sha1.update('abc'); + expect(toHex(sha1.digest())) + .toBe('a9993e364706816aba3e25717850c26c9cd0d89d'); + }); + + it('hashes a multi-block message correctly', () => { + sha1.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'); + expect(toHex(sha1.digest())) + .toBe('84983e441c3bd26ebaae4aa1f95129e5e54670f1'); + }); + + it('hashes a long message correctly', () => { + const thousandAs = 'a'.repeat(1000); + for (let i = 0; i < 1000; ++i) { + sha1.update(thousandAs); + } + expect(toHex(sha1.digest())) + .toBe('34aa973cd4c4daa4f61eeb2bdbad27316534016f'); + }); + + it('hashes a standard message correctly', () => { + sha1.update('The quick brown fox jumps over the lazy dog'); + expect(toHex(sha1.digest())) + .toBe('2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/string_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/string_utils.js new file mode 100644 index 00000000000..c21aee6dafc --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/string_utils.js @@ -0,0 +1,24 @@ +// 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. + +goog.module('mr.StringUtils'); + +class StringUtils { + /** + * Returns a string with at least 64-bits of randomness. + * + * Doesn't trust Javascript's random function entirely. Uses a combination of + * random and current timestamp, and then encodes the string in base-36 to + * make it shorter. + * + * @return {string} A random string, e.g. sn1s7vb4gcic. + */ + static getRandomString() { + var x = 2147483648; + return Math.floor(Math.random() * x).toString(36) + + Math.abs(Math.floor(Math.random() * x) ^ Date.now()).toString(36); + } +} + +exports = StringUtils; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/tab_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/tab_utils.js new file mode 100644 index 00000000000..730322e3d67 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/tab_utils.js @@ -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. + +/** + * @fileoverview Tab-related utilities methods. + + */ + +goog.provide('mr.TabUtils'); + + +/** + * @param {number} tabId + * @return {!Promise<!Tab>} A promise fulfilled with tab, or rejected if + * tab does not exist. + */ +mr.TabUtils.getTab = function(tabId) { + return new Promise((resolve, reject) => { + chrome.tabs.get(tabId, resolve); + }) + .then(tab => { + if (!tab) { + throw Error('No such tab ' + tabId); + } + return tab; + }); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle.js new file mode 100644 index 00000000000..ecc2967e9d5 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle.js @@ -0,0 +1,93 @@ +// 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. + +goog.module('mr.Throttle'); +goog.module.declareLegacyNamespace(); + + +/** + * Throttle will perform an action that is passed in no more than once + * per interval (specified in milliseconds). If it gets multiple signals + * to perform the action while it is waiting, it will only perform the action + * once at the end of the interval. + * @final + * @template T + */ +class Throttle { + /** + * @param {function(this: T, ...?)} listener Function to callback when the + * action is triggered. + * @param {number} interval Interval over which to throttle. The listener can + * only be called once per interval. + * @param {T=} handler Object in whose scope to call the listener. + */ + constructor(listener, interval, handler = undefined) { + /** @private @const */ + this.listener_ = handler != null ? listener.bind(handler) : listener; + + /** @private @const */ + this.interval_ = interval; + + /** @private @const */ + this.callback_ = this.onTimer_.bind(this); + + /** + * The last arguments passed into `fire`. + * @private {!Array<?>} + */ + this.args_ = []; + + /** + * Indicates that the action is pending and needs to be fired. + * @private {boolean} + */ + this.shouldFire_ = false; + + /** + * Timer for scheduling the next callback + * @private {?number} + */ + this.timerId_ = null; + } + + /** + * Notifies the throttle that the action has happened. It will throttle the + * call so that the callback is not called too often according to the interval + * parameter passed to the constructor, passing the arguments from the last + * call of this function into the throttled function. + * @param {...?} args Arguments to pass on to the throttled function. + */ + fire(...args) { + this.args_ = [...args]; + if (this.timerId_ == null) { + this.doAction_(); + } else { + this.shouldFire_ = true; + } + } + + /** + * Calls the callback + * @private + */ + doAction_() { + this.timerId_ = setTimeout(this.callback_, this.interval_); + this.listener_(...this.args_); + } + + /** + * Handler for the timer to fire the throttle + * @private + */ + onTimer_() { + this.timerId_ = null; + + if (this.shouldFire_) { + this.shouldFire_ = false; + this.doAction_(); + } + } +} + +exports = Throttle; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle_test.js new file mode 100644 index 00000000000..b048a452b5e --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/throttle_test.js @@ -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. + +goog.module('mr.Throttle.test'); +goog.setTestOnly(); + +const Throttle = goog.require('mr.Throttle'); +const UnitTestUtils = goog.require('mr.UnitTestUtils'); + + +describe('mr.Timer', () => { + let clock; + + beforeEach(() => { + clock = UnitTestUtils.useMockClockAndPromises(); + }); + + afterEach(() => { + UnitTestUtils.restoreRealClockAndPromises(); + }); + + it('works', () => { + let callBackCount = 0; + const callBackFunction = () => { + callBackCount++; + }; + + const throttle = new Throttle(callBackFunction, 100); + expect(callBackCount).toBe(0); + throttle.fire(); + expect(callBackCount).toBe(1); + throttle.fire(); + expect(callBackCount).toBe(1); + throttle.fire(); + throttle.fire(); + expect(callBackCount).toBe(1); + clock.tick(101); + expect(callBackCount).toBe(2); + clock.tick(101); + expect(callBackCount).toBe(2); + }); + + it('binds scope correctly', () => { + const interval = 500; + const x = {'y': 0}; + new Throttle(function() { + ++this['y']; + }, interval, x).fire(); + expect(x['y']).toBe(1); + }); + + + it('binds arguments correctly', () => { + const interval = 500; + let calls = 0; + const throttle = new Throttle((a, b, c) => { + ++calls; + expect(a).toBe(3); + expect(b).toBe('string'); + expect(c).toBe(false); + }, interval); + + throttle.fire(3, 'string', false); + expect(calls).toBe(1); + + // fire should always pass the last arguments passed to it into the + // decorated function, even if called multiple times. + throttle.fire(); + clock.tick(interval / 2); + throttle.fire(8, null, true); + throttle.fire(3, 'string', false); + clock.tick(interval); + expect(calls).toBe(2); + }); + + + it('binds arguments and scope correctly', () => { + const interval = 500; + const x = {'calls': 0}; + const throttle = new Throttle(function(a, b, c) { + ++this['calls']; + expect(a).toBe(3); + expect(b).toBe('string'); + expect(c).toBe(false); + }, interval, x); + + throttle.fire(3, 'string', false); + expect(x['calls']).toBe(1); + + // fire should always pass the last arguments passed to it into the + // decorated function, even if called multiple times. + throttle.fire(); + clock.tick(interval / 2); + throttle.fire(8, null, true); + throttle.fire(3, 'string', false); + clock.tick(interval); + expect(x['calls']).toBe(2); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/unit_test_utils.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/unit_test_utils.js new file mode 100644 index 00000000000..e2d8461b2ab --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/unit_test_utils.js @@ -0,0 +1,307 @@ +// 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 Test utilities for unit tests. + */ +goog.provide('mr.UnitTestUtils'); +goog.setTestOnly('mr.UnitTestUtils'); + +goog.require('mr.Assertions'); +goog.require('mr.MockClock'); +goog.require('mr.MockPromise'); + + +/** + * Creates a mock implementation of the RouteControllerCallbacks interface. + * @return {!Object} + */ +mr.UnitTestUtils.createRouteControllerCallbacksSpyObj = function() { + return jasmine.createSpyObj('RouteControllerCallbacks', [ + 'onRouteControllerInvalidated', 'sendMediaControlRequest', + 'sendVolumeRequest' + ]); +}; + + +/** + * Creates a Mojo InterfaceRequest-like object with Jasmine spy. + * @return {!Object} + */ +mr.UnitTestUtils.createMojoRequestSpyObj = function() { + return jasmine.createSpyObj('Request', ['close']); +}; + + +/** + * Creates a Mojo Binding-like object with Jasmine spy. + * @return {!Object} + */ +mr.UnitTestUtils.createMojoBindingSpyObj = function() { + return jasmine.createSpyObj( + 'Binding', ['bind', 'close', 'setConnectionErrorHandler']); +}; + + +/** + * Creates a Mojo MediaStatusObserver-like object with Jasmine spies. + * @return {!Object} + */ +mr.UnitTestUtils.createMojoMediaStatusObserverSpyObj = function() { + return { + ptr: jasmine.createSpyObj( + 'PtrController', ['reset', 'setConnectionErrorHandler']), + onMediaStatusUpdated: jasmine.createSpy('onMediaStatusUpdated') + }; +}; + + +/** + * Creates a Jasmine spy object with the same methods of the given constructor + * type. + * @param {Function} constructor The object's constructor to be mocked. E.g. + * MyClass.prototype. + * @param {string} mockName The mock's name (will show up in failed test + * results). + * @return {Object} + */ +mr.UnitTestUtils.createMock = function(constructor, mockName) { + const methodNames = new Set(); + while (constructor) { + for (let name of Object.getOwnPropertyNames(constructor)) { + methodNames.add(name); + } + constructor = Object.getPrototypeOf(constructor); + } + return jasmine.createSpyObj(mockName, [...methodNames]); +}; + +/** + * Replaces parts of the window.mojo API with Jasmine spys used by unit tests. + */ +mr.UnitTestUtils.mockMojoApi = function() { + window.mojo = { + Binding: {}, + HangoutsMediaRouteController: {}, + HangoutsMediaStatusExtraData: {}, + MediaStatus: { + PlayState: {PLAYING: 0, PAUSED: 1, BUFFERING: 2}, + }, + MediaController: {}, + RouteControllerType: {kNone: 0, kGeneric: 1, kHangouts: 2}, + TimeDelta: {}, + Origin: {} + }; + // We return a copy of the object used to construct the MediaStatus. + // This works because the fields in the object maps directly to the fields + // in MediaStatus. + spyOn(window.mojo, 'HangoutsMediaStatusExtraData') + .and.callFake(obj => Object.assign({}, obj)); + spyOn(window.mojo, 'MediaStatus').and.callFake(obj => Object.assign({}, obj)); + spyOn(window.mojo, 'TimeDelta').and.callFake(obj => Object.assign({}, obj)); + spyOn(window.mojo, 'Origin').and.callFake(obj => Object.assign({}, obj)); +}; + +/** + * Replaces parts of the chrome API with Jasmine spy objects. After calling this + * function, call restoreChromeApi() during test teardown to restore the + * original API. + */ +mr.UnitTestUtils.mockChromeApi = function() { + mr.UnitTestUtils.originalChromeApi_ = chrome; + chrome = { + cast: { + channel: { + onError: jasmine.createSpy('chrome.cast.channel.onError spy'), + onMessage: jasmine.createSpy('chrome.cast.channel.onMessage spy') + }, + SessionStatus: { + CONNECTED: 'connected', + DISCONNECTED: 'disconnected', + STOPPED: 'stopped' + }, + VolumeControlType: + {ATTENUATION: 'attenuation', FIXED: 'fixed', MASTER: 'master'} + }, + dial: { + onDeviceList: jasmine.createSpy('chrome.dial.onDeviceList spy'), + onError: jasmine.createSpy('chrome.dial.onError spy') + }, + identity: { + onSignInChanged: { + addListener: + jasmine.createSpy('chrome.identity.onSignInChanged.addListener spy') + } + }, + runtime: { + onMessage: { + addListener: + jasmine.createSpy('chrome.runtime.onMessage.addListener spy') + }, + onMessageExternal: { + addListener: jasmine.createSpy( + 'chrome.runtime.onMessageExternal.addListener spy') + }, + onStartup: { + addListener: + jasmine.createSpy('chrome.runtime.onStartup.addListener spy') + }, + onSuspend: { + addListener: + jasmine.createSpy('chrome.runtime.onSuspend.addListener spy') + }, + getManifest: () => { + return {version: '1.0'}; + }, + sendMessage: jasmine.createSpy('chrome.runtime.sendMessage spy'), + }, + mdns: {onSerivceList: jasmine.createSpy('chrome.mdns.onServiceList spy')}, + metricsPrivate: { + recordMediumTime: + jasmine.createSpy('chrome.metricsPrivate.recordMediumTime spy'), + recordUserAction: + jasmine.createSpy('chrome.metricsPrivate.recordUserAction spy'), + }, + networkingPrivate: { + onNetworksChanged: + jasmine.createSpy('chrome.networkingPrivate.onNetworksChanged spy') + }, + gcm: { + onMessage: { + addListener: jasmine.createSpy('chrome.gcm.onMessage.addListener spy') + } + }, + }; +}; + +/** + * Restores the original chrome API after it's been mocked by a mockChromeApi() + * call. + */ +mr.UnitTestUtils.restoreChromeApi = function() { + chrome = mr.UnitTestUtils.originalChromeApi_; +}; + +/** + * Stores a reference to the original chrome API while it is mocked. + * @private {?Object} + */ +mr.UnitTestUtils.originalChromeApi_ = null; + + + +/** + * @private {mr.MockClock} + */ +mr.UnitTestUtils.mockClock_ = null; + + +/** + * Installs a mock clock and replaces the native Promise type with goog.Promise. + * + + * + * @return {!mr.MockClock} + */ +mr.UnitTestUtils.useMockClockAndPromises = function() { + mr.Assertions.assert(mr.UnitTestUtils.mockClock_ == null); + mr.MockPromise.install(); + const mockClock = new mr.MockClock(true); + mr.UnitTestUtils.mockClock_ = mockClock; + return mockClock; +}; + + +/** + * Undoes the effect of calling useMockClockAndPromises(). + */ +mr.UnitTestUtils.restoreRealClockAndPromises = function() { + mr.UnitTestUtils.mockClock_.uninstall(); + mr.UnitTestUtils.mockClock_ = null; + mr.MockPromise.uninstall(); +}; + + +/** + * Asserts that mock clock and mock promises are in use. + * @private + */ +mr.UnitTestUtils.assertUsingMockPromises_ = function() { + mr.Assertions.assert(Promise === mr.MockPromise); +}; + + +/** + * Waits for the provided promise to resolve. + * @param {!Promise<T>} promise The promise to resolve. + * @template T + */ +mr.UnitTestUtils.awaitPromiseResolved = function(promise) { + mr.UnitTestUtils.assertUsingMockPromises_(); + let resolved = false; + promise.then(result => { + resolved = true; + }); + mr.MockPromise.callPendingHandlers(); + expect(resolved).toBe(true); +}; + + +/** + * Expects the provided promise to resolve to a value that makes the given + * predicate true. + * @param {!Promise<T>} promise The promise to resolve. + * @param {function(T):boolean} isCorrect A function called with the resolved + * value of the promise; expected to return true. + * @template T + */ +mr.UnitTestUtils.expectPromiseResult = function(promise, isCorrect) { + mr.UnitTestUtils.assertUsingMockPromises_(); + let resolved = false; + let actual; + promise.then(result => { + resolved = true; + actual = result; + }); + mr.MockPromise.callPendingHandlers(); + expect(resolved).toBe(true); + expect(isCorrect(actual)).toBe(true); +}; + + +/** + * Expects the provided promise to resolve to the given result. + * @param {!Promise} promise + * @param {*} expectedResult + */ +mr.UnitTestUtils.expectPromiseResultToEqual = function( + promise, expectedResult) { + mr.UnitTestUtils.assertUsingMockPromises_(); + let resolved = false; + let actual; + promise.then(result => { + resolved = true; + actual = result; + }); + mr.MockPromise.callPendingHandlers(); + expect(resolved).toBe(true); + expect(actual).toEqual(expectedResult); +}; + + +/** + * Expects the provided promise to resolve to the given result. + * @param {!Promise} promise + * @param {Error} expectedError + */ +mr.UnitTestUtils.expectPromiseRejection = function(promise, expectedError) { + mr.UnitTestUtils.assertUsingMockPromises_(); + let actual; + promise.catch(error => { + actual = error; + }); + mr.MockPromise.callPendingHandlers(); + expect(actual).toEqual(expectedError); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager.js new file mode 100644 index 00000000000..41017f63493 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager.js @@ -0,0 +1,191 @@ +// 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. + +goog.module('mr.XhrManager'); +goog.module.declareLegacyNamespace(); + +const Assertions = goog.require('mr.Assertions'); +const PromiseResolver = goog.require('mr.PromiseResolver'); + +/** + * Wraps XmlHttpRequest API with additional functionalities such as queueing, + * timeout, and retries. + */ +class XhrManager { + /** + * @param {number} maxRequests The maximum number of concurrent XHR + * requests. + * @param {number} defaultTimeoutMillis The default timeout for each XHR + * request. + * @param {number} defaultNumAttempts The default number of attempts for each + * operation. + */ + constructor(maxRequests, defaultTimeoutMillis, defaultNumAttempts) { + /** @private @const {number} */ + this.maxRequests_ = maxRequests; + + /** @private @const {number} */ + this.defaultTimeoutMillis_ = defaultTimeoutMillis; + + /** @private @const {number} */ + this.defaultNumAttempts_ = defaultNumAttempts; + + /** + * Number of pending requests. Does not exceed this.maxRequests_. + * @private {number} + */ + this.numPendingRequests_ = 0; + + /** + * Holds requests that have not yet been executed. + * @private {!Array<!QueueEntry_>} + */ + this.queuedRequests_ = []; + } + + /** + * Adds a request with the given parameters. + * @param {string} url + * @param {string} method + * @param {string=} body + * @param {{timeoutMillis: (number|undefined), + * numAttempts: (number|undefined), + * headers: (!Array<!Array<string>>|undefined), + * responseType: (string|undefined)}=} overrides "headers" is an Array + * of pairs of strings. "responseType" is a valid + * XMLHttpRequest.responseType enum value. + * @return {!Promise<!XMLHttpRequest>} Resolves with the response. Rejects if + * the request timed out on all attempts. + */ + send(url, method, body = undefined, { + timeoutMillis = this.defaultTimeoutMillis_, + numAttempts = this.defaultNumAttempts_, + headers = null, + responseType = '', + } = {}) { + const entry = { + resolver: new PromiseResolver(), + url: url, + method: method, + headers: headers, + responseType: responseType, + body: body, + timeoutMillis: timeoutMillis, + numAttemptsLeft: numAttempts + }; + + if (this.numPendingRequests_ < this.maxRequests_) { + this.startRequest_(entry); + } else { + this.queuedRequests_.push(entry); + } + + return entry.resolver.promise; + } + + /** + * Starts a request from the request queue if there is room for an additional + * pending request. + * @private + */ + startNextRequestFromQueue_() { + if (this.queuedRequests_.length > 0 && + this.numPendingRequests_ < this.maxRequests_) { + const request = this.queuedRequests_.shift(); + this.startRequest_(request); + } + } + + /** + * Attempts the given request once. If successful, resolves the + * PromiseResolver with the result. Otherwise, requeues the request for retry, + * or rejects the PromiseResolver if it ran out of attempts. Also processes a + * queued request, if any, at the end. + * @param {!QueueEntry_} request + * @private + */ + startRequest_(request) { + this.numPendingRequests_++; + Assertions.assert( + request.numAttemptsLeft > 0, 'request.numAttemptsLeft > 0'); + request.numAttemptsLeft--; + + const cleanUpAndStartNextRequest = () => { + this.numPendingRequests_--; + this.startNextRequestFromQueue_(); + }; + + this.sendOneAttempt_(request).then( + response => { + request.resolver.resolve(response); + cleanUpAndStartNextRequest(); + }, + e => { + if (request.numAttemptsLeft == 0) { + request.resolver.reject(e); + } else { + // Try it again later by re-adding the request to back of queue. + + this.queuedRequests_.push(request); + } + cleanUpAndStartNextRequest(); + }); + } + + /** + * Executes a XMLHttpRequest and returns a Promise that resolves with the + * response, or rejects if the request times out. + * @param {!QueueEntry_} request + * @return {!Promise<!XMLHttpRequest>} Resolves with the response. Rejects if + * the request timed out. + * @private + */ + sendOneAttempt_(request) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState == XMLHttpRequest.DONE) { + resolve(xhr); + } + }; + + xhr.timeout = request.timeoutMillis; + xhr.ontimeout = () => { + reject(new Error('Timed out')); + }; + + xhr.open(request.method, request.url, true); + if (request.headers == null) { + xhr.setRequestHeader( + 'Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'); + } else { + request.headers.forEach( + header => xhr.setRequestHeader(header[0], header[1])); + } + xhr.responseType = request.responseType; + xhr.send(request.body); + }); + } +} + +/** @private @record */ +const QueueEntry_ = class {}; +/** @type {!PromiseResolver<!XMLHttpRequest>} */ +QueueEntry_.prototype.resolver; +/** @type {string} */ +QueueEntry_.prototype.url; +/** @type {string} */ +QueueEntry_.prototype.method; +/** @type {?Array<!Array<string>>} */ +QueueEntry_.prototype.headers; +/** @type {string} */ +QueueEntry_.prototype.responseType; +/** @type {string|undefined} */ +QueueEntry_.prototype.body; +/** @type {number} */ +QueueEntry_.prototype.timeoutMillis; +/** @type {number} */ +QueueEntry_.prototype.numAttemptsLeft; + +exports = XhrManager; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager_test.js b/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager_test.js new file mode 100644 index 00000000000..44857e2a23d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/utils/xhr_manager_test.js @@ -0,0 +1,185 @@ +// 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. + +goog.module('mr.XhrManagerTest'); +goog.setTestOnly('mr.XhrManagerTest'); + +const XhrManager = goog.require('mr.XhrManager'); + +describe('XhrManager Tests', function() { + const MAX_REQUESTS = 5; + const DEFAULT_TIMEOUT = 10; + const DEFAULT_ATTEMPTS = 3; + const SEND_URL = 'https://www.google.com'; + const BODY = 'body'; + + let mockXhr; + let xhrManager; + + beforeEach(() => { + mockXhr = jasmine.createSpyObj('Xhr', ['open', 'send', 'setRequestHeader']); + xhrManager = + new XhrManager(MAX_REQUESTS, DEFAULT_TIMEOUT, DEFAULT_ATTEMPTS); + }); + + it('Resolves on first try with defaults', done => { + spyOn(window, 'XMLHttpRequest').and.returnValue(mockXhr); + mockXhr.open.and.callFake((method, url, async) => { + expect(mockXhr.onreadystatechange).toBeDefined(); + expect(mockXhr.timeout).toEqual(DEFAULT_TIMEOUT); + expect(mockXhr.ontimeout).toBeDefined(); + expect(method).toEqual('POST'); + expect(url).toEqual(SEND_URL); + expect(async).toBe(true); + }); + const headersThatWereSet = []; + mockXhr.setRequestHeader.and.callFake((key, value) => { + headersThatWereSet.push([key, value]); + }); + mockXhr.send.and.callFake(body => { + expect(mockXhr.open).toHaveBeenCalled(); + expect(mockXhr.setRequestHeader).toHaveBeenCalled(); + expect(headersThatWereSet).toEqual([ + ['Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'] + ]); + expect(mockXhr.responseType).toBe(''); + expect(body).toEqual(BODY); + + setTimeout(() => { + mockXhr.readyState = XMLHttpRequest.DONE; + mockXhr.onreadystatechange(); + }, 0); + }); + + xhrManager.send(SEND_URL, 'POST', BODY).then(response => { + expect(mockXhr.send).toHaveBeenCalled(); + done(); + }); + }); + + it('Resolves on first try with overrides', done => { + const overrides = { + timeoutMillis: 1234, + headers: [['Hello', 'World'], ['Eat', 'Cheese']], + responseType: 'arraybuffer' + }; + + spyOn(window, 'XMLHttpRequest').and.returnValue(mockXhr); + mockXhr.open.and.callFake((method, url, async) => { + expect(mockXhr.onreadystatechange).toBeDefined(); + expect(mockXhr.timeout).toEqual(overrides.timeoutMillis); + expect(mockXhr.ontimeout).toBeDefined(); + expect(method).toEqual('GET'); + expect(url).toEqual(SEND_URL); + expect(async).toBe(true); + }); + const headersThatWereSet = []; + mockXhr.setRequestHeader.and.callFake((key, value) => { + headersThatWereSet.push([key, value]); + }); + mockXhr.send.and.callFake(body => { + expect(mockXhr.open).toHaveBeenCalled(); + expect(mockXhr.setRequestHeader).toHaveBeenCalled(); + expect(headersThatWereSet).toEqual(overrides.headers); + expect(mockXhr.responseType).toBe(overrides.responseType); + expect(body).toEqual(BODY); + + setTimeout(() => { + mockXhr.readyState = XMLHttpRequest.DONE; + mockXhr.onreadystatechange(); + }, 0); + }); + + xhrManager.send(SEND_URL, 'GET', BODY, overrides).then(response => { + expect(mockXhr.send).toHaveBeenCalled(); + done(); + }); + }); + + it('Resolves on retry', done => { + spyOn(window, 'XMLHttpRequest').and.returnValue(mockXhr); + let numAttempts = 0; + mockXhr.send.and.callFake(() => { + expect(mockXhr.timeout).toBe(DEFAULT_TIMEOUT); + ++numAttempts; + if (numAttempts <= 1) { + setTimeout(() => mockXhr.ontimeout(), DEFAULT_TIMEOUT); + } else { + setTimeout(() => { + mockXhr.readyState = XMLHttpRequest.DONE; + mockXhr.onreadystatechange(); + }, 0); + } + }); + + xhrManager.send(SEND_URL, 'GET').then(() => { + expect(numAttempts).toBe(2); + done(); + }); + }); + + it('Queues requests', done => { + let xhrs = []; + spyOn(window, 'XMLHttpRequest').and.callFake(() => { + const mockXhr = + jasmine.createSpyObj('Xhr', ['open', 'send', 'setRequestHeader']); + mockXhr.send.and.callFake(() => { + expect(xhrs.length).toBeLessThan(MAX_REQUESTS); + xhrs.push(mockXhr); + }); + return mockXhr; + }); + + let promises1 = []; + let promises2 = []; + for (let i = 0; i < MAX_REQUESTS; i++) { + promises1.push(xhrManager.send(SEND_URL, 'POST', BODY)); + } + for (let i = 0; i < MAX_REQUESTS; i++) { + promises2.push(xhrManager.send(SEND_URL, 'POST', BODY)); + } + + // Resolve the first 5 requests. + expect(xhrs.length).toBe(MAX_REQUESTS); + while (xhrs.length > 0) { + let xhr = xhrs.shift(); + setTimeout(() => { + xhr.readyState = XMLHttpRequest.DONE; + xhr.onreadystatechange(); + }, 0); + } + + Promise.all(promises1).then(() => { + // Resolve the next 5 requests. + expect(xhrs.length).toBe(MAX_REQUESTS); + while (xhrs.length > 0) { + let xhr = xhrs.shift(); + setTimeout(() => { + xhr.readyState = XMLHttpRequest.DONE; + xhr.onreadystatechange(); + }, 0); + } + Promise.all(promises2).then(done); + }); + }); + + it('Rejects if all retries failed', done => { + spyOn(window, 'XMLHttpRequest').and.returnValue(mockXhr); + let numAttempts = 0; + mockXhr.send.and.callFake((path, init) => { + ++numAttempts; + setTimeout(() => mockXhr.ontimeout(), DEFAULT_TIMEOUT); + }); + + xhrManager.send(SEND_URL, 'GET') + .then( + _ => { + fail('send() unexpectedly succeeded.'); + }, + () => { + expect(numAttempts).toBe(DEFAULT_ATTEMPTS); + done(); + }); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/extension/src/webrtc/messages.js b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/messages.js new file mode 100644 index 00000000000..6f8e6d43b3c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/messages.js @@ -0,0 +1,200 @@ +// 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. + +goog.provide('mr.webrtc.AuthReadyMessageData'); +goog.provide('mr.webrtc.ChannelType'); +goog.provide('mr.webrtc.Message'); +goog.provide('mr.webrtc.MessageType'); +goog.provide('mr.webrtc.OfferMessageData'); + +goog.require('mr.mirror.Settings'); + +goog.scope(function() { + + +/** + * The channel types supported by the cloud MRP. + * @enum {string} + */ +mr.webrtc.ChannelType = { + WEAVE: 'weave', + SLARTI: 'slarti', + MESI: 'mesi' +}; + + +/** + * The types of messages used by the cloud MRP. + * @enum {string} + */ +mr.webrtc.MessageType = { + // TURN messages. + GET_TURN_CREDENTIALS: 'GET_TURN_CREDENTIALS', // Request for creds. + TURN_CREDENTIALS: 'TURN_CREDENTIALS', // Creds received. + + // Signaling messages. + OFFER: 'OFFER', // Peer connection offer. + ANSWER: 'ANSWER', // Peer connection answer. + KNOCK_ANSWER: 'KNOCK_ANSWER', // Knocking peer connection answer. + STOP: 'STOP', // Stop the session. + + // Event messages. + SESSION_START_SUCCESS: 'SESSION_START_SUCCESS', // Start success event. + SESSION_FAILURE: 'SESSION_FAILURE', // Start failure event. + SESSION_END: 'SESSION_END', // Session ended event. + + WEB_RTC_STATS: '__webrtc_stats__', // WebRTC stats message. + + // Hangout session control messages. + REFRESH_AUTH: 'REFRESH_AUTH', // Request to refresh the auth token. + // Message data for AUTH_READY messages will be an instance of + // mr.webwrtc.AuthReadyMessageData. + AUTH_READY: 'AUTH_READY', // Response that auth token was updated. + + // Route details control messages. + MUTE: 'MUTE', // Request to mute audio. + LOCAL_PRESENT: 'LOCAL_PRESENT', // Request to disable conf mode. + ROUTE_STATUS_REQUEST: 'STATUS_REQUEST', // Request for route status update. + ROUTE_STATUS_RESPONSE: + 'STATUS_RESPONSE', // Response to route details status. + + // Hangout issues. + HANGOUT_INVALID: 'HANGOUT_INVALID', // Hangout name could not be resolved. + HANGOUT_INACTIVE: 'HANGOUT_INACTIVE', // Not enough participants in Hangout. + + // Application messages sent through Presentation API. + PRESENTATION_CONNECTION_MESSAGE: 'PRESENTATION_CONNECTION_MESSAGE', +}; + + +/** + * Cloud message object used for internal communication. + */ +mr.webrtc.Message = class { + /** + * @param {mr.webrtc.MessageType} type + * @param {!Object|undefined=} opt_data + */ + constructor(type, opt_data) { + /** + * @type {mr.webrtc.MessageType} + * @export + */ + this.type = type; + /** + * @type {!Object|undefined} + * @export + */ + this.data = opt_data; + } + + /** + * Returns the message for the provided JSON string. + * @param {string} messageStr + * @return {!mr.webrtc.Message} + */ + static fromString(messageStr) { + const messageJson = JSON.parse(messageStr); + if (!messageJson['type']) { + throw Error('Invalid message'); + } + return new Message( + /** @type {mr.webrtc.MessageType} */ (messageJson['type']), + /** @type {!Object|undefined} */ (messageJson['data'])); + } + + /** + * Constructs an AUTH_READY message. + * @param {!mr.webrtc.AuthReadyMessageData} data + * @return {!mr.webrtc.Message} + */ + static authReady(data) { + return new Message(mr.webrtc.MessageType.AUTH_READY, data); + } + + /** + * Workaround for broken handling of ES6 classes in Jasmine. + * @return {string} + */ + jasmineToString() { + return '[mr.webrtc.Message instance]'; + } +}; + +const Message = mr.webrtc.Message; + + +/** + * The data for an offer message. Setting the presentation url in the WebRTC + * offer message indicates to the receiver that the session is a presentation + * session. + */ +mr.webrtc.OfferMessageData = class { + /** + * @param {RTCSessionDescription} description + * @param {mr.mirror.Settings=} opt_settings + * @param {MediaConstraints=} opt_mediaConstraints + * @param {string=} opt_presentationUrl + * @param {string=} opt_presentationId + */ + constructor( + description, opt_settings, opt_mediaConstraints, opt_presentationUrl, + opt_presentationId) { + /** + * @type {RTCSessionDescription} + * @export + */ + this.description = description; + + /** + * @type {?mr.mirror.Settings} + * @export + */ + this.settings = opt_settings || null; + + /** + * @type {?MediaConstraints} + * @export + */ + this.mediaConstraints = opt_mediaConstraints || null; + + /** + * @type {?string} + * @export + */ + this.presentationUrl = opt_presentationUrl || null; + + /** + * @type {?string} + * @export + */ + this.presentationId = opt_presentationId || null; + } +}; + + +/** + * Data associated with an AUTH_READY message. + * + * Fields: + * + * - isMeeting: True for Thor meetings, false for Hangouts. + * + * - hangoutId: The public ID of the Hangout/meeting. + * + * - resolvedId: The resolved (internal) ID of the Hangout/meeting. May be ''. + * + * - conferenceMode: If defined, used to override the setting of the + * isConferenceMode_ field of HangoutSession. + * + * @typedef {{ + * isMeeting: boolean, + * hangoutId: string, + * resolvedId: string, + * conferenceMode: (boolean|undefined) + * }} + */ +mr.webrtc.AuthReadyMessageData; + +}); // goog.scope diff --git a/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection.js b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection.js new file mode 100644 index 00000000000..4517bc2c85c --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection.js @@ -0,0 +1,570 @@ +// 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. + +goog.provide('mr.webrtc.PeerConnection'); +goog.provide('mr.webrtc.TurnCredential'); + +goog.require('mr.Assertions'); +goog.require('mr.Logger'); +goog.require('mr.PromiseResolver'); +goog.require('mr.webrtc.PeerConnectionAnalytics'); + +goog.scope(function() { + + +/** + * Type of credentials returned by the TURN credential service. Can be used + * as-is in the 'iceConnection' property of the configuration passed to the + * webkitRTCPeerConnection() constructor. + * + * @typedef {{ + * username: string, + * credential: string, + * url: string + * }} + */ +mr.webrtc.TurnCredential; + + +/** + * Creates a new PeerConnection. + */ +mr.webrtc.PeerConnection = class { + /** + * @param {string} routeId The route ID for the route to open this + * PeerConnection. Used as the data channel ID. + * @param {!Array<!mr.webrtc.TurnCredential>} turnCreds + * TURN server credentials. + */ + constructor(routeId, turnCreds) { + mr.Assertions.assert( + webkitRTCPeerConnection !== undefined, + 'webkitRTCPeerConnection is not available. Do you need to set flags?'); + + /** + * Logger instance. + * @private {mr.Logger} + */ + this.logger_ = mr.Logger.getInstance('cv2.PeerConnection'); + + /** + * The media constraints to assign to the PeerConnection. + * @type {!MediaConstraints | undefined} + * @private + */ + this.mediaConstraints_ = PeerConnection.MEDIA_CONSTRAINTS; + + /** + * WebRTC PeerConnection wrapped by this class. + * @private {webkitRTCPeerConnection} + */ + this.peerConnection_ = this.createPeerConnection_(turnCreds); + + /** + * WebRTC data channel (if it should be enabled). + * @type {!RTCDataChannel} + * @private + */ + this.dataChannel_ = this.createDataChannel_(routeId); + + + + /** + * Whether we will try an ICE restart when we are in a disconnected state. + * @private {boolean} + */ + this.enableIceRestart_ = true; + + + + /** + * Resolves when the Web RTC session description is received. + * @private {!mr.PromiseResolver<RTCSessionDescription>} + */ + this.sessionDescriptionResolver_ = new mr.PromiseResolver(); + + /** + * True if sessionDescriptionResolver_ has been resolved. + * @private {boolean} + */ + this.sessionDescriptionResolved_ = false; + + /** + * The number of ICE candidates that have been received so far. + * @private {number} + */ + this.numIceCandidatesReceived_ = 0; + + /** + * ID of the timer used to abort ICE candidate gathering. + * @private {?number} + */ + this.iceCandidateGatheringTimerId_ = null; + + /** + * The time when the offer is created. + * @private {number} + */ + this.offerCreationTime_ = 0; + + /** + * The time when the most recent ICE candidate was received. + * @private {number} + */ + this.lastIceCandidateTime_ = 0; + + /** + * True if the PeerConnection has been started. + * @private {boolean} + */ + this.started_ = false; + + /** + * Callback invoked when a PeerConnection has connection issues. + * @private {function()} + */ + this.onConnectionStale_ = () => {}; + + /** + * Callback invoked when the offer description is ready. + * @private {function(RTCSessionDescription)} + */ + this.onOfferDescription_ = () => {}; + + /** + * Callback invoked when the connection is successfully made. + * @private {function(mr.webrtc.PeerConnection.Event)} + */ + this.onConnectionSuccess_ = () => {}; + + /** + * Callback invoked when the connection fails. + * @private {function(mr.webrtc.PeerConnection.Event)} + */ + this.onConnectionFailure_ = () => {}; + + /** + * Callback invoked when the connection is closed. + * @type {function(mr.webrtc.PeerConnection.Event)} + * @private + */ + this.onConnectionClosed_ = () => {}; + } + + /** + * @param {function()} staleFn The callback. + */ + setOnConnectionStale(staleFn) { + this.onConnectionStale_ = staleFn; + } + + /** + * @param {function(mr.webrtc.PeerConnection.Event)} successFn + */ + setOnConnectionSuccess(successFn) { + this.onConnectionSuccess_ = successFn; + } + + /** + * @param {function(mr.webrtc.PeerConnection.Event)} failureFn + */ + setOnConnectionFailure(failureFn) { + this.onConnectionFailure_ = failureFn; + } + + /** + * @param {function(mr.webrtc.PeerConnection.Event)} closedFn + */ + setOnConnectionClosed(closedFn) { + this.onConnectionClosed_ = closedFn; + } + + /** + * @param {function(RTCSessionDescription)} onOfferFn + */ + setOnOfferDescriptionReady(onOfferFn) { + this.onOfferDescription_ = onOfferFn; + } + + /** + * @param {function(string)} onMessageFn + */ + setOnDataChannelMessage(onMessageFn) { + this.dataChannel_.onmessage = function(event) { + onMessageFn(event.data); + }; + } + + /** + * @param {boolean} shouldEnable Whether we should enable ICE restart. + */ + enableIceRestart(shouldEnable) { + this.enableIceRestart_ = shouldEnable; + } + + /** + * @return {boolean} true if the PeerConnection has been started. + */ + isStarted() { + return this.started_; + } + + /** + * Returns the configuration data for the WebRTC PeerConnection. + * @return {!RTCConfiguration} The configuration. + * @param {!Array<!mr.webrtc.TurnCredential>} turnCreds + * TURN server credentials. + * @private + * @suppress {invalidCasts} invalid cast - must be a subtype or supertype + * from: {} + * to : (!RTCConfiguration) + */ + getPeerConnectionConfig_(turnCreds) { + const server = {}; + server['url'] = 'stun:stun.l.google.com:19302'; + const config = {}; + config['iceServers'] = [server].concat(turnCreds); + return /** @type {!RTCConfiguration} */ (config); + } + + /** + * Creates the WebRTC PeerConnection object. + * @return {webkitRTCPeerConnection} The new PC. + * @param {!Array<!mr.webrtc.TurnCredential>} turnCreds + * TURN server credentials. + * @private + */ + createPeerConnection_(turnCreds) { + const config = this.getPeerConnectionConfig_(turnCreds); + const peerConnection = new webkitRTCPeerConnection(config); + peerConnection.onicecandidate = this.onIceCandidate_.bind(this); + peerConnection.onicegatheringstatechange = + this.onIceGatheringStateChange_.bind(this); + peerConnection.oniceconnectionstatechange = + this.onIceConnectionStateChange_.bind(this); + this.logger_.info( + () => 'Created webkitRTCPeerConnnection with config: ' + + JSON.stringify(config)); + return peerConnection; + } + + /** + * Creates a data channel. Must be called in the initiator before the SDP is + * created. + * @param {string} channelId + * @return {!RTCDataChannel} + * @private + */ + createDataChannel_(channelId) { + const dataChannel = + this.peerConnection_.createDataChannel(channelId, {'reliable': false}); + return dataChannel; + } + + /** + * Sends the provided message via the data channel. + * @param {!Object|string} message The message to send. + */ + sendDataChannelMessage(message) { + if (typeof message == 'string') { + this.dataChannel_.send(message); + } else { + this.dataChannel_.send(JSON.stringify(message)); + } + } + + /** + * Starts the PeerConnection with any added streams. + */ + start() { + if (!this.started_) { + this.started_ = true; + // Caller initiates offer to peer. + this.createOffer_(); + } + } + + /** + * Stops this PeerConnection. + */ + stop() { + this.logger_.info('Stopping peer connection...'); + if (this.started_) { + this.started_ = false; + if (this.peerConnection_.signalingState != 'closed') { + this.peerConnection_.close(); + } + } + this.peerConnection_ = null; + } + + /** + * Adds a stream to the PeerConnection. + * @param {!MediaStream} stream The media stream to add. + */ + addStream(stream) { + this.peerConnection_.addStream(stream); + } + + /** + * Removes a stream from the PeerConnection. + * @param {!MediaStream} stream The media stream to remove. + */ + removeStream(stream) { + if (this.started_) { + this.peerConnection_.removeStream(stream); + } + } + + /** + * Initiates a call to the peer. + * @private + */ + createOffer_() { + this.logger_.info('Sending offer to peer.'); + this.offerCreationTime_ = Date.now(); + + this.peerConnection_.createOffer( + this.setLocalDescription_.bind(this), error => { + this.logger_.warning('Error creating offer.', error); + }, this.mediaConstraints_); + this.getSessionDescription().then(sessionDescription => { + this.onOfferDescription_(sessionDescription); + }); + } + + /** + * Sets the local description for the session. + * @param {!RTCSessionDescription} sessionDescription The offer. + * @private + */ + setLocalDescription_(sessionDescription) { + + this.logger_.info( + () => + 'Setting local description: ' + JSON.stringify(sessionDescription)); + this.peerConnection_.setLocalDescription( + sessionDescription, + () => { + this.logger_.info('Local description set successfully'); + }, + error => { + this.logger_.warning('Error setting local description.', error); + }); + // Cloud connections only send messages when ICE gathering is complete. + // This is done in createOffer_ for cloud connections instead. + } + + /** + * There's currently a WebRTC "bug" which means we can't JSON.stringify an + * RTCSessionDescription. See http://b/19817649. So for now, we'll just put it + * in our own object. + + * @param {RTCSessionDescription} description + * @return {RTCSessionDescription} + * @private + */ + formDescriptionMessage_(description) { + return /** @type {RTCSessionDescription} */ ( + {'type': description.type, 'sdp': description.sdp}); + } + + /** + * Returns the session offer description (if this is the sender) or answer + * description (if this is the receiver). + * Note that this description contains all of the ICE candidates as well. + * @return {!Promise<RTCSessionDescription>} + */ + getSessionDescription() { + return this.sessionDescriptionResolver_.promise; + } + + /** + * Sets the remote description on the peer connection. + * @param {!RTCSessionDescription} sessionDescription + */ + setRemoteDescription(sessionDescription) { + this.logger_.fine(() => '<===: ' + JSON.stringify(sessionDescription)); + const description = new RTCSessionDescription(sessionDescription); + this.logger_.info( + () => 'Setting remote description: ' + JSON.stringify(description)); + // We received an answer! Just set the description. + this.peerConnection_.setRemoteDescription( + description, + () => { + this.logger_.info('Remote description set successfully.'); + }, + error => { + this.logger_.warning('Error setting remote description.', error); + }); + } + + /** + * Resolves the session description once all ice candidates have been + * received. + * @param {RTCPeerConnectionIceEvent} event The ICE candidate event. + * @private + */ + onIceCandidate_(event) { + if (event.candidate) { + this.numIceCandidatesReceived_++; + this.lastIceCandidateTime_ = Date.now(); + if (this.numIceCandidatesReceived_ == 1) { + // This is the first ICE candidate. Set a timer to abort gathering + // candidates. This is needed because sometimes the end of ICE + // candidate + // gathering is not detected right away even though all candidates have + // been gathered. + mr.Assertions.assert(this.iceCandidateGatheringTimerId_ == null); + this.iceCandidateGatheringTimerId_ = setTimeout(() => { + this.logger_.info('ICE candidate gathering timed out.'); + this.iceCandidateGatheringTimerId_ = null; + this.resolveSessionDescription_(); + }, PeerConnection.ICE_CANDIDATE_GATHERING_TIMEOUT_MS_); + } else if (this.sessionDescriptionResolved_) { + // This branch runs when additional ICE candidates are reported after + // the timeout above fires. + this.logger_.warning( + 'Received ICE candidate after resolving session description.'); + } + } else { + this.logger_.info('End of ICE candidates.'); + mr.webrtc.PeerConnectionAnalytics + .recordIceCandidateGatheringReportedDuration( + Date.now() - this.offerCreationTime_); + + // This is a no-op if the timout above has already fired. + this.resolveSessionDescription_(); + + // Record the true duration of candidate gathering based on the time the + // last candidate was reported. Don't record anything if no candidates + // were + // found, because the computed duration will be invalid. + if (this.numIceCandidatesReceived_ > 0) { + mr.webrtc.PeerConnectionAnalytics + .recordIceCandidateGatheringRealDuration( + this.lastIceCandidateTime_ - this.offerCreationTime_); + } + } + } + + /** + * Called when ICE gathering state changes. Tracks when the candidates are + * complete. + * @private + */ + onIceGatheringStateChange_() { + // This method never appears to be called. + const state = this.peerConnection_.iceGatheringState; + if (state == 'completed') { + this.resolveSessionDescription_(); + } + } + + /** + * Resolves the session description promise. Should be called after all ICE + * candidates have been received. + * @private + */ + resolveSessionDescription_() { + clearTimeout(this.iceCandidateGatheringTimerId_); + this.iceCandidateGatheringTimerId_ = null; + if (!this.sessionDescriptionResolved_) { + this.logger_.info( + 'Resolving sesion description after gathering ' + + this.numIceCandidatesReceived_ + ' ICE candidates.'); + this.sessionDescriptionResolver_.resolve( + this.formDescriptionMessage_(this.peerConnection_.localDescription)); + this.sessionDescriptionResolved_ = true; + } + } + + /** + * Handles ICE connection state changes. Tries to restart PeerConnection when + * we + * are in a disconnected state by creating a new offer with the IceRestart + * constraint. + * @param {Event} event + * @private + */ + onIceConnectionStateChange_(event) { + if (!this.peerConnection_) return; + + const state = this.peerConnection_.iceConnectionState; + this.logger_.info('New ICE connection state: ' + state + '.'); + if (state == 'connected') { + this.onConnectionSuccess_(PeerConnection.Event.ICE_CONNECTED); + } else if (state == 'completed') { + this.onConnectionSuccess_(PeerConnection.Event.ICE_COMPLETED); + } else if (state == 'failed') { + this.logger_.warning( + () => 'Ice connection failed: ' + JSON.stringify(event)); + this.onConnectionFailure_(PeerConnection.Event.ICE_FAILED); + } else if (state == 'closed') { + this.onConnectionClosed_(PeerConnection.Event.ICE_CLOSED); + } else if (state == 'disconnected') { + this.logger_.warning('Ice connection state is bad.'); + if (this.enableIceRestart_ && this.isStarted()) { + this.logger_.info('Restarting ICE.'); + this.peerConnection_.createOffer( + this.setLocalDescription_.bind(this), error => { + this.logger_.warning('Error creating new offer.', error); + }, PeerConnection.ICE_RESTART_MEDIA_CONSTRAINTS); + } else { + this.onConnectionStale_(); + } + } + } +}; + +const PeerConnection = mr.webrtc.PeerConnection; + + +/** + * Default media constraints for tab capture. + * @const {!MediaConstraints} + */ +PeerConnection.MEDIA_CONSTRAINTS = { + 'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true} +}; + + +/** + * Default media constraints during ICE restarts. + * @const {!MediaConstraints} + */ +PeerConnection.ICE_RESTART_MEDIA_CONSTRAINTS = { + 'mandatory': { + 'IceRestart': true, + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true + } +}; + + +/** + * PeerConnection event types. + * @enum {string} + */ +PeerConnection.Event = { + // events that have a corresponding on on<Event> callback. + ADD_STREAM: 'addstream', + REMOVE_STREAM: 'removestream', + ICE_CANDIDATE: 'icecandidate', + // events that do not have a corresponding on on<Event> callback. + ICE_CONNECTED: 'iceconnected', + ICE_COMPLETED: 'icecompleted', + ICE_FAILED: 'icefailed', + ICE_CLOSED: 'iceclosed' +}; + + +/** + * The maximum time to wait, in ms, for all ICE candidates to be gathered. + * Timing starts when the first ICE candidate is seen. + * @private @const + */ +PeerConnection.ICE_CANDIDATE_GATHERING_TIMEOUT_MS_ = 5 * 1000; + +}); // goog.scope diff --git a/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_analytics.js b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_analytics.js new file mode 100644 index 00000000000..69b5e3fabd8 --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_analytics.js @@ -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. + +/** + * @fileoverview Defines UMA analytics specific to peer connection. + */ + +goog.provide('mr.webrtc.PeerConnectionAnalytics'); + +goog.require('mr.Timing'); + + +/** @const {*} */ +mr.webrtc.PeerConnectionAnalytics = {}; + + +/** + * Histogram name for time taken to gather ICE candidates, from the start of + * candidate gethering to the time the last candidate is reported. + * @private @const {string} + */ +mr.webrtc.PeerConnectionAnalytics.ICE_CANDIDATE_GATHERING_REAL_DURATION_ = + 'MediaRouter.WebRtc.IceCandidateGathering.Duration.Real'; + + +/** + * Histogram name for time taken to gather ICE candidates, from the start of + * candidate gethering to the time the the end of collection is reported. + * @private @const {string} + */ +mr.webrtc.PeerConnectionAnalytics.ICE_CANDIDATE_GATHERING_REPORTED_DURATION_ = + 'MediaRouter.WebRtc.IceCandidateGathering.Duration.Reported'; + + +/** + * Records the real duration of ICE candidate gathering. + * @param {number} value + */ +mr.webrtc.PeerConnectionAnalytics.recordIceCandidateGatheringRealDuration = + function(value) { + mr.Timing.recordDuration( + mr.webrtc.PeerConnectionAnalytics.ICE_CANDIDATE_GATHERING_REAL_DURATION_, + value); +}; + + +/** + * Records the reported duration of ICE candidate gathering. + * @param {number} value + */ +mr.webrtc.PeerConnectionAnalytics.recordIceCandidateGatheringReportedDuration = + function(value) { + mr.Timing.recordDuration( + mr.webrtc.PeerConnectionAnalytics + .ICE_CANDIDATE_GATHERING_REPORTED_DURATION_, + value); +}; diff --git a/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_test.js b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_test.js new file mode 100644 index 00000000000..bd87b43971d --- /dev/null +++ b/chromium/chrome/browser/resources/media_router/extension/src/webrtc/peer_connection_test.js @@ -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. + +/** + * @fileoverview Tests for peer_connection. + */ +goog.setTestOnly('peer_connection_test'); + +goog.require('mr.webrtc.PeerConnection'); + +describe('mr.webrtc.PeerConnection', function() { + let peerConnection; + let mockWebkitPeerConnection, mockDataChannel; + let mockOnConnectionSuccess, mockOnConnectionStale; + let mockOnConnectionClosed, mockOnConnectionFailure; + let mockOnDescriptionFn, mockOnDataChannelMessage; + let mockClock; + + const DATA_CHANNEL_NAME = 'TEST_DATA_CHANNEL'; + + beforeEach(function() { + mockOnDescriptionFn = jasmine.createSpy('mockOnDescriptionFn'); + mockOnConnectionSuccess = jasmine.createSpy('mockOnConnectionSuccess'); + mockOnConnectionClosed = jasmine.createSpy('mockOnConnectionClosed'); + mockOnConnectionFailure = jasmine.createSpy('mockOnConnectionFailure'); + mockOnConnectionStale = jasmine.createSpy('mockOnConnectionStale'); + mockOnDataChannelMessage = jasmine.createSpy('mockOnDataChannelMessage'); + // There seems to no longer be a prototype exposed for + // webkitRTCPeerConnection as of cr43. Likely duplicate of b/19817649 (and + // related). So create a dumb Jasmine mock object with all methods we care + // about. + mockWebkitPeerConnection = jasmine.createSpyObj('peerConnection', [ + 'close', 'createOffer', 'createAnswer', 'createDataChannel', 'addStream', + 'setLocalDescription', 'setRemoteDescription', 'addIceCandidate', + 'removeStream', 'oniceconnected', 'onicecompleted', 'onicefailed', + 'onicecandidate', 'oniceconnectionstatechange' + ]); + mockDataChannel = + jasmine.createSpyObj('dataChannel', ['onmessage', 'send']); + mockWebkitPeerConnection.createDataChannel.and.returnValue(mockDataChannel); + webkitRTCPeerConnection = function(config) { + return mockWebkitPeerConnection; + }; + peerConnection = new mr.webrtc.PeerConnection(DATA_CHANNEL_NAME); + peerConnection.setOnConnectionSuccess(mockOnConnectionSuccess); + peerConnection.setOnConnectionClosed(mockOnConnectionClosed); + peerConnection.setOnConnectionFailure(mockOnConnectionFailure); + peerConnection.setOnConnectionStale(mockOnConnectionStale); + peerConnection.setOnOfferDescriptionReady(mockOnDescriptionFn); + }); + + it('constructor creates webkit peer connection with data channel, ' + + 'does not start', + function() { + expect(peerConnection.isStarted()).toEqual(false); + expect(peerConnection.peerConnection_).toEqual(mockWebkitPeerConnection); + expect(peerConnection.dataChannel_).toEqual(mockDataChannel); + expect(mockWebkitPeerConnection.createDataChannel) + .toHaveBeenCalledWith(DATA_CHANNEL_NAME, {'reliable': false}); + }); + + it('data channel onmessage calls callback', function() { + peerConnection.setOnDataChannelMessage(mockOnDataChannelMessage); + const event = {'data': 'DATA!!!'}; + mockDataChannel.onmessage(event); + expect(mockOnDataChannelMessage).toHaveBeenCalledWith(event.data); + }); + + it('sendDataChannelMessage sends message via data channel', function() { + // String message. + const stringMessage = 'String message!'; + peerConnection.sendDataChannelMessage(stringMessage); + expect(mockDataChannel.send).toHaveBeenCalledWith(stringMessage); + + // Object message. + const objMessage = {'obj': 'message'}; + peerConnection.sendDataChannelMessage(objMessage); + expect(mockDataChannel.send) + .toHaveBeenCalledWith(JSON.stringify(objMessage)); + }); + + it('start creates offer, sets local description and calls on description ' + + 'callback message when ready', + function(done) { + peerConnection.start(); + + expect(peerConnection.isStarted()).toEqual(true); + expect(mockWebkitPeerConnection.createOffer).toHaveBeenCalled(); + const createOfferArgs = + mockWebkitPeerConnection.createOffer.calls.mostRecent().args; + expect(createOfferArgs[2]) + .toEqual(mr.webrtc.PeerConnection.MEDIA_CONSTRAINTS); + + // Now call the local description callback (first arg) + const description = {'sdp': 'SDP!', 'type': 'TYPE!'}; + createOfferArgs[0](description); + const localDescriptionArgs = + mockWebkitPeerConnection.setLocalDescription.calls.mostRecent().args; + expect(localDescriptionArgs[0]).toEqual(description); + + // Now trigger the message sending by triggering ICE complete. + const webkitLocalDescription = { + 'sdp': 'Local SDP with ICE candidates!', + 'type': 'Local Type!' + }; + mockWebkitPeerConnection.localDescription = webkitLocalDescription; + peerConnection.onIceCandidate_({ + 'candidate': null // empty candidate signifies that it's done. + }); + mockOnDescriptionFn.and.callFake(arg => { + expect(arg).toEqual(webkitLocalDescription); + done(); + }); + }); + + it('stop closes the webkit peer connection', function() { + peerConnection.started_ = true; + peerConnection.stop(); + + expect(peerConnection.isStarted()).toEqual(false); + expect(mockWebkitPeerConnection.close).toHaveBeenCalled(); + }); + + it('addStream adds the stream to the webkit peer connection', function() { + const stream = {'fake': 'media stream'}; + peerConnection.addStream(stream); + + expect(mockWebkitPeerConnection.addStream).toHaveBeenCalledWith(stream); + }); + + it('removeStream removes the stream from the webkit peer connection', + function() { + const stream = {'fake': 'media stream'}; + peerConnection.started_ = true; + peerConnection.removeStream(stream); + + expect(mockWebkitPeerConnection.removeStream) + .toHaveBeenCalledWith(stream); + }); + + it('setRemoteDescription sets remote description', function() { + const description = {'sdp': 'SDP!', 'type': 'TYPE!'}; + const mockRtcSessionDescription = {'sdp': 'RTC SDP!', 'type': 'RTC TYPE!'}; + spyOn(window, 'RTCSessionDescription') + .and.returnValue(mockRtcSessionDescription); + + peerConnection.setRemoteDescription(description); + + const args = + mockWebkitPeerConnection.setRemoteDescription.calls.mostRecent().args; + expect(args[0]).toEqual(mockRtcSessionDescription); + }); + + it('onIceGatheringStateChange_ resolves the session description', done => { + const description = {'sdp': 'SDP!', 'type': 'TYPE!'}; + mockWebkitPeerConnection.iceGatheringState = 'completed'; + mockWebkitPeerConnection.localDescription = description; + peerConnection.onIceGatheringStateChange_(); + + peerConnection.sessionDescriptionResolver_.promise.then(value => { + expect(value).toEqual(description); + done(); + }); + }); + + it('onIceConnectionStateChange_ calls success callback when connected', + function() { + mockWebkitPeerConnection.iceConnectionState = 'connected'; + peerConnection.onIceConnectionStateChange_({}); + expect(mockOnConnectionSuccess) + .toHaveBeenCalledWith(mr.webrtc.PeerConnection.Event.ICE_CONNECTED); + + mockOnConnectionSuccess.calls.reset(); + mockWebkitPeerConnection.iceConnectionState = 'completed'; + peerConnection.onIceConnectionStateChange_({}); + expect(mockOnConnectionSuccess) + .toHaveBeenCalledWith(mr.webrtc.PeerConnection.Event.ICE_COMPLETED); + }); + + it('onConnectionStateChange_ calls closed callback when connection closes', + function() { + mockWebkitPeerConnection.iceConnectionState = 'closed'; + peerConnection.onIceConnectionStateChange_({}); + expect(mockOnConnectionClosed) + .toHaveBeenCalledWith(mr.webrtc.PeerConnection.Event.ICE_CLOSED); + }); + + it('onConnectionStateChange_ calls failure callback when connection fails', + function() { + mockWebkitPeerConnection.iceConnectionState = 'failed'; + peerConnection.onIceConnectionStateChange_({}); + expect(mockOnConnectionFailure) + .toHaveBeenCalledWith(mr.webrtc.PeerConnection.Event.ICE_FAILED); + }); + + it('onIceConnectionStateChange_ tries to re-connect when disconnected', + function() { + peerConnection.started_ = true; + peerConnection.enableIceRestart_ = true; + + mockWebkitPeerConnection.iceConnectionState = 'disconnected'; + peerConnection.onIceConnectionStateChange_({}); + + const args = + mockWebkitPeerConnection.createOffer.calls.mostRecent().args; + expect(args[2]).toEqual( + mr.webrtc.PeerConnection.ICE_RESTART_MEDIA_CONSTRAINTS); + + // Instead, try when enableIceRestart is false. + peerConnection.enableIceRestart_ = false; + peerConnection.onIceConnectionStateChange_({}); + expect(mockOnConnectionStale).toHaveBeenCalled(); + }); +}); diff --git a/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js b/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js index 8cd1ca3406d..2c1bce1a3ad 100644 --- a/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js +++ b/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js @@ -16,6 +16,9 @@ cr.define('media_router.ui', function() { // The route-controls element. Is null if the route details view isn't open. var routeControls = null; + // The initial height for |container|. + var initialMaxHeight = 0; + /** * Handles response of previous create route attempt. * @@ -64,6 +67,11 @@ cr.define('media_router.ui', function() { function setElements(mediaRouterContainer, mediaRouterHeader) { container = mediaRouterContainer; header = mediaRouterHeader; + + if (initialMaxHeight) { + container.updateMaxDialogHeight(initialMaxHeight); + initialMaxHeight = 0; + } } /** @@ -183,7 +191,12 @@ cr.define('media_router.ui', function() { * @param {number} height */ function updateMaxHeight(height) { - container.updateMaxDialogHeight(height); + if (container) { + container.updateMaxDialogHeight(height); + } else { + // Update the max height once |container| gets set. + initialMaxHeight = height; + } } /** diff --git a/chromium/chrome/browser/resources/net_internals/http_cache_view.html b/chromium/chrome/browser/resources/net_internals/http_cache_view.html index e2c45132725..9af570e3229 100644 --- a/chromium/chrome/browser/resources/net_internals/http_cache_view.html +++ b/chromium/chrome/browser/resources/net_internals/http_cache_view.html @@ -1,8 +1,4 @@ <div id=http-cache-view-tab-content class=content-box> - <div class="hide-when-not-capturing"> - <a href="chrome://view-http-cache" target=_blank>Explore cache entries</a> - </div> - <h4>Statistics</h4> <div id=http-cache-view-cache-stats>Nothing loaded yet.</div> </div> diff --git a/chromium/chrome/browser/resources/ntp4/new_tab.js b/chromium/chrome/browser/resources/ntp4/new_tab.js index c6564c4f062..98066bfe562 100644 --- a/chromium/chrome/browser/resources/ntp4/new_tab.js +++ b/chromium/chrome/browser/resources/ntp4/new_tab.js @@ -284,7 +284,8 @@ cr.define('ntp', function() { var headerContainer = $('login-status-header-container'); headerContainer.classList.toggle('login-status-icon', !!iconURL); - headerContainer.style.backgroundImage = iconURL ? url(iconURL) : 'none'; + headerContainer.style.backgroundImage = + iconURL ? getUrlForCss(iconURL) : 'none'; } if (shouldShowLoginBubble) { diff --git a/chromium/chrome/browser/resources/offline_pages/offline_internals.html b/chromium/chrome/browser/resources/offline_pages/offline_internals.html index 013cfd09390..f7ab768f209 100644 --- a/chromium/chrome/browser/resources/offline_pages/offline_internals.html +++ b/chromium/chrome/browser/resources/offline_pages/offline_internals.html @@ -58,24 +58,22 @@ <table class="stored-pages-table"> <thead> <tr> - <th> </th> + <th>#</th> + <th></th> <th>URL</th> <th>Namespace</th> <th>Size (Kb)</th> - <th>Expired</th> - <th>Request Origin</th> </tr> </thead> <tbody id="stored-pages"> </tbody> </table> <template id="stored-pages-table-row"> <tr> + <td></td> <td><input type="checkbox" name="stored"></td> <td><a></a></td> <td></td> <td></td> - <td></td> - <td></td> </tr> </template> <div id="page-actions-info" class="dump"></div> diff --git a/chromium/chrome/browser/resources/offline_pages/offline_internals.js b/chromium/chrome/browser/resources/offline_pages/offline_internals.js index d9034ccda18..f184c8c005b 100644 --- a/chromium/chrome/browser/resources/offline_pages/offline_internals.js +++ b/chromium/chrome/browser/resources/offline_pages/offline_internals.js @@ -35,18 +35,27 @@ cr.define('offlineInternals', function() { var template = $('stored-pages-table-row'); var td = template.content.querySelectorAll('td'); - for (let page of pages) { - var checkbox = td[0].querySelector('input'); + for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { + var page = pages[pageIndex]; + td[0].textContent = pageIndex; + var checkbox = td[1].querySelector('input'); checkbox.setAttribute('value', page.id); - var link = td[1].querySelector('a'); + var link = td[2].querySelector('a'); link.setAttribute('href', page.onlineUrl); - link.textContent = page.onlineUrl; + var maxUrlCharsPerLine = 50; + if (page.onlineUrl.length > maxUrlCharsPerLine) { + link.textContent = ''; + for (let i = 0; i < page.onlineUrl.length; i += maxUrlCharsPerLine) { + link.textContent += page.onlineUrl.slice(i, i + maxUrlCharsPerLine); + link.textContent += '\r\n'; + } + } else { + link.textContent = page.onlineUrl; + } - td[2].textContent = page.namespace; - td[3].textContent = Math.round(page.size / 1024); - td[4].textContent = page.isExpired; - td[5].textContent = page.requestOrigin; + td[3].textContent = page.namespace; + td[4].textContent = Math.round(page.size / 1024); var row = document.importNode(template.content, true); storedPagesTable.appendChild(row); diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html index 7320212941e..174b498e017 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html @@ -8,8 +8,8 @@ <template> <style> #item { - @apply(--layout-center); - @apply(--layout-horizontal); + @apply --layout-center; + @apply --layout-horizontal; cursor: pointer; height: 30px; position: relative; diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html index 8ada7527043..a2c53b9ee6d 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html @@ -15,10 +15,9 @@ } #pageselector { - --container: { visibility: hidden; }; + --container: { display: none; }; --paper-input-container-underline: var(--container); --paper-input-container-underline-focus: var(--container); - display: inline-block; padding: 0; width: 1ch; } @@ -26,6 +25,7 @@ #input { -webkit-margin-start: -3px; color: #fff; + height: 100%; line-height: 18px; padding: 3px; text-align: end; @@ -43,10 +43,19 @@ } #pagelength-spacer { - display: inline-block; + -webkit-margin-start: -2px; + padding-bottom: 1px; text-align: start; } + #pageselector, + #slash, + #pagelength-spacer { + display: inline-block; + margin-bottom: 2px; + vertical-align: middle; + } + #input, #slash, #pagelength { @@ -54,7 +63,7 @@ } </style> <paper-input-container id="pageselector" no-label-float> - <input id="input" is="iron-input" value="{{pageNo}}" + <input id="input" is="iron-input" value="{{pageNo}}" slot="input" prevent-invalid-input allowed-pattern="\d" on-mouseup="select" on-change="pageNoCommitted" aria-label$="{{strings.labelPageNumber}}"> </paper-input-container> diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html index 1d5c1ad9bb5..af50fd9f2ab 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html @@ -28,7 +28,7 @@ } #title { - @apply(--layout-flex-5); + @apply --layout-flex-5; font-size: 0.87rem; font-weight: 500; overflow: hidden; @@ -37,7 +37,7 @@ } #pageselector-container { - @apply(--layout-flex-1); + @apply --layout-flex-1; text-align: center; /* The container resizes according to the width of the toolbar. On small * screens with large numbers of pages, overflow page numbers without @@ -46,7 +46,7 @@ } #buttons { - @apply(--layout-flex-5); + @apply --layout-flex-5; text-align: end; user-select: none; } @@ -68,7 +68,7 @@ } #toolbar { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: rgb(50, 54, 57); color: rgb(241, 241, 241); display: flex; diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html index 2e3e6182753..95d75885720 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html @@ -26,7 +26,7 @@ } #dropdown { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: rgb(256, 256, 256); border-radius: 4px; color: var(--primary-text-color); diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html index 83d3d8df7f0..28ed7680a35 100644 --- a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html +++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html @@ -21,7 +21,7 @@ } paper-fab { - @apply(--shadow-elevation-4dp); + @apply --shadow-elevation-4dp; --paper-fab-keyboard-focus-background: var(--viewer-icon-ink-color); --paper-fab-mini: { height: 36px; diff --git a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js index e573656c3fd..5903e403366 100644 --- a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js +++ b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js @@ -2,25 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -var OpenPDFParamsParser; - (function() { 'use strict'; /** - * Creates a new OpenPDFParamsParser. This parses the open pdf parameters - * passed in the url to set initial viewport settings for opening the pdf. - * @param {!Function} getNamedDestinationsFunction The function called to fetch - * the page number for a named destination. - * @constructor + * Parses the open pdf parameters passed in the url to set initial viewport + * settings for opening the pdf. */ -OpenPDFParamsParser = function(getNamedDestinationsFunction) { - this.outstandingRequests_ = []; - this.getNamedDestinationsFunction_ = getNamedDestinationsFunction; -}; +window.OpenPDFParamsParser = class { + /** + * Constructor. + * @param {function(Object)} postMessageCallback + * Function called to fetch information for a named destination. + */ + constructor(postMessageCallback) { + /** @private {!Array<!Object>} */ + this.outstandingRequests_ = []; + + /** @private {!function(Object)} */ + this.postMessageCallback_ = postMessageCallback; + } -OpenPDFParamsParser.prototype = { /** * @private * Parse zoom parameter of open PDF parameters. The PDF should be opened at @@ -28,14 +31,14 @@ OpenPDFParamsParser.prototype = { * @param {string} paramValue zoom value. * @return {Object} Map with zoom parameters (zoom and position). */ - parseZoomParam_: function(paramValue) { - var paramValueSplit = paramValue.split(','); + parseZoomParam_(paramValue) { + const paramValueSplit = paramValue.split(','); if (paramValueSplit.length != 1 && paramValueSplit.length != 3) return {}; // User scale of 100 means zoom value of 100% i.e. zoom factor of 1.0. - var zoomFactor = parseFloat(paramValueSplit[0]) / 100; - if (isNaN(zoomFactor)) + const zoomFactor = parseFloat(paramValueSplit[0]) / 100; + if (Number.isNaN(zoomFactor)) return {}; // Handle #zoom=scale. @@ -44,12 +47,12 @@ OpenPDFParamsParser.prototype = { } // Handle #zoom=scale,left,top. - var position = { + const position = { x: parseFloat(paramValueSplit[1]), y: parseFloat(paramValueSplit[2]) }; return {'position': position, 'zoom': zoomFactor}; - }, + } /** * @private @@ -58,14 +61,14 @@ OpenPDFParamsParser.prototype = { * @param {string} paramValue view value. * @return {Object} Map with view parameters (view and viewPosition). */ - parseViewParam_: function(paramValue) { - var viewModeComponents = paramValue.toLowerCase().split(','); + parseViewParam_(paramValue) { + const viewModeComponents = paramValue.toLowerCase().split(','); if (viewModeComponents.length < 1) return {}; - var params = {}; - var viewMode = viewModeComponents[0]; - var acceptsPositionParam; + const params = {}; + const viewMode = viewModeComponents[0]; + let acceptsPositionParam; if (viewMode === 'fit') { params['view'] = FittingType.FIT_TO_PAGE; acceptsPositionParam = false; @@ -80,12 +83,12 @@ OpenPDFParamsParser.prototype = { if (!acceptsPositionParam || viewModeComponents.length < 2) return params; - var position = parseFloat(viewModeComponents[1]); - if (!isNaN(position)) + const position = parseFloat(viewModeComponents[1]); + if (!Number.isNaN(position)) params['viewPosition'] = position; return params; - }, + } /** * Parse the parameters encoded in the fragment of a URL into a dictionary. @@ -93,14 +96,14 @@ OpenPDFParamsParser.prototype = { * @param {string} url to parse * @return {Object} Key-value pairs of URL parameters */ - parseUrlParams_: function(url) { - var params = {}; + parseUrlParams_(url) { + const params = {}; - var paramIndex = url.search('#'); + const paramIndex = url.search('#'); if (paramIndex == -1) return params; - var paramTokens = url.substring(paramIndex + 1).split('&'); + const paramTokens = url.substring(paramIndex + 1).split('&'); if ((paramTokens.length == 1) && (paramTokens[0].search('=') == -1)) { // Handle the case of http://foo.com/bar#NAMEDDEST. This is not // explicitly mentioned except by example in the Adobe @@ -109,15 +112,15 @@ OpenPDFParamsParser.prototype = { return params; } - for (var i = 0; i < paramTokens.length; ++i) { - var keyValueSplit = paramTokens[i].split('='); + for (let paramToken of paramTokens) { + const keyValueSplit = paramToken.split('='); if (keyValueSplit.length != 2) continue; params[keyValueSplit[0]] = keyValueSplit[1]; } return params; - }, + } /** * Parse PDF url parameters used for controlling the state of UI. These need @@ -126,15 +129,15 @@ OpenPDFParamsParser.prototype = { * @param {string} url that needs to be parsed. * @return {Object} parsed url parameters. */ - getUiUrlParams: function(url) { - var params = this.parseUrlParams_(url); - var uiParams = {toolbar: true}; + getUiUrlParams(url) { + const params = this.parseUrlParams_(url); + const uiParams = {toolbar: true}; if ('toolbar' in params && params['toolbar'] == 0) uiParams.toolbar = false; return uiParams; - }, + } /** * Parse PDF url parameters. These parameters are mentioned in the url @@ -144,16 +147,16 @@ OpenPDFParamsParser.prototype = { * @param {string} url that needs to be parsed. * @param {Function} callback function to be called with viewport info. */ - getViewportFromUrlParams: function(url, callback) { - var params = {}; + getViewportFromUrlParams(url, callback) { + const params = {}; params['url'] = url; - var urlParams = this.parseUrlParams_(url); + const urlParams = this.parseUrlParams_(url); if ('page' in urlParams) { // |pageNumber| is 1-based, but goToPage() take a zero-based page number. - var pageNumber = parseInt(urlParams['page'], 10); - if (!isNaN(pageNumber) && pageNumber > 0) + const pageNumber = parseInt(urlParams['page'], 10); + if (!Number.isNaN(pageNumber) && pageNumber > 0) params['page'] = pageNumber - 1; } @@ -165,11 +168,14 @@ OpenPDFParamsParser.prototype = { if (params.page === undefined && 'nameddest' in urlParams) { this.outstandingRequests_.push({callback: callback, params: params}); - this.getNamedDestinationsFunction_(urlParams['nameddest']); + this.postMessageCallback_({ + type: 'getNamedDestination', + namedDestination: urlParams['nameddest'] + }); } else { callback(params); } - }, + } /** * This is called when a named destination is received and the page number @@ -177,12 +183,12 @@ OpenPDFParamsParser.prototype = { * @param {number} pageNumber The page corresponding to the named destination * requested. */ - onNamedDestinationReceived: function(pageNumber) { - var outstandingRequest = this.outstandingRequests_.shift(); + onNamedDestinationReceived(pageNumber) { + const outstandingRequest = this.outstandingRequests_.shift(); if (pageNumber != -1) outstandingRequest.params.page = pageNumber; outstandingRequest.callback(outstandingRequest.params); - }, + } }; }()); diff --git a/chromium/chrome/browser/resources/pdf/pdf.js b/chromium/chrome/browser/resources/pdf/pdf.js index 4a8cb93199e..029f5b231af 100644 --- a/chromium/chrome/browser/resources/pdf/pdf.js +++ b/chromium/chrome/browser/resources/pdf/pdf.js @@ -106,15 +106,20 @@ function PDFViewer(browserApi) { this.isUserInitiatedEvent_ = true; /** - * @type {PDFMetrics} + * @type {!PDFMetrics} */ this.metrics = (chrome.metricsPrivate ? new PDFMetricsImpl() : new PDFMetricsDummy()); this.metrics.onDocumentOpened(); + /** + * @private {!PDFCoordsTransformer} + */ + this.coordsTransformer_ = + new PDFCoordsTransformer(this.postMessage_.bind(this)); + // Parse open pdf parameters. - this.paramsParser_ = - new OpenPDFParamsParser(this.getNamedDestination_.bind(this)); + this.paramsParser_ = new OpenPDFParamsParser(this.postMessage_.bind(this)); var toolbarEnabled = this.paramsParser_.getUiUrlParams(this.originalUrl_).toolbar && !this.isPrintPreview_; @@ -224,9 +229,6 @@ function PDFViewer(browserApi) { this.toolbar_.docTitle = getFilenameFromURL(this.originalUrl_); } - this.coordsTransformer_ = - new PDFCoordsTransformer(this.plugin_.postMessage.bind(this.plugin_)); - document.body.addEventListener('change-page', e => { this.viewport_.goToPage(e.detail.page); if (e.detail.origin == 'bookmark') @@ -391,7 +393,7 @@ PDFViewer.prototype = { return; case 65: // 'a' key. if (e.ctrlKey || e.metaKey) { - this.plugin_.postMessage({type: 'selectAll'}); + this.postMessage_({type: 'selectAll'}); // Since we do selection ourselves. e.preventDefault(); } @@ -451,7 +453,7 @@ PDFViewer.prototype = { */ rotateClockwise_: function() { this.metrics.onRotation(); - this.plugin_.postMessage({type: 'rotateClockwise'}); + this.postMessage_({type: 'rotateClockwise'}); }, /** @@ -460,7 +462,7 @@ PDFViewer.prototype = { */ rotateCounterClockwise_: function() { this.metrics.onRotation(); - this.plugin_.postMessage({type: 'rotateCounterclockwise'}); + this.postMessage_({type: 'rotateCounterclockwise'}); }, /** @@ -488,7 +490,7 @@ PDFViewer.prototype = { * Notify the plugin to print. */ print_: function() { - this.plugin_.postMessage({type: 'print'}); + this.postMessage_({type: 'print'}); }, /** @@ -496,17 +498,7 @@ PDFViewer.prototype = { * Notify the plugin to save. */ save_: function() { - this.plugin_.postMessage({type: 'save'}); - }, - - /** - * Fetches the page number corresponding to the given named destination from - * the plugin. - * @param {string} name The namedDestination to fetch page number from plugin. - */ - getNamedDestination_: function(name) { - this.plugin_.postMessage( - {type: 'getNamedDestination', namedDestination: name}); + this.postMessage_({type: 'save'}); }, /** @@ -630,12 +622,22 @@ PDFViewer.prototype = { * @param {Object} event a password-submitted event. */ onPasswordSubmitted_: function(event) { - this.plugin_.postMessage( + this.postMessage_( {type: 'getPasswordComplete', password: event.detail.password}); }, /** * @private + * Post a message to the PPAPI plugin. Some messages will cause an async reply + * to be received through handlePluginMessage_(). + * @param {Object} message Message to post. + */ + postMessage_: function(message) { + this.plugin_.postMessage(message); + }, + + /** + * @private * An event handler for handling message events received from the plugin. * @param {MessageObject} message a message event. */ @@ -747,13 +749,13 @@ PDFViewer.prototype = { * reacting to scroll events while zoom is taking place to avoid flickering. */ beforeZoom_: function() { - this.plugin_.postMessage({type: 'stopScrolling'}); + this.postMessage_({type: 'stopScrolling'}); if (this.viewport_.pinchPhase == Viewport.PinchPhase.PINCH_START) { var position = this.viewport_.position; var zoom = this.viewport_.zoom; var pinchPhase = this.viewport_.pinchPhase; - this.plugin_.postMessage({ + this.postMessage_({ type: 'viewport', userInitiated: true, zoom: zoom, @@ -776,7 +778,7 @@ PDFViewer.prototype = { var pinchCenter = this.viewport_.pinchCenter || {x: 0, y: 0}; var pinchPhase = this.viewport_.pinchPhase; - this.plugin_.postMessage({ + this.postMessage_({ type: 'viewport', userInitiated: this.isUserInitiatedEvent_, zoom: zoom, @@ -935,7 +937,7 @@ PDFViewer.prototype = { case 'getSelectedText': case 'print': case 'selectAll': - this.plugin_.postMessage(message.data); + this.postMessage_(message.data); break; } }, @@ -952,7 +954,7 @@ PDFViewer.prototype = { switch (message.data.type.toString()) { case 'loadPreviewPage': - this.plugin_.postMessage(message.data); + this.postMessage_(message.data); return true; case 'resetPrintPreviewMode': this.loadState_ = LoadState.LOADING; @@ -977,7 +979,7 @@ PDFViewer.prototype = { this.pageIndicator_.pageLabels = message.data.pageNumbers; - this.plugin_.postMessage({ + this.postMessage_({ type: 'resetPrintPreviewMode', url: message.data.url, grayscale: message.data.grayscale, diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json index 9d4b50b6b41..d00ba641dd8 100644 --- a/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json +++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json @@ -1,5 +1,5 @@ { - "x-version": 28, + "x-version": 29, "google-talk": { "mime_types": [ ], @@ -80,9 +80,9 @@ ], "versions": [ { - "version": "28.0.0.161", + "version": "29.0.0.140", "status": "up_to_date", - "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-03.html" + "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-08.html" } ], "lang": "en-US", diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json index 5a7f063708f..a4feba50406 100644 --- a/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json +++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json @@ -1,5 +1,5 @@ { - "x-version": 34, + "x-version": 35, "google-talk": { "mime_types": [ ], @@ -115,9 +115,9 @@ ], "versions": [ { - "version": "28.0.0.161", + "version": "29.0.0.140", "status": "requires_authorization", - "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-03.html" + "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-08.html" } ], "lang": "en-US", diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json index 4d356b256f0..45880e4aefa 100644 --- a/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json +++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json @@ -1,5 +1,5 @@ { - "x-version": 43, + "x-version": 44, "google-talk": { "mime_types": [ ], @@ -137,9 +137,9 @@ ], "versions": [ { - "version": "28.0.0.161", + "version": "29.0.0.140", "status": "requires_authorization", - "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-03.html" + "reference": "https://helpx.adobe.com/security/products/flash-player/apsb18-08.html" } ], "lang": "en-US", diff --git a/chromium/chrome/browser/resources/policy.css b/chromium/chrome/browser/resources/policy.css index d9b74acf519..45df8882782 100644 --- a/chromium/chrome/browser/resources/policy.css +++ b/chromium/chrome/browser/resources/policy.css @@ -36,11 +36,6 @@ html[dir='rtl'] div.left-aligned-button { float: right; } -div.chrome-for-work { - -webkit-padding-start: 25px; - display: inline-block; -} - section.status-box-section { clear: both; }
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/policy.html b/chromium/chrome/browser/resources/policy.html index e03b6758dd7..93a74fdf41d 100644 --- a/chromium/chrome/browser/resources/policy.html +++ b/chromium/chrome/browser/resources/policy.html @@ -36,10 +36,6 @@ <div class="left-aligned-button"> <button id="export-policies">$i18n{exportPoliciesJSON}</button> </div> - <div class="chrome-for-work"> - <a href="http://g.co/chromeent/learn" target="_blank"> - <span>$i18n{chromeForWork}</span></a> - </div> <div id="show-unset-container" class="show-unset-checkbox"> <label> <input id="show-unset" type="checkbox"> diff --git a/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.html b/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.html index ad8c3a7cae5..61edce46557 100644 --- a/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.html +++ b/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.html @@ -1,50 +1,4 @@ <div id='rpp_enabled'> - <tabbox id="rpp_data"> - <tabs> - <tab>URL Table Cache</tab> - <tab>Host Table Cache</tab> - <tab>Origin Table Cache</tab> - </tabs> - <tabpanels> - <tabpanel> - <table> - <thead> - <tr> - <th>Main Frame Url</th> - <th>Resource Url</th> - <th>Resource Type</th> - <th>Num Hits</th> - <th>Num Misses</th> - <th>Consec Misses</th> - <th>Average Position</th> - <th>Score</th> - <th>Before FCP</th> - </tr> - </thead> - <tbody id="rpp_url_body"> - </tbody> - </table> - </tabpanel> - <tabpanel> - <table> - <thead> - <tr> - <th>Host</th> - <th>Resource Url</th> - <th>Resource Type</th> - <th>Num Hits</th> - <th>Num Misses</th> - <th>Consec Misses</th> - <th>Average Position</th> - <th>Score</th> - <th>Before FCP</th> - </tr> - </thead> - <tbody id="rpp_host_body"> - </tbody> - </table> - </tabpanel> - <tabpanel> <table> <thead> <tr> @@ -62,9 +16,6 @@ <tbody id="rpp_origin_body"> </tbody> </table> - </tabpanel> - </tabpanels> - </tabbox> </div> <div id='rpp_disabled'> Resource prefetch prediction is disabled. diff --git a/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.js b/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.js index a00b7d58a20..66af67b4186 100644 --- a/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.js +++ b/chromium/chrome/browser/resources/predictors/resource_prefetch_predictor.js @@ -45,62 +45,13 @@ function updateResourcePrefetchPredictorDbView(database) { $('rpp_enabled').style.display = 'block'; $('rpp_disabled').style.display = 'none'; - var hasUrlData = database.url_db && database.url_db.length > 0; - var hasHostData = database.host_db && database.host_db.length > 0; var hasOriginData = database.origin_db && database.origin_db.length > 0; - if (hasUrlData) - renderCacheData($('rpp_url_body'), database.url_db); - if (hasHostData) - renderCacheData($('rpp_host_body'), database.host_db); if (hasOriginData) renderOriginData($('rpp_origin_body'), database.origin_db); } /** - * Renders cache data for URL or host based data. - * @param {HTMLElement} body element of table to render into. - * @param {Object} database to render. - */ -function renderCacheData(body, database) { - body.textContent = ''; - for (let main of database) { - for (var j = 0; j < main.resources.length; ++j) { - var resource = main.resources[j]; - var row = document.createElement('tr'); - - if (j == 0) { - var t = document.createElement('td'); - t.rowSpan = main.resources.length; - t.textContent = truncateString(main.main_frame_url); - row.appendChild(t); - } - - row.className = - resource.is_prefetchable ? 'action-prerender' : 'action-none'; - - row.appendChild(document.createElement('td')).textContent = - truncateString(resource.resource_url); - row.appendChild(document.createElement('td')).textContent = - resource.resource_type; - row.appendChild(document.createElement('td')).textContent = - resource.number_of_hits; - row.appendChild(document.createElement('td')).textContent = - resource.number_of_misses; - row.appendChild(document.createElement('td')).textContent = - resource.consecutive_misses; - row.appendChild(document.createElement('td')).textContent = - resource.position; - row.appendChild(document.createElement('td')).textContent = - resource.score; - row.appendChild(document.createElement('td')).textContent = - resource.before_first_contentful_paint; - body.appendChild(row); - } - } -} - -/** * Renders the content of the predictor origin table. * @param {HTMLElement} body element of table to render into. * @param {Object} database to render. diff --git a/chromium/chrome/browser/resources/print_preview/OWNERS b/chromium/chrome/browser/resources/print_preview/OWNERS index ef10fbb92d5..e542f20827d 100644 --- a/chromium/chrome/browser/resources/print_preview/OWNERS +++ b/chromium/chrome/browser/resources/print_preview/OWNERS @@ -1,5 +1,3 @@ -dpapad@chromium.org -gene@chromium.org -vitalybuka@chromium.org +file://printing/OWNERS # COMPONENT: UI>Browser>PrintPreview diff --git a/chromium/chrome/browser/resources/print_preview/cloud_print_interface.html b/chromium/chrome/browser/resources/print_preview/cloud_print_interface.html new file mode 100644 index 00000000000..9997dacb339 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/cloud_print_interface.html @@ -0,0 +1,9 @@ +<link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="native_layer.html"> +<link rel="import" href="data/cloud_parsers.html"> +<link rel="import" href="data/destination.html"> +<link rel="import" href="data/document_info.html"> +<link rel="import" href="data/invitation.html"> +<link rel="import" href="data/user_info.html"> + +<script src="cloud_print_interface.js"></script> diff --git a/chromium/chrome/browser/resources/print_preview/data/cloud_parsers.html b/chromium/chrome/browser/resources/print_preview/data/cloud_parsers.html new file mode 100644 index 00000000000..12f99ebed56 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/data/cloud_parsers.html @@ -0,0 +1,5 @@ +<link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="destination.html"> +<link rel="import" href="invitation.html"> + +<script src="cloud_parsers.js"></script> diff --git a/chromium/chrome/browser/resources/print_preview/data/destination.js b/chromium/chrome/browser/resources/print_preview/data/destination.js index 46f97789af1..a3b1e5627d0 100644 --- a/chromium/chrome/browser/resources/print_preview/data/destination.js +++ b/chromium/chrome/browser/resources/print_preview/data/destination.js @@ -71,10 +71,40 @@ print_preview.DestinationCertificateStatus = { }; /** + * @typedef {{ + * display_name: (string), + * type: (string | undefined), + * value: (number | string | boolean), + * is_default: (boolean | undefined), + * }} + */ +print_preview.VendorCapabilitySelectOption; + +/** + * Specifies a custom vendor capability. + * @typedef {{ + * id: (string), + * display_name: (string), + * localized_display_name: (string | undefined), + * type: (string), + * select_cap: ({ + * option: (Array<!print_preview.VendorCapabilitySelectOption>|undefined), + * }|undefined), + * typed_value_cap: ({ + * default: (number | string | boolean | undefined), + * }|undefined), + * range_cap: ({ + * default: (number), + * }), + * }} + */ +print_preview.VendorCapability; + +/** * Capabilities of a print destination represented in a CDD. * * @typedef {{ - * vendor_capability: !Array<{Object}>, + * vendor_capability: !Array<!print_preview.VendorCapability>, * collate: ({default: (boolean|undefined)}|undefined), * color: ({ * option: !Array<{ @@ -537,10 +567,18 @@ cr.define('print_preview', function() { } /** + * @return {boolean} Whether the destination is offline or has an invalid + * certificate. + */ + get isOfflineOrInvalid() { + return this.isOffline || this.shouldShowInvalidCertificateError; + } + + /** * @return {string} Human readable status for a destination that is offline * or has a bad certificate. */ get connectionStatusText() { - if (!this.isOffline && !this.shouldShowInvalidCertificateError) + if (!this.isOfflineOrInvalid) return ''; const offlineDurationMs = Date.now() - this.lastAccessTime_; let statusMessageId; @@ -781,7 +819,6 @@ cr.define('print_preview', function() { LOCAL_2X: 'images/2x/printer.png', MOBILE: 'images/mobile.png', MOBILE_SHARED: 'images/mobile_shared.png', - THIRD_PARTY: 'images/third_party.png', PDF: 'images/pdf.png', DOCS: 'images/google_doc.png', ENTERPRISE: 'images/business.svg' diff --git a/chromium/chrome/browser/resources/print_preview/data/destination_store.html b/chromium/chrome/browser/resources/print_preview/data/destination_store.html index ff0c3f8f95c..67f7dbc63f8 100644 --- a/chromium/chrome/browser/resources/print_preview/data/destination_store.html +++ b/chromium/chrome/browser/resources/print_preview/data/destination_store.html @@ -1,7 +1,4 @@ <link rel="import" href="chrome://resources/html/cr.html"> -<link rel="import" href="chrome://resources/html/event_tracker.html"> -<link rel="import" href="chrome://resources/html/webui_listener_tracker.html"> -<link rel="import" href="chrome://resources/html/cr/event_target.html"> <link rel="import" href="../metrics.html"> <link rel="import" href="../native_layer.html"> <link rel="import" href="destination.html"> diff --git a/chromium/chrome/browser/resources/print_preview/data/invitation.html b/chromium/chrome/browser/resources/print_preview/data/invitation.html new file mode 100644 index 00000000000..0a72be7c1ea --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/data/invitation.html @@ -0,0 +1,4 @@ +<link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="destination.html"> + +<script src="invitation.js"></script> diff --git a/chromium/chrome/browser/resources/print_preview/images/2x/printer.png b/chromium/chrome/browser/resources/print_preview/images/2x/printer.png Binary files differindex b704e02f841..6bd2a925be3 100644 --- a/chromium/chrome/browser/resources/print_preview/images/2x/printer.png +++ b/chromium/chrome/browser/resources/print_preview/images/2x/printer.png diff --git a/chromium/chrome/browser/resources/print_preview/images/2x/printer_shared.png b/chromium/chrome/browser/resources/print_preview/images/2x/printer_shared.png Binary files differindex bbddfd04d2c..f27e672f7b9 100644 --- a/chromium/chrome/browser/resources/print_preview/images/2x/printer_shared.png +++ b/chromium/chrome/browser/resources/print_preview/images/2x/printer_shared.png diff --git a/chromium/chrome/browser/resources/print_preview/images/third_party.png b/chromium/chrome/browser/resources/print_preview/images/third_party.png Binary files differdeleted file mode 100644 index d15552d390c..00000000000 --- a/chromium/chrome/browser/resources/print_preview/images/third_party.png +++ /dev/null diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.html b/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.html index 554ba1337a4..5ecb91c14e2 100644 --- a/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.html @@ -1,7 +1,11 @@ <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="../data/destination.html"> +<link rel="import" href="advanced_settings_dialog.html"> <link rel="import" href="button_css.html"> <link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="settings_behavior.html"> <link rel="import" href="settings_section.html"> <dom-module id="print-preview-advanced-options-settings"> @@ -15,9 +19,16 @@ <print-preview-settings-section> <span slot="title">$i18n{advancedOptionsLabel}</span> <div slot="controls"> - <button>$i18n{showAdvancedOptions}</button> + <button disabled$="[[disabled]]" on-click="onButtonClick_"> + $i18n{showAdvancedOptions} + </button> </div> </print-preview-settings-section> + <template is="cr-lazy-render" id="advancedDialog"> + <print-preview-advanced-dialog + settings="{{settings}}" destination="[[destination]]"> + </print-preview-advanced-dialog> + </template> </template> <script src="advanced_options_settings.js"></script> </dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.js b/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.js index 6df05eb6896..b47ab004d75 100644 --- a/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_options_settings.js @@ -4,4 +4,23 @@ Polymer({ is: 'print-preview-advanced-options-settings', + + behaviors: [SettingsBehavior], + + properties: { + disabled: Boolean, + + /** @type {!print_preview.Destination} */ + destination: Object, + }, + + /** @private */ + onButtonClick_: function() { + const dialog = this.$.advancedDialog.get(); + // This async() call is a workaround to prevent a DCHECK - see + // https://crbug.com/804047. + this.async(() => { + dialog.show(); + }, 1); + }, }); diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.html b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.html new file mode 100644 index 00000000000..91e920b5383 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.html @@ -0,0 +1,47 @@ +<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/hidden_style_css.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="../data/destination.html"> +<link rel="import" href="advanced_settings_item.html"> +<link rel="import" href="settings_behavior.html"> +<link rel="import" href="button_css.html"> +<link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="search_dialog_css.html"> + +<dom-module id="print-preview-advanced-dialog"> + <style include="print-preview-shared search-dialog button cr-hidden-style"> + </style> + <template> + <dialog is="cr-dialog" id="dialog" on-close="onCloseOrCancel_"> + <div slot="title"> + <div>[[i18n('advancedSettingsDialogTitle', destination.displayName)]] + </div> + <print-preview-search-box id="searchBox" + label="$i18n{advancedSettingsSearchBoxPlaceholder}" + search-query="{{searchQuery_}}"> + </print-preview-search-box> + </div> + <div slot="body"> + <template is="dom-repeat" + items="[[destination.capabilities.printer.vendor_capability]]"> + <print-preview-advanced-settings-item capability="[[item]]" + settings="[[settings]]"> + </print-preview-advanced-settings-item> + </template> + <div class="no-settings-match-hint" + hidden$="[[!shouldShowHint_(hasMatching_)]]"> + $i18n{noAdvancedSettingsMatchSearchHint} + </div> + </div> + <div slot="button-container"> + <button on-click="onCancelButtonClick_">$i18n{cancel}</button> + <button on-click="onApplyButtonClick_"> + $i18n{advancedSettingsDialogConfirm} + </button> + </div> + </dialog> + </template> + <script src="advanced_settings_dialog.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.js b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.js new file mode 100644 index 00000000000..989de5b0285 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_dialog.js @@ -0,0 +1,82 @@ +// 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: 'print-preview-advanced-dialog', + + behaviors: [SettingsBehavior, I18nBehavior], + + properties: { + /** @type {!print_preview.Destination} */ + destination: Object, + + /** @private {?RegExp} */ + searchQuery_: { + type: Object, + value: null, + }, + + /** @private {boolean} */ + hasMatching_: { + type: Boolean, + notify: true, + computed: 'computeHasMatching_(searchQuery_)', + }, + }, + + /** + * @return {boolean} Whether there is a setting matching the query. + * @private + */ + computeHasMatching_: function() { + const listItems = this.shadowRoot.querySelectorAll( + 'print-preview-advanced-settings-item'); + let hasMatch = false; + listItems.forEach(item => { + const matches = item.hasMatch(this.searchQuery_); + item.hidden = !matches; + hasMatch = hasMatch || matches; + item.updateHighlighting(this.searchQuery_); + }); + return hasMatch; + }, + + /** + * @return {boolean} Whether the no matching settings hint should be shown. + * @private + */ + shouldShowHint_: function() { + return !!this.searchQuery_ && !this.hasMatching_; + }, + + /** @private */ + onCloseOrCancel_: function() { + if (this.searchQuery_) + this.$.searchBox.setValue(''); + }, + + /** @private */ + onCancelButtonClick_: function() { + this.$.dialog.cancel(); + }, + + /** @private */ + onApplyButtonClick_: function() { + const settingsValues = {}; + this.shadowRoot.querySelectorAll('print-preview-advanced-settings-item') + .forEach(item => { + settingsValues[item.capability.id] = item.getCurrentValue(); + }); + this.setSetting('vendorItems', settingsValues); + this.$.dialog.close(); + }, + + show: function() { + this.$.dialog.showModal(); + }, + + close: function() { + this.$.dialog.close(); + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.html b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.html new file mode 100644 index 00000000000..af531c738fa --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.html @@ -0,0 +1,68 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/search_highlight_style_css.html"> +<link rel="import" href="../print_preview_utils.html"> +<link rel="import" href="../data/destination.html"> +<link rel="import" href="highlight_utils.html"> +<link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="select_css.html"> +<link rel="import" href="settings_behavior.html"> + +<dom-module id="print-preview-advanced-settings-item"> + <style include="print-preview-shared select search-highlight-style"> + :host { + display: flex; + position: relative; + } + + :host > * { + overflow: hidden; + padding-bottom: 15px; + padding-top: 10px; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + } + + :host .label { + -webkit-padding-end: 20px; + display: flex; + flex-direction: column; + justify-content: center; + width: 250px; + } + + :host .value { + width: 175px; + } + + :host input { + height: 28px; + line-height: 24px; + width: 175px; + } + </style> + <template> + <label class="label searchable">[[getDisplayName_(capability)]]</label> + <div class="value"> + <template is="dom-if" if="[[isCapabilityTypeSelect_(capability)]]" + restamp> + <div> + <select on-change="onUserInput_"> + <template is="dom-repeat" items="[[capability.select_cap.option]]"> + <option class="searchable" text="[[getDisplayName_(item)]]" + value="[[item.value]]" + selected="[[isOptionSelected_(item, currentValue_)]]"> + </option> + </template> + </select> + </div> + </template> + <span hidden$="[[isCapabilityTypeSelect_(capability)]]"> + <input type="text" on-input="onUserInput_" + placeholder="[[getCapabilityPlaceholder_(capability)]]"> + </span> + </div> + </template> + <script src="advanced_settings_item.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.js b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.js new file mode 100644 index 00000000000..3de026d756b --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/advanced_settings_item.js @@ -0,0 +1,154 @@ +// 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: 'print-preview-advanced-settings-item', + + behaviors: [SettingsBehavior], + + properties: { + /** @type {!print_preview.VendorCapability} */ + capability: Object, + + /** @private {(number | string | boolean)} */ + currentValue_: { + type: Object, + value: null, + }, + }, + + observers: [ + 'updateFromSettings_(capability, settings.vendorItems.value)', + ], + + /** @private {boolean} */ + highlighted_: false, + + /** @private */ + updateFromSettings_: function() { + const settings = this.getSetting('vendorItems').value; + + // The settings may not have a property with the id if they were populated + // from sticky settings from a different destination or if the + // destination's capabilities changed since the sticky settings were + // generated. + if (!settings.hasOwnProperty(this.capability.id)) + return; + + const value = settings[this.capability.id]; + if (this.isCapabilityTypeSelect_()) { + // Ignore a value that can't be selected. + if (this.hasOptionWithValue_(value)) + this.currentValue_ = value; + } else { + this.currentValue_ = value; + this.$$('input[type="text"]').value = this.currentValue_; + } + }, + + /** + * @param {!print_preview.VendorCapability | + * !print_preview.VendorCapabilitySelectOption} item + * @return {string} The display name for the setting. + * @private + */ + getDisplayName_: function(item) { + let displayName = item.display_name; + if (!displayName && item.display_name_localized) + displayName = getStringForCurrentLocale(item.display_name_localized); + return displayName || ''; + }, + + /** + * @return {boolean} Whether the capability represented by this item is + * of type select. + * @private + */ + isCapabilityTypeSelect_: function() { + return this.capability.type == 'SELECT'; + }, + + /** + * @param {!print_preview.VendorCapabilitySelectOption} option The option + * for a select capability. + * @return {boolean} Whether the option is selected. + * @private + */ + isOptionSelected_: function(option) { + return (this.currentValue_ !== null && + option.value === this.currentValue_) || + (this.currentValue_ == null && !!option.is_default); + }, + + /** + * @return {string} The placeholder value for the capability's text input. + * @private + */ + getCapabilityPlaceholder_: function() { + if (this.capability.type == 'TYPED_VALUE' && + this.capability.typed_value_cap && + this.capability.typed_value_cap.default != undefined) { + return this.capability.typed_value_cap.default.toString() || ''; + } + if (this.capability.type == 'RANGE' && this.capability.range_cap && + this.capability.range_cap.default != undefined) + return this.capability.range_cap.default.toString() || ''; + return ''; + }, + + /** + * @return {boolean} + * @private + */ + hasOptionWithValue_: function(value) { + return !!this.capability.select_cap && + !!this.capability.select_cap.option && + this.capability.select_cap.option.some(option => option.value == value); + }, + + /** + * @param {?RegExp} query The current search query. + * @return {boolean} Whether the item has a match for the query. + */ + hasMatch: function(query) { + if (!query || this.getDisplayName_(this.capability).match(query)) + return true; + + if (!this.isCapabilityTypeSelect_()) + return false; + + for (let option of + /** @type {!Array<!print_preview.VendorCapabilitySelectOption>} */ ( + this.capability.select_cap.option)) { + if (this.getDisplayName_(option).match(query)) + return true; + } + return false; + }, + + /** + * @param {!Event} e Event containing the new value. + * @private + */ + onUserInput_: function(e) { + this.currentValue_ = e.target.value; + }, + + /** + * @return {(number | string | boolean)} The current value of the setting. + */ + getCurrentValue: function() { + return this.currentValue_; + }, + + /** + * @param {?RegExp} query The current search query. + * @return {boolean} Whether the current query is a match for this item. + */ + updateHighlighting: function(query) { + this.highlighted_ = + print_preview.updateHighlights(this, query, this.highlighted_); + return this.highlighted_ || !query; + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/new/app.html b/chromium/chrome/browser/resources/print_preview/new/app.html index fd29781eb45..dbfe5590f85 100644 --- a/chromium/chrome/browser/resources/print_preview/new/app.html +++ b/chromium/chrome/browser/resources/print_preview/new/app.html @@ -2,6 +2,7 @@ <link rel="import" href="chrome://resources/html/event_tracker.html"> <link rel="import" href="chrome://resources/html/webui_listener_tracker.html"> +<link rel="import" href="../cloud_print_interface.html"> <link rel="import" href="../native_layer.html"> <link rel="import" href="../data/destination.html"> <link rel="import" href="../data/destination_store.html"> @@ -9,6 +10,7 @@ <link rel="import" href="../data/measurement_system.html"> <link rel="import" href="../data/user_info.html"> <link rel="import" href="settings_behavior.html"> +<link rel="import" href="state.html"> <link rel="import" href="model.html"> <link rel="import" href="header.html"> <link rel="import" href="preview_area.html"> @@ -48,64 +50,82 @@ overflow: auto; } - #preview-area { + #preview-area-container { -webkit-border-start: 1px solid #dcdcdc; align-items: center; background-color: #e6e6e6; flex: 1; } </style> + <print-preview-state id="state" state="{{state}}"></print-preview-state> <print-preview-model id="model" settings="{{settings}}" destination="{{destination_}}" document-info="{{documentInfo_}}" recent-destinations="{{recentDestinations_}}" on-save-sticky-settings="onSaveStickySettings_"> </print-preview-model> - <div id="sidebar"> - <print-preview-header destination="[[destination_]]" state="{{state_}}" - settings="[[settings]]"></print-preview-header> + <div id="sidebar" on-setting-valid-changed="onSettingValidChanged_"> + <print-preview-header destination="[[destination_]]" state="[[state]]" + error-message="[[errorMessage_]]" settings="[[settings]]" + on-print-requested="onPrintRequested_" + on-cancel-requested="onCancelRequested_"> + </print-preview-header> <div id="settings-sections"> - <print-preview-destination-settings destination="[[destination_]]"> + <print-preview-destination-settings id="destinationSettings" + destination="[[destination_]]" + destination-store="[[destinationStore_]]" + disabled="[[controlsDisabled_]]" state="[[state]]" + recent-destinations="[[recentDestinations_]]" + user-info="{{userInfo_}}"> </print-preview-destination-settings> <print-preview-pages-settings settings="{{settings}}" - document-info="[[documentInfo_]]" + document-info="[[documentInfo_]]" disabled="[[controlsDisabled_]]" hidden$="[[!settings.pages.available]]"> </print-preview-pages-settings> <print-preview-copies-settings settings="{{settings}}" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.copies.available]]"> </print-preview-copies-settings> <print-preview-layout-settings settings="{{settings}}" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.layout.available]]"> </print-preview-layout-settings> <print-preview-color-settings settings="{{settings}}" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.color.available]]"> </print-preview-color-settings> <print-preview-media-size-settings settings="{{settings}}" capability="[[destination_.capabilities.printer.media_size]]" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.mediaSize.available]]"> </print-preview-media-size-settings> <print-preview-margins-settings settings="{{settings}}" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.margins.available]]"> </print-preview-margins-settings> <print-preview-dpi-settings settings="{{settings}}" capability="[[destination_.capabilities.printer.dpi]]" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.dpi.available]]"> </print-preview-dpi-settings> <print-preview-scaling-settings settings="{{settings}}" - document-info="[[documentInfo_]]" + document-info="[[documentInfo_]]" disabled="[[controlsDisabled_]]" hidden$="[[!settings.scaling.available]]"> </print-preview-scaling-settings> <print-preview-other-options-settings settings="{{settings}}" + disabled="[[controlsDisabled_]]" hidden$="[[!settings.otherOptions.available]]"> </print-preview-other-options-settings> <print-preview-advanced-options-settings settings="{{settings}}" + destination="[[destination_]]" disabled="[[controlsDisabled_]]" hidden$="[[!settings.vendorItems.available]]"> </print-preview-advanced-options-settings> </div> </div> - <div id="preview-area"> - <print-preview-preview-area settings="{{settings}}" + <div id="preview-area-container"> + <print-preview-preview-area id="previewArea" settings="{{settings}}" destination="[[destination_]]" document-info="{{documentInfo_}}" - state="{{state_}}"> + state="[[state]]" on-preview-failed="onPreviewFailed_" + on-preview-loaded="onPreviewLoaded_"> </print-preview-preview-area> </div> </template> diff --git a/chromium/chrome/browser/resources/print_preview/new/app.js b/chromium/chrome/browser/resources/print_preview/new/app.js index c45886a2ec6..0e4dcebab1d 100644 --- a/chromium/chrome/browser/resources/print_preview/new/app.js +++ b/chromium/chrome/browser/resources/print_preview/new/app.js @@ -17,14 +17,21 @@ Polymer({ notify: true, }, - /** @private {print_preview.DocumentInfo} */ - documentInfo_: { + /** @private {print_preview.Destination} */ + destination_: { type: Object, notify: true, }, - /** @private {print_preview.Destination} */ - destination_: { + /** @private {?print_preview.DestinationStore} */ + destinationStore_: { + type: Object, + notify: true, + value: null, + }, + + /** @private {print_preview.DocumentInfo} */ + documentInfo_: { type: Object, notify: true, }, @@ -35,40 +42,55 @@ Polymer({ notify: true, }, - /** @private {!print_preview_new.State} */ - state_: { + /** @type {!print_preview_new.State} */ + state: { + type: Number, + observer: 'onStateChanged_', + }, + + /** @private {?print_preview.UserInfo} */ + userInfo_: { type: Object, notify: true, - value: { - previewLoading: false, - previewFailed: false, - cloudPrintError: '', - privetExtensionError: '', - invalidSettings: false, - initialized: false, - cancelled: false, - }, + value: null, }, - }, - /** @private {?print_preview.NativeLayer} */ - nativeLayer_: null, + /** @private {string} */ + errorMessage_: { + type: String, + notify: true, + value: '', + }, - /** @private {?print_preview.UserInfo} */ - userInfo_: null, + /** @private {boolean} */ + controlsDisabled_: { + type: Boolean, + notify: true, + computed: 'computeControlsDisabled_(state)', + } + }, /** @private {?WebUIListenerTracker} */ listenerTracker_: null, - /** @private {?print_preview.DestinationStore} */ - destinationStore_: null, + /** @type {!print_preview.MeasurementSystem} */ + measurementSystem_: new print_preview.MeasurementSystem( + ',', '.', print_preview.MeasurementSystemUnitType.IMPERIAL), + + /** @private {?print_preview.NativeLayer} */ + nativeLayer_: null, + + /** @private {?cloudprint.CloudPrintInterface} */ + cloudPrintInterface_: null, /** @private {!EventTracker} */ tracker_: new EventTracker(), - /** @type {!print_preview.MeasurementSystem} */ - measurementSystem_: new print_preview.MeasurementSystem( - ',', '.', print_preview.MeasurementSystemUnitType.IMPERIAL), + /** @private {boolean} */ + cancelled_: false, + + /** @private {boolean} */ + isInAppKioskMode_: false, /** @override */ attached: function() { @@ -76,6 +98,8 @@ Polymer({ this.documentInfo_ = new print_preview.DocumentInfo(); this.userInfo_ = new print_preview.UserInfo(); this.listenerTracker_ = new WebUIListenerTracker(); + this.listenerTracker_.add( + 'use-cloud-print', this.onCloudPrintEnable_.bind(this)); this.destinationStore_ = new print_preview.DestinationStore( this.userInfo_, this.listenerTracker_); this.tracker_.add( @@ -98,6 +122,14 @@ Polymer({ }, /** + * @return {boolean} Whether the controls should be disabled. + * @private + */ + computeControlsDisabled_: function() { + return this.state != print_preview_new.State.READY; + }, + + /** * @param {!print_preview.NativeInitialSettings} settings * @private */ @@ -109,7 +141,7 @@ Polymer({ this.notifyPath('documentInfo_.hasSelection'); this.notifyPath('documentInfo_.title'); this.notifyPath('documentInfo_.pageCount'); - this.$.model.updateFromStickySettings(settings.serializedAppStateStr); + this.$.model.setStickySettings(settings.serializedAppStateStr); this.measurementSystem_.setSystem( settings.thousandsDelimeter, settings.decimalDelimeter, settings.unitType); @@ -120,9 +152,42 @@ Polymer({ this.recentDestinations_); }, + /** + * Called when Google Cloud Print integration is enabled by the + * PrintPreviewHandler. + * Fetches the user's cloud printers. + * @param {string} cloudPrintUrl The URL to use for cloud print servers. + * @param {boolean} appKioskMode Whether to print automatically for kiosk + * mode. + * @private + */ + onCloudPrintEnable_: function(cloudPrintUrl, appKioskMode) { + assert(!this.cloudPrintInterface_); + this.cloudPrintInterface_ = new cloudprint.CloudPrintInterface( + cloudPrintUrl, assert(this.nativeLayer_), assert(this.userInfo_), + appKioskMode); + this.tracker_.add( + assert(this.cloudPrintInterface_), + cloudprint.CloudPrintInterfaceEventType.SUBMIT_DONE, + this.close_.bind(this)); + [cloudprint.CloudPrintInterfaceEventType.SEARCH_FAILED, + cloudprint.CloudPrintInterfaceEventType.SUBMIT_FAILED, + cloudprint.CloudPrintInterfaceEventType.PRINTER_FAILED, + ].forEach(eventType => { + this.tracker_.add( + assert(this.cloudPrintInterface_), eventType, + this.onCloudPrintError_.bind(this)); + }); + + this.destinationStore_.setCloudPrintInterface(this.cloudPrintInterface_); + if (this.$.destinationSettings.isDialogOpen()) + this.destinationStore_.startLoadCloudDestinations(); + }, + /** @private */ onDestinationSelect_: function() { this.destination_ = this.destinationStore_.selectedDestination; + this.$.state.transitTo(print_preview_new.State.NOT_READY); }, /** @private */ @@ -130,16 +195,10 @@ Polymer({ this.set( 'destination_.capabilities', this.destinationStore_.selectedDestination.capabilities); - if (!this.state_.initialized) - this.set('state_.initialized', true); - }, - - /** @private */ - onPreviewCancelled_: function() { - if (!this.state_.cancelled) - return; - this.detached(); - this.nativeLayer_.dialogClose(true); + if (this.state != print_preview_new.State.READY) + this.$.state.transitTo(print_preview_new.State.READY); + if (!this.$.model.initialized()) + this.$.model.applyStickySettings(); }, /** @@ -149,4 +208,128 @@ Polymer({ onSaveStickySettings_: function(e) { this.nativeLayer_.saveAppState(/** @type {string} */ (e.detail)); }, + + /** @private */ + onStateChanged_: function() { + if (this.state == print_preview_new.State.CLOSING) { + this.remove(); + this.nativeLayer_.dialogClose(this.cancelled_); + } else if (this.state == print_preview_new.State.HIDDEN) { + this.nativeLayer_.hidePreview(); + } else if (this.state == print_preview_new.State.PRINTING) { + const destination = assert(this.destinationStore_.selectedDestination); + const whenPrintDone = + this.nativeLayer_.print(this.$.model.createPrintTicket(destination)); + if (destination.isLocal) { + const onError = destination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF ? + this.onFileSelectionCancel_.bind(this) : + this.onPrintFailed_.bind(this); + whenPrintDone.then(this.close_.bind(this), onError); + } else { + // Cloud print resolves when print data is returned to submit to cloud + // print, or if print ticket cannot be read, no PDF data is found, or + // PDF is oversized. + whenPrintDone.then( + this.onPrintToCloud_.bind(this), this.onPrintFailed_.bind(this)); + } + } + }, + + /** @private */ + onPreviewLoaded_: function() { + if (this.state == print_preview_new.State.HIDDEN) + this.$.state.transitTo(print_preview_new.State.PRINTING); + }, + + /** @private */ + onPrintRequested_: function() { + this.$.state.transitTo( + this.$.previewArea.previewLoaded() ? print_preview_new.State.PRINTING : + print_preview_new.State.HIDDEN); + }, + + /** @private */ + onCancelRequested_: function() { + this.cancelled_ = true; + this.$.state.transitTo(print_preview_new.State.CLOSING); + }, + + /** + * @param {!CustomEvent} e The event containing the new validity. + * @private + */ + onSettingValidChanged_: function(e) { + this.$.state.transitTo( + /** @type {boolean} */ (e.detail) ? + print_preview_new.State.READY : + print_preview_new.State.INVALID_TICKET); + }, + + /** @private */ + onFileSelectionCancel_: function() { + this.$.state.transitTo(print_preview_new.State.READY); + }, + + /** + * Called when the native layer has retrieved the data to print to Google + * Cloud Print. + * @param {string} data The body to send in the HTTP request. + * @private + */ + onPrintToCloud_: function(data) { + assert( + this.cloudPrintInterface_ != null, 'Google Cloud Print is not enabled'); + const destination = assert(this.destinationStore_.selectedDestination); + this.cloudPrintInterface_.submit( + destination, this.$.model.createCloudJobTicket(destination), + assert(this.documentInfo_), data); + }, + + /** + * Called when printing to a privet, cloud, or extension printer fails. + * @param {*} httpError The HTTP error code, or -1 or a string describing + * the error, if not an HTTP error. + * @private + */ + onPrintFailed_: function(httpError) { + console.error('Printing failed with error code ' + httpError); + this.errorMessage_ = httpError.toString(); + this.$.state.transitTo(print_preview_new.State.FATAL_ERROR); + }, + + /** @private */ + onPreviewFailed_: function() { + this.$.state.transitTo(print_preview_new.State.FATAL_ERROR); + }, + + /** + * Called when there was an error communicating with Google Cloud print. + * Displays an error message in the print header. + * @param {!Event} event Contains the error message. + * @private + */ + onCloudPrintError_: function(event) { + if (event.status == 0) { + return; // Ignore, the system does not have internet connectivity. + } + if (event.status == 403) { + if (!this.isInAppKioskMode_) { + this.$.destinationSettings.showCloudPrintPromo(); + } + } else { + this.set('state_.cloudPrintError', event.message); + } + if (event.status == 200) { + console.error( + `Google Cloud Print Error: (${event.errorCode}) ${event.message}`); + } else { + console.error(`Google Cloud Print Error: HTTP status ${event.status}`); + } + }, + + /** @private */ + close_: function() { + this.$.state.transitTo(print_preview_new.State.CLOSING); + }, }); diff --git a/chromium/chrome/browser/resources/print_preview/new/button_css.html b/chromium/chrome/browser/resources/print_preview/new/button_css.html index d33555445a4..77481fa255a 100644 --- a/chromium/chrome/browser/resources/print_preview/new/button_css.html +++ b/chromium/chrome/browser/resources/print_preview/new/button_css.html @@ -13,13 +13,13 @@ <if expr="not is_ios"> button:enabled:hover { background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); - @apply(--print-preview-hover); + @apply --print-preview-hover; } </if> button:enabled:active { background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); - @apply(--print-preview-active); + @apply --print-preview-active; } </style> </template> diff --git a/chromium/chrome/browser/resources/print_preview/new/checkbox_radio_css.html b/chromium/chrome/browser/resources/print_preview/new/checkbox_radio_css.html index f2564079cbd..159ad3c3dbb 100644 --- a/chromium/chrome/browser/resources/print_preview/new/checkbox_radio_css.html +++ b/chromium/chrome/browser/resources/print_preview/new/checkbox_radio_css.html @@ -48,14 +48,14 @@ [type='radio']) { background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); - @apply(--print-preview-hover); + @apply --print-preview-hover; } </if> input:enabled:active:-webkit-any([type='checkbox'], [type='radio']) { background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); - @apply(--print-preview-active); + @apply --print-preview-active; } input:disabled:-webkit-any([type='checkbox'], diff --git a/chromium/chrome/browser/resources/print_preview/new/color_settings.html b/chromium/chrome/browser/resources/print_preview/new/color_settings.html index b95459a0f05..88adf84c90c 100644 --- a/chromium/chrome/browser/resources/print_preview/new/color_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/color_settings.html @@ -11,7 +11,8 @@ <print-preview-settings-section> <span id="color-label" slot="title">$i18n{optionColor}</span> <div slot="controls"> - <select aria-labelledby="color-label" on-change="onChange_"> + <select aria-labelledby="color-label" on-change="onChange_" + disabled$="[[disabled]]"> <option value="bw" selected>$i18n{optionBw}</option> <option value="color">$i18n{optionColor}</option> </select> diff --git a/chromium/chrome/browser/resources/print_preview/new/color_settings.js b/chromium/chrome/browser/resources/print_preview/new/color_settings.js index 075141f2a01..4c8631aa5ec 100644 --- a/chromium/chrome/browser/resources/print_preview/new/color_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/color_settings.js @@ -7,6 +7,10 @@ Polymer({ behaviors: [SettingsBehavior], + properties: { + disabled: Boolean, + }, + observers: ['onColorSettingChange_(settings.color.value)'], /** diff --git a/chromium/chrome/browser/resources/print_preview/new/compiled_resources2.gyp b/chromium/chrome/browser/resources/print_preview/new/compiled_resources2.gyp index 0441a3c1e62..e75ada55312 100644 --- a/chromium/chrome/browser/resources/print_preview/new/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/print_preview/new/compiled_resources2.gyp @@ -21,6 +21,7 @@ 'preview_area', 'model', 'state', + '../compiled_resources2.gyp:cloud_print_interface', '../compiled_resources2.gyp:native_layer', '../data/compiled_resources2.gyp:destination', '../data/compiled_resources2.gyp:destination_store', @@ -39,6 +40,7 @@ 'model', 'settings_behavior', 'state', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], @@ -47,6 +49,11 @@ 'target_name': 'destination_settings', 'dependencies': [ '../data/compiled_resources2.gyp:destination', + '../data/compiled_resources2.gyp:destination_store', + '../data/compiled_resources2.gyp:user_info', + 'destination_dialog', + 'state', + '<(DEPTH)/ui/webui/resources/cr_elements/cr_lazy_render/compiled_resources2.gyp:cr_lazy_render', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, @@ -55,7 +62,6 @@ 'dependencies': [ 'settings_behavior', '../data/compiled_resources2.gyp:document_info', - '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], @@ -126,6 +132,9 @@ { 'target_name': 'advanced_options_settings', 'dependencies': [ + '../data/compiled_resources2.gyp:destination', + 'advanced_settings_dialog', + 'settings_behavior', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, @@ -156,6 +165,7 @@ 'target_name': 'preview_area', 'dependencies': [ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:web_ui_listener_behavior', '../../pdf/compiled_resources2.gyp:pdf_scripting_api', '../compiled_resources2.gyp:native_layer', @@ -166,12 +176,80 @@ '../data/compiled_resources2.gyp:size', '../data/compiled_resources2.gyp:margins', '../data/compiled_resources2.gyp:printable_area', + 'model', 'settings_behavior', 'state', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { + 'target_name': 'destination_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + '../data/compiled_resources2.gyp:destination', + '../data/compiled_resources2.gyp:destination_store', + '../data/compiled_resources2.gyp:user_info', + 'destination_list', + 'print_preview_search_box', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'destination_list', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '../compiled_resources2.gyp:native_layer', + '../data/compiled_resources2.gyp:destination', + 'destination_list_item', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'destination_list_item', + 'dependencies': [ + 'highlight_utils', + '../data/compiled_resources2.gyp:destination', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'advanced_settings_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '../data/compiled_resources2.gyp:destination', + 'advanced_settings_item', + 'print_preview_search_box', + 'settings_behavior', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'advanced_settings_item', + 'dependencies': [ + 'highlight_utils', + '../compiled_resources2.gyp:print_preview_utils', + '../data/compiled_resources2.gyp:destination', + 'settings_behavior', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'print_preview_search_box', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_search_field/compiled_resources2.gyp:cr_search_field_behavior', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'highlight_utils', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:search_highlight_utils', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { 'target_name': 'model', 'dependencies': [ 'settings_behavior', diff --git a/chromium/chrome/browser/resources/print_preview/new/copies_settings.html b/chromium/chrome/browser/resources/print_preview/new/copies_settings.html index f1168f6f5d8..f64b68121a0 100644 --- a/chromium/chrome/browser/resources/print_preview/new/copies_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/copies_settings.html @@ -11,12 +11,13 @@ </style> <print-preview-number-settings-section max-value="999" min-value=1 default-value="1" input-label="$i18n{copiesLabel}" - input-string="{{inputString_}}" input-valid="{{inputValid_}}" - hint-message="$i18n{copiesInstruction}"> + disabled="[[disabled]]" current-value="{{currentValue_}}" + input-valid="{{inputValid_}}" hint-message="$i18n{copiesInstruction}"> <div slot="opt-inside-content" class="checkbox" aria-live="polite" - hidden$="[[collateHidden_(inputString_, inputValid_)]]"> + hidden$="[[collateHidden_(currentValue_, inputValid_)]]"> <label> <input id="collate" type="checkbox" on-change="onCollateChange_" + disabled$="[[getDisabled(state)]]" aria-labelledby="copies-collate-label"> <span id="copies-collate-label">$i18n{optionCollate}</span> </label> diff --git a/chromium/chrome/browser/resources/print_preview/new/copies_settings.js b/chromium/chrome/browser/resources/print_preview/new/copies_settings.js index 826c5de2217..1115ac4c99f 100644 --- a/chromium/chrome/browser/resources/print_preview/new/copies_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/copies_settings.js @@ -9,17 +9,19 @@ Polymer({ properties: { /** @private {string} */ - inputString_: String, + currentValue_: String, /** @private {boolean} */ inputValid_: Boolean, + + disabled: Boolean, }, /** @private {boolean} */ isInitialized_: false, observers: [ - 'onInputChanged_(inputString_, inputValid_)', + 'onInputChanged_(currentValue_, inputValid_)', 'onInitialized_(settings.copies.value, settings.collate.value)' ], @@ -32,7 +34,7 @@ Polymer({ return; this.isInitialized_ = true; const copies = this.getSetting('copies'); - this.inputString_ = /** @type {string} */ (copies.value.toString()); + this.currentValue_ = /** @type {string} */ (copies.value.toString()); const collate = this.getSetting('collate'); this.$.collate.checked = /** @type {boolean} */ (collate.value); }, @@ -44,7 +46,7 @@ Polymer({ */ onInputChanged_: function() { this.setSetting( - 'copies', this.inputValid_ ? parseInt(this.inputString_, 10) : 1); + 'copies', this.inputValid_ ? parseInt(this.currentValue_, 10) : 1); this.setSettingValid('copies', this.inputValid_); }, @@ -53,7 +55,7 @@ Polymer({ * @private */ collateHidden_: function() { - return !this.inputValid_ || parseInt(this.inputString_, 10) == 1; + return !this.inputValid_ || parseInt(this.currentValue_, 10) == 1; }, /** @private */ diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_dialog.html b/chromium/chrome/browser/resources/print_preview/new/destination_dialog.html new file mode 100644 index 00000000000..540227ae57d --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_dialog.html @@ -0,0 +1,132 @@ +<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/hidden_style_css.html"> +<link rel="import" href="chrome://resources/html/action_link_css.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="../data/destination.html"> +<link rel="import" href="../data/destination_store.html"> +<link rel="import" href="button_css.html"> +<link rel="import" href="destination_list.html"> +<link rel="import" href="print_preview_search_box.html"> +<link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="search_dialog_css.html"> +<link rel="import" href="select_css.html"> + +<dom-module id="print-preview-destination-dialog"> + <template> + <style include="print-preview-shared button action-link select cr-hidden-style search-dialog"> + :host #dialog { + width: 640px; + } + + :host .user-info { + font-size: calc(13/15 * 1em); + margin-top: 14px; + } + + :host .user-info .account-select-label { + -webkit-padding-end: 18px; + } + + :host .user-info .account-select { + width: auto + } + + :host #dialog .cloudprint-promo { + align-items: center; + background-color: #f5f5f5; + border-color: #e7e7e7; + border-top-style: solid; + border-width: 1px; + color: #888; + display: flex; + padding: 14px 17px; + } + + :host .cloudprint-promo .promo-text { + flex: 1; + } + + :host .cloudprint-promo .icon { + -webkit-margin-end: 12px; + display: block; + height: 24px; + width: 24px; + } + + :host .cloudprint-promo .close-button { + -webkit-margin-start: 12px; + background-image: -webkit-image-set( + url(chrome://theme/IDR_CLOSE_DIALOG) 1x, + url(chrome://theme/IDR_CLOSE_DIALOG@2x) 2x); + background-repeat: no-repeat; + background-size: 14px; + height: 14px; + width: 14px; + } + + :host .cloudprint-promo .close-button:hover { + background-image: -webkit-image-set( + url(chrome://theme/IDR_CLOSE_DIALOG_H) 1x, + url(chrome://theme/IDR_CLOSE_DIALOG_H@2x) 2x); + } + + :host .cloudprint-promo .close-button:active { + background-image: -webkit-image-set( + url(chrome://theme/IDR_CLOSE_DIALOG_P) 1x, + url(chrome://theme/IDR_CLOSE_DIALOG_P@2x) 2x); + } + </style> + <dialog is="cr-dialog" id="dialog" on-close="onCloseOrCancel_"> + <div slot="title"> + <div>$i18n{destinationSearchTitle}</div> + <div class="user-info" hidden$="[[!userInfo.loggedIn]]"> + <label class="account-select-label" id="accountSelectLabel"> + $i18n{accountSelectTitle} + </label> + <select class="account-select" aria-labelledby="accountSelectLabel" + on-change="onUserChange_"> + <template is="dom-repeat" items="[[userInfo.users]]"> + <option selected="[[isSelected_(item, userInfo.activeUser)]]" + value="[[item]]"> + [[item]] + </option> + </template> + <option value="">$i18n{addAccountTitle}</option> + </select> + </div> + <print-preview-search-box id="searchBox" + label="$i18n{searchBoxPlaceholder}" search-query="{{searchQuery_}}"> + </print-preview-search-box> + </div> + <div slot="body" scrollable> + <print-preview-destination-list + destinations="[[recentDestinationList_]]" + search-query="[[searchQuery_]]" + title="$i18n{recentDestinationsTitle}" + on-destination-selected="onDestinationSelected_"> + </print-preview-destination-list> + <print-preview-destination-list destinations="[[destinations_]]" + has-action-link loading-destinations="[[loadingDestinations_]]" + search-query="[[searchQuery_]]" + title="$i18n{printDestinationsTitle}" + on-destination-selected="onDestinationSelected_"> + </print-preview-destination-list> + </div> + <div slot="button-container"> + <button class="cancel-button" on-click="onCancelButtonClick_"> + $i18n{cancel} + </button> + </div> + <div class="cloudprint-promo" slot="footer" + hidden$="[[!showCloudPrintPromo]]"> + <img src="../images/cloud.png" class="icon" alt=""> + <div class="promo-text"></div> + <div class="close-button"></div> + </div> + </dialog> + </template> + <script src="destination_dialog.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_dialog.js b/chromium/chrome/browser/resources/print_preview/new/destination_dialog.js new file mode 100644 index 00000000000..7e2162941c6 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_dialog.js @@ -0,0 +1,205 @@ +// 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: 'print-preview-destination-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @type {?print_preview.DestinationStore} */ + destinationStore: { + type: Object, + observer: 'onDestinationStoreSet_', + }, + + /** @type {!print_preview.UserInfo} */ + userInfo: { + type: Object, + notify: true, + }, + + /** @type {boolean} */ + showCloudPrintPromo: { + type: Boolean, + notify: true, + }, + + /** @private {!Array<!print_preview.Destination>} */ + destinations_: { + type: Array, + notify: true, + value: [], + }, + + /** @private {boolean} */ + loadingDestinations_: { + type: Boolean, + value: false, + }, + + /** @type {!Array<!print_preview.RecentDestination>} */ + recentDestinations: Array, + + /** @private {!Array<!print_preview.Destination>} */ + recentDestinationList_: { + type: Array, + notify: true, + computed: 'computeRecentDestinationList_(' + + 'destinationStore, recentDestinations, recentDestinations.*, ' + + 'userInfo, destinations_.*)', + }, + + /** @private {?RegExp} */ + searchQuery_: { + type: Object, + value: null, + }, + }, + + /** @private {!EventTracker} */ + tracker_: new EventTracker(), + + /** @override */ + ready: function() { + this.$$('.promo-text').innerHTML = + this.i18nAdvanced('cloudPrintPromotion', { + substitutions: ['<a is="action-link" class="sign-in">', '</a>'], + attrs: { + 'is': (node, v) => v == 'action-link', + 'class': (node, v) => v == 'sign-in', + }, + }); + }, + + /** @override */ + attached: function() { + this.tracker_.add( + assert(this.$$('.sign-in')), 'click', this.onSignInClick_.bind(this)); + this.tracker_.add( + assert(this.$$('.cloudprint-promo > .close-button')), 'click', + this.onCloudPrintPromoDismissed_.bind(this)); + }, + + /** @private */ + onDestinationStoreSet_: function() { + assert(this.destinations_.length == 0); + const destinationStore = assert(this.destinationStore); + this.tracker_.add( + destinationStore, + print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED, + this.updateDestinations_.bind(this)); + this.tracker_.add( + destinationStore, + print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE, + this.updateDestinations_.bind(this)); + }, + + /** @private */ + updateDestinations_: function() { + this.notifyPath('userInfo.users'); + this.notifyPath('userInfo.activeUser'); + this.notifyPath('userInfo.loggedIn'); + if (this.userInfo.loggedIn) + this.showCloudPrintPromo = false; + + this.destinations_ = this.userInfo ? + this.destinationStore.destinations(this.userInfo.activeUser) : + []; + this.loadingDestinations_ = + this.destinationStore.isPrintDestinationSearchInProgress; + }, + + /** + * @return {!Array<!print_preview.Destination>} + * @private + */ + computeRecentDestinationList_: function() { + let recentDestinations = []; + const filterAccount = this.userInfo.activeUser; + this.recentDestinations.forEach((recentDestination) => { + const destination = this.destinationStore.getDestination( + recentDestination.origin, recentDestination.id, + recentDestination.account || ''); + if (destination && + (!destination.account || destination.account == filterAccount)) { + recentDestinations.push(destination); + } + }); + return recentDestinations; + }, + + /** @private */ + onCloseOrCancel_: function() { + if (this.searchQuery_) + this.$.searchBox.setValue(''); + }, + + /** @private */ + onCancelButtonClick_: function() { + this.$.dialog.cancel(); + }, + + /** + * @param {!CustomEvent} e Event containing the selected destination. + * @private + */ + onDestinationSelected_: function(e) { + this.destinationStore.selectDestination( + /** @type {!print_preview.Destination} */ (e.detail)); + this.$.dialog.close(); + }, + + show: function() { + this.loadingDestinations_ = + this.destinationStore.isPrintDestinationSearchInProgress; + this.$.dialog.showModal(); + }, + + /** @return {boolean} Whether the dialog is open. */ + isOpen: function() { + return this.$.dialog.hasAttribute('open'); + }, + + /** @private */ + isSelected_: function(account) { + return account == this.userInfo.activeUser; + }, + + /** @private */ + onSignInClick_: function() { + print_preview.NativeLayer.getInstance().signIn(false).then(() => { + this.destinationStore.onDestinationsReload(); + }); + }, + + /** @private */ + onCloudPrintPromoDismissed_: function() { + this.showCloudPrintPromo = false; + }, + + /** @private */ + onUserChange_: function() { + const select = this.$$('select'); + const account = select.value; + if (account) { + this.showCloudPrintPromo = false; + this.userInfo.activeUser = account; + this.notifyPath('userInfo.activeUser'); + this.notifyPath('userInfo.loggedIn'); + this.destinationStore.reloadUserCookieBasedDestinations(); + } else { + print_preview.NativeLayer.getInstance().signIn(true).then( + this.destinationStore.onDestinationsReload.bind( + this.destinationStore)); + const options = select.querySelectorAll('option'); + for (let i = 0; i < options.length; i++) { + if (options[i].value == this.userInfo.activeUser) { + select.selectedIndex = i; + break; + } + } + } + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_list.html b/chromium/chrome/browser/resources/print_preview/new/destination_list.html new file mode 100644 index 00000000000..3a008a5df4d --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_list.html @@ -0,0 +1,102 @@ +<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/html/action_link_css.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="../native_layer.html"> +<link rel="import" href="../data/destination.html"> +<link rel="import" href="destination_list_item.html"> +<link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="throbber_css.html"> + +<dom-module id="print-preview-destination-list"> + <template> + <style include="print-preview-shared action-link cr-hidden-style throbber"> + :host { + padding: 0 14px 18px; + user-select: none; + } + + :host > header { + -webkit-padding-end: 19px; + -webkit-padding-start: 0; + background-color: transparent; + border-bottom: 1px solid #d2d2d2; + padding-bottom: 8px; + } + + :host :-webkit-any(.title, .action-link, .total) { + -webkit-padding-end: 8px; + -webkit-padding-start: 4px; + display: inline; + vertical-align: middle; + } + + :host .throbber-container { + -webkit-padding-end: 16px; + -webkit-padding-start: 8px; + display: inline-block; + position: relative; + vertical-align: middle; + } + + :host .throbber { + vertical-align: middle; + } + + :host .no-destinations-message { + -webkit-padding-start: 18px; + color: #999; + padding-bottom: 8px; + padding-top: 8px; + } + + :host .list-item { + -webkit-padding-end: 2px; + -webkit-padding-start: 18px; + cursor: default; + display: flex; + padding-bottom: 3px; + padding-top: 3px; + } + + :not(.moving).list-item { + transition: background-color 150ms; + } + + .list-item:hover, + .list-item:focus { + background-color: rgb(228, 236, 247); + } + + .list-item:focus { + outline: none; + } + </style> + <header> + <h4 class="title">[[title]]</h4> + <span class="total" hidden$="[[!showDestinationsTotal_]]"> + [[i18n('destinationCount', matchingDestinationsCount_)]] + </span> + <a is="action-link" class="action-link" hidden$="[[!hasActionLink]]" + on-click="onActionLinkClick_"> + $i18n{manage} + </a> + <div class="throbber-container" hidden$="[[!loadingDestinations]]"> + <div class="throbber"></div> + </div> + </header> + <template is="dom-repeat" items="[[destinations]]" notify-dom-change + on-dom-change="updateIfNeeded_"> + <print-preview-destination-list-item class="list-item" + search-query="[[searchQuery]]" destination="[[item]]" + on-click="onDestinationSelected_"> + </print-preview-destination-list-item> + </template> + <div class="no-destinations-message" hidden$="[[hasDestinations_]]"> + $i18n{noDestinationsMessage} + </div> + </template> + <script src="destination_list.js"></script> +</dom-module> + diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_list.js b/chromium/chrome/browser/resources/print_preview/new/destination_list.js new file mode 100644 index 00000000000..b83f9309149 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_list.js @@ -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. + +(function() { +'use strict'; + +Polymer({ + is: 'print-preview-destination-list', + + behaviors: [I18nBehavior], + + properties: { + /** @type {Array<!print_preview.Destination>} */ + destinations: { + type: Array, + observer: 'destinationsChanged_', + }, + + /** @type {boolean} */ + hasActionLink: { + type: Boolean, + value: false, + }, + + /** @type {boolean} */ + loadingDestinations: { + type: Boolean, + value: false, + }, + + /** @type {?RegExp} */ + searchQuery: { + type: Object, + observer: 'update_', + }, + + /** @type {boolean} */ + title: String, + + /** @private {number} */ + matchingDestinationsCount_: { + type: Number, + value: 0, + }, + + /** @private {boolean} */ + hasDestinations_: { + type: Boolean, + computed: 'computeHasDestinations_(matchingDestinationsCount_)', + }, + + /** @private {boolean} */ + showDestinationsTotal_: { + type: Boolean, + computed: 'computeShowDestinationsTotal_(matchingDestinationsCount_)', + }, + }, + + /** @private {boolean} */ + newDestinations_: false, + + /** + * @param {!Array<!print_preview.Destination>} current + * @param {?Array<!print_preview.Destination>} previous + * @private + */ + destinationsChanged_: function(current, previous) { + if (previous == undefined) { + this.matchingDestinationsCount_ = this.destinations.length; + } else { + this.newDestinations_ = true; + } + }, + + /** @private */ + updateIfNeeded_: function() { + if (!this.newDestinations_) + return; + this.newDestinations_ = false; + this.update_(); + }, + + /** @private */ + update_: function() { + if (!this.destinations) + return; + + const listItems = + this.shadowRoot.querySelectorAll('print-preview-destination-list-item'); + + let matchCount = 0; + listItems.forEach(item => { + item.hidden = + !!this.searchQuery && !item.destination.matches(this.searchQuery); + if (!item.hidden) { + matchCount++; + item.update(); + } + }); + + this.matchingDestinationsCount_ = + !this.searchQuery ? listItems.length : matchCount; + }, + + /** + * @return {boolean} + * @private + */ + computeHasDestinations_: function() { + return !this.destinations || this.matchingDestinationsCount_ > 0; + }, + + /** + * @return {boolean} + * @private + */ + computeShowDestinationsTotal_: function() { + return this.matchingDestinationsCount_ > 4; + }, + + /** @private */ + onActionLinkClick_: function() { + print_preview.NativeLayer.getInstance().managePrinters(); + }, + + /** + * @param {!Event} e Event containing the destination that was selected. + * @private + */ + onDestinationSelected_: function(e) { + this.fire( + 'destination-selected', + /** @type {PrintPreviewDestinationListItemElement} */ + (e.target).destination); + }, +}); +})(); diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_list_item.html b/chromium/chrome/browser/resources/print_preview/new/destination_list_item.html new file mode 100644 index 00000000000..ad4503b6c68 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_list_item.html @@ -0,0 +1,136 @@ +<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="../native_layer.html"> +<link rel="import" href="../data/destination.html"> +<link rel="import" href="highlight_utils.html"> +<link rel="import" href="print_preview_shared_css.html"> + +<dom-module id="print-preview-destination-list-item"> + <template> + <style include="print-preview-shared action-link cr-hidden-style"> + :host .icon { + -webkit-margin-end: 8px; + display: inline-block; + flex: 0 0 auto; + height: 24px; + transition: opacity 150ms; + vertical-align: middle; + width: 24px; + } + + :host .name, + :host .search-hint { + flex: 0 1 auto; + line-height: 24px; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + } + + :host .search-hint { + -webkit-margin-start: 1em; + color: #999; + font-size: 75%; + } + + :host .connection-status, + :host .learn-more-link { + -webkit-margin-start: 1em; + flex: 0 0 auto; + font-size: 75%; + line-height: 24px; + vertical-align: middle; + } + + :host .learn-more-link { + color: rgb(51, 103, 214); + } + + :host .register-promo { + -webkit-margin-start: 1em; + flex: 0 0 auto; + } + + :host .extension-controlled-indicator { + display: flex; + flex: 1; + justify-content: flex-end; + min-width: 150px; + } + + :host .extension-name { + -webkit-margin-start: 1em; + color: #777; + line-height: 24px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + :host .extension-icon { + background-position: center; + background-repeat: no-repeat; + cursor: pointer; + flex: 0 0 auto; + height: 24px; + margin: 0 3px; + width: 24px; + } + + :host .configuring-in-progress-text, + :host .configuring-failed-text { + -webkit-margin-start: 1em; + flex: 0 1 auto; + line-height: 24px; + vertical-align: middle; + } + + :host .configuring-failed-text { + color: red; + font-style: italic; + } + + :host([stale_]) :-webkit-any(.icon, .name, .connection-status) { + opacity: 0.4; + } + </style> + <img class="icon" src="[[destination.iconUrl]]" + srcset="[[destination.srcSet]]"> + <span class="name searchable">[[destination.displayName]]</span> + <span class="search-hint searchable">[[searchHint_]]</span> + <span class="connection-status" + hidden$="[[!destination.isOfflineOrInvalid]]"> + [[destination.connectionStatusText]] + </span> + <a is="action-link" class="learn-more-link" + hidden$="[[!destination.shouldShowInvalidCertificateError]]"> + $i18n{learnMore} + </a> + <span class="register-promo" hidden$="[[!destination.isUnregistered]]"> + <button class="register-promo-button"> + $i18n{registerPromoButtonText} + </button> + </span> + <span class="extension-controlled-indicator" + hidden$="[[!destination.isExtension]]"> + <span class="extension-name searchable"> + [[destination.extensionName]] + </span> + <span class="extension-icon" role="button" tabindex="0"></span> + </span> +<if expr="chromeos"> + <span class="configuring-in-progress-text" hidden> + $i18n{configuringInProgressText} + <span class="configuring-text-jumping-dots"> + <span>.</span><span>.</span><span>.</span> + </span> + </span> + <span class="configuring-failed-text" hidden> + $i18n{configuringFailedText} + </span> +</if> + </template> + <script src="destination_list_item.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_list_item.js b/chromium/chrome/browser/resources/print_preview/new/destination_list_item.js new file mode 100644 index 00000000000..7e790c0644e --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/destination_list_item.js @@ -0,0 +1,58 @@ +// 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: 'print-preview-destination-list-item', + + properties: { + /** @type {!print_preview.Destination} */ + destination: Object, + + /** @type {?RegExp} */ + searchQuery: Object, + + /** @private */ + stale_: { + type: Boolean, + reflectToAttribute: true, + }, + + /** @private {string} */ + searchHint_: String, + }, + + observers: [ + 'onDestinationPropertiesChange_(' + + 'destination.displayName, destination.isOfflineOrInvalid)', + ], + + /** @private {boolean} */ + highlighted_: false, + + /** @private */ + onDestinationPropertiesChange_: function() { + this.title = this.destination.displayName; + this.stale_ = this.destination.isOfflineOrInvalid; + }, + + update: function() { + this.updateSearchHint_(); + this.updateHighlighting_(); + }, + + /** @private */ + updateSearchHint_: function() { + this.searchHint_ = !this.searchQuery ? + '' : + this.destination.extraPropertiesToMatch + .filter(p => p.match(this.searchQuery)) + .join(' '); + }, + + /** @private */ + updateHighlighting_: function() { + this.highlighted_ = print_preview.updateHighlights( + this, this.searchQuery, this.highlighted_); + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_settings.html b/chromium/chrome/browser/resources/print_preview/new/destination_settings.html index 69f1f01c23f..b95e9e8be34 100644 --- a/chromium/chrome/browser/resources/print_preview/new/destination_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/destination_settings.html @@ -1,8 +1,13 @@ <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/cr_elements/hidden_style_css.html"> -<link rel="import" href="button_css.html"> +<link rel="import" href="chrome://resources/html/event_tracker.html"> <link rel="import" href="../data/destination.html"> +<link rel="import" href="../data/destination_store.html"> +<link rel="import" href="../data/user_info.html"> +<link rel="import" href="button_css.html"> +<link rel="import" href="destination_dialog.html"> <link rel="import" href="print_preview_shared_css.html"> <link rel="import" href="throbber_css.html"> <link rel="import" href="settings_section.html"> @@ -69,15 +74,28 @@ <img class="destination-icon" src="[[destination.iconUrl]]" alt=""> <div class="destination-info-wrapper"> - <div class="destination-name">[[destination.id]]</div> + <div class="destination-name">[[destination.displayName]]</div> <div class="destination-location">[[destination.hint]]</div> <div class="destination-connection-status"> [[destination.connectionStatusText]]</div> </div> </div> - <button>$i18n{changeDestination}</button> + <button + disabled$="[[shouldDisableButton_(destinationStore, disabled, + state)]]" + on-click="onChangeButtonClick_"> + $i18n{changeDestination} + </button> </div> </print-preview-settings-section> + <template is="cr-lazy-render" id="destinationDialog"> + <print-preview-destination-dialog + destination-store="[[destinationStore]]" + recent-destinations="[[recentDestinations]]" + user-info="{{userInfo}}" + show-cloud-print-promo="{{showCloudPrintPromo_}}"> + </print-preview-destination-dialog> + </template> </template> <script src="destination_settings.js"></script> </dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/destination_settings.js b/chromium/chrome/browser/resources/print_preview/new/destination_settings.js index 6fde6cc0895..e61f33c637c 100644 --- a/chromium/chrome/browser/resources/print_preview/new/destination_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/destination_settings.js @@ -9,19 +9,72 @@ Polymer({ /** @type {!print_preview.Destination} */ destination: Object, + /** @type {?print_preview.DestinationStore} */ + destinationStore: Object, + + /** @type {!Array<!print_preview.RecentDestination>} */ + recentDestinations: Array, + + /** @type {!print_preview.UserInfo} */ + userInfo: { + type: Object, + notify: true, + }, + + disabled: Boolean, + + /** @type {!print_preview_new.State} */ + state: Number, + + /** @private {boolean} */ + showCloudPrintPromo_: { + type: Boolean, + value: false, + }, + /** @private {boolean} */ - loadingDestination_: Boolean, + loadingDestination_: { + type: Boolean, + value: true, + }, }, - /** @override */ - ready: function() { - this.loadingDestination_ = true; - // Simulate transition from spinner to destination. - setTimeout(this.doneLoading_.bind(this), 5000); + observers: ['onDestinationSet_(destination, destination.id)'], + + /** + * @return {boolean} Whether the destination change button should be disabled. + * @private + */ + shouldDisableButton_: function() { + return !this.destinationStore || + (this.disabled && + this.state != print_preview_new.State.INVALID_PRINTER); }, /** @private */ - doneLoading_: function() { - this.loadingDestination_ = false; + onDestinationSet_: function() { + if (this.destination && this.destination.id) + this.loadingDestination_ = false; + }, + + /** @private */ + onChangeButtonClick_: function() { + this.destinationStore.startLoadAllDestinations(); + const dialog = this.$.destinationDialog.get(); + // This async() call is a workaround to prevent a DCHECK - see + // https://crbug.com/804047. + this.async(() => { + dialog.show(); + }, 1); + }, + + showCloudPrintPromo: function() { + this.showCloudPrintPromo_ = true; + }, + + /** @return {boolean} Whether the destinations dialog is open. */ + isDialogOpen: function() { + const destinationDialog = this.$$('print-preview-destination-dialog'); + return destinationDialog && destinationDialog.isOpen(); }, }); diff --git a/chromium/chrome/browser/resources/print_preview/new/dpi_settings.html b/chromium/chrome/browser/resources/print_preview/new/dpi_settings.html index 2e448060168..1714dc87c91 100644 --- a/chromium/chrome/browser/resources/print_preview/new/dpi_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/dpi_settings.html @@ -15,7 +15,7 @@ <div slot="controls"> <print-preview-settings-select aria-labelled-by="dpi-label" capability="[[capabilityWithLabels_]]" setting-name="dpi" - settings="{{settings}}"> + settings="{{settings}}" disabled="[[disabled]]"> </print-preview-settings-select> </div> </print-preview-settings-section> diff --git a/chromium/chrome/browser/resources/print_preview/new/dpi_settings.js b/chromium/chrome/browser/resources/print_preview/new/dpi_settings.js index c5d1c8d9b11..31e95f6d35b 100644 --- a/chromium/chrome/browser/resources/print_preview/new/dpi_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/dpi_settings.js @@ -30,6 +30,8 @@ Polymer({ /** @type {{ option: Array<!print_preview_new.SelectOption> }} */ capability: Object, + disabled: Boolean, + /** @private {{ option: Array<!print_preview_new.SelectOption> }} */ capabilityWithLabels_: { type: Object, diff --git a/chromium/chrome/browser/resources/print_preview/new/header.html b/chromium/chrome/browser/resources/print_preview/new/header.html index e16008da496..70468e7670d 100644 --- a/chromium/chrome/browser/resources/print_preview/new/header.html +++ b/chromium/chrome/browser/resources/print_preview/new/header.html @@ -1,9 +1,11 @@ <link rel="import" href="chrome://resources/html/polymer.html"> +<link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="button_css.html"> <link rel="import" href="../data/destination.html"> <link rel="import" href="settings_behavior.html"> <link rel="import" href="print_preview_shared_css.html"> +<link rel="import" href="state.html"> <link rel="import" href="strings.html"> <dom-module id="print-preview-header"> @@ -67,16 +69,13 @@ } </style> <h1 class="title">$i18n{title}</h1> - <span class="summary" - aria-label="[[getSummaryLabel_(currentErrorOrState_, labelInfo_)]]" - inner-h-t-m-l="[[getSummary_(currentErrorOrState_, labelInfo_)]]"> + <span class="summary" aria-label$="[[summaryLabel_]]" + inner-h-t-m-l="[[summary_]]"> </span> <div id="button-strip"> - <button class="cancel" on-tap="onCancelButtonTap_"> - $i18n{cancel} - </button> - <button class="print default" on-tap="onPrintButtonTap_" - disabled$="[[printButtonDisabled_(currentErrorOrState_)]]"> + <button class="cancel" on-click="onCancelClick_">$i18n{cancel}</button> + <button class="print default" on-click="onPrintClick_" + disabled$="[[!printButtonEnabled_]]"> [[getPrintButton_(destination.id)]] </button> </div> diff --git a/chromium/chrome/browser/resources/print_preview/new/header.js b/chromium/chrome/browser/resources/print_preview/new/header.js index 00ebc16df7f..b1bf7920e69 100644 --- a/chromium/chrome/browser/resources/print_preview/new/header.js +++ b/chromium/chrome/browser/resources/print_preview/new/header.js @@ -2,6 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +cr.exportPath('print_preview_new.Header'); + +/** + * @typedef {{numPages: number, + * numSheets: number, + * pagesLabel: string, + * summaryLabel: string}} + */ +print_preview_new.Header.LabelInfo; + Polymer({ is: 'print-preview-header', @@ -12,51 +22,43 @@ Polymer({ destination: Object, /** @type {!print_preview_new.State} */ - state: { - type: Object, - notify: true, - }, + state: Number, /** @private {boolean} */ - printInProgress_: { + printButtonEnabled_: { type: Boolean, - notify: true, value: false, }, - /** - * @private {?string} Null value indicates that there is no error or - * state to display in the summary. - */ - currentErrorOrState_: { + /** @private {?string} */ + summary_: { type: String, - computed: 'computeErrorOrStateString_(state.*, ' + - 'settings.copies.valid, settings.scaling.valid, ' + - 'settings.pages.valid, printInProgress_)' + notify: true, + value: null, }, - /** - * @private {{numPages: number, - * numSheets: number, - * pagesLabel: string, - * summaryLabel: string}} - */ - labelInfo_: { - type: Object, - computed: 'getLabelInfo_(currentErrorOrState_, destination.id, ' + - 'settings.copies.value, settings.pages.value, ' + - 'settings.duplex.value)' + /** @private {?string} */ + summaryLabel_: { + type: String, + notify: true, + value: null, }, + + errorMessage: String, }, + observers: + ['update_(settings.copies.value, settings.duplex.value, ' + + 'settings.pages.value, state)'], + /** @private */ - onPrintButtonTap_: function() { - this.printInProgress_ = true; + onPrintClick_: function() { + this.fire('print-requested'); }, /** @private */ - onCancelButtonTap_: function() { - this.set('state.cancelled', true); + onCancelClick_: function() { + this.fire('cancel-requested'); }, /** @@ -81,34 +83,10 @@ Polymer({ }, /** - * @return {?string} + * @return {!print_preview_new.Header.LabelInfo} * @private */ - computeErrorOrStateString_: function() { - if (this.state.cloudPrintError != '') - return this.state.cloudPrintError; - if (this.state.privetExtensionError != '') - return this.state.privetExtensionError; - if (this.state.invalidSettings || this.state.previewFailed || - this.state.previewLoading || !this.getSetting('copies').valid || - !this.getSetting('scaling').valid || !this.getSetting('pages').valid) { - return ''; - } - if (this.printInProgress_) { - return loadTimeData.getString( - this.isPdfOrDrive_() ? 'saving' : 'printing'); - } - return null; - }, - - /** - * @return {{numPages: number, - * numSheets: number, - * pagesLabel: string, - * summaryLabel: string}} - * @private - */ - getLabelInfo_: function() { + computeLabelInfo_: function() { const saveToPdfOrDrive = this.isPdfOrDrive_(); let numPages = this.getSetting('pages').value.length; let numSheets = numPages; @@ -139,23 +117,41 @@ Polymer({ }; }, - /** - * @return {boolean} - * @private - */ - printButtonDisabled_: function() { - return this.currentErrorOrState_ != null; + /** @private */ + update_: function() { + switch (this.state) { + case (print_preview_new.State.PRINTING): + this.printButtonEnabled_ = false; + this.summary_ = loadTimeData.getString( + this.isPdfOrDrive_() ? 'saving' : 'printing'); + this.summaryLabel_ = this.summary_; + break; + case (print_preview_new.State.READY): + this.printButtonEnabled_ = true; + const labelInfo = this.computeLabelInfo_(); + this.summary_ = this.getSummary_(labelInfo); + this.summaryLabel_ = this.getSummaryLabel_(labelInfo); + break; + case (print_preview_new.State.FATAL_ERROR): + this.printButtonEnabled_ = false; + this.summary_ = this.errorMessage; + this.summaryLabel_ = this.errorMessage; + break; + default: + this.summary_ = null; + this.summaryLabel_ = null; + this.printButtonEnabled_ = false; + break; + } }, /** + * @param {!print_preview_new.Header.LabelInfo} labelInfo * @return {string} * @private */ - getSummary_: function() { - let html = this.currentErrorOrState_; - if (html != null) - return html; - const labelInfo = this.labelInfo_; + getSummary_: function(labelInfo) { + let html = null; if (labelInfo.numPages != labelInfo.numSheets) { html = loadTimeData.getStringF( 'printPreviewSummaryFormatLong', @@ -175,13 +171,11 @@ Polymer({ }, /** + * @param {!print_preview_new.Header.LabelInfo} labelInfo * @return {string} * @private */ - getSummaryLabel_: function() { - if (this.currentErrorOrState_ != null) - return this.currentErrorOrState_; - const labelInfo = this.labelInfo_; + getSummaryLabel_: function(labelInfo) { if (labelInfo.numPages != labelInfo.numSheets) { return loadTimeData.getStringF( 'printPreviewSummaryFormatLong', labelInfo.numSheets.toLocaleString(), diff --git a/chromium/chrome/browser/resources/print_preview/new/highlight_utils.html b/chromium/chrome/browser/resources/print_preview/new/highlight_utils.html new file mode 100644 index 00000000000..ad6c80409c2 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/highlight_utils.html @@ -0,0 +1,3 @@ +<link rel="import" href="chrome://resources/html/search_highlight_utils.html"> + +<script src="highlight_utils.js"></script> diff --git a/chromium/chrome/browser/resources/print_preview/new/highlight_utils.js b/chromium/chrome/browser/resources/print_preview/new/highlight_utils.js new file mode 100644 index 00000000000..0a8df2f4780 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/highlight_utils.js @@ -0,0 +1,60 @@ +// 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('print_preview', function() { + 'use strict'; + + /** + * @param {!HTMLElement} element The element to update. Element should have a + * shadow root. + * @param {?RegExp} query The current search query + * @param {boolean} wasHighlighted Whether the element was previously + * highlighted. + * @return {boolean} Whether the element is highlighted after the update. + */ + function updateHighlights(element, query, wasHighlighted) { + if (wasHighlighted) { + cr.search_highlight_utils.findAndRemoveHighlights(element); + cr.search_highlight_utils.findAndRemoveBubbles(element); + } + + if (!query) + return false; + + let isHighlighted = false; + element.shadowRoot.querySelectorAll('.searchable').forEach(childElement => { + childElement.childNodes.forEach(node => { + if (node.nodeType != Node.TEXT_NODE) + return; + + const textContent = node.nodeValue.trim(); + if (textContent.length == 0) + return; + + if (query.test(textContent)) { + isHighlighted = true; + // Don't highlight <select> nodes, yellow rectangles can't be + // displayed within an <option>. + if (node.parentNode.nodeName != 'OPTION') { + cr.search_highlight_utils.highlight(node, textContent.split(query)); + } else { + const selectNode = node.parentNode.parentNode; + // The bubble should be parented by the select node's parent. + // Note: The bubble's ::after element, a yellow arrow, will not + // appear correctly in print preview without SPv175 enabled. See + // https://crbug.com/817058. + cr.search_highlight_utils.highlightControlWithBubble( + /** @type {!HTMLElement} */ (assert(selectNode.parentNode)), + textContent.match(query)[0]); + } + } + }); + }); + return isHighlighted; + } + + return { + updateHighlights: updateHighlights, + }; +}); diff --git a/chromium/chrome/browser/resources/print_preview/new/layout_settings.html b/chromium/chrome/browser/resources/print_preview/new/layout_settings.html index afbc18b2d85..0b0e7ae3098 100644 --- a/chromium/chrome/browser/resources/print_preview/new/layout_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/layout_settings.html @@ -11,7 +11,8 @@ <print-preview-settings-section> <span id="layout-label" slot="title">$i18n{layoutLabel}</span> <div slot="controls"> - <select aria-labelledby="layout-label" on-change="onChange_"> + <select aria-labelledby="layout-label" on-change="onChange_" + disabled$="[[disabled]]"> <option value="portrait" selected>$i18n{optionPortrait}</option> <option value="landscape">$i18n{optionLandscape}</option> </select> diff --git a/chromium/chrome/browser/resources/print_preview/new/layout_settings.js b/chromium/chrome/browser/resources/print_preview/new/layout_settings.js index 77d431925c4..b84fbe2bd00 100644 --- a/chromium/chrome/browser/resources/print_preview/new/layout_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/layout_settings.js @@ -7,6 +7,10 @@ Polymer({ behaviors: [SettingsBehavior], + properties: { + disabled: Boolean, + }, + observers: ['onLayoutSettingChange_(settings.layout.value)'], /** diff --git a/chromium/chrome/browser/resources/print_preview/new/margins_settings.html b/chromium/chrome/browser/resources/print_preview/new/margins_settings.html index dbc9ac60d9c..7bf74d1b5a3 100644 --- a/chromium/chrome/browser/resources/print_preview/new/margins_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/margins_settings.html @@ -12,7 +12,8 @@ <print-preview-settings-section> <span id="margins-label" slot="title">$i18n{marginsLabel}</span> <div slot="controls"> - <select aria-labelledby="margins-label" on-change="onChange_"> + <select aria-labelledby="margins-label" on-change="onChange_" + disabled$="[[disabled]]"> <!-- The order of these options must match the natural order of their values, which come from print_preview.ticket_items.MarginsTypeValue. --> diff --git a/chromium/chrome/browser/resources/print_preview/new/margins_settings.js b/chromium/chrome/browser/resources/print_preview/new/margins_settings.js index 65ec5a10e3e..c641b40c73b 100644 --- a/chromium/chrome/browser/resources/print_preview/new/margins_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/margins_settings.js @@ -7,6 +7,10 @@ Polymer({ behaviors: [SettingsBehavior], + properties: { + disabled: Boolean, + }, + observers: ['onMarginsSettingChange_(settings.margins.value)'], /** diff --git a/chromium/chrome/browser/resources/print_preview/new/media_size_settings.html b/chromium/chrome/browser/resources/print_preview/new/media_size_settings.html index 540f8f0972c..03223dfdce2 100644 --- a/chromium/chrome/browser/resources/print_preview/new/media_size_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/media_size_settings.html @@ -14,7 +14,7 @@ <div slot="controls"> <print-preview-settings-select aria-labelledby="media-size-label" capability="[[capability]]" setting-name="mediaSize" - settings="{{settings}}"> + settings="{{settings}}" disabled="[[disabled]]"> </print-preview-settings-select> </div> </print-preview-settings-section> diff --git a/chromium/chrome/browser/resources/print_preview/new/media_size_settings.js b/chromium/chrome/browser/resources/print_preview/new/media_size_settings.js index 55dc7ebcb8b..1ff1bfb0715 100644 --- a/chromium/chrome/browser/resources/print_preview/new/media_size_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/media_size_settings.js @@ -9,6 +9,8 @@ Polymer({ properties: { capability: Object, + + disabled: Boolean, }, observers: diff --git a/chromium/chrome/browser/resources/print_preview/new/model.html b/chromium/chrome/browser/resources/print_preview/new/model.html index 30924113655..3e3ea6386f2 100644 --- a/chromium/chrome/browser/resources/print_preview/new/model.html +++ b/chromium/chrome/browser/resources/print_preview/new/model.html @@ -1,6 +1,7 @@ <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="settings_behavior.html"> <link rel="import" href="../data/destination.html"> <link rel="import" href="../data/document_info.html"> <link rel="import" href="../data/margins.html"> diff --git a/chromium/chrome/browser/resources/print_preview/new/model.js b/chromium/chrome/browser/resources/print_preview/new/model.js index bf4fe074786..0b90df49409 100644 --- a/chromium/chrome/browser/resources/print_preview/new/model.js +++ b/chromium/chrome/browser/resources/print_preview/new/model.js @@ -33,6 +33,16 @@ cr.exportPath('print_preview_new'); */ print_preview_new.SerializedSettings; +/** + * Constant values matching printing::DuplexMode enum. + * @enum {number} + */ +print_preview_new.DuplexMode = { + SIMPLEX: 0, + LONG_EDGE: 1, + UNKNOWN_DUPLEX_MODE: -1 +}; + (function() { 'use strict'; @@ -42,16 +52,28 @@ const NUM_DESTINATIONS = 3; /** * Sticky setting names. Alphabetical except for fitToPage, which must be set * after scaling in updateFromStickySettings(). - * @type {Array<string>} + * @type {!Array<string>} */ const STICKY_SETTING_NAMES = [ - 'collate', 'color', 'cssBackground', 'dpi', 'duplex', 'headerFooter', - 'layout', 'margins', 'mediaSize', 'scaling', 'fitToPage' + 'collate', + 'color', + 'cssBackground', + 'dpi', + 'duplex', + 'headerFooter', + 'layout', + 'margins', + 'mediaSize', + 'scaling', + 'fitToPage', + 'vendorItems', ]; Polymer({ is: 'print-preview-model', + behaviors: [SettingsBehavior], + properties: { /** * Object containing current settings of Print Preview, for use by Polymer @@ -175,7 +197,7 @@ Polymer({ unavailableValue: {}, valid: true, available: true, - key: '', + key: 'vendorOptions', }, // This does not represent a real setting value, and is used only to // expose the availability of the other options settings section. @@ -228,12 +250,19 @@ Polymer({ 'settings.mediaSize.value, settings.margins.value, ' + 'settings.dpi.value, settings.fitToPage.value, ' + 'settings.scaling.value, settings.duplex.value, ' + - 'settings.headerFooter.value, settings.cssBackground.value)', + 'settings.headerFooter.value, settings.cssBackground.value, ' + + 'settings.vendorItems.value)', ], /** @private {boolean} */ initialized_: false, + /** @private {?print_preview_new.SerializedSettings} */ + stickySettings_: null, + + /** @private {?print_preview.Cdd} */ + lastDestinationCapabilities_: null, + /** * Updates the availability of the settings sections and values of dpi and * media size settings. @@ -244,6 +273,14 @@ Polymer({ this.destination.capabilities.printer : null; this.updateSettingsAvailability_(caps); + + if (!caps) + return; + + if (this.destination.capabilities == this.lastDestinationCapabilities_) + return; + + this.lastDestinationCapabilities_ = this.destination.capabilities; this.updateSettingsValues_(caps); }, @@ -290,6 +327,8 @@ Polymer({ this.settings.selectionOnly.available || this.settings.headerFooter.available || this.settings.rasterize.available); + this.set( + 'settings.vendorItems.available', !!caps && !!caps.vendor_capability); }, /** @@ -318,25 +357,38 @@ Polymer({ */ updateSettingsValues_: function(caps) { if (this.settings.mediaSize.available) { - for (const option of caps.media_size.option) { - if (option.is_default) { - this.set('settings.mediaSize.value', option); - break; - } - } + const defaultOption = caps.media_size.option.find(o => !!o.is_default); + this.set('settings.mediaSize.value', defaultOption); } - if (this.settings.dpi.available) { - for (const option of caps.dpi.option) { - if (option.is_default) { - this.set('settings.dpi.value', option); - break; - } - } + const defaultOption = caps.dpi.option.find(o => !!o.is_default); + this.set('settings.dpi.value', defaultOption); } else if ( caps && caps.dpi && caps.dpi.option && caps.dpi.option.length > 0) { this.set('settings.dpi.value', caps.dpi.option[0]); } + + if (this.settings.vendorItems.available) { + const vendorSettings = {}; + for (const item of caps.vendor_capability) { + let defaultValue = null; + if (item.type == 'SELECT' && !!item.select_cap && + !!item.select_cap.option) { + const defaultOption = + item.select_cap.option.find(o => !!o.is_default); + defaultValue = !!defaultOption ? defaultOption.value : null; + } else if (item.type == 'RANGE') { + if (!!item.range_cap) + defaultValue = item.range_cap.default || null; + } else if (item.type == 'TYPED_VALUE') { + if (!!item.typed_value_cap) + defaultValue = item.typed_value_cap.default || null; + } + if (defaultValue != null) + vendorSettings[item.id] = defaultValue; + } + this.set('settings.vendorItems.value', vendorSettings); + } }, /** @private */ @@ -371,18 +423,20 @@ Polymer({ this.recentDestinations.splice(indexFound, 1); // Add the most recent destination - this.recentDestinations.splice(0, 0, newDestination); - this.notifyPath('recentDestinations'); + this.splice('recentDestinations', 0, 0, newDestination); // Persist sticky settings. this.stickySettingsChanged_(); }, /** + * Caches the sticky settings and sets up the recent destinations. Sticky + * settings will be applied when destinaton capabilities have been retrieved. * @param {?string} savedSettingsStr The sticky settings from native layer */ - updateFromStickySettings: function(savedSettingsStr) { - this.initialized_ = true; + setStickySettings: function(savedSettingsStr) { + assert(!this.stickySettings_ && this.recentDestinations.length == 0); + if (!savedSettingsStr) return; @@ -403,16 +457,26 @@ Polymer({ } this.recentDestinations = recentDestinations; - // Reset initialized, or stickySettingsChanged_ will get called for - // every setting that gets set below. - this.initialized_ = false; - STICKY_SETTING_NAMES.forEach(settingName => { - const setting = this.get(settingName, this.settings); - const value = savedSettings[setting.key]; - if (value != undefined) - this.set(`settings.${settingName}.value`, value); - }); + this.stickySettings_ = savedSettings; + }, + + applyStickySettings: function() { + if (this.stickySettings_) { + STICKY_SETTING_NAMES.forEach(settingName => { + const setting = this.get(settingName, this.settings); + const value = this.stickySettings_[setting.key]; + if (value != undefined) + this.set(`settings.${settingName}.value`, value); + }); + } this.initialized_ = true; + this.stickySettings_ = null; + this.stickySettingsChanged_(); + }, + + /** @return {boolean} Whether the model has been initialized. */ + initialized: function() { + return this.initialized_; }, /** @private */ @@ -431,5 +495,168 @@ Polymer({ }); this.fire('save-sticky-settings', JSON.stringify(serialization)); }, + + /** + * Creates a string that represents a print ticket. + * @param {!print_preview.Destination} destination Destination to print to. + * @return {string} Serialized print ticket. + */ + createPrintTicket: function(destination) { + const dpi = /** @type {{horizontal_dpi: (number | undefined), + vertical_dpi: (number | undefined), + vendor_id: (number | undefined)}} */ ( + this.getSettingValue('dpi')); + + const ticket = { + mediaSize: this.getSettingValue('mediaSize'), + pageCount: this.getSettingValue('pages').length, + landscape: this.getSettingValue('layout'), + color: destination.getNativeColorModel( + /** @type {boolean} */ (this.getSettingValue('color'))), + headerFooterEnabled: false, // only used in print preview + marginsType: this.getSettingValue('margins'), + duplex: this.getSettingValue('duplex') ? + print_preview_new.DuplexMode.LONG_EDGE : + print_preview_new.DuplexMode.SIMPLEX, + copies: this.getSettingValue('copies'), + collate: this.getSettingValue('collate'), + shouldPrintBackgrounds: this.getSettingValue('cssBackground'), + shouldPrintSelectionOnly: false, // only used in print preview + previewModifiable: this.documentInfo.isModifiable, + printToPDF: destination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, + printWithCloudPrint: !destination.isLocal, + printWithPrivet: destination.isPrivet, + printWithExtension: destination.isExtension, + rasterizePDF: this.getSettingValue('rasterize'), + scaleFactor: parseInt(this.getSettingValue('scaling'), 10), + dpiHorizontal: (dpi && 'horizontal_dpi' in dpi) ? dpi.horizontal_dpi : 0, + dpiVertical: (dpi && 'vertical_dpi' in dpi) ? dpi.vertical_dpi : 0, + deviceName: destination.id, + fitToPageEnabled: this.getSettingValue('fitToPage'), + pageWidth: this.documentInfo.pageSize.width, + pageHeight: this.documentInfo.pageSize.height, + showSystemDialog: false, + }; + + // Set 'cloudPrintID' only if the destination is not local. + if (!destination.isLocal) + ticket.cloudPrintID = destination.id; + + if (this.getSettingValue('margins') == + print_preview.ticket_items.MarginsTypeValue.CUSTOM) { + // TODO (rbpotter): Replace this with real values when custom margins are + // implemented. + ticket.marginsCustom = { + marginTop: 70, + marginRight: 70, + marginBottom: 70, + marginLeft: 70, + }; + } + + if (destination.isPrivet || destination.isExtension) { + // TODO (rbpotter): Get local and PDF printers to use the same ticket and + // send only this ticket instead of nesting it in a larger ticket. + ticket.ticket = this.createCloudJobTicket(destination); + ticket.capabilities = JSON.stringify(destination.capabilities); + } + return JSON.stringify(ticket); + }, + + /** + * Creates an object that represents a Google Cloud Print print ticket. + * @param {!print_preview.Destination} destination Destination to print to. + * @return {string} Google Cloud Print print ticket. + */ + createCloudJobTicket: function(destination) { + assert( + !destination.isLocal || destination.isPrivet || destination.isExtension, + 'Trying to create a Google Cloud Print print ticket for a local ' + + ' non-privet and non-extension destination'); + assert( + destination.capabilities, + 'Trying to create a Google Cloud Print print ticket for a ' + + 'destination with no print capabilities'); + + // Create CJT (Cloud Job Ticket) + const cjt = {version: '1.0', print: {}}; + if (this.settings.collate.available) + cjt.print.collate = {collate: this.settings.collate.value}; + if (this.settings.color.available) { + const selectedOption = destination.getSelectedColorOption( + /** @type {boolean} */ (this.settings.color.value)); + if (!selectedOption) { + console.error('Could not find correct color option'); + } else { + cjt.print.color = {type: selectedOption.type}; + if (selectedOption.hasOwnProperty('vendor_id')) { + cjt.print.color.vendor_id = selectedOption.vendor_id; + } + } + } else { + // Always try setting the color in the print ticket, otherwise a + // reasonable reader of the ticket will have to do more work, or process + // the ticket sub-optimally, in order to safely handle the lack of a + // color ticket item. + const defaultOption = destination.defaultColorOption; + if (defaultOption) { + cjt.print.color = {type: defaultOption.type}; + if (defaultOption.hasOwnProperty('vendor_id')) { + cjt.print.color.vendor_id = defaultOption.vendor_id; + } + } + } + if (this.settings.copies.available) + cjt.print.copies = {copies: this.settings.copies.value}; + if (this.settings.duplex.available) { + cjt.print.duplex = { + type: this.settings.duplex.value ? 'LONG_EDGE' : 'NO_DUPLEX' + }; + } + if (this.settings.mediaSize.available) { + const mediaValue = this.settings.mediaSize.value; + cjt.print.media_size = { + width_microns: mediaValue.width_microns, + height_microns: mediaValue.height_microns, + is_continuous_feed: mediaValue.is_continuous_feed, + vendor_id: mediaValue.vendor_id + }; + } + if (!this.settings.layout.available) { + // In this case "orientation" option is hidden from user, so user can't + // adjust it for page content, see Landscape.isCapabilityAvailable(). + // We can improve results if we set AUTO here. + const capability = destination.capabilities.printer ? + destination.capabilities.printer.page_orientation : + null; + if (capability && capability.option && + capability.option.some(option => option.type == 'AUTO')) { + cjt.print.page_orientation = {type: 'AUTO'}; + } + } else { + cjt.print.page_orientation = { + type: this.settings.layout ? 'LANDSCAPE' : 'PORTRAIT' + }; + } + if (this.settings.dpi.available) { + const dpiValue = this.settings.dpi.value; + cjt.print.dpi = { + horizontal_dpi: dpiValue.horizontal_dpi, + vertical_dpi: dpiValue.vertical_dpi, + vendor_id: dpiValue.vendor_id + }; + } + if (this.settings.vendorItems.available) { + const items = this.settings.vendorItems.value; + cjt.print.vendor_ticket_item = []; + for (const itemId in items) { + if (items.hasOwnProperty(itemId)) { + cjt.print.vendor_ticket_item.push({id: itemId, value: items[itemId]}); + } + } + } + return JSON.stringify(cjt); + }, }); })(); diff --git a/chromium/chrome/browser/resources/print_preview/new/number_settings_section.html b/chromium/chrome/browser/resources/print_preview/new/number_settings_section.html index dfea6be0f89..c56de1f3557 100644 --- a/chromium/chrome/browser/resources/print_preview/new/number_settings_section.html +++ b/chromium/chrome/browser/resources/print_preview/new/number_settings_section.html @@ -32,13 +32,16 @@ <div slot="controls"> <slot name="opt-outside-content"></slot> <span class="input-wrapper"> - <input class="user-value" type="number" value="{{inputString::input}}" - max="[[maxValue]]" min="[[minValue]]" on-blur="onBlur_" - on-keydown="onKeydown_" aria-labelled-by="section-title"> + <input class="user-value" type="number" + value="{{inputString_::input}}" + max="[[maxValue]]" min="[[minValue]]" + disabled$="[[getDisabled_(inputValid, disabled)]]" + on-blur="onBlur_" on-keydown="onKeydown_" + aria-labelled-by="section-title"> <slot name="opt-inside-content"></slot> </span> <span class="hint" aria-live="polite" - hidden$="[[hintHidden_(inputString, inputValid)]]"> + hidden$="[[hintHidden_(inputString_, inputValid)]]"> [[hintMessage]] </span> </div> diff --git a/chromium/chrome/browser/resources/print_preview/new/number_settings_section.js b/chromium/chrome/browser/resources/print_preview/new/number_settings_section.js index 4084b852b71..ed173077085 100644 --- a/chromium/chrome/browser/resources/print_preview/new/number_settings_section.js +++ b/chromium/chrome/browser/resources/print_preview/new/number_settings_section.js @@ -6,33 +6,46 @@ Polymer({ is: 'print-preview-number-settings-section', properties: { - /** @type {string} */ - inputString: { + /** @private {string} */ + inputString_: { type: String, notify: true, + observer: 'onInputChanged_', }, /** @type {boolean} */ inputValid: { type: Boolean, notify: true, - computed: 'computeValid_(inputString)', + value: true, }, /** @type {string} */ + currentValue: { + type: String, + notify: true, + observer: 'onCurrentValueChanged_', + }, + defaultValue: String, - /** @type {number} */ maxValue: Number, - /** @type {number} */ minValue: Number, - /** @type {string} */ inputLabel: String, - /** @type {string} */ hintMessage: String, + + disabled: Boolean, + }, + + /** + * @return {boolean} Whether the input should be disabled. + * @private + */ + getDisabled_: function() { + return this.disabled && this.inputValid; }, /** @@ -45,19 +58,31 @@ Polymer({ /** @private */ onBlur_: function() { - if (this.inputString == '') - this.set('inputString', this.defaultValue); + if (this.inputString_ == '') + this.set('inputString_', this.defaultValue); + }, + + /** @private */ + onInputChanged_: function() { + this.inputValid = this.computeValid_(); + if (this.inputValid) + this.currentValue = this.inputString_; + }, + + /** @private */ + onCurrentValueChanged_: function() { + this.inputString_ = this.currentValue; }, /** - * @return {boolean} Whether input value represented by inputString is + * @return {boolean} Whether input value represented by inputString_ is * valid. * @private */ computeValid_: function() { - // Make sure value updates first, in case inputString was updated by JS. - this.$$('.user-value').value = this.inputString; - return this.$$('.user-value').validity.valid && this.inputString != ''; + // Make sure value updates first, in case inputString_ was updated by JS. + this.$$('.user-value').value = this.inputString_; + return this.$$('.user-value').validity.valid && this.inputString_ != ''; }, /** @@ -65,6 +90,6 @@ Polymer({ * @private */ hintHidden_: function() { - return this.inputValid || this.inputString == ''; + return this.inputValid || this.inputString_ == ''; }, }); diff --git a/chromium/chrome/browser/resources/print_preview/new/other_options_settings.html b/chromium/chrome/browser/resources/print_preview/new/other_options_settings.html index b816a3274e9..afd187b856d 100644 --- a/chromium/chrome/browser/resources/print_preview/new/other_options_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/other_options_settings.html @@ -19,12 +19,14 @@ <label aria-live="polite" hidden$="[[!settings.headerFooter.available]]"> <input type="checkbox" id="header-footer" + disabled$="[[disabled]]" on-change="onHeaderFooterChange_" checked$="[[settings.headerFooter.value]]"> <span>$i18n{optionHeaderFooter}</span> </label> <label aria-live="polite" hidden$="[[!settings.duplex.available]]"> <input type="checkbox" id="duplex" on-change="onDuplexChange_" + disabled$="[[disabled]]" checked$="[[settings.duplex.value]]"> <span>$i18n{optionTwoSided}</span> </label> @@ -32,18 +34,20 @@ hidden$="[[!settings.cssBackground.available]]"> <input type="checkbox" id="css-background" on-change="onCssBackgroundChange_" + disabled$="[[disabled]]" checked$="[[settings.cssBackground.value]]"> <span>$i18n{optionBackgroundColorsAndImages}</span> </label> <label aria-live="polite" hidden$="[[!settings.rasterize.available]]"> <input type="checkbox" id="rasterize" - on-change="onRasterizeChange_" + disabled$="[[disabled]]" on-change="onRasterizeChange_" checked$="[[settings.rasterize.value]]"> <span>$i18n{optionRasterize}</span> </label> <label aria-live="polite" hidden$="[[!settings.selectionOnly.available]]"> <input type="checkbox" id="selection-only" + disabled$="[[disabled]]" on-change="onSelectionOnlyChange_" checked$="[[settings.selectionOnly.value]]"> <span>$i18n{optionSelectionOnly}</span> diff --git a/chromium/chrome/browser/resources/print_preview/new/other_options_settings.js b/chromium/chrome/browser/resources/print_preview/new/other_options_settings.js index 07e21f5de60..07d9f82eb58 100644 --- a/chromium/chrome/browser/resources/print_preview/new/other_options_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/other_options_settings.js @@ -7,6 +7,10 @@ Polymer({ behaviors: [SettingsBehavior], + properties: { + disabled: Boolean, + }, + observers: [ 'onHeaderFooterSettingChange_(settings.headerFooter.value)', 'onDuplexSettingChange_(settings.duplex.value)', diff --git a/chromium/chrome/browser/resources/print_preview/new/pages_settings.html b/chromium/chrome/browser/resources/print_preview/new/pages_settings.html index 72fc32d2160..2777b3b0973 100644 --- a/chromium/chrome/browser/resources/print_preview/new/pages_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/pages_settings.html @@ -1,6 +1,5 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="checkbox_radio_css.html"> <link rel="import" href="../data/document_info.html"> <link rel="import" href="input_css.html"> @@ -30,16 +29,19 @@ <div slot="controls"> <div class="radio"> <label><input type="radio" name="pages" id="all-radio-button" - checked="{{allSelected_::change}}"> + checked="{{allSelected_::change}}" + disabled$="[[getDisabled_(disabled, settings.pages.valid)]]"> <span>$i18n{optionAllPages}</span> </label> <label class="custom-input-wrapper" for="page-settings-custom-input" tabindex=-1> <input type="radio" name="pages" id="custom-radio-button" + disabled$="[[getDisabled_(disabled, settings.pages.valid)]]" on-click="onCustomRadioClick_"> <input class="user-value" type="text" value="{{inputString_::input}}" id="page-settings-custom-input" checked="{{customSelected_::change}}" + disabled$="[[getDisabled_(dsiabled, settings.pages.valid)]]" pattern="([0-9]*(-)?[0-9]*(,)( )?)*([0-9]*(-)?[0-9]*(,)?( )?)?" on-focus="onCustomInputFocus_" on-blur="onCustomInputBlur_" placeholder="$i18n{examplePageRangeText}" diff --git a/chromium/chrome/browser/resources/print_preview/new/pages_settings.js b/chromium/chrome/browser/resources/print_preview/new/pages_settings.js index e230e2c3500..4b6e5276fc1 100644 --- a/chromium/chrome/browser/resources/print_preview/new/pages_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/pages_settings.js @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -cr.exportPath('print_preview_new'); +(function() { +'use strict'; /** @enum {number} */ const PagesInputErrorState = { @@ -44,6 +45,14 @@ Polymer({ value: false, }, + disabled: Boolean, + + /** @private {number} */ + errorState_: { + type: Number, + value: PagesInputErrorState.NO_ERROR, + }, + /** @private {!Array<number>} */ pagesToPrint_: { type: Array, @@ -56,13 +65,6 @@ Polymer({ type: Array, computed: 'computeRangesToPrint_(pagesToPrint_, allPagesArray_)', }, - - /** @private {!PagesInputErrorState} */ - errorState_: { - type: Number, - computed: 'computeErrorState_(documentInfo.pageCount, pagesToPrint_)', - }, - }, observers: [ @@ -71,6 +73,14 @@ Polymer({ ], /** + * @return {boolean} Whether the controls should be disabled. + * @private + */ + getDisabled_: function() { + return this.getSetting('pages').valid && this.disabled; + }, + + /** * @return {!Array<number>} * @private */ @@ -88,10 +98,14 @@ Polymer({ * @private */ computePagesToPrint_: function() { - if (this.allSelected_ || this.inputString_.trim() == '') + if (this.allSelected_ || this.inputString_.trim() == '') { + this.errorState_ = PagesInputErrorState.NO_ERROR; return this.allPagesArray_; - if (!this.$$('.user-value').validity.valid) - return []; + } + if (!this.$$('.user-value').validity.valid) { + this.errorState_ = PagesInputErrorState.INVALID_SYNTAX; + return this.pagesToPrint_; + } const pages = []; const added = {}; @@ -103,11 +117,15 @@ Polymer({ continue; const limits = range.split('-'); let min = parseInt(limits[0], 10); - if (min < 1) - return []; + if (min < 1) { + this.errorState_ = PagesInputErrorState.OUT_OF_BOUNDS; + return this.pagesToPrint_; + } if (limits.length == 1) { - if (min > maxPage) - return [-1]; + if (min > maxPage) { + this.errorState_ = PagesInputErrorState.OUT_OF_BOUNDS; + return this.pagesToPrint_; + } if (!added.hasOwnProperty(min)) { pages.push(min); added[min] = true; @@ -120,10 +138,14 @@ Polymer({ min = 1; if (isNaN(max)) max = maxPage; - if (min > max) - return []; - if (max > maxPage) - return [-1]; + if (min > max) { + this.errorState_ = PagesInputErrorState.INVALID_SYNTAX; + return this.pagesToPrint_; + } + if (max > maxPage) { + this.errorState_ = PagesInputErrorState.OUT_OF_BOUNDS; + return this.pagesToPrint_; + } for (let i = min; i <= max; i++) { if (!added.hasOwnProperty(i)) { pages.push(i); @@ -162,17 +184,17 @@ Polymer({ }, /** - * @return {!PagesInputErrorState} - * @private + * @return {boolean} Whether pages setting and pagesToPrint_ match. */ - computeErrorState_: function() { - if (this.documentInfo.pageCount == 0) // page count not yet initialized - return PagesInputErrorState.NO_ERROR; - if (this.pagesToPrint_.length == 0) - return PagesInputErrorState.INVALID_SYNTAX; - if (this.pagesToPrint_[0] == -1) - return PagesInputErrorState.OUT_OF_BOUNDS; - return PagesInputErrorState.NO_ERROR; + settingMatches_: function() { + const setting = this.getSetting('pages').value; + if (setting.length != this.pagesToPrint_.length) + return false; + for (let index = 0; index < this.pagesToPrint_.length; index++) { + if (this.pagesToPrint_[index] != setting[index]) + return false; + } + return true; }, /** @@ -188,8 +210,10 @@ Polymer({ } this.$$('.user-value').classList.remove('invalid'); this.setSettingValid('pages', true); - this.setSetting('pages', this.pagesToPrint_); - this.setSetting('ranges', this.rangesToPrint_); + if (!this.settingMatches_()) { + this.setSetting('pages', this.pagesToPrint_); + this.setSetting('ranges', this.rangesToPrint_); + } }, /** @private */ @@ -249,3 +273,4 @@ Polymer({ return this.errorState_ == PagesInputErrorState.NO_ERROR; } }); +})(); diff --git a/chromium/chrome/browser/resources/print_preview/new/preview_area.html b/chromium/chrome/browser/resources/print_preview/new/preview_area.html index 716d3b5e6f3..a7a9d5cd05b 100644 --- a/chromium/chrome/browser/resources/print_preview/new/preview_area.html +++ b/chromium/chrome/browser/resources/print_preview/new/preview_area.html @@ -1,6 +1,7 @@ <link rel="import" href="chrome://resources/html/polymer.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="../native_layer.html"> <link rel="import" href="../print_preview_utils.html"> @@ -10,6 +11,8 @@ <link rel="import" href="../data/margins.html"> <link rel="import" href="../data/printable_area.html"> <link rel="import" href="../data/size.html"> +<link rel="import" href="model.html"> +<link rel="import" href="state.html"> <link rel="import" href="settings_behavior.html"> <dom-module id="print-preview-preview-area"> @@ -108,37 +111,20 @@ } </style> - <div class="preview-area-overlay-layer"> + <div class$="preview-area-overlay-layer [[getInvisible_(previewState_)]]" + aria-hidden$="[[previewLoaded(previewState_)]]"> <div class="preview-area-messages"> - - <div class="preview-area-loading-message preview-area-message" - hidden$="[[!state.previewLoading]]"> - <span>$i18n{loading}</span> - <span class="preview-area-loading-message-jumping-dots jumping-dots" - ><span>.</span><span>.</span><span>.</span></span> - </div> - - <div class="preview-area-custom-message preview-area-message" hidden> - <div class="preview-area-custom-message-text"></div> - <div class="preview-area-custom-action-area"> - <button class="preview-area-open-system-dialog-button"> - $i18n{launchNativeDialog} - </button> - <div - class="preview-area-open-system-dialog-button-throbber throbber" - hidden></div> + <div class="preview-area-message"> + <div> + <span>[[currentMessage_(previewState_)]]</span> + <span class$="preview-area-loading-message-jumping-dots + [[getJumpingDots_(previewState_)]]" + hidden$="[[!isPreviewLoading_(previewState_)]]"> + <span>.</span><span>.</span><span>.</span> + </span> </div> - </div> - - <div class="preview-area-preview-failed-message preview-area-message" - hidden$="[[!state.previewFailed]]"> - $i18n{previewFailed} - </div> - - <div class="preview-area-print-failed preview-area-message" - hidden$="[[!state.invalidSettings]]"> - <div>$i18n{invalidPrinterSettings}</div> - <div class="preview-area-print-failed-action-area"> + <div class="preview-area-action-area" + hidden$="[[!displaySystemDialogButton_(previewState_)]]"> <button class="preview-area-open-system-dialog-button"> $i18n{launchNativeDialog} </button> @@ -147,7 +133,6 @@ hidden></div> </div> </div> - </div> </div> <div class="preview-area-plugin-wrapper"> diff --git a/chromium/chrome/browser/resources/print_preview/new/preview_area.js b/chromium/chrome/browser/resources/print_preview/new/preview_area.js index 6a1b53b0d1a..9c456b76ccf 100644 --- a/chromium/chrome/browser/resources/print_preview/new/preview_area.js +++ b/chromium/chrome/browser/resources/print_preview/new/preview_area.js @@ -34,20 +34,23 @@ cr.exportPath('print_preview_new'); */ print_preview_new.PDFPlugin; -/** - * Constant values matching printing::DuplexMode enum. - * @enum {number} - */ -print_preview_new.DuplexMode = { - SIMPLEX: 0, - LONG_EDGE: 1, - UNKNOWN_DUPLEX_MODE: -1 +(function() { +'use strict'; + +/** @enum {string} */ +const PreviewAreaState_ = { + LOADING: 'loading', + DISPLAY_PREVIEW: 'display-preview', + OPEN_IN_PREVIEW: 'open-in-preview', + INVALID_SETTINGS: 'invalid-settings', + PREVIEW_FAILED: 'preview-failed', }; Polymer({ is: 'print-preview-preview-area', - behaviors: [WebUIListenerBehavior, SettingsBehavior], + behaviors: [WebUIListenerBehavior, SettingsBehavior, I18nBehavior], + properties: { /** @type {print_preview.DocumentInfo} */ documentInfo: Object, @@ -55,10 +58,17 @@ Polymer({ /** @type {print_preview.Destination} */ destination: Object, - /** @type {print_preview_new.State} */ + /** @type {!print_preview_new.State} */ state: { - type: Object, + type: Number, + observer: 'onStateChanged_', + }, + + /** @private {string} */ + previewState_: { + type: String, notify: true, + value: PreviewAreaState_.LOADING, }, }, @@ -68,9 +78,7 @@ Polymer({ 'settings.layout.value, settings.margins.value, ' + 'settings.mediaSize.value, settings.ranges.value,' + 'settings.selectionOnly.value, settings.scaling.value, ' + - 'destination.id, destination.capabilities, state.initialized)', - 'onPreviewStateChanged_(state.previewLoading, state.invalidSettings, ' + - 'state.previewFailed)', + 'settings.rasterize.value, destination.id, destination.capabilities)', ], /** @private {print_preview.NativeLayer} */ @@ -79,13 +87,16 @@ Polymer({ /** @private {number} */ inFlightRequestId_: -1, + /** @private {boolean} */ + requestPreviewWhenReady_: false, + /** @private {HTMLEmbedElement|print_preview_new.PDFPlugin} */ plugin_: null, - /** @private {boolean} */ + /** @private {boolean} Whether the plugin is loaded */ pluginLoaded_: false, - /** @private {boolean} */ + /** @private {boolean} Whether the document is ready */ documentReady_: false, /** @override */ @@ -102,59 +113,119 @@ Polymer({ this.$$('.preview-area-compatibility-object-out-of-process'); const isOOPCompatible = oopCompatObj.postMessage; oopCompatObj.parentElement.removeChild(oopCompatObj); - if (!isOOPCompatible) - this.set('state.previewFailed', true); - else - this.set('state.previewLoading', true); + if (!isOOPCompatible) { + this.previewState_ = PreviewAreaState_.PREVIEW_FAILED; + this.fire('preview-failed'); + } + }, + + /** @return {boolean} Whether the preview is loaded. */ + previewLoaded: function() { + return this.previewState_ == PreviewAreaState_.DISPLAY_PREVIEW; }, /** @private */ onSettingsChanged_: function() { - if (!this.state.initialized || !this.getSetting('scaling').valid || - !this.getSetting('pages').valid || !this.getSetting('copies').valid || - !this.destination || !this.destination.capabilities) { + if (this.state == print_preview_new.State.READY) { + this.startPreview_(); return; } + this.requestPreviewWhenReady_ = true; + }, + + /** + * @return {string} 'invisible' if overlay is invisible, '' otherwise. + * @private + */ + getInvisible_: function() { + return this.previewLoaded() ? 'invisible' : ''; + }, + + /** + * @return {boolean} Whether the preview is currently loading. + * @private + */ + isPreviewLoading_: function() { + return this.previewState_ == PreviewAreaState_.LOADING; + }, + + /** + * @return {string} 'jumping-dots' to enable animation, '' otherwise. + * @private + */ + getJumpingDots_: function() { + return this.isPreviewLoading_() ? 'jumping-dots' : ''; + }, + + /** + * @return {boolean} Whether the system dialog button should be shown. + * @private + */ + displaySystemDialogButton_: function() { + return this.previewState_ == PreviewAreaState_.INVALID_SETTINGS || + this.previewState_ == PreviewAreaState_.OPEN_IN_PREVIEW; + }, + + /** + * @return {string} The current preview area message to display. + * @private + */ + currentMessage_: function() { + if (this.previewState_ == PreviewAreaState_.LOADING) + return this.i18n('loading'); + if (this.previewState_ == PreviewAreaState_.OPEN_IN_PREVIEW) + return this.i18n('openPdfInPreview'); + if (this.previewState_ == PreviewAreaState_.INVALID_SETTINGS) + return this.i18n('invalidSettings'); + if (this.previewState_ == PreviewAreaState_.PREVIEW_FAILED) + return this.i18n('previewFailed'); + return ''; + }, + + /** @private */ + startPreview_: function() { + this.previewState_ = PreviewAreaState_.LOADING; this.documentReady_ = false; - this.set('state.previewLoading', true); this.getPreview_().then( previewUid => { if (!this.documentInfo.isModifiable) this.onPreviewStart_(previewUid, -1); this.documentReady_ = true; - if (this.pluginLoaded_) - this.set('state.previewLoading', false); + if (this.pluginLoaded_) { + this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW; + this.fire('preview-loaded'); + } }, type => { if (/** @type{string} */ (type) == 'SETTINGS_INVALID') - this.set('state.invalidSettings', true); - else if (/** @type{string} */ (type) != 'CANCELLED') - this.set('state.previewFailed', true); - this.set('state.previewLoading', false); + this.previewState_ = PreviewAreaState_.INVALID_SETTINGS; + else if (/** @type{string} */ (type) != 'CANCELLED') { + this.previewState_ = PreviewAreaState_.PREVIEW_FAILED; + this.fire('preview-failed'); + } }); }, - /** - * Set the visibility of the message overlay. - * @param {boolean} visible Whether to make the overlay visible or not - * @private - */ - setOverlayVisible_: function(visible) { - const overlayEl = this.$$('.preview-area-overlay-layer'); - overlayEl.classList.toggle('invisible', !visible); - overlayEl.setAttribute('aria-hidden', !visible); - }, - /** @private */ - onPreviewStateChanged_: function() { - // update the appearance here. - const visible = this.state.previewLoading || this.state.previewFailed || - this.state.invalidSettings; - this.setOverlayVisible_(visible); - - // Disable jumping animation to conserve cycles. - const jumpingDotsEl = this.$$('.preview-area-loading-message-jumping-dots'); - jumpingDotsEl.classList.toggle('jumping-dots', this.state.previewLoading); + onStateChanged_: function() { + switch (this.state) { + case (print_preview_new.State.NOT_READY): + // Resetting the destination clears the invalid settings error. + this.previewState_ = PreviewAreaState_.LOADING; + break; + case (print_preview_new.State.READY): + // Request a new preview. + if (this.requestPreviewWhenReady_) { + this.startPreview_(); + this.requestPreviewWhenReady_ = false; + } + break; + case (print_preview_new.State.INVALID_PRINTER): + this.previewState_ = PreviewAreaState_.INVALID_SETTINGS; + break; + default: + break; + } }, /** @@ -240,8 +311,10 @@ Polymer({ */ onPluginLoad_: function() { this.pluginLoaded_ = true; - if (this.documentReady_) - this.set('state.previewLoading', false); + if (this.documentReady_) { + this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW; + this.fire('preview-loaded'); + } }, /** @@ -345,7 +418,7 @@ Polymer({ printWithCloudPrint: !this.destination.isLocal, printWithPrivet: this.destination.isPrivet, printWithExtension: this.destination.isExtension, - rasterizePDF: false, + rasterizePDF: this.getSettingValue('rasterize'), }; // Set 'cloudPrintID' only if the this.destination is not local. @@ -372,3 +445,4 @@ Polymer({ return this.nativeLayer_.getPreview(JSON.stringify(ticket), pageCount); }, }); +})(); diff --git a/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.html b/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.html new file mode 100644 index 00000000000..50e421bb32b --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.html @@ -0,0 +1,41 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field_behavior.html"> +<link rel="import" href="print_preview_shared_css.html"> + +<dom-module id="print-preview-search-box"> + <template> + <style include="print-preview-shared"> + :host { + display: flex; + position: relative; + user-select: none; + } + + .search-box-icon { + display: inline-block; + height: 1em; + left: 8px; + position: absolute; + right: 8px; + top: 6px; + user-select: none; + width: 1em; + } + + .search-box-input { + text-indent: 2em; + width: 100%; + } + + .search-box-input::-webkit-search-cancel-button { + -webkit-appearance: none; + } + </style> + <img src="../images/search.png" class="search-box-icon" alt=""> + <input type="search" id="searchInput" class="search-box-input" + on-search="onSearchTermSearch" on-input="onSearchTermInput" + incremental aria-label$="[[label]]" placeholder="[[label]]"> + </template> + <script src="print_preview_search_box.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.js b/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.js new file mode 100644 index 00000000000..c8890137541 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/print_preview_search_box.js @@ -0,0 +1,42 @@ +// 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() { +'use strict'; + +/** @type {!RegExp} */ +const SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g; + +Polymer({ + is: 'print-preview-search-box', + + behaviors: [CrSearchFieldBehavior], + + properties: { + /** @type {?RegExp} */ + searchQuery: { + type: Object, + notify: true, + }, + }, + + listeners: { + 'search-changed': 'onSearchChanged_', + }, + + /** @return {!HTMLInputElement} */ + getSearchInput: function() { + return this.$.searchInput; + }, + + /** + * @param {!CustomEvent} e Event containing the new search. + */ + onSearchChanged_: function(e) { + const safeQuery = e.detail.trim().replace(SANITIZE_REGEX, '\\$&'); + this.searchQuery = + safeQuery.length > 0 ? new RegExp(`(${safeQuery})`, 'i') : null; + }, +}); +})(); diff --git a/chromium/chrome/browser/resources/print_preview/new/scaling_settings.html b/chromium/chrome/browser/resources/print_preview/new/scaling_settings.html index fbb0f21462f..6fc479afebf 100644 --- a/chromium/chrome/browser/resources/print_preview/new/scaling_settings.html +++ b/chromium/chrome/browser/resources/print_preview/new/scaling_settings.html @@ -12,12 +12,14 @@ </style> <print-preview-number-settings-section max-value=200 min-value=10 default-value="100" input-label="$i18n{scalingLabel}" - input-string="{{inputString_}}" input-valid="{{inputValid_}}" - hint-message="$i18n{scalingInstruction}" class="multirow-controls"> + disabled="[[disabled]]" current-value="{{currentValue_}}" + input-valid="{{inputValid_}}" hint-message="$i18n{scalingInstruction}" + class="multirow-controls"> <div slot="opt-outside-content" class="checkbox" hidden$="[[!settings.fitToPage.available]]"> <label aria-live="polite"> <input type="checkbox" id="fit-to-page-checkbox" + disabled$="[[getDisabled_(disabled, inputValid_)]]" on-change="onFitToPageChange_"> <span>$i18n{optionFitToPage}</span> </label> diff --git a/chromium/chrome/browser/resources/print_preview/new/scaling_settings.js b/chromium/chrome/browser/resources/print_preview/new/scaling_settings.js index 8117771923e..4588514c6a8 100644 --- a/chromium/chrome/browser/resources/print_preview/new/scaling_settings.js +++ b/chromium/chrome/browser/resources/print_preview/new/scaling_settings.js @@ -12,10 +12,12 @@ Polymer({ documentInfo: Object, /** @private {string} */ - inputString_: String, + currentValue_: String, /** @private {boolean} */ inputValid_: Boolean, + + disabled: Boolean, }, /** @private {string} */ @@ -27,7 +29,7 @@ Polymer({ observers: [ 'onFitToPageSettingChange_(settings.fitToPage.value, ' + 'settings.fitToPage.available, documentInfo.fitToPageScaling)', - 'onInputChanged_(inputString_, inputValid_)', + 'onInputChanged_(currentValue_, inputValid_)', 'onScalingSettingChanged_(settings.scaling.value)', ], @@ -39,13 +41,13 @@ Polymer({ this.$$('#fit-to-page-checkbox').checked = fitToPage.value; if (!fitToPage.value) { // Fit to page is no longer checked. Update the display. - this.inputString_ = this.lastValidScaling_; + this.currentValue_ = this.lastValidScaling_; } else if (fitToPage.value) { // Set flag to number of expected calls to onInputChanged_. If scaling - // is valid, 1 call will occur due to the change to |inputString_|. If + // is valid, 1 call will occur due to the change to |currentValue_|. If // not, 2 calls will occur, since |inputValid_| will also change. this.fitToPageFlag_ = this.inputValid_ ? 1 : 2; - this.inputString_ = this.documentInfo.fitToPageScaling; + this.currentValue_ = this.documentInfo.fitToPageScaling; } }, @@ -57,7 +59,7 @@ Polymer({ // Update last valid scaling and ensure input string matches. this.lastValidScaling_ = /** @type {string} */ (this.getSetting('scaling').value); - this.inputString_ = this.lastValidScaling_; + this.currentValue_ = this.lastValidScaling_; }, /** @@ -70,8 +72,9 @@ Polymer({ if (fitToPage && this.fitToPageFlag_ == 0) { // User modified scaling while fit to page was checked. Uncheck fit to // page. - if (this.inputValid_) - this.setSetting('scaling', this.inputString_); + const wasValid = this.getSetting('scaling').valid; + if (this.inputValid_ && wasValid) + this.setSetting('scaling', this.currentValue_); else this.setSettingValid('scaling', false); this.$$('#fit-to-page-checkbox').checked = false; @@ -83,9 +86,10 @@ Polymer({ } else { // User modified scaling while fit to page was not checked or // scaling setting was set. + const wasValid = this.getSetting('scaling').valid; this.setSettingValid('scaling', this.inputValid_); - if (this.inputValid_) - this.setSetting('scaling', this.inputString_); + if (this.inputValid_ && wasValid) + this.setSetting('scaling', this.currentValue_); } }, @@ -93,4 +97,12 @@ Polymer({ onFitToPageChange_: function() { this.setSetting('fitToPage', this.$$('#fit-to-page-checkbox').checked); }, + + /** + * @return {boolean} Whether the input should be disabled. + * @private + */ + getDisabled_: function() { + return this.disabled && this.inputValid_; + }, }); diff --git a/chromium/chrome/browser/resources/print_preview/new/search_dialog_css.html b/chromium/chrome/browser/resources/print_preview/new/search_dialog_css.html new file mode 100644 index 00000000000..6ec25973375 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/search_dialog_css.html @@ -0,0 +1,48 @@ +<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="button_css.html"> +<link rel="import" href="print_preview_search_box.html"> +<link rel="import" href="print_preview_shared_css.html"> + +<dom-module id="search-dialog"> + <template> + <style include="print-preview-shared button"> + #dialog::backdrop { + background-color: rgba(255, 255, 255, 0.75); + } + + #dialog { + --cr-dialog-close-image: { + background-image: url(chrome://theme/IDR_CLOSE_DIALOG); + } + --cr-dialog-close-image-active: { + background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P); + } + --cr-dialog-close-image-hover: { + background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H); + } + --cr-icon-ripple-size: 0; + --cr-icon-size: 14px; + --cr-dialog-body: { + box-sizing: border-box; + padding-top: 0; + } + --cr-dialog-wrapper: { + max-height: calc(100vh - 40px); + } + box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), + 0 2px 6px rgba(0, 0, 0, 0.15); + } + + #searchBox { + font-size: calc(13/15 * 1em); + margin-top: 14px; + } + + #body { + height: 100vh; + } + </style> + </template> +</dom-module> diff --git a/chromium/chrome/browser/resources/print_preview/new/select_css.html b/chromium/chrome/browser/resources/print_preview/new/select_css.html index 5a316158da1..04838c08b04 100644 --- a/chromium/chrome/browser/resources/print_preview/new/select_css.html +++ b/chromium/chrome/browser/resources/print_preview/new/select_css.html @@ -21,14 +21,14 @@ select:enabled:hover { background-image: url(chrome://resources/images/select.png), linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); - @apply(--print-preview-hover); + @apply --print-preview-hover; } </if> select:enabled:active { background-image: url(chrome://resources/images/select.png), linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); - @apply(--print-preview-active); + @apply --print-preview-active; } select:disabled { diff --git a/chromium/chrome/browser/resources/print_preview/new/settings_behavior.js b/chromium/chrome/browser/resources/print_preview/new/settings_behavior.js index aa4c95dc575..b28e5fcd8ae 100644 --- a/chromium/chrome/browser/resources/print_preview/new/settings_behavior.js +++ b/chromium/chrome/browser/resources/print_preview/new/settings_behavior.js @@ -91,6 +91,8 @@ const SettingsBehavior = { // is no way for the user to change the value in this case. if (!valid) assert(setting.available, 'Setting is not available: ' + settingName); + if (valid != setting.valid) + this.fire('setting-valid-changed', valid); this.set(`settings.${settingName}.valid`, valid); } }; diff --git a/chromium/chrome/browser/resources/print_preview/new/settings_select.html b/chromium/chrome/browser/resources/print_preview/new/settings_select.html index 0525c00e2b5..9b867bec3b0 100644 --- a/chromium/chrome/browser/resources/print_preview/new/settings_select.html +++ b/chromium/chrome/browser/resources/print_preview/new/settings_select.html @@ -9,9 +9,10 @@ <template> <style include="print-preview-shared select"> </style> - <select on-change="onChange_"> + <select on-change="onChange_" disabled$="[[disabled]]"> <template is="dom-repeat" items="[[capability.option]]"> - <option selected="[[item.is_default]]" value="[[getValue_(item)]]"> + <option selected="[[isSelected_(item, selectedValue_)]]" + value="[[getValue_(item)]]"> [[getDisplayName_(item)]] </option> </template> diff --git a/chromium/chrome/browser/resources/print_preview/new/settings_select.js b/chromium/chrome/browser/resources/print_preview/new/settings_select.js index aba0680710a..4877620f573 100644 --- a/chromium/chrome/browser/resources/print_preview/new/settings_select.js +++ b/chromium/chrome/browser/resources/print_preview/new/settings_select.js @@ -23,13 +23,31 @@ Polymer({ /** @type {{ option: Array<!print_preview_new.SelectOption> }} */ capability: Object, - /** @type {string} */ settingName: String, + + disabled: Boolean, + + /** @private {string} */ + selectedValue_: { + type: String, + notify: true, + value: '', + }, + }, + + /** + * @param {!print_preview_new.SelectOption} option Option to check. + * @return {boolean} Whether the option is selected. + * @private + */ + isSelected_: function(option) { + return this.getValue_(option) == this.selectedValue_ || + (!!option.is_default && this.selectedValue_ == ''); }, /** @param {string} value The value to select. */ selectValue: function(value) { - this.$$('select').value = value; + this.selectedValue_ = value; }, /** diff --git a/chromium/chrome/browser/resources/print_preview/new/state.html b/chromium/chrome/browser/resources/print_preview/new/state.html new file mode 100644 index 00000000000..12da96dc34d --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/new/state.html @@ -0,0 +1,5 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/html/cr.html"> + +<script src="state.js"></script> diff --git a/chromium/chrome/browser/resources/print_preview/new/state.js b/chromium/chrome/browser/resources/print_preview/new/state.js index 99cb889343e..26d1a636918 100644 --- a/chromium/chrome/browser/resources/print_preview/new/state.js +++ b/chromium/chrome/browser/resources/print_preview/new/state.js @@ -1,18 +1,68 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// 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('print_preview_new'); -/** - * @typedef {{ - * previewLoading: boolean, - * previewFailed: boolean, - * cloudPrintError: string, - * privetExtensionError: string, - * invalidSettings: boolean, - * initialized: boolean, - * cancelled: boolean, - * }} - */ -print_preview_new.State; +/** @enum {number} */ +print_preview_new.State = { + NOT_READY: 0, + READY: 1, + HIDDEN: 2, + PRINTING: 3, + INVALID_TICKET: 4, + INVALID_PRINTER: 5, + FATAL_ERROR: 6, + CLOSING: 7, +}; + +Polymer({ + is: 'print-preview-state', + + properties: { + /** @type {print_preview_new.State} */ + state: { + type: Number, + notify: true, + value: print_preview_new.State.NOT_READY, + }, + }, + + /** @param {print_preview_new.State} newState The state to transition to. */ + transitTo: function(newState) { + switch (newState) { + case (print_preview_new.State.NOT_READY): + assert( + this.state == print_preview_new.State.NOT_READY || + this.state == print_preview_new.State.READY || + this.state == print_preview_new.State.INVALID_PRINTER); + break; + case (print_preview_new.State.READY): + assert( + this.state == print_preview_new.State.INVALID_TICKET || + this.state == print_preview_new.State.NOT_READY || + this.state == print_preview_new.State.PRINTING); + break; + case (print_preview_new.State.HIDDEN): + assert(this.state == print_preview_new.State.READY); + break; + case (print_preview_new.State.PRINTING): + assert( + this.state == print_preview_new.State.READY || + this.state == print_preview_new.State.HIDDEN); + break; + case (print_preview_new.State.INVALID_TICKET): + assert(this.state == print_preview_new.State.READY); + break; + case (print_preview_new.State.INVALID_PRINTER): + assert( + this.state == print_preview_new.State.NOT_READY || + this.state == print_preview_new.State.READY); + break; + case (print_preview_new.State.CLOSING): + assert(this.state != print_preview_new.State.HIDDEN); + break; + } + this.state = newState; + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/preview_generator.js b/chromium/chrome/browser/resources/print_preview/preview_generator.js index 4cfb3d90ede..832529cca5f 100644 --- a/chromium/chrome/browser/resources/print_preview/preview_generator.js +++ b/chromium/chrome/browser/resources/print_preview/preview_generator.js @@ -101,6 +101,12 @@ cr.define('print_preview', function() { this.scalingValue_ = 100; /** + * Whether the PDF should be rasterized for printing. + * @private {boolean} + */ + this.rasterize_ = false; + + /** * Page ranges setting used used to generate the last preview. * @private {Array<{from: number, to: number}>} */ @@ -176,6 +182,7 @@ cr.define('print_preview', function() { this.printTicketStore_.headerFooter.getValue(); this.colorValue_ = this.printTicketStore_.color.getValue(); this.isFitToPageEnabled_ = this.printTicketStore_.fitToPage.getValue(); + this.rasterize_ = this.printTicketStore_.rasterize.getValue(); this.scalingValue_ = this.printTicketStore_.scaling.getValueAsNumber(); this.pageRanges_ = this.printTicketStore_.pageRange.getPageRanges(); this.marginsType_ = this.printTicketStore_.marginsType.getValue(); @@ -240,7 +247,7 @@ cr.define('print_preview', function() { printWithCloudPrint: !destination.isLocal, printWithPrivet: destination.isPrivet, printWithExtension: destination.isExtension, - rasterizePDF: false, + rasterizePDF: printTicketStore.rasterize.getValue(), shouldPrintBackgrounds: printTicketStore.cssBackground.getValue(), shouldPrintSelectionOnly: printTicketStore.selectionOnly.getValue() }; @@ -321,6 +328,7 @@ cr.define('print_preview', function() { !ticketStore.color.isValueEqual(this.colorValue_) || !ticketStore.scaling.isValueEqual(this.scalingValue_) || !ticketStore.fitToPage.isValueEqual(this.isFitToPageEnabled_) || + !ticketStore.rasterize.isValueEqual(this.rasterize_) || (!ticketStore.marginsType.isValueEqual(this.marginsType_) && !ticketStore.marginsType.isValueEqual( print_preview.ticket_items.MarginsTypeValue.CUSTOM)) || diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css index a6ce9ed4fae..fd272fa1824 100644 --- a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css +++ b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css @@ -80,7 +80,6 @@ } #preview-area .learn-more-link { - -webkit-margin-start: 0.5em; color: rgb(51, 103, 214); } diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js index d1cb9dedd2d..89ceb0d1654 100644 --- a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js +++ b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js @@ -81,7 +81,8 @@ cr.define('print_preview', function() { this.printTicketStore_ = printTicketStore; /** - * Used to contruct the preview generator. + * Used to construct the preview generator and to open the GCP learn more + * help link. * @type {!print_preview.NativeLayer} * @private */ @@ -149,6 +150,13 @@ cr.define('print_preview', function() { this.isDocumentReady_ = false; /** + * Whether the current destination is valid. + * @type {boolean} + * @private + */ + this.isDestinationValid_ = true; + + /** * Timeout object used to display a loading message if the preview is taking * a long time to generate. * @type {?number} @@ -213,7 +221,8 @@ cr.define('print_preview', function() { 'preview-area-open-system-dialog-button-throbber', OVERLAY: 'preview-area-overlay-layer', MARGIN_CONTROL: 'margin-control', - PREVIEW_AREA: 'preview-area-plugin-wrapper' + PREVIEW_AREA: 'preview-area-plugin-wrapper', + GCP_ERROR_LEARN_MORE_LINK: 'learn-more-link' }; /** @@ -321,6 +330,16 @@ cr.define('print_preview', function() { this.showMessage_(print_preview.PreviewAreaMessageId_.CUSTOM, message); }, + /** @param {boolean} valid Whether the current destination is valid. */ + setDestinationValid: function(valid) { + this.isDestinationValid_ = valid; + // If destination is valid and preview is ready, hide the error message. + if (valid && this.isPluginReloaded_ && this.isDocumentReady_) { + this.setOverlayVisible_(false); + this.dispatchPreviewGenerationDoneIfReady_(); + } + }, + /** @override */ enterDocument: function() { print_preview.Component.prototype.enterDocument.call(this); @@ -328,6 +347,9 @@ cr.define('print_preview', function() { this.tracker.add( assert(this.openSystemDialogButton_), 'click', this.onOpenSystemDialogButtonClick_.bind(this)); + this.tracker.add( + assert(this.gcpErrorLearnMoreLink_), 'click', + this.onGcpErrorLearnMoreClick_.bind(this)); const TicketStoreEvent = print_preview.PrintTicketStore.EventType; [TicketStoreEvent.INITIALIZE, TicketStoreEvent.CAPABILITIES_CHANGE, @@ -377,6 +399,7 @@ cr.define('print_preview', function() { print_preview.Component.prototype.exitDocument.call(this); this.overlayEl_ = null; this.openSystemDialogButton_ = null; + this.gcpErrorLearnMoreLink_ = null; }, /** @override */ @@ -386,6 +409,8 @@ cr.define('print_preview', function() { PreviewArea.Classes_.OVERLAY)[0]; this.openSystemDialogButton_ = this.getElement().getElementsByClassName( PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0]; + this.gcpErrorLearnMoreLink_ = this.getElement().getElementsByClassName( + PreviewArea.Classes_.GCP_ERROR_LEARN_MORE_LINK)[0]; }, /** @@ -526,11 +551,22 @@ cr.define('print_preview', function() { }, /** + * Called when the learn more link for a cloud destination with an invalid + * certificate is clicked. Calls nativeLayer to open a new tab with the help + * page. + * @private + */ + onGcpErrorLearnMoreClick_: function() { + this.nativeLayer_.forceOpenNewTab( + loadTimeData.getString('gcpCertificateErrorLearnMoreURL')); + }, + + /** * Called when the print ticket changes. Updates the preview. * @private */ onTicketChange_: function() { - if (!this.previewGenerator_) + if (!this.previewGenerator_ || !this.isDestinationValid_) return; const previewRequest = this.previewGenerator_.requestPreview(); if (previewRequest.id <= -1) { diff --git a/chromium/chrome/browser/resources/print_preview/print_preview.js b/chromium/chrome/browser/resources/print_preview/print_preview.js index 918f2ad7db4..ce0d488008a 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview.js +++ b/chromium/chrome/browser/resources/print_preview/print_preview.js @@ -1026,6 +1026,7 @@ cr.define('print_preview', function() { this.uiState_ = PrintPreviewUiState_.ERROR; this.isPreviewGenerationInProgress_ = false; this.printHeader_.isPrintButtonEnabled = false; + this.previewArea_.setDestinationValid(false); }, /** @@ -1311,8 +1312,10 @@ cr.define('print_preview', function() { } // Reset if we had a bad settings fetch since the user selected a new // printer. - if (this.uiState_ == PrintPreviewUiState_.ERROR) + if (this.uiState_ == PrintPreviewUiState_.ERROR) { this.uiState_ = PrintPreviewUiState_.READY; + this.previewArea_.setDestinationValid(true); + } if (this.destinationStore_.selectedDestination && this.isInKioskAutoPrintMode_) { this.onPrintButtonClick_(); diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_new.html b/chromium/chrome/browser/resources/print_preview/print_preview_new.html index 303438ada23..b7a82538e9b 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_new.html +++ b/chromium/chrome/browser/resources/print_preview/print_preview_new.html @@ -1,11 +1,14 @@ <!doctype html> <html dir="$i18n{textdirection}" lang="$i18n{language}"> <head> -<meta charset="utf-8"> + <meta charset="utf-8"> <title></title> -</head> -<body> <style> + html { + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; + } + html, body { height: 100%; @@ -14,6 +17,8 @@ width: 100%; } </style> +</head> +<body> <print-preview-app></print-preview-app> <link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> <link rel="import" href="new/app.html"> diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd b/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd index 943dce160ce..089ce3ff322 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd +++ b/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd @@ -29,6 +29,12 @@ <structure name="IDR_PRINT_PREVIEW_NEW_MODEL_JS" file="new/model.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_CLOUD_PRINT_INTERFACE_HTML" + file="cloud_print_interface.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_CLOUD_PRINT_INTERFACE_JS" + file="cloud_print_interface.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_NATIVE_LAYER_HTML" file="native_layer.html" type="chrome_html" /> @@ -53,12 +59,24 @@ <structure name="IDR_PRINT_PREVIEW_DATA_DESTINATION_STORE_JS" file="data/destination_store.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_DATA_CLOUD_PARSERS_HTML" + file="data/cloud_parsers.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_DATA_CLOUD_PARSERS_JS" + file="data/cloud_parsers.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_DATA_LOCAL_PARSERS_HTML" file="data/local_parsers.html" type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_DATA_LOCAL_PARSERS_JS" file="data/local_parsers.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_DATA_INVITATION_HTML" + file="data/invitation.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_DATA_INVITATION_JS" + file="data/invitation.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_DATA_MARGINS_HTML" file="data/margins.html" type="chrome_html" /> @@ -134,6 +152,12 @@ <structure name="IDR_PRINT_PREVIEW_NEW_SETTINGS_BEHAVIOR_JS" file="new/settings_behavior.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_STATE_HTML" + file="new/state.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_STATE_JS" + file="new/state.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_NEW_SETTINGS_SECTION_HTML" file="new/settings_section.html" type="chrome_html" /> @@ -212,12 +236,57 @@ <structure name="IDR_PRINT_PREVIEW_NEW_ADVANCED_OPTIONS_SETTINGS_JS" file="new/advanced_options_settings.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_ADVANCED_SETTINGS_DIALOG_HTML" + file="new/advanced_settings_dialog.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_ADVANCED_SETTINGS_DIALOG_JS" + file="new/advanced_settings_dialog.js" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_ADVANCED_SETTINGS_ITEM_HTML" + file="new/advanced_settings_item.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_ADVANCED_SETTINGS_ITEM_JS" + file="new/advanced_settings_item.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_NEW_NUMBER_SETTINGS_SECTION_HTML" file="new/number_settings_section.html" type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_NEW_NUMBER_SETTINGS_SECTION_JS" file="new/number_settings_section.js" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_DIALOG_HTML" + file="new/destination_dialog.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_DIALOG_JS" + file="new/destination_dialog.js" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_LIST_HTML" + file="new/destination_list.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_LIST_JS" + file="new/destination_list.js" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_LIST_ITEM_HTML" + file="new/destination_list_item.html" + type="chrome_html" + preprocess="true" /> + <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_LIST_ITEM_JS" + file="new/destination_list_item.js" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_PRINT_PREVIEW_SEARCH_BOX_HTML" + file="new/print_preview_search_box.html" + type="chrome_html" + flattenhtml="true" + allowexternalscript="true" /> + <structure name="IDR_PRINT_PREVIEW_NEW_PRINT_PREVIEW_SEARCH_BOX_JS" + file="new/print_preview_search_box.js" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_HIGHLIGHT_UTILS_HTML" + file="new/highlight_utils.html" + type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_NEW_HIGHLIGHT_UTILS_JS" + file="new/highlight_utils.js" + type="chrome_html" /> <structure name="IDR_PRINT_PREVIEW_NEW_PRINT_PREVIEW_SHARED_CSS_HTML" file="new/print_preview_shared_css.html" type="chrome_html" @@ -240,6 +309,9 @@ <structure name="IDR_PRINT_PREVIEW_NEW_THROBBER_CSS_HTML" file="new/throbber_css.html" type="chrome_html"/> + <structure name="IDR_PRINT_PREVIEW_NEW_SEARCH_DIALOG_CSS_HTML" + file="new/search_dialog_css.html" + type="chrome_html"/> <structure name="IDR_PRINT_PREVIEW_NEW_STRINGS_HTML" file="new/strings.html" type="chrome_html" /> diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_utils.js b/chromium/chrome/browser/resources/print_preview/print_preview_utils.js index cd931980ba4..0220810fe82 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_utils.js +++ b/chromium/chrome/browser/resources/print_preview/print_preview_utils.js @@ -114,7 +114,7 @@ function pageRangeTextToPageRanges(pageRangeText, opt_totalPageCount) { opt_totalPageCount ? opt_totalPageCount : MAX_PAGE_NUMBER; const regex = /^\s*([0-9]*)\s*-\s*([0-9]*)\s*$/; - const parts = pageRangeText.split(/,/); + const parts = pageRangeText.split(/,|\u3001/); const pageRanges = []; for (let i = 0; i < parts.length; ++i) { diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs b/chromium/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs index ff15d6fa61e..17fa25be71e 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs +++ b/chromium/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs @@ -86,6 +86,12 @@ TEST_F('PrintPreviewUtilsUnitTest', 'PageRanges', function() { pageRangeTextToPageRanges("-", null)); assertRangesEqual([[1, 1000000000]], pageRangeTextToPageRanges("-", 0)); + + // https://crbug.com/806165 + assertRangesEqual([1, 2, 3, 1, 56], + pageRangeTextToPageRanges("1\u30012\u30013\u30011\u300156", 100)); + assertRangesEqual([1, 2, 3, 1, 56], + pageRangeTextToPageRanges("1,2,3\u30011\u300156", 100)); }); TEST_F('PrintPreviewUtilsUnitTest', 'InvalidPageRanges', function() { @@ -101,6 +107,11 @@ TEST_F('PrintPreviewUtilsUnitTest', 'InvalidPageRanges', function() { pageRangeTextToPageRanges("1,2,56-40", 100)); assertEquals(PageRangeStatus.LIMIT_ERROR, pageRangeTextToPageRanges("101-110", 100)); + + assertEquals(PageRangeStatus.SYNTAX_ERROR, + pageRangeTextToPageRanges("1\u30012\u30010\u300156", 100)); + assertEquals(PageRangeStatus.SYNTAX_ERROR, + pageRangeTextToPageRanges("-1,1,2\u3001\u300156", 100)); }); TEST_F('PrintPreviewUtilsUnitTest', 'PageRangeTextToPageList', function() { diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js index c6ef0c7b846..e31d26c99c1 100644 --- a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js +++ b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js @@ -72,6 +72,9 @@ cr.define('print_preview', function() { this.tracker.add( this.getChildElement('.register-promo-button'), 'click', this.onRegisterPromoClicked_.bind(this)); + this.tracker.add( + this.getChildElement('.learn-more-link'), 'click', + this.onGcpErrorLearnMoreClick_.bind(this)); }, /** @return {!print_preview.Destination} */ @@ -199,15 +202,10 @@ cr.define('print_preview', function() { // Initialize the element which renders the destination's connection // status. this.getElement().classList.toggle( - 'stale', - this.destination_.isOffline || - this.destination_.shouldShowInvalidCertificateError); + 'stale', this.destination_.isOfflineOrInvalid); const connectionStatusEl = this.getChildElement('.connection-status'); connectionStatusEl.textContent = this.destination_.connectionStatusText; - setIsVisible( - connectionStatusEl, - this.destination_.isOffline || - this.destination_.shouldShowInvalidCertificateError); + setIsVisible(connectionStatusEl, this.destination_.isOfflineOrInvalid); setIsVisible( this.getChildElement('.learn-more-link'), this.destination_.shouldShowInvalidCertificateError); @@ -324,6 +322,16 @@ cr.define('print_preview', function() { }, /** + * Called when the learn more link for an unsupported cloud destination is + * clicked. Opens the help page via native layer. + * @private + */ + onGcpErrorLearnMoreClick_: function() { + print_preview.NativeLayer.getInstance().forceOpenNewTab( + loadTimeData.getString('gcpCertificateErrorLearnMoreURL')); + }, + + /** * Handles click and 'Enter' key down events for the extension icon element. * It opens extensions page with the extension associated with the * destination highlighted. diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.css b/chromium/chrome/browser/resources/print_preview/search/destination_search.css index fd7fa54103b..8eddc19d078 100644 --- a/chromium/chrome/browser/resources/print_preview/search/destination_search.css +++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.css @@ -89,6 +89,10 @@ -webkit-margin-start: 10px; } +#destination-search .invitation-cloud-print-information { + padding-top: 12px; +} + #destination-search #invitation-process-throbber { display: block; } diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.html b/chromium/chrome/browser/resources/print_preview/search/destination_search.html index 7d3c4c325e0..554855e835c 100644 --- a/chromium/chrome/browser/resources/print_preview/search/destination_search.html +++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.html @@ -23,6 +23,9 @@ <button class="invitation-reject-button">$i18n{reject}</button> <div id="invitation-process-throbber" class="throbber" hidden></div> </div> + <div class="invitation-cloud-print-information"> + $i18nRaw{registerPrinterInformationMessage} + </div> </div> <div class="cloudprint-promo gray-bottom-bar" hidden> <img src="../images/cloud.png" class="icon" alt=""> diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.js b/chromium/chrome/browser/resources/print_preview/search/destination_search.js index 2262082c5eb..6235d6f3c12 100644 --- a/chromium/chrome/browser/resources/print_preview/search/destination_search.js +++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.js @@ -791,7 +791,7 @@ cr.define('print_preview', function() { */ onWindowResize_: function() { this.reflowLists_(); - } + }, }; // Export diff --git a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js index b5ce64999e5..9c54b548ce9 100644 --- a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js +++ b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js @@ -1,24 +1,6 @@ // 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. -/** - * Specifies a custom vendor capability. - * @typedef {{ - * id: (string), - * display_name: (string), - * localized_display_name: (string | undefined), - * type: (string), - * select_cap: ({ - * option: (Array<{ - * display_name: (string), - * type: (string | undefined), - * value: (number | string | boolean), - * is_default: (boolean | undefined) - * }>|undefined) - * }|undefined) - * }} - */ -print_preview.VendorCapability; cr.define('print_preview', function() { 'use strict'; diff --git a/chromium/chrome/browser/resources/print_preview/settings/destination_settings.js b/chromium/chrome/browser/resources/print_preview/settings/destination_settings.js index f8061808678..04b0362e2c4 100644 --- a/chromium/chrome/browser/resources/print_preview/settings/destination_settings.js +++ b/chromium/chrome/browser/resources/print_preview/settings/destination_settings.js @@ -139,15 +139,23 @@ cr.define('print_preview', function() { locationEl.textContent = hint; locationEl.title = hint; - const connectionStatusText = destination.connectionStatusText; + const showDestinationInvalid = + (destination.hasInvalidCertificate && + !loadTimeData.getBoolean('isEnterpriseManaged')); + let connectionStatusText = ''; + if (showDestinationInvalid) { + connectionStatusText = + loadTimeData.getString('noLongerSupportedFragment'); + } else { + connectionStatusText = destination.connectionStatusText; + } const connectionStatusEl = this.getChildElement('.destination-settings-connection-status'); connectionStatusEl.textContent = connectionStatusText; connectionStatusEl.title = connectionStatusText; - const hasConnectionError = destination.isOffline || - (destination.hasInvalidCertificate && - !loadTimeData.getBoolean('isEnterpriseManaged')); + const hasConnectionError = + destination.isOffline || showDestinationInvalid; destinationSettingsBoxEl.classList.toggle( print_preview.DestinationSettingsClasses_.STALE, hasConnectionError); diff --git a/chromium/chrome/browser/resources/safe_browsing/download_file_types.asciipb b/chromium/chrome/browser/resources/safe_browsing/download_file_types.asciipb index 04a91c17835..8634478bccc 100644 --- a/chromium/chrome/browser/resources/safe_browsing/download_file_types.asciipb +++ b/chromium/chrome/browser/resources/safe_browsing/download_file_types.asciipb @@ -8,7 +8,7 @@ ## ## Top level settings ## -version_id: 15 +version_id: 16 sampled_ping_probability: 0.01 max_archived_binaries_to_report: 10 default_file_type { @@ -797,7 +797,7 @@ file_types { file_types { # Opened by uTorrent and Transmission (can be a renamed .torrent) # Added crbug.com/767502 - extension: "btbtskin" + extension: "btskin" uma_value: 299 ping_setting: FULL_PING } diff --git a/chromium/chrome/browser/resources/settings/PRESUBMIT.py b/chromium/chrome/browser/resources/settings/PRESUBMIT.py new file mode 100644 index 00000000000..dcc145b7f91 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/PRESUBMIT.py @@ -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. + + +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 index 8e9979679bd..3714da1774e 100644 --- a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html +++ b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html @@ -24,7 +24,7 @@ pref="{{prefs.settings.a11y.enable_menu}}"> </settings-toggle-button> <div id="subpage-trigger" class="settings-box two-line" - on-tap="onManageAccessibilityFeaturesTap_" actionable> + on-click="onManageAccessibilityFeaturesTap_" actionable> <div class="start"> $i18n{manageAccessibilityFeatures} <div class="secondary" id="themesSecondary"> 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 index 08720eb78c2..59420dcd63f 100644 --- a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html +++ b/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html @@ -16,11 +16,7 @@ -webkit-padding-start: var(--settings-box-row-padding); } - .list-item settings-dropdown-menu { - -webkit-margin-start: 16px; - } - - .sub-item > .start { + .sub-item { -webkit-margin-start: var(--settings-indent-width); } @@ -50,7 +46,7 @@ </settings-toggle-button> <iron-collapse opened="[[prefs.settings.accessibility.value]]"> <div class="settings-box" - on-tap="onChromeVoxSettingsTap_" actionable> + on-click="onChromeVoxSettingsTap_" actionable> <div class="start">$i18n{chromeVoxOptionsLabel}</div> <button class="icon-external" is="paper-icon-button-light" aria-label="$i18n{chromeVoxOptionsLabel}"></button> @@ -63,7 +59,7 @@ </settings-toggle-button> <iron-collapse opened="[[prefs.settings.a11y.select_to_speak.value]]"> <div class="settings-box" - on-tap="onSelectToSpeakSettingsTap_" actionable> + on-click="onSelectToSpeakSettingsTap_" actionable> <div class="start">$i18n{selectToSpeakOptionsLabel}</div> <button class="icon-external" is="paper-icon-button-light" aria-label="$i18n{selectToSpeakOptionsLabel}"></button> @@ -77,9 +73,33 @@ </settings-toggle-button> <settings-toggle-button class="continuation" pref="{{prefs.settings.a11y.screen_magnifier}}" - label="$i18n{screenMagnifierLabel}"> + label="$i18n{screenMagnifierLabel}" + disabled="[[prefs.ash.docked_magnifier.enabled.value]]"> </settings-toggle-button> - <div class="settings-box two-line" on-tap="onDisplayTap_" actionable> + <div class="settings-box continuation"> + <div class="start sub-item">$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 is="dom-if" if="[[dockedMagnifierFeatureEnabled_]]" restamp> + <settings-toggle-button class="continuation" + pref="{{prefs.ash.docked_magnifier.enabled}}" + label="$i18n{dockedMagnifierLabel}" + disabled="[[prefs.settings.a11y.screen_magnifier.value]]"> + </settings-toggle-button> + <div class="settings-box continuation"> + <div class="start sub-item">$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> + <div class="settings-box two-line" on-click="onDisplayTap_" actionable> <div class="start"> $i18n{displaySettingsTitle} <div class="secondary">$i18n{displaySettingsDescription}</div> @@ -88,7 +108,7 @@ aria-label="$i18n{displaySettingsTitle}" aria-describedby="displaySettingsSecondary"></button> </div> - <div class="settings-box two-line" on-tap="onAppearanceTap_" actionable> + <div class="settings-box two-line" on-click="onAppearanceTap_" actionable> <div class="start"> $i18n{appearanceSettingsTitle} <div class="secondary" id="appearanceSettingsSecondary"> @@ -123,13 +143,13 @@ label="$i18n{switchAccessLabel}"> <button is="paper-icon-button-light" class="icon-settings" slot="more-actions" - on-tap="onSwitchAccessSettingsTap_" + on-click="onSwitchAccessSettingsTap_" hidden="[[!prefs.settings.a11y.switch_access.value]]" aria-label="$i18n{selectToSpeakOptionsLabel}"> </button> </settings-toggle-button> </template> - <div class="settings-box two-line" on-tap="onKeyboardTap_" actionable> + <div class="settings-box two-line" on-click="onKeyboardTap_" actionable> <div class="start"> $i18n{keyboardSettingsTitle} <div class="secondary" id="keyboardSettingsSecondary"> @@ -146,15 +166,13 @@ pref="{{prefs.settings.a11y.autoclick}}" label="$i18n{clickOnStopLabel}"> </settings-toggle-button> - <div class="settings-box block first"> - <div class="list-item sub-item"> - <div class="start">$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="settings-box continuation"> + <div class="start sub-item">$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> <settings-toggle-button class="continuation" pref="{{prefs.settings.touchpad.enable_tap_dragging}}" @@ -164,23 +182,21 @@ pref="{{prefs.settings.a11y.large_cursor_enabled}}" label="$i18n{largeMouseCursorLabel}"> </settings-toggle-button> - <div class="settings-box block continuation" + <div class="settings-box continuation" hidden$="[[!prefs.settings.a11y.large_cursor_enabled.value]]"> - <div class="list-item sub-item"> - <div class="start">$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> + <div class="start sub-item">$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 class="continuation" pref="{{prefs.settings.a11y.cursor_highlight}}" label="$i18n{cursorHighlightLabel}"> </settings-toggle-button> - <div class="settings-box two-line" on-tap="onMouseTap_" actionable> + <div class="settings-box two-line" on-click="onMouseTap_" actionable> <div class="start"> $i18n{mouseSettingsTitle} <div class="secondary" id="mouseSettingsSecondary"> 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 index 79e39c1dd0e..090a8a7f74a 100644 --- a/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js +++ b/chromium/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js @@ -19,6 +19,28 @@ Polymer({ 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, @@ -56,6 +78,17 @@ Polymer({ }, }, + /** + * Whether the docked magnifier flag is enabled. + * @private {boolean} + */ + dockedMagnifierFeatureEnabled_: { + type: Boolean, + value: function() { + return loadTimeData.getBoolean('dockedMagnifierFeatureEnabled'); + }, + }, + /** @private */ isGuest_: { type: Boolean, diff --git a/chromium/chrome/browser/resources/settings/about_page/about_page.html b/chromium/chrome/browser/resources/settings/about_page/about_page.html index 4f2f097bbe7..9053116b11d 100644 --- a/chromium/chrome/browser/resources/settings/about_page/about_page.html +++ b/chromium/chrome/browser/resources/settings/about_page/about_page.html @@ -78,7 +78,7 @@ <if expr="_google_chrome and is_macosx"> #promoteUpdater[disabled] { - @apply(--cr-secondary-text); + @apply --cr-secondary-text; } </if> </style> @@ -88,7 +88,7 @@ focus-config="[[focusConfig_]]"> <neon-animatable route-path="default"> <div class="settings-box two-line"> - <img id="product-logo" on-tap="onProductLogoTap_" + <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}"> @@ -153,18 +153,18 @@ <div class="separator" hidden="[[!showButtonContainer_]]"></div> <span id="buttonContainer" hidden="[[!showButtonContainer_]]"> <paper-button id="relaunch" class="secondary-button" - hidden="[[!showRelaunch_]]" on-tap="onRelaunchTap_"> + hidden="[[!showRelaunch_]]" on-click="onRelaunchTap_"> $i18n{aboutRelaunch} </paper-button> <if expr="chromeos"> <paper-button id="relaunchAndPowerwash" class="secondary-button" hidden="[[!showRelaunchAndPowerwash_]]" - on-tap="onRelaunchAndPowerwashTap_"> + on-click="onRelaunchAndPowerwashTap_"> $i18n{aboutRelaunchAndPowerwash} </paper-button> <paper-button id="checkForUpdates" class="secondary-button" hidden="[[!showCheckUpdates_]]" - on-tap="onCheckUpdatesTap_"> + on-click="onCheckUpdatesTap_"> $i18n{aboutCheckForUpdates} </paper-button> </if> @@ -173,13 +173,13 @@ <if expr="chromeos"> <div id="aboutTPMFirmwareUpdate" class="settings-box two-line" hidden$="[[!showTPMFirmwareUpdateLineItem_]]" - on-tap="onTPMFirmwareUpdateTap_" actionable> + on-click="onTPMFirmwareUpdateTap_" actionable> <div class="start"> <div>$i18n{aboutTPMFirmwareUpdateTitle}</div> <div class="secondary"> $i18n{aboutTPMFirmwareUpdateDescription} <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}" - target="_blank" on-tap="onLearnMoreTap_"> + target="_blank" on-click="onLearnMoreTap_"> $i18n{learnMore} </a> </div> @@ -194,12 +194,12 @@ <div id="promoteUpdater" class="settings-box" disabled$="[[promoteUpdaterStatus_.disabled]]" actionable$="[[promoteUpdaterStatus_.actionable]]" - on-tap="onPromoteUpdaterTap_"> + on-click="onPromoteUpdaterTap_"> <div class="start"> [[promoteUpdaterStatus_.text]] <a href="https://support.google.com/chrome/answer/95414" target="_blank" id="updaterLearnMore" - on-tap="onLearnMoreTap_"> + on-click="onLearnMoreTap_"> $i18n{learnMore} </a> </div> @@ -211,21 +211,22 @@ </div> </template> </if> - <div id="help" class="settings-box" on-tap="onHelpTap_" actionable> + <div id="help" class="settings-box" on-click="onHelpTap_" + actionable> <div class="start">$i18n{aboutGetHelpUsingChrome}</div> <button class="icon-external" is="paper-icon-button-light" aria-labelledby="help"></button> </div> <if expr="_google_chrome"> <div id="reportIssue" class="settings-box" actionable - on-tap="onReportIssueTap_"> + on-click="onReportIssueTap_"> <div class="start">$i18n{aboutReportAnIssue}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-labelledby="reportIssue"></button> </div> </if> <if expr="chromeos"> - <div class="settings-box" on-tap="onDetailedBuildInfoTap_" + <div class="settings-box" on-click="onDetailedBuildInfoTap_" actionable> <div class="start">$i18n{aboutDetailedBuildInfo}</div> <button id="detailed-build-info-trigger" class="subpage-arrow" 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 index 9f58ef3661b..70cbc55efe9 100644 --- a/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html +++ b/chromium/chrome/browser/resources/settings/about_page/channel_switcher_dialog.html @@ -53,15 +53,15 @@ </iron-selector> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" id="cancel">$i18n{cancel}</paper-button> <paper-button id="changeChannel" class="action-button" - on-tap="onChangeChannelTap_" + on-click="onChangeChannelTap_" hidden="[[!shouldShowButtons_.changeChannel]]"> $i18n{aboutChangeChannel} </paper-button> <paper-button id="changeChannelAndPowerwash" class="action-button" - on-tap="onChangeChannelAndPowerwashTap_" + on-click="onChangeChannelAndPowerwashTap_" hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]"> $i18n{aboutChangeChannelAndPowerwash} </paper-button> 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 index a1ae3142cd9..08ab773cc7c 100644 --- a/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html +++ b/chromium/chrome/browser/resources/settings/about_page/detailed_build_info.html @@ -39,7 +39,7 @@ <div class="secondary">[[currentlyOnChannelText_]]</div> </div> <div class="separator"></div> - <paper-button on-tap="onChangeChannelTap_" + <paper-button on-click="onChangeChannelTap_" disabled="[[!canChangeChannel_]]"> $i18n{aboutChangeChannel} </paper-button> 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 index 3a336109f1f..e6dd4ab2540 100644 --- a/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html +++ b/chromium/chrome/browser/resources/settings/about_page/update_warning_dialog.html @@ -16,9 +16,9 @@ </div> <div slot="button-container"> <paper-button id="cancel" class="cancel-button" - on-tap="onCancelTap_">$i18n{cancel}</paper-button> + on-click="onCancelTap_">$i18n{cancel}</paper-button> <paper-button id="continue" class="action-button" - on-tap="onContinueTap_"> + on-click="onContinueTap_"> $i18n{aboutUpdateWarningContinue} </paper-button> </div> 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 index a59514198c0..db780b0d718 100644 --- 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 @@ -23,7 +23,7 @@ <template is="dom-if" if="[[havePlayStoreApp]]" restamp> <div id="android-apps" class="settings-box two-line first" actionable$="[[androidAppsInfo.playStoreEnabled]]" - on-tap="onSubpageTap_"> + on-click="onSubpageTap_"> <div class="start"> $i18n{androidAppsPageLabel} <div class="secondary" id="secondaryText" @@ -43,7 +43,7 @@ <div class="separator"></div> <paper-button id="enable" class="secondary-button" disabled="[[isEnforced_(prefs.arc.enabled)]]" - on-tap="onEnableTap_" + on-click="onEnableTap_" aria-label="$i18n{androidAppsPageTitle}" aria-describedby="secondaryText"> $i18n{androidAppsEnable} 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 index 58cc44902e0..771918fe76a 100644 --- 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 @@ -21,7 +21,7 @@ </template> <template is="dom-if" if="[[allowRemove_(prefs.arc.enabled.*)]]"> - <div id="remove" class="settings-box" actionable on-tap="onRemoveTap_"> + <div id="remove" class="settings-box" actionable on-click="onRemoveTap_"> <div class="start">$i18n{androidAppsRemove}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{androidAppsRemove}"> @@ -37,11 +37,11 @@ <div slot="body" inner-h-t-m-l="[[dialogBody_]]"></div> <div slot="button-container"> <paper-button class="cancel-button" - on-tap="onConfirmDisableDialogCancel_"> + on-click="onConfirmDisableDialogCancel_"> $i18n{cancel} </paper-button> <paper-button class="action-button" - on-tap="onConfirmDisableDialogConfirm_"> + on-click="onConfirmDisableDialogConfirm_"> $i18n{androidAppsDisableDialogRemove} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/android_apps_page/android_settings_element.html b/chromium/chrome/browser/resources/settings/android_apps_page/android_settings_element.html index 17b6e861109..eb65859100f 100644 --- a/chromium/chrome/browser/resources/settings/android_apps_page/android_settings_element.html +++ b/chromium/chrome/browser/resources/settings/android_apps_page/android_settings_element.html @@ -12,7 +12,7 @@ <style include="settings-shared"></style> <div id="manageApps" class="settings-box first" on-keydown="onManageAndroidAppsKeydown_" - on-tap="onManageAndroidAppsTap_" actionable> + on-click="onManageAndroidAppsTap_" actionable> <div class="start"> <div>$i18n{androidAppsManageApps}</div> </div> 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 index 3b014cc73a3..d82f8743fac 100644 --- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html +++ b/chromium/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html @@ -121,7 +121,7 @@ </div> <template is="dom-if" if="[[!isGuest_]]"> <div class="settings-box two-line" id="advancedButton" - on-tap="openAdvancedExtension_" actionable> + on-click="openAdvancedExtension_" actionable> <div class="start"> $i18n{advancedFontSettings} <div class="secondary" id="advancedButtonSublabel"> diff --git a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html b/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html index 7ac5845998d..6667159ff4e 100644 --- a/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html +++ b/chromium/chrome/browser/resources/settings/appearance_page/appearance_page.html @@ -60,7 +60,7 @@ <button icon-class="icon-external" id="wallpaperButton" is="cr-link-row" hidden="[[!pageVisibility.setWallpaper]]" - on-tap="openWallpaperManager_" + on-click="openWallpaperManager_" label="$i18n{setWallpaper}" sub-label="$i18n{openWallpaperApp}" disabled="[[isWallpaperPolicyControlled_]]"> <template is="dom-if" if="[[isWallpaperPolicyControlled_]]"> @@ -76,11 +76,11 @@ <button class="first" icon-class="icon-external" is="cr-link-row" hidden="[[!pageVisibility.setTheme]]" label="$i18n{themes}" sub-label="[[themeSublabel_]]" - on-tap="openThemeUrl_"></button> + on-click="openThemeUrl_"></button> <if expr="not is_linux or chromeos"> <template is="dom-if" if="[[prefs.extensions.theme.id.value]]"> <div class="separator"></div> - <paper-button id="useDefault" on-tap="onUseDefaultTap_" + <paper-button id="useDefault" on-click="onUseDefaultTap_" class="secondary-button"> $i18n{resetToDefaultTheme} </paper-button> @@ -94,14 +94,14 @@ <div class="separator"></div> <template is="dom-if" if="[[showUseClassic_( prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp> - <paper-button id="useDefault" on-tap="onUseDefaultTap_" + <paper-button id="useDefault" on-click="onUseDefaultTap_" class="secondary-button"> $i18n{useClassicTheme} </paper-button> </template> <template is="dom-if" if="[[showUseSystem_( prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp> - <paper-button id="useSystem" on-tap="onUseSystemTap_" + <paper-button id="useSystem" on-click="onUseSystemTap_" class="secondary-button"> $i18n{useSystemTheme} </paper-button> @@ -168,7 +168,7 @@ </div> <button class="hr" is="cr-link-row" icon-class="subpage-arrow" id="customize-fonts-subpage-trigger" - label="$i18n{customizeFonts}" on-tap="onCustomizeFontsTap_"> + label="$i18n{customizeFonts}" on-click="onCustomizeFontsTap_"> </button> <div class="settings-box" hidden="[[!pageVisibility.pageZoom]]"> <div id="pageZoom" class="start">$i18n{pageZoom}</div> diff --git a/chromium/chrome/browser/resources/settings/basic_page/basic_page.html b/chromium/chrome/browser/resources/settings/basic_page/basic_page.html index 94029ec4769..80ab0b65c36 100644 --- a/chromium/chrome/browser/resources/settings/basic_page/basic_page.html +++ b/chromium/chrome/browser/resources/settings/basic_page/basic_page.html @@ -46,7 +46,7 @@ --paper-button: { text-transform: none; } - @apply(--settings-actionable); + @apply --settings-actionable; align-items: center; display: flex; margin-bottom: 3px; @@ -56,7 +56,7 @@ } #secondaryUserBanner { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; align-items: center; background-color: white; border-radius: 2px; 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 index b7134f661f3..c3ddc427f33 100644 --- 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 @@ -33,14 +33,15 @@ <span hidden$="[[!device.connecting]]">$i18n{bluetoothConnecting}</span> <div hidden$="[[!device.paired]]"> <button is="paper-icon-button-light" class="icon-more-vert" - on-tap="onMenuButtonTap_" tabindex$="[[tabindex]]" + on-click="onMenuButtonTap_" tabindex$="[[tabindex]]" title="$i18n{moreActions}" on-keydown="ignoreEnterKey_"> </button> <dialog id="dotsMenu" is="cr-action-menu"> - <button class="dropdown-item" on-tap="onConnectActionTap_"> + <button slot="item" class="dropdown-item" + on-click="onConnectActionTap_"> [[getConnectActionText_(device.connected)]] </button> - <button class="dropdown-item" on-tap="onRemoveTap_"> + <button slot="item" class="dropdown-item" on-click="onRemoveTap_"> $i18n{bluetoothRemove} </button> </dialog> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html index fb27688ce72..1e386deda42 100644 --- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html @@ -21,7 +21,7 @@ focus-config="[[focusConfig_]]"> <neon-animatable route-path="default"> <div id="bluetoothDevices" - class="settings-box two-line" actionable on-tap="onTap_"> + class="settings-box two-line" actionable on-click="onTap_"> <iron-icon icon="[[getIcon_(bluetoothToggleState_)]]"></iron-icon> <div class="middle"> $i18n{bluetoothPageTitle} @@ -36,7 +36,7 @@ </cr-policy-pref-indicator> <template is="dom-if" if="[[bluetoothToggleState_]]"> <button class="subpage-arrow" is="paper-icon-button-light" - on-tap="onSubpageArrowTap_" + on-click="onSubpageArrowTap_" aria-label="$i18n{bluetoothPageTitle}" aria-describedby="bluetoothSecondary"> </button> @@ -46,7 +46,7 @@ checked="{{bluetoothToggleState_}}" disabled$= "[[!isToggleEnabled_(adapterState_, stateChangeInProgress_)]]" - on-tap="stopTap_" + on-click="stopTap_" aria-label="$i18n{bluetoothToggleA11yLabel}"> </paper-toggle-button> </div> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html index 6ca9d1b16fa..018a505ac33 100644 --- a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html @@ -15,7 +15,7 @@ <template> <style include="settings-shared iron-flex"> .container { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; display: flex; flex-direction: column; min-height: 10px; @@ -27,7 +27,7 @@ } paper-spinner-lite { - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; } #onOff { @@ -39,7 +39,7 @@ } </style> - <div class="settings-box first" actionable on-tap="onEnableTap_"> + <div class="settings-box first" actionable on-click="onEnableTap_"> <div id="onOff" class="start" on$="[[bluetoothToggleState]]"> [[getOnOffString_(bluetoothToggleState, '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]] @@ -48,7 +48,7 @@ checked="{{bluetoothToggleState}}" disabled$="[[!isToggleEnabled_(adapterState, stateChangeInProgress)]]" aria-label="$i18n{bluetoothToggleA11yLabel}" - on-tap="stopTap_"> + on-click="stopTap_"> </paper-toggle-button> </div> 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 index deb22750820..ceaae386226 100644 --- 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 @@ -46,7 +46,7 @@ </div> <div class="separator"></div> <paper-button class="primary-button" id="changePassword" - on-tap="changePassword_"> + on-click="changePassword_"> $i18n{changePasswordPageButton} </paper-button> </div> 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 index 245dbdd0765..d0cb57647aa 100644 --- 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 @@ -84,11 +84,11 @@ </iron-icon> </div> <div class="start"> - <div>[[title_]]</div> + <div role="status">[[title_]]</div> <div hidden="[[!showExplanation_]]"> <span class="secondary">[[explanation_]]</span> <a id="learn-more" href="$i18n{chromeCleanupLearnMoreUrl}" - on-tap="learnMore_" target="_blank" + on-click="learnMore_" target="_blank" hidden="[[!showLearnMore_]]"> $i18n{learnMore} </a> @@ -97,7 +97,7 @@ <template is="dom-if" if="[[showActionButton_]]"> <div class="separator"></div> <paper-button id="action-button" class="primary-button" - on-tap="proceed_"> + on-click="proceed_"> [[actionButtonLabel_]] </paper-button> </template> @@ -112,7 +112,7 @@ on-settings-boolean-control-change="changeLogsPermission_"> </settings-toggle-button> <div id="show-items-button" class="settings-box" actionable - on-tap="toggleExpandButton_" hidden="[[!showItemsToRemove_]]"> + on-click="toggleExpandButton_" hidden="[[!showItemsToRemove_]]"> <div class="start">[[showItemsLinkLabel_]]</div> <cr-expand-button expanded="{{itemsToRemoveSectionExpanded_}}" alt="[[showItemsLinkLabel_]]"> 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 index ec8cc5aa7cc..7a31f0eef1d 100644 --- 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 @@ -767,14 +767,14 @@ Polymer({ }, DISMISS_CLEANUP_SUCCESS: { - label: this.i18n('chromeCleanupDoneButtonLabel'), + label: this.i18n('done'), doAction: this.dismiss_.bind( this, settings.ChromeCleanupDismissSource.CLEANUP_SUCCESS_DONE_BUTTON), }, DISMISS_CLEANUP_FAILURE: { - label: this.i18n('chromeCleanupDoneButtonLabel'), + label: this.i18n('done'), doAction: this.dismiss_.bind( this, settings.ChromeCleanupDismissSource.CLEANUP_FAILURE_DONE_BUTTON), 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 index 6f8b7eb1f77..21e3bb1cedc 100644 --- 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 @@ -20,18 +20,34 @@ color: var(--google-blue-500); cursor: pointer; } + + #remaining-list { + margin-top: -13px; + } </style> <div id="title" class="secondary" hidden="[[!titleVisible]]"> [[title]] </div> - <ul id="list" class="secondary"> - <template is="dom-repeat" items="[[visibleItems_]]"> + <ul class="secondary"> + <template is="dom-repeat" items="[[initialItems_]]"> <li class="visible-item">[[item]]</li> </template> - <li id="more-items-link" hidden="[[expanded_]]" on-tap="expandList_"> + <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_)]]">[[item]]</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 index e628437c50e..8b9c803fb3b 100644 --- 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 @@ -71,11 +71,21 @@ Polymer({ }, /** - * The list of items to actually present on the card. If |expanded_|, then - * it's the same as |itemsToShow|. + * The items to be shown to the user the first time this component is + * rendered. If |initiallyExpanded| is true, then it includes all items + * from |itemsToShow|. Otherwise, it contains the first + * |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items. * @private {?Array<string>} */ - visibleItems_: Array, + 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<string>} + */ + remainingItems_: Array, /** * The text for the "show more" link available if not all files are visible @@ -93,7 +103,6 @@ Polymer({ /** @private */ expandList_: function() { this.expanded_ = true; - this.visibleItems_ = this.itemsToShow; this.moreItemsLinkText_ = ''; }, @@ -118,25 +127,34 @@ Polymer({ updateVisibleState_: function(itemsToShow, initiallyExpanded) { // Start expanded if there are less than // |settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items to show. - this.expanded_ = this.initiallyExpanded || - this.itemsToShow.length <= - settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW; + this.expanded_ = initiallyExpanded || + itemsToShow.length <= settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW; if (this.expanded_) { - this.visibleItems_ = this.itemsToShow; + this.initialItems_ = itemsToShow; + this.remainingItems_ = []; this.moreItemsLinkText_ = ''; return; } - this.visibleItems_ = this.itemsToShow.slice( - 0, settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1); + 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.itemsToShow.length - this.visibleItems_.length) + 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'; + }, }); 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 index ec3fad23b77..dbbc94a3556 100644 --- 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 @@ -2,8 +2,10 @@ <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.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-button/paper-button.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-tabs/paper-tabs.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"> @@ -12,12 +14,29 @@ <link rel="import" href="../controls/settings_dropdown_menu.html"> <link rel="import" href="../icons.html"> <link rel="import" href="../settings_shared_css.html"> +<link rel="import" href="../settings_vars_css.html"> -<!-- This file is forked as clear_browsing_data_dialog_tabs.html until the new - CBD UI is launched. --> <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 { + --cr-dialog-top-container-min-height: 42px; + --cr-dialog-title: { + padding-bottom: 8px; + }; + --cr-dialog-body-container: { + border-top: 1px solid var(--paper-grey-300); + height: var(--body-container-height); + }; + } + #clearBrowsingDataDialog:not(.fully-rendered) { visibility: hidden; } @@ -26,6 +45,16 @@ color: var(--paper-grey-600); } + #clearBrowsingDataDialog [slot=body] { + padding-top: 8px; + } + + #importantSitesDialog { + --cr-dialog-body-container: { + height: var(--body-container-height); + }; + } + .row { align-items: center; display: flex; @@ -43,56 +72,36 @@ --settings-row-two-line-min-height: 48px; --settings-checkbox-label: { line-height: 1.25rem; - }; - } - - #generalFooter { - margin: 0; - min-height: 18px; - } - - #generalFooter iron-icon { - height: 18px; - padding: 1px; - width: 18px; - } - - #googleFooter { - margin: 0 0 0.8em 0; - min-height: 16px; - } - - #googleFooter iron-icon { - height: 16px; - padding: 2px; - width: 16px; - } - - [slot=footer] iron-icon { - margin: auto; + } } - .clear-browsing-data-footer { - -webkit-padding-start: 4px; - align-items: flex-start; - display: flex; - line-height: 1.538em; /* 20px/13px */ + #basic-tab settings-checkbox + settings-checkbox { + --settings-checkbox-margin-top: 12px; } - .clear-browsing-data-footer .footer-text { - -webkit-margin-start: 16px; + paper-tabs { + --paper-tabs-selection-bar-color: var(--google-blue-500); + --paper-tabs: { + font-size: 100%; + height: 40px; + } } - .clear-browsing-data-footer iron-icon { - flex-shrink: 0; + paper-tab { + --paper-tab-content: { + color: var(--google-blue-700); + }; + --paper-tab-content-unselected: { + opacity: 1; + color: var(--paper-grey-600); + }; } - .clear-browsing-data-footer a { - text-decoration: none; + .time-range-row { + margin-bottom: 12px; } - #clearFrom { - -webkit-margin-start: 0.5em; + .time-range-select { /* Adjust for md-select-underline and 1px additional bottom padding * to keep md-select's text (without the underline) aligned with * neighboring text that does not have an underline. */ @@ -103,115 +112,153 @@ font-size: calc(13 / 15 * 100%); padding-top: 8px; } - - /* Cap the height on smaller screens to avoid unfavorable clipping. - * Replace the bottom margin with padding to avoid the gap between - * the scrollbar and the bottom separator. */ - @media all and (max-height: 724px) { - #clearBrowsingDataDialog { - /* crbug.com/652027: Show four and a *half* items in the list. */ - --cr-dialog-body-container: { - max-height: 280px; - }; - } - } </style> <dialog is="cr-dialog" id="clearBrowsingDataDialog" on-close="onClearBrowsingDataDialogClose_" - close-text="$i18n{close}" ignore-popstate> - <div slot="title">$i18n{clearBrowsingData}</div> + close-text="$i18n{close}" ignore-popstate has-tabs> + <div slot="title"> + <div>$i18n{clearBrowsingData}</div> + </div> + <div slot="header"> + <paper-tabs noink on-selected-changed="recordTabChange_" + selected="{{prefs.browser.last_clear_browsing_data_tab.value}}"> + <paper-tab>$i18n{basicPageTitle}</paper-tab> + <paper-tab>$i18n{advancedPageTitle}</paper-tab> + </paper-tabs> + </div> <div slot="body"> - <div class="row"> - $i18n{clearFollowingItemsFrom} - <settings-dropdown-menu id="clearFrom" - label="$i18n{clearFollowingItemsFrom}" - pref="{{prefs.browser.clear_data.time_period}}" - 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="browsingCheckbox" class="browsing-data-checkbox" - 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" class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.download_history}}" - label="$i18n{clearDownloadHistory}" - sub-label="[[counters_.download_history]]" - disabled="[[clearingInProgress_]]" - hidden="[[isSupervised_]]"> - </settings-checkbox> - <settings-checkbox id="cacheCheckbox" class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.cache}}" - label="$i18n{clearCache}" - sub-label="[[counters_.cache]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - <settings-checkbox id="cookiesCheckbox" class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.cookies}}" - label="$i18n{clearCookies}" - sub-label="$i18n{clearCookiesCounter}" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - <settings-checkbox class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.passwords}}" - label="$i18n{clearPasswords}" - sub-label="[[counters_.passwords]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - <settings-checkbox class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.form_data}}" - label="$i18n{clearFormData}" - sub-label="[[counters_.form_data]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - <settings-checkbox class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.hosted_apps_data}}" - label="$i18n{clearHostedAppData}" - sub-label="[[counters_.hosted_apps_data]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - <settings-checkbox class="browsing-data-checkbox" - pref="{{prefs.browser.clear_data.media_licenses}}" - label="$i18n{clearMediaLicenses}" - sub-label="[[counters_.media_licenses]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> + <iron-pages id="tabs" + selected="[[prefs.browser.last_clear_browsing_data_tab.value]]"> + <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_, + '$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="$i18n{clearCookiesSummary}" + 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="[[siteSettingsLabel_( + '$i18nPolymer{siteSettings}', + '$i18nPolymer{contentSettings}')]]" + 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> + <settings-checkbox + pref="{{prefs.browser.clear_data.media_licenses}}" + label="$i18n{clearMediaLicenses}" + sub-label="[[counters_.media_licenses]]" + disabled="[[clearingInProgress_]]"> + </settings-checkbox> + </div> + </iron-pages> </div> <div slot="button-container"> <paper-spinner-lite active="[[clearingInProgress_]]"> </paper-spinner-lite> <paper-button class="cancel-button" disabled="[[clearingInProgress_]]" - on-tap="onCancelTap_">$i18n{cancel}</paper-button> + on-click="onCancelTap_">$i18n{cancel}</paper-button> <paper-button id="clearBrowsingDataConfirm" class="action-button" disabled="[[clearingInProgress_]]" - on-tap="onClearBrowsingDataTap_"> - $i18n{clearBrowsingData} + on-click="onClearBrowsingDataTap_"> + $i18n{clearData} </paper-button> </div> - <div slot="footer"> - <div id="googleFooter" class="clear-browsing-data-footer"> - <iron-icon icon="settings:googleg"></iron-icon> - <div class="footer-text">$i18nRaw{otherFormsOfBrowsingHistory}</div> - </div> - <div id="generalFooter" class="clear-browsing-data-footer"> - <iron-icon icon="settings:info"></iron-icon> - <div class="footer-text"> - <span id="syncedDataSentence">$i18n{clearsSyncedData}</span> - <span>$i18n{warnAboutNonClearedData}</span> - <a id="clear-browser-data-old-learn-more-link" - href="$i18n{clearBrowsingDataLearnMoreUrl}" - target="_blank">$i18n{learnMore}</a> - </div> - </div> - </div> </dialog> <template is="dom-if" if="[[showImportantSitesDialog_]]"> @@ -220,13 +267,12 @@ <div slot="title"> $i18n{clearBrowsingData} <div class="secondary"> - <template is="dom-if" - if="[[!prefs.browser.clear_data.cache.value]]"> + <span hidden$="[[showImportantSitesCacheSubtitle_]]"> $i18n{importantSitesSubtitleCookies} - </template> - <template is="dom-if" if="[[prefs.browser.clear_data.cache.value]]"> + </span> + <span hidden$="[[!showImportantSitesCacheSubtitle_]]"> $i18n{importantSitesSubtitleCookiesAndCache} - </template> + </span> </div> </div> <div slot="body"> @@ -243,10 +289,10 @@ <paper-spinner-lite active="[[clearingInProgress_]]"> </paper-spinner-lite> <paper-button class="cancel-button" disabled="[[clearingInProgress_]]" - on-tap="onImportantSitesCancelTap_">$i18n{cancel}</paper-button> + on-click="onImportantSitesCancelTap_">$i18n{cancel}</paper-button> <paper-button id="importantSitesConfirm" class="action-button" disabled="[[clearingInProgress_]]" - on-tap="onImportantSitesConfirmTap_"> + on-click="onImportantSitesConfirmTap_"> $i18n{importantSitesConfirm} </paper-button> </div> 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 index 60ec1ccbf33..97beb3c7906 100644 --- 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 @@ -3,16 +3,13 @@ // found in the LICENSE file. /** - * @fileoverview 'settings-clear-browsing-data-dialog' allows the user to delete - * browsing data that has been cached by Chromium. - * - * This file is forked as clear_browsing_data_dialog_tabs.js until the new - * CBD UI is launched. + * @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], + behaviors: [WebUIListenerBehavior, settings.RouteObserverBehavior], properties: { /** @@ -45,11 +42,11 @@ Polymer({ readOnly: true, type: Array, value: [ - {value: 0, name: loadTimeData.getString('clearDataHour')}, - {value: 1, name: loadTimeData.getString('clearDataDay')}, - {value: 2, name: loadTimeData.getString('clearDataWeek')}, - {value: 3, name: loadTimeData.getString('clearData4Weeks')}, - {value: 4, name: loadTimeData.getString('clearDataEverything')}, + {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')}, ], }, @@ -73,6 +70,18 @@ Polymer({ value: false, }, + /** @private */ + isSignedIn_: { + type: Boolean, + value: false, + }, + + /** @private */ + isSyncingHistory_: { + type: Boolean, + value: false, + }, + /** @private {!Array<ImportantSite>} */ importantSites_: { type: Array, @@ -90,7 +99,25 @@ Polymer({ }, /** @private */ - showImportantSitesDialog_: {type: Boolean, value: false}, + showImportantSitesDialog_: { + type: Boolean, + value: false, + }, + + /** @private */ + showImportantSitesCacheSubtitle_: { + type: Boolean, + value: false, + }, + + /** + * Time in ms, when the dialog was opened. + * @private + */ + dialogOpenedTime_: { + type: Number, + value: 0, + } }, /** @private {settings.ClearBrowsingDataBrowserProxy} */ @@ -98,7 +125,6 @@ Polymer({ /** @override */ ready: function() { - this.$.clearFrom.menuOptions = this.clearFromOptions_; this.addWebUIListener( 'update-sync-state', this.updateSyncState_.bind(this)); this.addWebUIListener( @@ -109,6 +135,7 @@ Polymer({ attached: function() { this.browserProxy_ = settings.ClearBrowsingDataBrowserProxyImpl.getInstance(); + this.dialogOpenedTime_ = Date.now(); this.browserProxy_.initialize().then(() => { this.$.clearBrowsingDataDialog.showModal(); }); @@ -121,21 +148,67 @@ Polymer({ }, /** - * Updates the footer to show only those sentences that are relevant to this - * user. + * 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 data. - * @param {boolean} otherFormsOfBrowsingHistory Whether the user has other - * forms of browsing history in their account. + * @param {boolean} syncing Whether the user is syncing history. * @private */ - updateSyncState_: function(signedIn, syncing, otherFormsOfBrowsingHistory) { - this.$.googleFooter.hidden = !otherFormsOfBrowsingHistory; - this.$.syncedDataSentence.hidden = !syncing; + updateSyncState_: function(signedIn, syncing) { + this.isSignedIn_ = signedIn; + this.isSyncingHistory_ = syncing; this.$.clearBrowsingDataDialog.classList.add('fully-rendered'); }, /** + * Choose a summary checkbox label. + * @param {boolean} isSignedIn + * @param {boolean} isSyncingHistory + * @param {string} historySummary + * @param {string} historySummarySigned + * @param {string} historySummarySynced + * @return {string} + * @private + */ + browsingCheckboxLabel_: function( + isSignedIn, isSyncingHistory, historySummary, historySummarySigned, + historySummarySynced) { + if (isSyncingHistory) { + return historySummarySynced; + } else if (isSignedIn) { + return historySummarySigned; + } + return historySummary; + }, + + /** + * Choose a content/site settings label. + * @param {string} siteSettings + * @param {string} contentSettings + * @return {string} + * @private + */ + siteSettingsLabel_: function(siteSettings, contentSettings) { + return loadTimeData.getBoolean('enableSiteSettings') ? siteSettings : + contentSettings; + }, + + /** * Updates the text of a browsing data counter corresponding to the given * preference. * @param {string} prefName Browsing data type deletion preference. @@ -156,8 +229,10 @@ Polymer({ shouldShowImportantSites_: function() { if (!this.importantSitesFlagEnabled_) return false; - if (!this.$.cookiesCheckbox.checked) + const tab = this.$.tabs.selectedItem; + if (!tab.querySelector('.cookies-checkbox').checked) { return false; + } const haveImportantSites = this.importantSites_.length > 0; chrome.send( @@ -172,12 +247,13 @@ Polymer({ */ onClearBrowsingDataTap_: function() { if (this.shouldShowImportantSites_()) { + const tab = this.$.tabs.selectedItem; this.showImportantSitesDialog_ = true; + this.showImportantSitesCacheSubtitle_ = + tab.querySelector('.cache-checkbox').checked; this.$.clearBrowsingDataDialog.close(); // Show important sites dialog after dom-if is applied. - this.async(function() { - this.$$('#importantSitesDialog').showModal(); - }); + this.async(() => this.$$('#importantSitesDialog').showModal()); } else { this.clearBrowsingData_(); } @@ -200,21 +276,31 @@ Polymer({ */ clearBrowsingData_: function() { this.clearingInProgress_ = true; + const tab = this.$.tabs.selectedItem; - const checkboxes = this.root.querySelectorAll('.browsing-data-checkbox'); + const checkboxes = tab.querySelectorAll('settings-checkbox'); const dataTypes = []; checkboxes.forEach((checkbox) => { if (checkbox.checked) dataTypes.push(checkbox.pref.key); }); - const timePeriod = this.$.clearFrom.pref.value; + 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, this.importantSites_) .then(shouldShowNotice => { this.clearingInProgress_ = false; this.showHistoryDeletionDialog_ = shouldShowNotice; + chrome.metricsPrivate.recordMediumTime( + 'History.ClearBrowsingData.TimeSpentInDialog', + Date.now() - this.dialogOpenedTime_); if (!shouldShowNotice) this.closeDialogs_(); }); @@ -257,4 +343,20 @@ Polymer({ this.showHistoryDeletionDialog_ = false; this.closeDialogs_(); }, + + /** + * 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'); + } + }, + }); diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.html b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.html deleted file mode 100644 index b9118df038f..00000000000 --- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.html +++ /dev/null @@ -1,311 +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/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-button/paper-button.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-tabs/paper-tabs.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/important_site_checkbox.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="../settings_shared_css.html"> -<link rel="import" href="../settings_vars_css.html"> - -<!-- This file is a fork of clear_browsing_data_dialog.html until the new CBD - UI is launched. --> -<dom-module id="settings-clear-browsing-data-dialog-tabs"> - <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 { - --cr-dialog-top-container-min-height: 42px; - --cr-dialog-title: { - padding-bottom: 8px; - }; - --cr-dialog-body-container: { - border-top: 1px solid var(--paper-grey-300); - height: var(--body-container-height); - }; - } - - #clearBrowsingDataDialog:not(.fully-rendered) { - visibility: hidden; - } - - #clearBrowsingDataDialog [slot=footer] { - color: var(--paper-grey-600); - } - - #clearBrowsingDataDialog [slot=body] { - padding-top: 8px; - } - - #importantSitesDialog { - --cr-dialog-body-container: { - height: var(--body-container-height); - }; - } - - .row { - align-items: center; - display: flex; - min-height: 40px; - } - - paper-spinner-lite { - -webkit-margin-end: 16px; - margin-bottom: auto; - margin-top: auto; - } - - settings-checkbox, - important-site-checkbox { - --settings-row-two-line-min-height: 48px; - --settings-checkbox-label: { - line-height: 1.25rem; - } - } - - #basic-tab settings-checkbox + settings-checkbox { - --settings-checkbox-margin-top: 12px; - } - - paper-tabs { - --paper-tabs-selection-bar-color: var(--google-blue-500); - --paper-tabs: { - font-size: 100%; - height: 40px; - } - } - - paper-tab { - --paper-tab-content: { - color: var(--google-blue-700); - }; - --paper-tab-content-unselected: { - opacity: 1; - color: var(--paper-grey-600); - }; - } - - .time-range-row { - margin-bottom: 12px; - } - - .time-range-select { - /* Adjust for md-select-underline and 1px additional bottom padding - * to keep md-select's text (without the underline) aligned with - * neighboring text that does not have an underline. */ - margin-top: 3px; - } - - [slot=title] .secondary { - font-size: calc(13 / 15 * 100%); - padding-top: 8px; - } - </style> - - <dialog is="cr-dialog" id="clearBrowsingDataDialog" - on-close="onClearBrowsingDataDialogClose_" - close-text="$i18n{close}" ignore-popstate has-tabs> - <div slot="title"> - <div>$i18n{clearBrowsingData}</div> - </div> - <div slot="header"> - <paper-tabs noink on-selected-changed="recordTabChange_" - selected="{{prefs.browser.last_clear_browsing_data_tab.value}}"> - <paper-tab>$i18n{basicPageTitle}</paper-tab> - <paper-tab>$i18n{advancedPageTitle}</paper-tab> - </paper-tabs> - </div> - <div slot="body"> - <iron-pages id="tabs" - selected="[[prefs.browser.last_clear_browsing_data_tab.value]]"> - <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_, - '$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="$i18n{clearCookiesSummary}" - 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="[[siteSettingsLabel_( - '$i18nPolymer{siteSettings}', - '$i18nPolymer{contentSettings}')]]" - 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> - <settings-checkbox - pref="{{prefs.browser.clear_data.media_licenses}}" - label="$i18n{clearMediaLicenses}" - sub-label="[[counters_.media_licenses]]" - disabled="[[clearingInProgress_]]"> - </settings-checkbox> - </div> - </iron-pages> - </div> - <div slot="button-container"> - <paper-spinner-lite active="[[clearingInProgress_]]"> - </paper-spinner-lite> - <paper-button class="cancel-button" disabled="[[clearingInProgress_]]" - on-tap="onCancelTap_">$i18n{cancel}</paper-button> - <paper-button id="clearBrowsingDataConfirm" - class="action-button" disabled="[[clearingInProgress_]]" - on-tap="onClearBrowsingDataTap_"> - $i18n{clearData} - </paper-button> - </div> - </dialog> - - <template is="dom-if" if="[[showImportantSitesDialog_]]"> - <dialog is="cr-dialog" id="importantSitesDialog" close-text="$i18n{close}" - show-scroll-borders ignore-popstate> - <div slot="title"> - $i18n{clearBrowsingData} - <div class="secondary"> - <span hidden$="[[showImportantSitesCacheSubtitle_]]"> - $i18n{importantSitesSubtitleCookies} - </span> - <span hidden$="[[!showImportantSitesCacheSubtitle_]]"> - $i18n{importantSitesSubtitleCookiesAndCache} - </span> - </div> - </div> - <div slot="body"> - <template is="dom-repeat" items="[[importantSites_]]"> - <div class="row"> - <important-site-checkbox - site="[[item]]" - disabled="[[clearingInProgress_]]"> - </important-site-checkbox> - </div> - </template> - </div> - <div slot="button-container"> - <paper-spinner-lite active="[[clearingInProgress_]]"> - </paper-spinner-lite> - <paper-button class="cancel-button" disabled="[[clearingInProgress_]]" - on-tap="onImportantSitesCancelTap_">$i18n{cancel}</paper-button> - <paper-button id="importantSitesConfirm" - class="action-button" disabled="[[clearingInProgress_]]" - on-tap="onImportantSitesConfirmTap_"> - $i18n{importantSitesConfirm} - </paper-button> - </div> - </dialog> - </template> - - <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_tabs.js"></script> -</dom-module> diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.js b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.js deleted file mode 100644 index 27f055c5e4b..00000000000 --- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.js +++ /dev/null @@ -1,367 +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-tabs' allows the user to - * delete browsing data that has been cached by Chromium. - * - * This file is a fork of clear_browsing_data_dialog.js until the new CBD UI is - * launched. - */ -Polymer({ - is: 'settings-clear-browsing-data-dialog-tabs', - - behaviors: [WebUIListenerBehavior, settings.RouteObserverBehavior], - - properties: { - /** - * Preferences state. - */ - prefs: { - type: Object, - notify: true, - }, - - /** - * 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 */ - 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 {!Array<ImportantSite>} */ - importantSites_: { - type: Array, - value: function() { - return []; - } - }, - - /** @private */ - importantSitesFlagEnabled_: { - type: Boolean, - value: function() { - return loadTimeData.getBoolean('importantSitesInCbd'); - }, - }, - - /** @private */ - showImportantSitesDialog_: { - type: Boolean, - value: false, - }, - - /** @private */ - showImportantSitesCacheSubtitle_: { - type: Boolean, - value: false, - }, - - /** - * Time in ms, when the dialog was opened. - * @private - */ - dialogOpenedTime_: { - type: Number, - value: 0, - } - }, - - /** @private {settings.ClearBrowsingDataBrowserProxy} */ - browserProxy_: null, - - /** @override */ - ready: function() { - 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(); - }); - - if (this.importantSitesFlagEnabled_) { - this.browserProxy_.getImportantSites().then(sites => { - this.importantSites_ = sites; - }); - } - }, - - /** - * 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} otherFormsOfBrowsingHistory Whether the user has other - * forms of browsing history in their account. - * @private - */ - updateSyncState_: function(signedIn, syncing, otherFormsOfBrowsingHistory) { - this.isSignedIn_ = signedIn; - this.isSyncingHistory_ = syncing; - this.$.clearBrowsingDataDialog.classList.add('fully-rendered'); - }, - - /** - * Choose a summary checkbox label. - * @param {boolean} isSignedIn - * @param {boolean} isSyncingHistory - * @param {string} historySummary - * @param {string} historySummarySigned - * @param {string} historySummarySynced - * @return {string} - * @private - */ - browsingCheckboxLabel_: function( - isSignedIn, isSyncingHistory, historySummary, historySummarySigned, - historySummarySynced) { - if (isSyncingHistory) { - return historySummarySynced; - } else if (isSignedIn) { - return historySummarySigned; - } - return historySummary; - }, - - /** - * Choose a content/site settings label. - * @param {string} siteSettings - * @param {string} contentSettings - * @return {string} - * @private - */ - siteSettingsLabel_: function(siteSettings, contentSettings) { - return loadTimeData.getBoolean('enableSiteSettings') ? siteSettings : - contentSettings; - }, - - /** - * 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); - }, - - /** - * @return {boolean} Whether the ImportantSites dialog should be shown. - * @private - */ - shouldShowImportantSites_: function() { - if (!this.importantSitesFlagEnabled_) - return false; - const tab = this.$.tabs.selectedItem; - if (!tab.querySelector('.cookies-checkbox').checked) { - return false; - } - - const haveImportantSites = this.importantSites_.length > 0; - chrome.send( - 'metricsHandler:recordBooleanHistogram', - ['History.ClearBrowsingData.ImportantDialogShown', haveImportantSites]); - return haveImportantSites; - }, - - /** - * Handles the tap on the Clear Data button. - * @private - */ - onClearBrowsingDataTap_: function() { - if (this.shouldShowImportantSites_()) { - const tab = this.$.tabs.selectedItem; - this.showImportantSitesDialog_ = true; - this.showImportantSitesCacheSubtitle_ = - tab.querySelector('.cache-checkbox').checked; - this.$.clearBrowsingDataDialog.close(); - // Show important sites dialog after dom-if is applied. - this.async(() => this.$$('#importantSitesDialog').showModal()); - } else { - this.clearBrowsingData_(); - } - }, - - /** - * Handles closing of the clear browsing data dialog. Stops the close - * event from propagating if another dialog is shown to prevent the - * privacy-page from closing this dialog. - * @private - */ - onClearBrowsingDataDialogClose_: function(event) { - if (this.showImportantSitesDialog_) - event.stopPropagation(); - }, - - /** - * Clears browsing data and maybe shows a history notice. - * @private - */ - clearBrowsingData_: function() { - this.clearingInProgress_ = true; - const tab = this.$.tabs.selectedItem; - - checkboxes = tab.querySelectorAll('settings-checkbox'); - const dataTypes = []; - checkboxes.forEach((checkbox) => { - if (checkbox.checked) - dataTypes.push(checkbox.pref.key); - }); - - 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, this.importantSites_) - .then(shouldShowNotice => { - this.clearingInProgress_ = false; - this.showHistoryDeletionDialog_ = shouldShowNotice; - chrome.metricsPrivate.recordMediumTime( - 'History.ClearBrowsingData.TimeSpentInDialog', - Date.now() - this.dialogOpenedTime_); - if (!shouldShowNotice) - this.closeDialogs_(); - }); - }, - - /** - * Closes the clear browsing data or important site dialog if they are open. - * @private - */ - closeDialogs_: function() { - if (this.$.clearBrowsingDataDialog.open) - this.$.clearBrowsingDataDialog.close(); - if (this.showImportantSitesDialog_) - this.$$('#importantSitesDialog').close(); - }, - - /** @private */ - onCancelTap_: function() { - this.$.clearBrowsingDataDialog.cancel(); - }, - - /** - * Handles the tap confirm button in important sites. - * @private - */ - onImportantSitesConfirmTap_: function() { - this.clearBrowsingData_(); - }, - - /** @private */ - onImportantSitesCancelTap_: function() { - /** @type {!CrDialogElement} */ (this.$$('#importantSitesDialog')).cancel(); - }, - - /** - * Handles the closing of the notice about other forms of browsing history. - * @private - */ - onHistoryDeletionDialogClose_: function() { - this.showHistoryDeletionDialog_ = false; - this.closeDialogs_(); - }, - - /** - * 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'); - } - }, - -}); diff --git a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/compiled_resources2.gyp b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/compiled_resources2.gyp index 518e48ef804..8660449e98a 100644 --- a/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/settings/clear_browsing_data_dialog/compiled_resources2.gyp @@ -14,6 +14,7 @@ { 'target_name': 'clear_browsing_data_dialog', 'dependencies': [ + '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-pages/compiled_resources2.gyp:iron-pages-extracted', '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-resizable-behavior/compiled_resources2.gyp:iron-resizable-behavior-extracted', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', 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 index a17572fb569..52429899184 100644 --- 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 @@ -11,7 +11,7 @@ <div slot="title">$i18n{historyDeletionDialogTitle}</div> <div slot="body">$i18nRaw{historyDeletionDialogBody}</div> <div slot="button-container"> - <paper-button class="action-button" on-tap="onOkTap_"> + <paper-button class="action-button" on-click="onOkTap_"> $i18n{historyDeletionDialogOK} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/compiled_resources2.gyp b/chromium/chrome/browser/resources/settings/compiled_resources2.gyp index a0f14ce056a..f56f7ebe8c4 100644 --- a/chromium/chrome/browser/resources/settings/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/settings/compiled_resources2.gyp @@ -57,6 +57,7 @@ 'target_name': 'search_settings', 'dependencies': [ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:search_highlight_utils', ], 'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'], }, @@ -79,6 +80,7 @@ 'default_browser_page/compiled_resources2.gyp:*', 'device_page/compiled_resources2.gyp:*', 'downloads_page/compiled_resources2.gyp:*', + 'incompatible_applications_page/compiled_resources2.gyp:*', 'internet_page/compiled_resources2.gyp:*', 'languages_page/compiled_resources2.gyp:*', 'on_startup_page/compiled_resources2.gyp:*', diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_button.html b/chromium/chrome/browser/resources/settings/controls/controlled_button.html index e0134790c7d..a5cf488854f 100644 --- a/chromium/chrome/browser/resources/settings/controls/controlled_button.html +++ b/chromium/chrome/browser/resources/settings/controls/controlled_button.html @@ -49,7 +49,7 @@ <paper-button disabled="[[enforced_]]">[[label]]</paper-button> <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]" restamp> - <cr-policy-pref-indicator pref="[[pref]]" on-tap="onIndicatorTap_" + <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_" icon-aria-label="[[label]]"> </cr-policy-pref-indicator> </template> diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_button.js b/chromium/chrome/browser/resources/settings/controls/controlled_button.js index 307475678a9..49d46860349 100644 --- a/chromium/chrome/browser/resources/settings/controls/controlled_button.js +++ b/chromium/chrome/browser/resources/settings/controls/controlled_button.js @@ -32,7 +32,7 @@ Polymer({ * @private */ onIndicatorTap_: function(e) { - // Disallow <controlled-button on-tap="..."> when controlled. + // Disallow <controlled-button on-click="..."> when controlled. e.preventDefault(); e.stopPropagation(); }, diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html index 10b1bca7fe2..d6fcdf26b06 100644 --- a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html +++ b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.html @@ -12,7 +12,7 @@ :host { --ink-to-circle: calc((var(--paper-radio-button-ink-size) - var(--paper-radio-button-size)) / 2); - @apply(--settings-actionable); + @apply --settings-actionable; align-items: center; display: flex; outline: none; @@ -24,7 +24,7 @@ } #label { - color: var(--paper-radio-button-label-color, --primary-text-color); + color: var(--paper-radio-button-label-color, var(--primary-text-color)); } .circle, @@ -53,11 +53,12 @@ .circle { border: 2px solid var(--paper-radio-button-unchecked-color, - --primary-text-color); + var(--primary-text-color)); } :host([checked]) .circle { - border-color: var(--paper-radio-button-checked-color, --primary-color); + border-color: var(--paper-radio-button-checked-color, + var(--primary-color)); } .disc { @@ -69,23 +70,23 @@ :host([checked]) .disc { background-color: var(--paper-radio-button-checked-color, - --primary-color); + var(--primary-color)); transform: scale(0.5); } paper-ripple { color: var(--paper-radio-button-unchecked-ink-color, - --primary-text-color); + var(--primary-text-color)); opacity: .6; } :host([checked]) paper-ripple { color: var(--paper-radio-button-checked-ink-color, - --primary-text-color); + var(--primary-text-color)); } :host(:not([controlled_])) { - @apply(--settings-actionable); + @apply --settings-actionable; } :host([controlled_]) { @@ -100,12 +101,12 @@ :host([controlled_]) .circle { border-color: var(--paper-radio-button-unchecked-color, - --primary-text-color); + var(--primary-text-color)); } :host([controlled_][checked]) .disc { background-color: var(--paper-radio-button-unchecked-color, - --primary-text-color); + var(--primary-text-color)); } :host([controlled_]) #labelWrapper { @@ -132,7 +133,7 @@ </div> <template is="dom-if" if="[[showIndicator_(controlled_, name, pref.*)]]"> - <cr-policy-pref-indicator pref="[[pref]]" on-tap="onIndicatorTap_" + <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_" icon-aria-label="[[label]]"> </cr-policy-pref-indicator> </template> diff --git a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js index ca55d6b2bb1..53bd0505a3c 100644 --- a/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js +++ b/chromium/chrome/browser/resources/settings/controls/controlled_radio_button.js @@ -48,7 +48,6 @@ Polymer({ 'blur': 'updatePressed_', 'down': 'updatePressed_', 'focus': 'updatePressed_', - 'tap': 'onTap_', 'up': 'updatePressed_', }, @@ -88,17 +87,11 @@ Polymer({ * @private */ onIndicatorTap_: function(e) { - // Disallow <controlled-radio-button on-tap="..."> when controlled. + // Disallow <controlled-radio-button on-click="..."> when controlled. e.preventDefault(); e.stopPropagation(); }, - /** @private */ - onTap_: function() { - if (!this.controlled_) - this.checked = true; - }, - /** * @param {!Event} e * @private diff --git a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html b/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html index ff53538c98f..50e3313c33c 100644 --- a/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html +++ b/chromium/chrome/browser/resources/settings/controls/extension_controlled_indicator.html @@ -17,7 +17,7 @@ } img { - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; -webkit-margin-end: 16px; } @@ -32,7 +32,7 @@ <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> - <paper-button class="secondary-button" on-tap="onDisableTap_"> + <paper-button class="secondary-button" on-click="onDisableTap_"> $i18n{disable} </paper-button> </template> diff --git a/chromium/chrome/browser/resources/settings/controls/important_site_checkbox.html b/chromium/chrome/browser/resources/settings/controls/important_site_checkbox.html index 0a0dd1b1937..e70da80b8f5 100644 --- a/chromium/chrome/browser/resources/settings/controls/important_site_checkbox.html +++ b/chromium/chrome/browser/resources/settings/controls/important_site_checkbox.html @@ -20,7 +20,7 @@ } paper-checkbox:not([checked]) .secondary { - @apply(--settings-secondary-unchecked); + @apply --settings-secondary-unchecked; } .middot { @@ -28,7 +28,7 @@ } .label { - @apply(--settings-checkbox-label); + @apply --settings-checkbox-label; } </style> <div id="outerRow"> diff --git a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html index b46ee716595..0563536cb09 100644 --- a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html +++ b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.html @@ -30,7 +30,7 @@ } paper-checkbox:not([checked]) .secondary { - @apply(--settings-secondary-unchecked); + @apply --settings-secondary-unchecked; } cr-policy-pref-indicator { @@ -38,7 +38,7 @@ } .label { - @apply(--settings-checkbox-label); + @apply --settings-checkbox-label; } </style> <div id="outerRow" noSubLabel$="[[!hasSubLabel_(subLabel, subLabelHtml)]]"> diff --git a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js index 3c0e8ded868..c4482b15c39 100644 --- a/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js +++ b/chromium/chrome/browser/resources/settings/controls/settings_checkbox.js @@ -33,7 +33,7 @@ Polymer({ subLabelHtmlChanged_: function() { const links = this.root.querySelectorAll('.secondary.label a'); links.forEach((link) => { - link.addEventListener('tap', this.stopPropagation); + link.addEventListener('click', this.stopPropagation); }); }, diff --git a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html b/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html index 7cdb86fdff3..2277abc6f7e 100644 --- a/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html +++ b/chromium/chrome/browser/resources/settings/controls/settings_toggle_button.html @@ -1,5 +1,6 @@ <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"> @@ -10,7 +11,7 @@ <template> <style include="settings-shared iron-flex"> :host { - @apply(--cr-section); + @apply --cr-section; } :host(.first), @@ -29,7 +30,7 @@ } :host([elide-label]) .label { - @apply(--settings-text-elide); + @apply --cr-text-elide; } #outerRow { 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 index c064fabcbef..20b846cb2e7 100644 --- 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 @@ -45,7 +45,7 @@ if="[[prefs.cros.flags.fine_grained_time_zone_detection_enabled.value]]" restamp> <div id="timeZoneSettingsTrigger" class="settings-box first" - on-tap="onTimeZoneSettings_" actionable> + on-click="onTimeZoneSettings_" actionable> <div id="timeZoneButton" class="two-line"> $i18n{timeZoneButton} <div class="secondary"> @@ -79,7 +79,7 @@ label="$i18n{use24HourClock}"> </settings-toggle-button> <div class="settings-box" id="setDateTime" actionable - on-tap="onSetDateTimeTap_" hidden$="[[!canSetDateTime_]]"> + on-click="onSetDateTimeTap_" hidden$="[[!canSetDateTime_]]"> <div class="start">$i18n{setDateTime}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{setDateTime}"></button> 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 index ee383626bcc..c24783b0bd2 100644 --- 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 @@ -17,7 +17,7 @@ </div> <div class="separator"></div> <paper-button class="secondary-button" - on-tap="onSetDefaultBrowserTap_"> + on-click="onSetDefaultBrowserTap_"> $i18n{defaultBrowserMakeDefaultButton} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/device_page/device_page.html b/chromium/chrome/browser/resources/settings/device_page/device_page.html index 56cc4fcfd76..cf90e9489fb 100644 --- a/chromium/chrome/browser/resources/settings/device_page/device_page.html +++ b/chromium/chrome/browser/resources/settings/device_page/device_page.html @@ -24,7 +24,7 @@ focus-config="[[focusConfig_]]"> <neon-animatable id="main" route-path="default"> <div id="pointersRow" class="settings-box first" - on-tap="onPointersTap_" actionable> + on-click="onPointersTap_" actionable> <div class="start"> [[getPointersTitle_(hasMouse_, hasTouchpad_)]] </div> @@ -32,34 +32,34 @@ aria-label$="[[getPointersTitle_(hasMouse_, hasTouchpad_)]]"></button> </div> - <div id="keyboardRow" class="settings-box" on-tap="onKeyboardTap_" + <div id="keyboardRow" class="settings-box" on-click="onKeyboardTap_" actionable> <div class="start">$i18n{keyboardTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{keyboardTitle}"></button> </div> <template is="dom-if" if="[[hasStylus_]]"> - <div id="stylusRow" class="settings-box" on-tap="onStylusTap_" + <div id="stylusRow" class="settings-box" on-click="onStylusTap_" actionable> <div class="start">$i18n{stylusTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{stylusTitle}"></button> </div> </template> - <div id="displayRow" class="settings-box" on-tap="onDisplayTap_" + <div id="displayRow" class="settings-box" on-click="onDisplayTap_" actionable> <div class="start">$i18n{displayTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{displayTitle}"></button> </div> - <div id="storageRow" class="settings-box" on-tap="onStorageTap_" + <div id="storageRow" class="settings-box" on-click="onStorageTap_" actionable> <div class="start">$i18n{storageTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{storageTitle}"></button> </div> <template is="dom-if" if="[[enablePowerSettings_]]"> - <div id="powerRow" class="settings-box" on-tap="onPowerTap_" + <div id="powerRow" class="settings-box" on-click="onPowerTap_" actionable> <div class="start">$i18n{powerTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" 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 index 0344e145e0b..ae2fb6d008c 100644 --- 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 @@ -185,7 +185,7 @@ cr.define('settings', function() { /** override */ handleLinkEvent(e) { - // Prevent the link from activating its parent element when tapped or + // Prevent the link from activating its parent element when clicked or // when Enter is pressed. if (e.type != 'keydown' || e.keyCode == 13) e.stopPropagation(); diff --git a/chromium/chrome/browser/resources/settings/device_page/display.html b/chromium/chrome/browser/resources/settings/device_page/display.html index 81cf27e5056..0eec56f0f97 100644 --- a/chromium/chrome/browser/resources/settings/device_page/display.html +++ b/chromium/chrome/browser/resources/settings/device_page/display.html @@ -82,7 +82,7 @@ restamp> <div class="secondary self-start"> <paper-checkbox checked="[[isMirrored_(displays)]]" - on-tap="onMirroredTap_" + on-click="onMirroredTap_" aria-label="[[getDisplayMirrorText_(displays)]]"> <div class="text-area">[[getDisplayMirrorText_(displays)]]</div> </paper-checkbox> @@ -188,7 +188,7 @@ <button is="cr-link-row" icon-class="subpage-arrow" class="indented hr" id="overscan" label="$i18n{displayOverscanPageTitle}" - sub-label="$i18n{displayOverscanPageText}" on-tap="onOverscanTap_" + sub-label="$i18n{displayOverscanPageText}" on-click="onOverscanTap_" hidden$="[[!showOverscanSetting_(selectedDisplay)]]"> </button> @@ -198,7 +198,7 @@ </settings-display-overscan-dialog> <div class="settings-box indented two-line" - on-tap="onTouchCalibrationTap_" + on-click="onTouchCalibrationTap_" hidden$="[[!showTouchCalibrationSetting_(selectedDisplay)]]" actionable> <div class="start"> diff --git a/chromium/chrome/browser/resources/settings/device_page/display.js b/chromium/chrome/browser/resources/settings/device_page/display.js index c21864b443b..2fcb479af3b 100644 --- a/chromium/chrome/browser/resources/settings/device_page/display.js +++ b/chromium/chrome/browser/resources/settings/device_page/display.js @@ -699,25 +699,18 @@ Polymer({ // 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(); - let id = ''; - /** @type {!chrome.system.display.DisplayProperties} */ - const properties = {}; - if (this.isMirrored_(this.displays)) { - id = this.primaryDisplayId; - properties.mirroringSourceId = ''; - } else { - // Set the mirroringSourceId of the secondary (first non-primary) display. - for (let i = 0; i < this.displays.length; ++i) { - const display = this.displays[i]; - if (display.id != this.primaryDisplayId) { - id = display.id; - break; - } - } - properties.mirroringSourceId = this.primaryDisplayId; - } - settings.display.systemDisplayApi.setDisplayProperties( - id, properties, this.setPropertiesCallback_.bind(this)); + + /** @type {!chrome.system.display.MirrorModeInfo} */ + let mirrorModeInfo = { + mode: this.isMirrored_(this.displays) ? + chrome.system.display.MirrorMode.OFF : + chrome.system.display.MirrorMode.NORMAL + }; + settings.display.systemDisplayApi.setMirrorMode(mirrorModeInfo, () => { + let error = chrome.runtime.lastError; + if (error) + console.error('setMirrorMode Error: ' + error.message); + }); }, /** @private */ diff --git a/chromium/chrome/browser/resources/settings/device_page/display_layout.html b/chromium/chrome/browser/resources/settings/device_page/display_layout.html index 289ebf9c8f2..4b138cb2b17 100644 --- a/chromium/chrome/browser/resources/settings/device_page/display_layout.html +++ b/chromium/chrome/browser/resources/settings/device_page/display_layout.html @@ -54,7 +54,7 @@ } .display.elevate { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; } </style> <div id="displayArea" on-iron-resize="calculateVisualScale_"> @@ -67,15 +67,11 @@ </template> <template is="dom-repeat" items="[[displays]]"> <div id="_[[item.id]]" class="display elevate" - draggable="[[dragEnabled]]" on-tap="onSelectDisplayTap_" + draggable="[[dragEnabled]]" on-click="onSelectDisplayTap_" style$="[[getDivStyle_(item.id, item.bounds, visualScale)]]" selected$="[[isSelected_(item, selectedDisplay)]]"> - <div hidden$="[[mirroring]]"> - [[item.name]] - </div> - <div hidden$="[[!mirroring]]"> - $i18n{displayMirrorDisplayName} - </div> + [[getDisplayName_(mirroring, item.name, + '$i18nPolymer{displayMirrorDisplayName}')]] </div> </template> </div> diff --git a/chromium/chrome/browser/resources/settings/device_page/display_layout.js b/chromium/chrome/browser/resources/settings/device_page/display_layout.js index 4b9f4192931..6cd70ca9581 100644 --- a/chromium/chrome/browser/resources/settings/device_page/display_layout.js +++ b/chromium/chrome/browser/resources/settings/device_page/display_layout.js @@ -191,6 +191,17 @@ Polymer({ }, /** + * @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} 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 index 08e263c7738..5c90de1c152 100644 --- a/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html +++ b/chromium/chrome/browser/resources/settings/device_page/display_overscan_dialog.html @@ -74,10 +74,10 @@ </div> </div> <div slot="button-container"> - <paper-button id="reset" class="cancel-button" on-tap="onResetTap_"> + <paper-button id="reset" class="cancel-button" on-click="onResetTap_"> $i18n{displayOverscanReset} </paper-button> - <paper-button class="action-button" on-tap="onSaveTap_"> + <paper-button class="action-button" on-click="onSaveTap_"> $i18n{ok} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/device_page/drive_cache_dialog.html b/chromium/chrome/browser/resources/settings/device_page/drive_cache_dialog.html index 696cbad5190..d111fcf73a3 100644 --- a/chromium/chrome/browser/resources/settings/device_page/drive_cache_dialog.html +++ b/chromium/chrome/browser/resources/settings/device_page/drive_cache_dialog.html @@ -17,11 +17,11 @@ </div> <div slot="button-container"> <paper-button id="cancelButton" class="cancel-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> <paper-button id="deleteButton" class="action-button" - on-tap="onDeleteTap_"> + on-click="onDeleteTap_"> $i18n{storageDeleteAllButtonTitle} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/device_page/keyboard.html b/chromium/chrome/browser/resources/settings/device_page/keyboard.html index cabf3ca8e43..1ec10018922 100644 --- a/chromium/chrome/browser/resources/settings/device_page/keyboard.html +++ b/chromium/chrome/browser/resources/settings/device_page/keyboard.html @@ -104,12 +104,12 @@ </div> </iron-collapse> <div id="keyboardOverlay" class="settings-box" - on-tap="onShowKeyboardShortcutsOverlayTap_" actionable> + on-click="onShowKeyboardShortcutsOverlayTap_" actionable> <div class="start">$i18n{showKeyboardShortcutsOverlay}</div> <button class="icon-external" is="paper-icon-button-light" aria-label="$i18n{showKeyboardShortcutsOverlay}"></button> </div> - <div class="settings-box" on-tap="onShowLanguageInputTap_" actionable> + <div class="settings-box" on-click="onShowLanguageInputTap_" actionable> <div class="start">$i18n{keyboardShowLanguageAndInput}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{keyboardShowLanguageAndInput}"></button> diff --git a/chromium/chrome/browser/resources/settings/device_page/pointers.html b/chromium/chrome/browser/resources/settings/device_page/pointers.html index d93db637cf5..0fb133e2933 100644 --- a/chromium/chrome/browser/resources/settings/device_page/pointers.html +++ b/chromium/chrome/browser/resources/settings/device_page/pointers.html @@ -85,7 +85,7 @@ <paper-radio-button name="true"> $i18n{naturalScrollLabel} <a href="$i18n{naturalScrollLearnMoreLink}" target="_blank" - on-tap="onLearnMoreLinkActivated_" + on-click="onLearnMoreLinkActivated_" on-keydown="onLearnMoreLinkActivated_"> $i18n{naturalScrollLearnMore} </a> diff --git a/chromium/chrome/browser/resources/settings/device_page/storage.html b/chromium/chrome/browser/resources/settings/device_page/storage.html index a10225b2f15..fc8cc1df590 100644 --- a/chromium/chrome/browser/resources/settings/device_page/storage.html +++ b/chromium/chrome/browser/resources/settings/device_page/storage.html @@ -195,7 +195,7 @@ </div> </div> </div> - <div class="settings-box two-line" on-tap="onDownloadsTap_" actionable> + <div class="settings-box two-line" on-click="onDownloadsTap_" actionable> <div class="start"> $i18n{storageItemDownloads} <div id="downloadsSize" class="secondary"> @@ -207,7 +207,7 @@ aria-describedby="downloadsSize"></button> </div> <template is="dom-if" if="[[driveEnabled_]]"> - <div class="settings-box two-line" on-tap="onDriveCacheTap_" + <div class="settings-box two-line" on-click="onDriveCacheTap_" actionable$="[[hasDriveCache_]]" > <div class="start"> $i18n{storageItemDriveCache} @@ -221,7 +221,7 @@ </button> </div> </template> - <div class="settings-box two-line" on-tap="onBrowsingDataTap_" actionable> + <div class="settings-box two-line" on-click="onBrowsingDataTap_" actionable> <div class="start"> $i18n{storageItemBrowsingData} <div id="browsingDataSize" class="secondary"> @@ -233,7 +233,7 @@ aria-describedby="browsingDataSize"></button> </div> <template is="dom-if" if="[[androidEnabled_]]"> - <div class="settings-box two-line" on-tap="onAndroidTap_" actionable> + <div class="settings-box two-line" on-click="onAndroidTap_" actionable> <div class="start"> $i18n{storageItemAndroid} <div id="androidSize" class="secondary"> @@ -246,7 +246,7 @@ </div> </template> <template is="dom-if" if="[[!isGuest_]]"> - <div class="settings-box two-line" on-tap="onOtherUsersTap_" actionable> + <div class="settings-box two-line" on-click="onOtherUsersTap_" actionable> <div class="start"> $i18n{storageItemOtherUsers} <div id="otherUsersSize" class="secondary"> diff --git a/chromium/chrome/browser/resources/settings/device_page/stylus.html b/chromium/chrome/browser/resources/settings/device_page/stylus.html index 9aa157eceab..93252074b04 100644 --- a/chromium/chrome/browser/resources/settings/device_page/stylus.html +++ b/chromium/chrome/browser/resources/settings/device_page/stylus.html @@ -19,7 +19,7 @@ paper-spinner-lite { margin-left: 12px; - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; } cr-policy-indicator { @@ -77,7 +77,7 @@ <!-- TODO(scottchen): Make a proper a[href].settings-box with icon-external (see: https://crbug.com/684005)--> - <div class="settings-box two-line" on-tap="onFindAppsTap_" actionable + <div class="settings-box two-line" on-click="onFindAppsTap_" actionable hidden$="[[!prefs.arc.enabled.value]]"> <div class="start"> $i18n{stylusFindMoreAppsPrimary} @@ -97,7 +97,7 @@ <div class="settings-box first"> <div id="lock-screen-toggle-label" class="start" actionable$="[[!disallowedOnLockScreenByPolicy_(selectedApp_)]]" - on-tap="toggleLockScreenSupport_"> + on-click="toggleLockScreenSupport_"> $i18n{stylusNoteTakingAppEnabledOnLockScreen} </div> <template is="dom-if" diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html index 2f213e62e6a..ea4a199cb0a 100644 --- a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html +++ b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html @@ -29,7 +29,7 @@ <div class="separator"></div> <controlled-button class="secondary-button" id="changeDownloadsPath" label="$i18n{changeDownloadLocation}" - on-tap="selectDownloadLocation_" + on-click="selectDownloadLocation_" pref="[[prefs.download.default_directory]]" end-justified> </controlled-button> @@ -52,7 +52,7 @@ </div> <div class="separator"></div> <paper-button id="resetAutoOpenFileTypes" class="secondary-button" - on-tap="onClearAutoOpenFileTypesTap_"> + on-click="onClearAutoOpenFileTypesTap_"> $i18n{clear} </paper-button> </div> 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 index b96f029582e..46081160c54 100644 --- 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 @@ -30,7 +30,7 @@ on-change="onGoogleAssistantContextEnableChange_"> </settings-toggle-button> <div id="googleAssistantSettings" class="settings-box" - on-tap="onGoogleAssistantSettingsTapped_" actionable> + on-click="onGoogleAssistantSettingsTapped_" actionable> <div class="start"> $i18n{googleAssistantSettings} </div> diff --git a/chromium/chrome/browser/resources/settings/icons.html b/chromium/chrome/browser/resources/settings/icons.html index c15dd7ba4a7..bf9a0008176 100644 --- a/chromium/chrome/browser/resources/settings/icons.html +++ b/chromium/chrome/browser/resources/settings/icons.html @@ -10,9 +10,7 @@ List icons here rather than importing large sets of (e.g. Polymer) icons. <defs> <!-- Ads icon in the Content Settings --> <g id="ads"> - <path d="M19,3H5C3.89,3,3,3.9,3,5v14c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M11,15H9.5v-1.5h-2V15H6v-4.5V9h1.5h2H10h1V15z M18,14c0,0.55-0.45,1-1,1h-4V9h4c0.55,0,1,0.45,1,1V14z"></path> - <rect x="7.5" y="10.5" width="2" height="1.5"></rect> - <rect x="14.5" y="10.5" width="2" height="3"></rect> + <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@ --> @@ -104,6 +102,7 @@ List icons here rather than importing large sets of (e.g. Polymer) icons. <g id="refresh"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></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="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="security"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"></path></g> <if expr="chromeos"> <g id="alert-device-out-of-range" fill="none" fill-rule="evenodd"><path d="M-1-1h20v20H-1z"></path><path fill="#C53929" fill-rule="nonzero" d="M8.167 11.5h1.666v1.667H8.167V11.5zm0-6.667h1.666v5H8.167v-5zM8.992.667C4.392.667.667 4.4.667 9s3.725 8.333 8.325 8.333c4.608 0 8.341-3.733 8.341-8.333S13.6.667 8.992.667zm.008 15A6.665 6.665 0 0 1 2.333 9 6.665 6.665 0 0 1 9 2.333 6.665 6.665 0 0 1 15.667 9 6.665 6.665 0 0 1 9 15.667z"></path></g> diff --git a/chromium/chrome/browser/resources/settings/images/sync_banner.svg b/chromium/chrome/browser/resources/settings/images/sync_banner.svg new file mode 100644 index 00000000000..d2ecb8603a7 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/images/sync_banner.svg @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="155px" height="131px" viewBox="0 0 155 131" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <polygon fill="#3874D5" points="0 114.5 154.75 114.5 154.75 0 0 0"></polygon> + <g transform="translate(61.000000, 53.250000)"> + <path d="M59.25,77 L19,77 C17.35,77 16,75.65 16,74 L16,3 C16,1.35 17.35,0 19,0 L59.25,0 C60.9,0 62.25,1.35 62.25,3 L62.25,74 C62.25,75.65 60.9,77 59.25,77" fill="#4285F4"></path> + <path d="M31.4473,61.5527 C31.4473,70.0837 24.5313,76.9997 16.0003,76.9997 C7.4683,76.9997 0.5523,70.0837 0.5523,61.5527 C0.5523,53.0207 7.4683,46.1047 16.0003,46.1047 C24.5313,46.1047 31.4473,53.0207 31.4473,61.5527" fill="#FABB05"></path> + </g> + </g> +</svg>
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/settings/incompatible_applications_page/compiled_resources2.gyp b/chromium/chrome/browser/resources/settings/incompatible_applications_page/compiled_resources2.gyp new file mode 100644 index 00000000000..c73f9ebe50a --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/compiled_resources2.gyp @@ -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. +{ + 'targets': [ + { + 'target_name': 'incompatible_applications_browser_proxy', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'incompatible_applications_page', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:web_ui_listener_behavior', + 'incompatible_applications_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'incompatible_application_item', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'incompatible_applications_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} 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 new file mode 100644 index 00000000000..1f8027ae747 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.html @@ -0,0 +1,25 @@ +<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/polymer/v1_0/paper-button/paper-button.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> + <paper-button class="primary-button" on-click="onActionTap_"> + [[getActionName_(actionType)]] + </paper-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 new file mode 100644 index 00000000000..72dd7f1941e --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.js @@ -0,0 +1,103 @@ +// 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/conflicts/problematic_programs_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 startProgramUninstallation() + * call. + */ + applicationName: String, + + /** + * The type of the action to be taken on this incompatible application. Must + * be one of BlacklistMessageType in + * chrome/browser/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_.startProgramUninstallation(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 new file mode 100644 index 00000000000..8d55a232daa --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.html @@ -0,0 +1,2 @@ +<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 new file mode 100644 index 00000000000..7017e0a469d --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_browser_proxy.js @@ -0,0 +1,122 @@ +// 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/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 'programName'. + * @param {string} programName + */ + startProgramUninstallation(programName) {} + + /** + * 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 */ + startProgramUninstallation(programName) { + chrome.send('startProgramUninstallation', [programName]); + } + + /** @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 new file mode 100644 index 00000000000..9fcccb14ef3 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.html @@ -0,0 +1,59 @@ +<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/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(--google-blue-500); + } + </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 two-line"> + <iron-icon icon="settings: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"> + <div class="secondary">[[listTitleText_]]</div> + </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 new file mode 100644 index 00000000000..27426d236a8 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/incompatible_applications_page/incompatible_applications_page.js @@ -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. + +/** + * @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. + let 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; + 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/internet_config.html b/chromium/chrome/browser/resources/settings/internet_page/internet_config.html index 4fa0a79d5e8..fce0c7e3d29 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_config.html +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_config.html @@ -32,7 +32,7 @@ share-allow-enable="[[shareAllowEnable_]]" share-default="[[shareDefault_]]" error="{{error_}}" - on-close="close"> + on-close="onClose_"> </network-config> </div> @@ -40,17 +40,17 @@ <template is="dom-if" if="[[error_]]" restamp> <div class="flex error">[[getError_(error_)]]</div> </template> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <template is="dom-if" if="[[isConfigured_(networkProperties_, guid)]]"> - <paper-button class="action-button" on-tap="onSaveOrConnectTap_" + <template is="dom-if" if="[[!showConnect]]"> + <paper-button class="action-button" on-click="onSaveTap_" disabled="[[!enableSave_]]"> $i18n{save} </paper-button> </template> - <template is="dom-if" if="[[!isConfigured_(networkProperties_, guid)]]"> - <paper-button class="action-button" on-tap="onSaveOrConnectTap_" + <template is="dom-if" if="[[showConnect]]"> + <paper-button class="action-button" on-click="onConnectTap_" disabled="[[!enableConnect_]]"> $i18n{networkButtonConnect} </paper-button> diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_config.js b/chromium/chrome/browser/resources/settings/internet_page/internet_config.js index b6bda81171f..ba7c90de4ab 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_config.js +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_config.js @@ -56,6 +56,12 @@ Polymer({ */ name: String, + /** + * Set to true to show the 'connect' button instead of 'save'. + * @private + */ + showConnect: Boolean, + /** @private */ enableConnect_: Boolean, @@ -103,6 +109,16 @@ Polymer({ }, /** + * @param {!Event} event + * @private + */ + onClose_: function(event) { + this.close(); + this.fire('networks-changed'); + event.stopPropagation(); + }, + + /** * @return {string} * @private */ @@ -124,22 +140,18 @@ Polymer({ return this.i18n('networkErrorUnknown'); }, - /** - * @return {boolean} - * @private - */ - isConfigured_: function() { - const source = this.networkProperties_.Source; - return !!this.guid && !!source && source != CrOnc.Source.NONE; - }, - /** @private */ onCancelTap_: function() { this.close(); }, /** @private */ - onSaveOrConnectTap_: function() { - this.$.networkConfig.saveOrConnect(); + 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 index e3afc520289..cbee01f9b6e 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html @@ -81,30 +81,30 @@ </template> </div> <template is="dom-if" if="[[!isSecondaryUser_]]"> - <paper-button on-tap="onForgetTap_" + <paper-button on-click="onForgetTap_" hidden$="[[!showForget_(networkProperties)]]"> $i18n{networkButtonForget} </paper-button> - <paper-button on-tap="onViewAccountTap_" + <paper-button on-click="onViewAccountTap_" hidden$="[[!showViewAccount_(networkProperties)]]"> $i18n{networkButtonViewAccount} </paper-button> - <paper-button on-tap="onActivateTap_" + <paper-button on-click="onActivateTap_" hidden$="[[!showActivate_(networkProperties)]]"> $i18n{networkButtonActivate} </paper-button> - <paper-button on-tap="onConfigureTap_" + <paper-button on-click="onConfigureTap_" hidden$="[[!showConfigure_(networkProperties, globalPolicy)]]"> $i18n{networkButtonConfigure} </paper-button> </template> - <paper-button class="primary-button" on-tap="onConnectTap_" + <paper-button class="primary-button" on-click="onConnectTap_" hidden$="[[!showConnect_(networkProperties, globalPolicy)]]" disabled="[[!enableConnect_(networkProperties, defaultNetwork, globalPolicy, networkPropertiesReceived_, outOfRange_)]]"> $i18n{networkButtonConnect} </paper-button> - <paper-button class="primary-button" on-tap="onDisconnectTap_" + <paper-button class="primary-button" on-click="onDisconnectTap_" hidden$="[[!showDisconnect_(networkProperties)]]"> $i18n{networkButtonDisconnect} </paper-button> @@ -203,7 +203,7 @@ <template is="dom-if" if="[[showAdvanced_(networkProperties)]]"> <!-- Advanced toggle. --> - <div class="settings-box" actionable on-tap="toggleAdvancedExpanded_"> + <div class="settings-box" actionable on-click="toggleAdvancedExpanded_"> <div class="flex">$i18n{networkSectionAdvanced}</div> <cr-expand-button expanded="{{advancedExpanded_}}" alt="$i18n{networkSectionAdvancedA11yLabel}"> @@ -232,7 +232,7 @@ <template is="dom-if" if="[[hasNetworkSection_(networkProperties)]]"> <!-- Network toggle --> - <div class="settings-box" actionable on-tap="toggleNetworkExpanded_"> + <div class="settings-box" actionable on-click="toggleNetworkExpanded_"> <div class="start">$i18n{networkSectionNetwork}</div> <cr-expand-button expanded="{{networkExpanded_}}" alt="$i18n{networkSectionNetworkExpandA11yLabel}"> @@ -274,7 +274,7 @@ <template is="dom-if" if="[[hasProxySection_(networkProperties)]]"> <!-- Proxy toggle --> - <div class="settings-box" actionable on-tap="toggleProxyExpanded_"> + <div class="settings-box" actionable on-click="toggleProxyExpanded_"> <div class="start">$i18n{networkSectionProxy}</div> <cr-expand-button expanded="{{proxyExpanded_}}" alt="$i18n{networkSectionProxyExpandA11yLabel}"> 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 index b8c3480ddcd..c21b36319f5 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js @@ -630,7 +630,6 @@ Polymer({ this.showTetherDialog_(); return; } - this.fire('network-connect', {networkProperties: this.networkProperties}); }, 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 index 6d2f1357614..4466b23e664 100644 --- 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 @@ -36,12 +36,12 @@ </cr-policy-indicator> </template> <button class="subpage-arrow" is="paper-icon-button-light" - actionable on-tap="fireShowDetails_" tabindex$="[[tabindex]]" + actionable on-click="fireShowDetails_" tabindex$="[[tabindex]]" aria-label$="[[item.Name]]"> </button> <div class="separator"></div> <button is="paper-icon-button-light" class="icon-more-vert" - preferred tabindex$="[[tabindex]]" on-tap="onMenuButtonTap_" + preferred tabindex$="[[tabindex]]" on-click="onMenuButtonTap_" title="$i18n{moreActions}"> </button> </div> @@ -63,12 +63,12 @@ </cr-policy-indicator> </template> <button class="subpage-arrow" is="paper-icon-button-light" - actionable on-tap="fireShowDetails_" tabindex$="[[tabindex]]" + actionable on-click="fireShowDetails_" tabindex$="[[tabindex]]" aria-label$="[[item.Name]]"> </button> <div class="separator"></div> <button is="paper-icon-button-light" class="icon-more-vert" - tabindex$="[[tabindex]]" on-tap="onMenuButtonTap_" + tabindex$="[[tabindex]]" on-click="onMenuButtonTap_" title="$i18n{moreActions}"> </button> </div> @@ -76,16 +76,16 @@ </div> <dialog id="dotsMenu" is="cr-action-menu"> - <button class="dropdown-item" hidden="[[!showAddPreferred_]]" - on-tap="onAddPreferredTap_"> + <button slot="item" class="dropdown-item" hidden="[[!showAddPreferred_]]" + on-click="onAddPreferredTap_"> $i18n{knownNetworksMenuAddPreferred} </button> - <button class="dropdown-item" hidden="[[!showRemovePreferred_]]" - on-tap="onRemovePreferredTap_"> + <button slot="item" class="dropdown-item" + hidden="[[!showRemovePreferred_]]" on-click="onRemovePreferredTap_"> $i18n{knownNetworksMenuRemovePreferred} </button> - <button class="dropdown-item" disabled="[[!enableForget_]]" - on-tap="onForgetTap_"> + <button slot="item" class="dropdown-item" disabled="[[!enableForget_]]" + on-click="onForgetTap_"> $i18n{knownNetworksMenuForget} </button> </dialog> diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_page.html index cd6b2afbadb..0c7cf9c5c72 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_page.html +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_page.html @@ -38,7 +38,7 @@ </network-summary> <template is="dom-if" if="[[allowAddConnection_(globalPolicy_)]]"> <div actionable class="settings-box two-line" - on-tap="onExpandAddConnectionsTap_"> + on-click="onExpandAddConnectionsTap_"> <div class="start layout horizontal center"> <div>$i18n{internetAddConnection}</div> </div> @@ -50,7 +50,7 @@ <div class="list-frame vertical-list"> <template is="dom-if" if="[[deviceIsEnabled_(deviceStates.WiFi)]]"> - <div actionable class="list-item" on-tap="onAddWiFiTap_"> + <div actionable class="list-item" on-click="onAddWiFiTap_"> <div class="start">$i18n{internetAddWiFi}</div> <button class$="[[getAddNetworkClass_('WiFi')]]" is="paper-icon-button-light" @@ -58,7 +58,7 @@ </button> </div> </template> - <div actionable class="list-item" on-tap="onAddVPNTap_"> + <div actionable class="list-item" on-click="onAddVPNTap_"> <div class="start">$i18n{internetAddVPN}</div> <button class$="[[getAddNetworkClass_('VPN')]]" is="paper-icon-button-light" @@ -67,7 +67,7 @@ </div> <template is="dom-repeat" items="[[thirdPartyVpnProviders_]]"> <div actionable class="list-item" - on-tap="onAddThirdPartyVpnTap_" provider="[[item]]"> + on-click="onAddThirdPartyVpnTap_" provider="[[item]]"> <div class="start">[[getAddThirdPartyVpnLabel_(item)]]</div> <button class="icon-external" is="paper-icon-button-light" aria-label$="[[getAddThirdPartyVpnLabel_(item)]]"> @@ -76,7 +76,7 @@ </template> <template is="dom-if" if="[[arcVpnProviders_.length]]"> <div actionable class="list-item" id="addArcVpn" - on-tap="onAddArcVpnTap_"> + on-click="onAddArcVpnTap_"> <div class="start">$i18n{internetAddArcVPN}</div> <button class="icon-external" is="paper-icon-button-light" aria-label$="$i18n{internetAddArcVPN}"> diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_page.js index 3b8126607c7..ed1eda816ef 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_page.js +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_page.js @@ -272,27 +272,27 @@ Polymer({ */ onShowConfig_: function(event) { const properties = event.detail; + let configAndConnect = !properties.GUID; // New configuration this.showConfig_( - properties.Type, properties.GUID, CrOnc.getNetworkName(properties)); + configAndConnect, properties.Type, properties.GUID, + CrOnc.getNetworkName(properties)); }, /** + * @param {boolean} configAndConnect * @param {string} type * @param {string=} guid * @param {string=} name * @private */ - showConfig_: function(type, guid, name) { - if (!loadTimeData.getBoolean('networkSettingsConfig')) { - chrome.send('configureNetwork', [guid]); - return; - } + showConfig_: function(configAndConnect, type, guid, name) { const configDialog = /** @type {!InternetConfigElement} */ (this.$.configDialog); configDialog.type = /** @type {chrome.networkingPrivate.NetworkType} */ (type); configDialog.guid = guid || ''; configDialog.name = name || ''; + configDialog.showConnect = configAndConnect; configDialog.open(); }, @@ -369,7 +369,7 @@ Polymer({ }, /** - * Event triggered when the 'Add connections' div is tapped. + * Event triggered when the 'Add connections' div is clicked. * @param {!Event} event * @private */ @@ -382,7 +382,7 @@ Polymer({ /** @private */ onAddWiFiTap_: function() { if (loadTimeData.getBoolean('networkSettingsConfig')) - this.showConfig_(CrOnc.Type.WI_FI); + this.showConfig_(true /* configAndConnect */, CrOnc.Type.WI_FI); else chrome.send('addNetwork', [CrOnc.Type.WI_FI]); }, @@ -390,7 +390,7 @@ Polymer({ /** @private */ onAddVPNTap_: function() { if (loadTimeData.getBoolean('networkSettingsConfig')) - this.showConfig_(CrOnc.Type.VPN); + this.showConfig_(true /* configAndConnect */, CrOnc.Type.VPN); else chrome.send('addNetwork', [CrOnc.Type.VPN]); }, @@ -604,7 +604,8 @@ Polymer({ } if (properties.Connectable === false || properties.ErrorState) { - this.showConfig_(properties.Type, properties.GUID, name); + this.showConfig_( + true /* configAndConnect */, properties.Type, properties.GUID, name); return; } @@ -618,7 +619,9 @@ Polymer({ console.error( 'networkingPrivate.startConnect error: ' + message + ' For: ' + properties.GUID); - this.showConfig_(properties.Type, properties.GUID, name); + this.showConfig_( + true /* configAndConnect */, properties.Type, properties.GUID, + name); } }); }, diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html index 3119ac4ec1c..ad9e3d48712 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.html @@ -65,12 +65,12 @@ } #gmscore-notifications-device-string { - @apply(--cr-secondary-text); + @apply --cr-secondary-text; margin-top: 5px; } #gmscore-notifications-instructions { - @apply(--cr-secondary-text); + @apply --cr-secondary-text; -webkit-padding-start: 15px; margin: 0; } @@ -86,19 +86,19 @@ <button is="paper-icon-button-light" id="addButton" hidden$="[[!showAddButton_(deviceState, globalPolicy)]]" aria-label="$i18n{internetAddWiFi}" class="icon-add-wifi" - on-tap="onAddButtonTap_" tabindex$="[[tabindex]]"> + on-click="onAddButtonTap_" tabindex$="[[tabindex]]"> </button> <paper-toggle-button id="deviceEnabledButton" aria-label$="[[getToggleA11yString_(deviceState)]]" checked="[[deviceIsEnabled_(deviceState)]]" disabled="[[!enableToggleIsEnabled_(deviceState)]]" - on-tap="onDeviceEnabledTap_"> + on-click="onDeviceEnabledTap_"> </paper-toggle-button> </div> </template> <template is="dom-if" if="[[knownNetworksIsVisible_(deviceState)]]"> - <div actionable class="settings-box" on-tap="onKnownNetworksTap_"> + <div actionable class="settings-box" on-click="onKnownNetworksTap_"> <div class="start">$i18n{knownNetworksButton}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{knownNetworksButton}"> @@ -114,7 +114,7 @@ <div class="flex">$i18n{networkVpnBuiltin}</div> <button is="paper-icon-button-light" class="icon-add-circle" aria-label="$i18n{internetAddVPN}" - on-tap="onAddButtonTap_" tabindex$="[[tabindex]]"> + on-click="onAddButtonTap_" tabindex$="[[tabindex]]"> </button> </div> </template> @@ -143,6 +143,7 @@ <li>$i18n{gmscoreNotificationsFirstStep}</li> <li>$i18n{gmscoreNotificationsSecondStep}</li> <li>$i18n{gmscoreNotificationsThirdStep}</li> + <li>$i18n{gmscoreNotificationsFourthStep}</li> </ol> </div> </template> @@ -165,7 +166,7 @@ <div class="flex">[[item.ProviderName]]</div> <button is="paper-icon-button-light" class="icon-add-circle" aria-label$="[[getAddThirdPartyVpnA11yString_(item)]]" - on-tap="onAddThirdPartyVpnTap_" tabindex$="[[tabindex]]"> + on-click="onAddThirdPartyVpnTap_" tabindex$="[[tabindex]]"> </button> </div> <cr-network-list show-buttons @@ -185,7 +186,7 @@ <div class="flex">[[item.ProviderName]]</div> <button is="paper-icon-button-light" class="icon-add-circle" aria-label$="[[getAddArcVpnAllyString_(item)]]" - on-tap="onAddArcVpnTap_" tabindex$="[[tabindex]]"> + on-click="onAddArcVpnTap_" tabindex$="[[tabindex]]"> </button> </div> <cr-network-list show-buttons @@ -204,7 +205,7 @@ <template is="dom-if" if="[[tetherToggleIsVisible_(deviceState, tetherDeviceState)]]"> <div class="settings-box two-line" actionable - on-tap="onTetherEnabledTap_"> + on-click="onTetherEnabledTap_"> <div class="start"> $i18n{internetToggleTetherLabel} <div id="tetherSecondary" class="secondary"> diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js index 2d1c08ee297..c45566055aa 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js +++ b/chromium/chrome/browser/resources/settings/internet_page/internet_subpage.js @@ -442,7 +442,7 @@ Polymer({ }, /** - * Event triggered when the known networks button is tapped. + * Event triggered when the known networks button is clicked. * @private */ onKnownNetworksTap_: function() { 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 index f5d88e89c41..7aa2e646067 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html +++ b/chromium/chrome/browser/resources/settings/internet_page/network_proxy_section.html @@ -90,11 +90,11 @@ </div> <div slot="button-container"> <paper-button class="cancel-button" - on-tap="onAllowSharedDialogCancel_"> + on-click="onAllowSharedDialogCancel_"> $i18n{cancel} </paper-button> <paper-button class="action-button" - on-tap="onAllowSharedDialogConfirm_"> + on-click="onAllowSharedDialogConfirm_"> $i18n{confirm} </paper-button> </div> 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 index 812bf51fff9..2df92961938 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html +++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html @@ -34,7 +34,7 @@ font-weight: 400; } </style> - <div actionable class="settings-box two-line" on-tap="onShowDetailsTap_"> + <div actionable class="settings-box two-line" on-click="onShowDetailsTap_"> <div id="details" no-flex$="[[showSimInfo_(deviceState)]]"> <cr-network-icon network-state="[[activeNetworkState]]" device-state="[[deviceState]]"> @@ -48,7 +48,7 @@ </div> <template is="dom-if" if="[[showSimInfo_(deviceState)]]" restamp> - <network-siminfo editable on-tap="doNothing_" + <network-siminfo editable on-click="doNothing_" network-properties="[[getCellularState_(deviceState)]]" networking-private="[[networkingPrivate]]"> </network-siminfo> @@ -57,7 +57,7 @@ <template is="dom-if" if="[[showPolicyIndicator_(activeNetworkState)]]"> <cr-policy-indicator indicator-type="[[getIndicatorTypeForSource( activeNetworkState.Source)]]" - on-tap="doNothing_"> + on-click="doNothing_"> </cr-policy-indicator> </template> @@ -75,7 +75,7 @@ aria-label$="[[getToggleA11yString_(deviceState)]]" checked="[[deviceIsEnabled_(deviceState)]]" disabled="[[!enableToggleIsEnabled_(deviceState)]]" - on-tap="onDeviceEnabledTap_"> + on-click="onDeviceEnabledTap_"> </paper-toggle-button> </template> </div> 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 index 65c882d595d..e7a54cb245a 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js +++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js @@ -68,10 +68,9 @@ Polymer({ * @private */ getNetworkStateText_: function(activeNetworkState, deviceState) { - const state = activeNetworkState.ConnectionState; - const name = CrOnc.getNetworkName(activeNetworkState); - if (state) - return this.getConnectionStateText_(state, name); + const stateText = this.getConnectionStateText_(activeNetworkState); + if (stateText) + return stateText; // No network state, use device state. if (deviceState) { // Type specific scanning or initialization states. @@ -98,12 +97,15 @@ Polymer({ }, /** - * @param {CrOnc.ConnectionState} state - * @param {string} name + * @param {!CrOnc.NetworkStateProperties} networkState * @return {string} * @private */ - getConnectionStateText_: function(state, name) { + getConnectionStateText_: function(networkState) { + const state = networkState.ConnectionState; + if (!state) + return ''; + const name = CrOnc.getNetworkName(networkState); switch (state) { case CrOnc.ConnectionState.CONNECTED: return name; @@ -112,6 +114,10 @@ Polymer({ return CrOncStrings.networkListItemConnectingTo.replace('$1', name); return CrOncStrings.networkListItemConnecting; case CrOnc.ConnectionState.NOT_CONNECTED: + if (networkState.Type == CrOnc.Type.CELLULAR && networkState.Cellular && + networkState.Cellular.Scanning) { + return this.i18n('internetMobileSearching'); + } return CrOncStrings.networkListItemNotConnected; } assertNotReached(); 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 index 51e02e88fb0..c54c5c1186a 100644 --- a/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html +++ b/chromium/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html @@ -108,11 +108,11 @@ </ul> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onNotNowTap_"> + <paper-button class="cancel-button" on-click="onNotNowTap_"> $i18n{tetherConnectionNotNowButton} </paper-button> <paper-button id="connectButton" class="action-button" - on-tap="onConnectTap_" disabled="[[outOfRange]]"> + on-click="onConnectTap_" disabled="[[outOfRange]]"> $i18n{tetherConnectionConnectButton} </paper-button> </div> 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 index 6a6a1512872..a3f6edd6f54 100644 --- a/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html +++ b/chromium/chrome/browser/resources/settings/languages_page/add_languages_dialog.html @@ -59,10 +59,10 @@ </iron-list> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelButtonTap_"> + <paper-button class="cancel-button" on-click="onCancelButtonTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onActionButtonTap_" + <paper-button class="action-button" on-click="onActionButtonTap_" disabled="[[disableActionButton_]]"> $i18n{add} </paper-button> 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 index d5d0912c66d..ce5c803d292 100644 --- a/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html +++ b/chromium/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html @@ -42,7 +42,7 @@ '$i18nPolymer{addDictionaryWordDuplicateError}', '$i18nPolymer{addDictionaryWordLengthError}')]]"></paper-input> </div> - <paper-button class="secondary-button" on-tap="onAddWordTap_" + <paper-button class="secondary-button" on-click="onAddWordTap_" disabled="[[disableAddButton_(newWordValue_)]]" id="addWord"> $i18n{addDictionaryWordButton} </paper-button> @@ -58,7 +58,7 @@ <div class="list-item"> <div class="word text-elide">[[item]]</div> <button is="paper-icon-button-light" class="icon-clear" - on-tap="onRemoveWordTap_" tabindex$="[[tabIndex]]"> + on-click="onRemoveWordTap_" tabindex$="[[tabIndex]]"> </button> </div> </template> diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_page.html b/chromium/chrome/browser/resources/settings/languages_page/languages_page.html index 608022db7bb..7b70201c2cb 100644 --- a/chromium/chrome/browser/resources/settings/languages_page/languages_page.html +++ b/chromium/chrome/browser/resources/settings/languages_page/languages_page.html @@ -84,7 +84,7 @@ focus-config="[[focusConfig_]]"> <neon-animatable route-path="default"> <div class$="settings-box first [[getLanguageListTwoLine_()]]" - actionable on-tap="toggleExpandButton_"> + actionable on-click="toggleExpandButton_"> <div class="start"> <div>$i18n{languagesListTitle}</div> <if expr="chromeos or is_win"> @@ -127,20 +127,20 @@ <if expr="chromeos or is_win"> <template is="dom-if" if="[[isRestartRequired_( item.language.code, languages.prospectiveUILanguage)]]"> - <paper-button on-tap="onRestartTap_"> + <paper-button on-click="onRestartTap_"> $i18n{restart} </paper-button> </template> </if> <button is="paper-icon-button-light" title="$i18n{moreActions}" - id="more-[[item.language.code]]" on-tap="onDotsTap_" + id="more-[[item.language.code]]" on-click="onDotsTap_" class="icon-more-vert"> </button> </div> </template> <div class="list-item"> <a is="action-link" class="list-button" id="addLanguages" - on-tap="onAddLanguagesTap_"> + on-click="onAddLanguagesTap_"> $i18n{addLanguages} </a> </div> @@ -153,7 +153,7 @@ <if expr="chromeos"> <div id="manageInputMethodsSubpageTrigger" class="settings-box two-line" actionable - on-tap="toggleExpandButton_"> + on-click="toggleExpandButton_"> <div class="start"> <div>$i18n{inputMethodsListTitle}</div> <div class="secondary"> @@ -171,7 +171,7 @@ items="[[languages.inputMethods.enabled]]"> <div class$="list-item [[getInputMethodItemClass_( item.id, languages.inputMethods.currentId)]]" - on-tap="onInputMethodTap_" on-keypress="onInputMethodTap_" + on-click="onInputMethodTap_" on-keypress="onInputMethodTap_" actionable tabindex="0"> <div class="start"> <div>[[item.displayName]]</div> @@ -182,12 +182,13 @@ </div> </div> <button class="icon-external" is="paper-icon-button-light" - on-tap="onInputMethodOptionsTap_" + on-click="onInputMethodOptionsTap_" hidden="[[!item.hasOptionsPage]]"> </button> </div> </template> - <div class="list-item" on-tap="onManageInputMethodsTap_" actionable> + <div class="list-item" on-click="onManageInputMethodsTap_" + actionable> <div class="start" id="manageInputMethods"> $i18n{manageInputMethods} </div> @@ -207,7 +208,7 @@ class$="settings-box [[getSpellCheckListTwoLine_( spellCheckSecondaryText_)]]" actionable$="[[!spellCheckDisabled_]]" - on-tap="toggleExpandButton_"> + on-click="toggleExpandButton_"> <div class="start"> <div>$i18n{spellCheckListTitle}</div> <div class="secondary">[[spellCheckSecondaryText_]]</div> @@ -230,7 +231,7 @@ <template is="dom-repeat" items="[[spellCheckLanguages_]]"> <div class="list-item"> <template is="dom-if" if="[[!item.isManaged]]"> - <div class="start" on-tap="onSpellCheckChange_" + <div class="start" on-click="onSpellCheckChange_" actionable$="[[item.language.supportsSpellcheck]]"> [[item.language.displayName]] </div> @@ -250,7 +251,7 @@ </template> </div> </template> - <div class="list-item" on-tap="onEditDictionaryTap_" actionable> + <div class="list-item" on-click="onEditDictionaryTap_" actionable> <div class="start" id="customSpelling"> $i18n{manageSpellCheck} </div> @@ -265,7 +266,8 @@ <dialog is="cr-action-menu" class$="[[getMenuClass_(prefs.translate.enabled.value)]]"> <if expr="chromeos or is_win"> - <paper-checkbox id="uiLanguageItem" class="dropdown-item" + <paper-checkbox id="uiLanguageItem" slot="item" + class="dropdown-item" checked="[[isProspectiveUILanguage_( detailLanguage_.language.code, languages.prospectiveUILanguage)]]" @@ -275,7 +277,8 @@ $i18n{displayInThisLanguage} </paper-checkbox> </if> - <paper-checkbox id="offerTranslations" class="dropdown-item" + <paper-checkbox id="offerTranslations" slot="item" + class="dropdown-item" checked="[[detailLanguage_.translateEnabled]]" on-change="onTranslateCheckboxChange_" hidden="[[!prefs.translate.enabled.value]]" @@ -283,26 +286,26 @@ detailLanguage_.language, languages.translateTarget)]]"> $i18n{offerToTranslateInThisLanguage} </paper-checkbox> - <hr> - <button class="dropdown-item" role="menuitem" - on-tap="onMoveToTopTap_" + <hr slot="item"> + <button slot="item" class="dropdown-item" role="menuitem" + on-click="onMoveToTopTap_" hidden="[[isNthLanguage_( 0, detailLanguage_, languages.enabled.*)]]"> $i18n{moveToTop} </button> - <button class="dropdown-item" role="menuitem" - on-tap="onMoveUpTap_" + <button slot="item" class="dropdown-item" role="menuitem" + on-click="onMoveUpTap_" hidden="[[!showMoveUp_(detailLanguage_, languages.enabled.*)]]"> $i18n{moveUp} </button> - <button class="dropdown-item" role="menuitem" - on-tap="onMoveDownTap_" + <button slot="item" class="dropdown-item" role="menuitem" + on-click="onMoveDownTap_" hidden="[[!showMoveDown_( detailLanguage_, languages.enabled.*)]]"> $i18n{moveDown} </button> - <button class="dropdown-item" role="menuitem" - on-tap="onRemoveLanguageTap_" + <button slot="item" class="dropdown-item" role="menuitem" + on-click="onRemoveLanguageTap_" hidden="[[!detailLanguage_.removable]]"> $i18n{removeLanguage} </button> diff --git a/chromium/chrome/browser/resources/settings/languages_page/languages_page.js b/chromium/chrome/browser/resources/settings/languages_page/languages_page.js index 43787f93abb..d9df072708b 100644 --- a/chromium/chrome/browser/resources/settings/languages_page/languages_page.js +++ b/chromium/chrome/browser/resources/settings/languages_page/languages_page.js @@ -116,11 +116,13 @@ Polymer({ }, }, + // <if expr="not is_macosx"> observers: [ 'updateSpellcheckLanguages_(languages.enabled.*, ' + 'languages.forcedSpellCheckLanguages.*)', 'updateSpellcheckEnabled_(prefs.browser.enable_spellchecking.*)', ], + // </if> /** * Stamps and opens the Add Languages dialog, registering a listener to @@ -628,7 +630,7 @@ Polymer({ /** * Closes the shared action menu after a short delay, so when a checkbox is - * tapped it can be seen to change state before disappearing. + * clicked it can be seen to change state before disappearing. * @private */ closeMenuSoon_: function() { 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 index 40bfadfde9d..f4bdec1ade6 100644 --- 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 @@ -23,13 +23,11 @@ label="$i18n{onStartupOpenNewTab}" no-extension-indicator> </controlled-radio-button> - <template is="dom-if" if="[[showIndicator_( - ntpExtension_, prefs.session.restore_on_startup.value)]]"> + <template is="dom-if" if="[[ntpExtension_]]"> <extension-controlled-indicator extension-id="[[ntpExtension_.id]]" extension-name="[[ntpExtension_.name]]" - extension-can-be-disabled="[[ntpExtension_.canBeDisabled]]" - on-extension-disable="getNtpExtension_"> + extension-can-be-disabled="[[ntpExtension_.canBeDisabled]]"> </extension-controlled-indicator> </template> <controlled-radio-button name="[[prefValues_.CONTINUE]]" 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 index 26484951dcf..aff7303d079 100644 --- 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 @@ -37,29 +37,13 @@ Polymer({ /** @override */ attached: function() { - this.getNtpExtension_(); - this.addWebUIListener('update-ntp-extension', ntpExtension => { + const updateNtpExtension = ntpExtension => { // Note that |ntpExtension| is empty if there is no NTP extension. this.ntpExtension_ = ntpExtension; - }); - }, - - /** @private */ - getNtpExtension_: function() { + }; settings.OnStartupBrowserProxyImpl.getInstance().getNtpExtension().then( - function(ntpExtension) { - this.ntpExtension_ = ntpExtension; - }.bind(this)); - }, - - /** - * @param {?NtpExtension} ntpExtension - * @param {number} restoreOnStartup Value of prefs.session.restore_on_startup. - * @return {boolean} - * @private - */ - showIndicator_: function(ntpExtension, restoreOnStartup) { - return !!ntpExtension && restoreOnStartup == this.prefValues_.OPEN_NEW_TAB; + updateNtpExtension); + this.addWebUIListener('update-ntp-extension', updateNtpExtension); }, /** 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 index d2139335785..313bdf7aba0 100644 --- 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 @@ -21,10 +21,10 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" id="cancel">$i18n{cancel}</paper-button> <paper-button id="actionButton" class="action-button" - on-tap="onActionButtonTap_">[[actionButtonText_]]</paper-button> + on-click="onActionButtonTap_">[[actionButtonText_]]</paper-button> </div> </dialog> </template> 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 index cf1a7a9cf81..99699c9a021 100644 --- 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 @@ -25,16 +25,17 @@ <div class="text-elide secondary">[[model.url]]</div> </div> <template is="dom-if" if="[[editable]]"> - <button is="paper-icon-button-light" id="dots" on-tap="onDotsTap_" + <button is="paper-icon-button-light" id="dots" on-click="onDotsTap_" title="$i18n{moreActions}" focus-row-control focus-type="menu" class="icon-more-vert"> </button> <template is="cr-lazy-render" id="menu"> <dialog is="cr-action-menu"> - <button class="dropdown-item" on-tap="onEditTap_"> + <button slot="item" class="dropdown-item" on-click="onEditTap_"> $i18n{edit} </button> - <button class="dropdown-item" id="remove" on-tap="onRemoveTap_"> + <button slot="item" class="dropdown-item" id="remove" + on-click="onRemoveTap_"> $i18n{onStartupRemove} </button> </dialog> 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 index 8c4460dc865..dd6ac50a045 100644 --- 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 @@ -12,7 +12,7 @@ cr.exportPath('settings'); /** * The name of the event fired from this element when the "Edit" option is - * tapped. + * clicked. * @type {string} */ settings.EDIT_STARTUP_URL_EVENT = 'edit-startup-url'; 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 index 02b87b2093a..93cd7cd0c94 100644 --- 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 @@ -18,7 +18,7 @@ <template> <style include="settings-shared action-link iron-flex"> .list-frame { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; } .list-frame > div { @@ -26,7 +26,7 @@ } #outer { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; max-height: 355px; /** Enough height to show six entries. */ } @@ -53,13 +53,13 @@ <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-tap="onAddPageTap_"> + <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-tap="onUseCurrentPagesTap_"> + on-click="onUseCurrentPagesTap_"> $i18n{onStartupUseCurrent} </a> </div> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/address_edit_dialog.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/address_edit_dialog.html index 37c189b5231..8d6515df3a8 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/address_edit_dialog.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/address_edit_dialog.html @@ -51,7 +51,7 @@ transform: scale(0.75); transform-origin: left; width: 133%; - @apply(--paper-input-container-label-floating); + @apply --paper-input-container-label-floating; } :host-context([dir=rtl]) #select-label { @@ -144,11 +144,11 @@ </div> <div slot="button-container"> <paper-button id="cancelButton" class="cancel-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> <paper-button id="saveButton" class="action-button" - disabled="[[!canSave_]]" on-tap="onSaveButtonTap_"> + disabled="[[!canSave_]]" on-click="onSaveButtonTap_"> $i18n{save} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html index 4da90c016dc..e5193b1a7d1 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html @@ -65,9 +65,9 @@ } </style> <settings-toggle-button id="autofillToggle" - class="first primary-toggle" + class="first" aria-label="$i18n{autofill}" no-extension-indicator - label="[[getOnOffLabel_(prefs.autofill.enabled.value)]]" + label="$i18n{autofillFormsLabel}" pref="{{prefs.autofill.enabled}}"> </settings-toggle-button> <template is="dom-if" if="[[prefs.autofill.enabled.extensionId]]"> @@ -85,7 +85,7 @@ <h2 class="start">$i18n{addresses}</h2> <paper-button id="addAddress" class="secondary-button header-aligned-button" - on-tap="onAddAddressTap_"> + on-click="onAddAddressTap_"> $i18n{add} </paper-button> </div> @@ -108,13 +108,13 @@ </div> <template is="dom-if" if="[[item.metadata.isLocal]]"> <button is="paper-icon-button-light" id="addressMenu" - class="icon-more-vert" on-tap="onAddressMenuTap_" + class="icon-more-vert" on-click="onAddressMenuTap_" title="$i18n{moreActions}"> </button> </template> <template is="dom-if" if="[[!item.metadata.isLocal]]"> <button is="paper-icon-button-light" class="icon-external" - on-tap="onRemoteEditAddressTap_" actionable></button> + on-click="onRemoteEditAddressTap_" actionable></button> </template> </div> </template> @@ -125,10 +125,10 @@ </div> </div> <dialog is="cr-action-menu" id="addressSharedMenu"> - <button id="menuEditAddress" class="dropdown-item" - on-tap="onMenuEditAddressTap_">$i18n{edit}</button> - <button id="menuRemoveAddress" class="dropdown-item" - on-tap="onMenuRemoveAddressTap_">$i18n{removeAddress}</button> + <button id="menuEditAddress" slot="item" class="dropdown-item" + on-click="onMenuEditAddressTap_">$i18n{edit}</button> + <button id="menuRemoveAddress" slot="item" class="dropdown-item" + on-click="onMenuRemoveAddressTap_">$i18n{removeAddress}</button> </dialog> <template is="dom-if" if="[[showAddressDialog_]]" restamp> <settings-address-edit-dialog address="[[activeAddress]]" @@ -139,7 +139,7 @@ <h2 class="start">$i18n{creditCards}</h2> <paper-button id="addCreditCard" class="secondary-button header-aligned-button" - on-tap="onAddCreditCardTap_" + on-click="onAddCreditCardTap_" hidden$="[[isDisabled_(prefs.autofill.credit_card_enabled)]]"> $i18n{add} </paper-button> @@ -173,13 +173,13 @@ <template is="dom-if" if="[[showDots_(item.metadata)]]"> <button is="paper-icon-button-light" id="creditCardMenu" class="icon-more-vert" title="$i18n{moreActions}" - on-tap="onCreditCardMenuTap_"> + on-click="onCreditCardMenuTap_"> </button> </template> <template is="dom-if" if="[[!showDots_(item.metadata)]]"> <button is="paper-icon-button-light" id="remoteCreditCardLink" class="icon-external" - on-tap="onRemoteEditCreditCardTap_" actionable></button> + on-click="onRemoteEditCreditCardTap_" actionable></button> </template> </div> </div> @@ -198,13 +198,13 @@ </div> </div> <dialog is="cr-action-menu" id="creditCardSharedMenu"> - <button id="menuEditCreditCard" class="dropdown-item" - on-tap="onMenuEditCreditCardTap_">$i18n{edit}</button> - <button id="menuRemoveCreditCard" class="dropdown-item" + <button id="menuEditCreditCard" slot="item" class="dropdown-item" + on-click="onMenuEditCreditCardTap_">$i18n{edit}</button> + <button id="menuRemoveCreditCard" slot="item" class="dropdown-item" hidden$="[[!activeCreditCard.metadata.isLocal]]" - on-tap="onMenuRemoveCreditCardTap_">$i18n{removeCreditCard}</button> - <button id="menuClearCreditCard" class="dropdown-item" - on-tap="onMenuClearCreditCardTap_" + on-click="onMenuRemoveCreditCardTap_">$i18n{removeCreditCard}</button> + <button id="menuClearCreditCard" slot="item" class="dropdown-item" + on-click="onMenuClearCreditCardTap_" hidden$="[[!activeCreditCard.metadata.isCached]]"> $i18n{clearCreditCard} </button> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/credit_card_edit_dialog.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/credit_card_edit_dialog.html index 041413545b9..6465c600227 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/credit_card_edit_dialog.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/credit_card_edit_dialog.html @@ -89,9 +89,9 @@ </div> <div slot="button-container"> <paper-button id="cancelButton" class="cancel-button" - on-tap="onCancelButtonTap_">$i18n{cancel}</paper-button> + on-click="onCancelButtonTap_">$i18n{cancel}</paper-button> <paper-button id="saveButton" class="action-button" - on-tap="onSaveButtonTap_" disabled>$i18n{save}</paper-button> + on-click="onSaveButtonTap_" disabled>$i18n{save}</paper-button> </div> </dialog> </template> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_edit_dialog.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_edit_dialog.html index 0a0814697a6..ae402835da2 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_edit_dialog.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_edit_dialog.html @@ -22,7 +22,7 @@ }; --paper-input-container-label-focus: { - color: var(--paper-input-container-color, --secondary-text-color); + color: var(--secondary-text-color); }; } @@ -61,15 +61,15 @@ <button is="paper-icon-button-light" id="showPasswordButton" class$="[[getIconClass_(item.password)]]" hidden$="[[item.entry.federationText]]" - on-tap="onShowPasswordButtonTap_" + on-click="onShowPasswordButtonTap_" title="[[showPasswordTitle_(item.password, '$i18nPolymer{hidePassword}','$i18nPolymer{showPassword}')]]"> </button> </div> </div> <div slot="button-container"> - <paper-button class="action-button" on-tap="onActionButtonTap_"> - $i18n{passwordsDone} + <paper-button class="action-button" on-click="onActionButtonTap_"> + $i18n{done} </paper-button> </div> </dialog> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html index 85dda982896..59f3d8a1e3c 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html @@ -48,12 +48,12 @@ <template is="dom-if" if="[[!item.entry.federationText]]"> <input id="password" aria-label=$i18n{editPasswordPasswordLabel} type="[[getPasswordInputType_(item.password)]]" - on-tap="onReadonlyInputTap_" class="password-field" readonly + on-click="onReadonlyInputTap_" class="password-field" readonly disabled$="[[!item.password]]" value="[[getPassword_(item.password)]]"> <button is="paper-icon-button-light" id="showPasswordButton" class$="[[getIconClass_(item.password)]]" - on-tap="onShowPasswordButtonTap_" + on-click="onShowPasswordButtonTap_" title="[[showPasswordTitle_(item.password, '$i18nPolymer{hidePassword}','$i18nPolymer{showPassword}')]]" focus-row-control focus-type="showPassword"> @@ -66,7 +66,7 @@ </template> </div> <button is="paper-icon-button-light" id="passwordMenu" - class="icon-more-vert" on-tap="onPasswordMenuTap_" + class="icon-more-vert" on-click="onPasswordMenuTap_" title="$i18n{moreActions}" focus-row-control focus-type="passwordMenu"> </button> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_and_forms_page.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_and_forms_page.html index 55dd3fd8ed6..a994bde577f 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_and_forms_page.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_and_forms_page.html @@ -25,10 +25,10 @@ <neon-animatable route-path="default"> <button is="cr-link-row" icon-class="subpage-arrow" id="autofillManagerButton" label="$i18n{autofill}" - sub-label="$i18n{autofillDetail}" on-tap="onAutofillTap_"> + sub-label="$i18n{autofillDetail}" on-click="onAutofillTap_"> </button> <div class="settings-box two-line"> - <div class="start two-line" on-tap="onPasswordsTap_" actionable + <div class="start two-line" on-click="onPasswordsTap_" actionable id="passwordManagerButton"> <div class="flex"> $i18n{passwords} diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.html index 9353ae7e5fd..2cfe136b936 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.html @@ -8,8 +8,15 @@ <dom-module id="passwords-export-dialog"> <template> <style include="settings-shared iron-flex"> + paper-progress { + width: 100%; + --paper-progress-active-color: var(--google-blue-500); + } + .action-button { + -webkit-margin-start: 8px; + } </style> - <dialog is="cr-dialog" id="dialog" close-text="$i18n{close}"> + <dialog is="cr-dialog" id="dialog_start" close-text="$i18n{close}"> <div slot="title">$i18n{exportPasswordsTitle}</div> <div slot="body"> <div class="layout horizontal center"> @@ -18,15 +25,51 @@ </div> <div slot="button-container"> <paper-button class="secondary-button header-aligned-button" - on-tap="onCancelButtonTap_"> + on-click="onCancelButtonTap_" id="cancelButton"> $i18n{cancel} </paper-button> <paper-button class="action-button header-aligned-button" - on-tap="onExportTap_" id="exportPasswordsButton"> + on-click="onExportTap_" id="exportPasswordsButton"> $i18n{exportPasswords} </paper-button> </div> </dialog> + + <dialog is="cr-dialog" id="dialog_progress" no-cancel="true"> + <div slot="title">$i18n{exportingPasswordsTitle}</div> + <div slot="body"> + <paper-progress indeterminate class="blue"></paper-progress> + </div> + <div slot="button-container"> + <paper-button id="cancel_progress_button" + class="secondary-button header-aligned-button" + on-click="onCancelProgressButtonTap_"> + $i18n{cancel} + </paper-button> + </div> + </dialog> + + <dialog is="cr-dialog" id="dialog_error" close-text="$i18n{close}"> + <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"> + <paper-button class="secondary-button header-aligned-button" + on-click="onCancelButtonTap_" id="cancelErrorButton"> + $i18n{cancel} + </paper-button> + <paper-button class="action-button header-aligned-button" + on-click="onExportTap_" id="tryAgainButton"> + $i18n{exportPasswordsTryAgain} + </paper-button> + </div> + </dialog> + </template> <script src="passwords_export_dialog.js"></script> -</dom-module>
\ No newline at end of file +</dom-module> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.js b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.js index da06563ff1c..737b3f504e4 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.js +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_export_dialog.js @@ -10,9 +10,43 @@ (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, + }, + + listeners: {'cancel': 'close'}, + /** * The interface for callbacks to the browser. * Defined in passwords_section.js @@ -21,16 +55,114 @@ Polymer({ */ passwordManager_: null, + /** @private {function(!PasswordManager.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 {?PasswordManager.PasswordExportProgress} + */ + delayedProgress_: null, + /** @override */ attached: function() { - this.$.dialog.showModal(); - 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 {!PasswordManager.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() { - this.$.dialog.close(); + clearTimeout(this.progressTaskToken_); + clearTimeout(this.delayedCompletionToken_); + this.progressTaskToken_ = null; + this.delayedCompletionToken_ = null; + this.passwordManager_.removePasswordsFileExportProgressListener( + this.onPasswordsFileExportProgressListener_); + if (this.$.dialog_start.open) + this.$.dialog_start.close(); + if (this.$.dialog_progress.open) + this.$.dialog_progress.close(); + if (this.$.dialog_error.open) + this.$.dialog_error.close(); }, /** @@ -38,7 +170,56 @@ Polymer({ * @private */ onExportTap_: function() { - this.passwordManager_.exportPasswords(); + 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 {!PasswordManager.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.$.dialog_start.open = false; + this.$.dialog_error.open = false; + this.$.dialog_progress.open = false; + + if (state == States.START) + this.$.dialog_start.showModal(); + if (state == States.ERROR) + this.$.dialog_error.showModal(); + if (state == States.IN_PROGRESS) + this.$.dialog_progress.showModal(); }, /** @@ -48,5 +229,15 @@ Polymer({ 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(); + }, }); })();
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html index 4b47e458f25..956780bb648 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.html @@ -52,12 +52,16 @@ #undoToast { z-index: 1; - } + } + + #exportImportMenuButton { + -webkit-margin-end: 0; + } </style> <settings-toggle-button id="passwordToggle" - class="first primary-toggle" + class="first" aria-label="$i18n{passwords}" no-extension-indicator - label="[[getOnOffLabel_(prefs.credentials_enable_service.value)]]" + label="$i18n{passwordsSavePasswordsLabel}" pref="{{prefs.credentials_enable_service}}"> </settings-toggle-button> <template is="dom-if" @@ -89,7 +93,7 @@ if="[[showImportOrExportPasswords_( showExportPasswords_, showImportPasswords_)]]"> <button is="paper-icon-button-light" id="exportImportMenuButton" - class="icon-more-vert" on-tap="onImportExportMenuTap_" + class="icon-more-vert" on-click="onImportExportMenuTap_" title="$i18n{moreActions}" focus-type="exportImportMenuButton"> </button> </template> @@ -122,20 +126,20 @@ </div> </div> <dialog is="cr-action-menu" id="menu"> - <button id="menuEditPassword" class="dropdown-item" - on-tap="onMenuEditPasswordTap_">$i18n{passwordViewDetails}</button> - <button id="menuRemovePassword" class="dropdown-item" - on-tap="onMenuRemovePasswordTap_">$i18n{removePassword}</button> + <button id="menuEditPassword" slot="item" class="dropdown-item" + on-click="onMenuEditPasswordTap_">$i18n{passwordViewDetails}</button> + <button id="menuRemovePassword" slot="item" class="dropdown-item" + on-click="onMenuRemovePasswordTap_">$i18n{removePassword}</button> </dialog> <dialog is="cr-action-menu" id="exportImportMenu"> - <template is="dom-if" if="[[showImportPasswords_]]"> - <button id="menuImportPassword" class="dropdown-item" - on-tap="onImportTap_">$i18n{import}</button> - </template> - <template is="dom-if" if="[[showExportPasswords_]]"> - <button id="menuExportPassword" class="dropdown-item" - on-tap="onExportTap_">$i18n{export}</button> - </template> + <button id="menuImportPassword" slot="item" class="dropdown-item" + on-click="onImportTap_" hidden="[[!showImportPasswords_]]"> + $i18n{import} + </button> + <button id="menuExportPassword" slot="item" class="dropdown-item" + on-click="onExportTap_" hidden="[[!showExportPasswords_]]"> + $i18n{exportMenuItem} + </button> </dialog> <template is="dom-if" if="[[showPasswordsExportDialog_]]" restamp> <passwords-export-dialog on-close="onPasswordsExportDialogClosed_"> @@ -148,7 +152,7 @@ </template> <cr-toast id="undoToast" duration="[[toastDuration_]]"> <div id="undoLabel">$i18n{passwordDeleted}</div> - <paper-button id="undoButton" on-tap="onUndoButtonTap_"> + <paper-button id="undoButton" on-click="onUndoButtonTap_"> $i18n{undoRemovePassword} </paper-button> </cr-toast> @@ -165,7 +169,7 @@ </a> </div> <button is="paper-icon-button-light" id="removeExceptionButton" - class="icon-clear" on-tap="onRemoveExceptionButtonTap_" + class="icon-clear" on-click="onRemoveExceptionButtonTap_" tabindex$="[[tabIndex]]" title="$i18n{deletePasswordException}"> </button> diff --git a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.js b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.js index d4f0f70ac06..33cb93e78a8 100644 --- a/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.js +++ b/chromium/chrome/browser/resources/settings/passwords_and_forms_page/passwords_section.js @@ -10,6 +10,8 @@ /** * Interface for all callbacks to the password API. + * TODO(crbug.com/802352) Move the PasswordManager proxy to a separate + * location. * @interface */ class PasswordManager { @@ -84,8 +86,32 @@ class PasswordManager { /** * Triggers the dialogue for exporting passwords. + * @param {function():void} callback */ - exportPasswords() {} + exportPasswords(callback) {} + + /** + * Cancels the ongoing export of passwords. + */ + cancelExportPasswords(callback) {} + + /** + * Queries the status of any ongoing export. + * @param {function(!PasswordManager.ExportProgressStatus):void} callback + */ + requestExportProgressStatus(callback) {} + + /** + * Add an observer to the export progress. + * @param {function(!PasswordManager.PasswordExportProgress):void} listener + */ + addPasswordsFileExportProgressListener(listener) {} + + /** + * Remove an observer from the export progress. + * @param {function(!PasswordManager.PasswordExportProgress):void} listener + */ + removePasswordsFileExportProgressListener(listener) {} } /** @typedef {chrome.passwordsPrivate.PasswordUiEntry} */ @@ -103,6 +129,12 @@ PasswordManager.PlaintextPasswordEvent; /** @typedef {{ entry: !PasswordManager.PasswordUiEntry, password: string }} */ PasswordManager.UiEntryWithPassword; +/** @typedef {chrome.passwordsPrivate.PasswordExportProgress} */ +PasswordManager.PasswordExportProgress; + +/** @typedef {chrome.passwordsPrivate.ExportProgressStatus} */ +PasswordManager.ExportProgressStatus; + /** * Implementation that accesses the private API. * @implements {PasswordManager} @@ -176,8 +208,29 @@ class PasswordManagerImpl { } /** @override */ - exportPasswords() { - chrome.passwordsPrivate.exportPasswords(); + exportPasswords(callback) { + chrome.passwordsPrivate.exportPasswords(callback); + } + + /** @override */ + cancelExportPasswords() { + chrome.passwordsPrivate.cancelExportPasswords(); + } + + /** @override */ + requestExportProgressStatus(callback) { + chrome.passwordsPrivate.requestExportProgressStatus(callback); + } + + /** @override */ + addPasswordsFileExportProgressListener(listener) { + chrome.passwordsPrivate.onPasswordsFileExportProgress.addListener(listener); + } + + /** @override */ + removePasswordsFileExportProgressListener(listener) { + chrome.passwordsPrivate.onPasswordsFileExportProgress.removeListener( + listener); } } @@ -251,10 +304,7 @@ Polymer({ /** @private */ showExportPasswords_: { type: Boolean, - value: function() { - return loadTimeData.valueExists('showExportPasswords') && - loadTimeData.getBoolean('showExportPasswords'); - } + computed: 'showExportPasswordsAndReady_(savedPasswords)' }, /** @private */ @@ -489,6 +539,7 @@ Polymer({ */ onImportTap_: function() { this.passwordManager_.importPasswords(); + this.$.exportImportMenu.close(); }, /** @@ -503,6 +554,8 @@ Polymer({ /** @private */ onPasswordsExportDialogClosed_: function() { this.showPasswordsExportDialog_ = false; + cr.ui.focusWithoutInk(assert(this.activeDialogAnchor_)); + this.activeDialogAnchor_ = null; }, /** @@ -538,6 +591,16 @@ Polymer({ /** * @private + * @param {!Array<!PasswordManager.PasswordUiEntry>} savedPasswords + */ + showExportPasswordsAndReady_: function(savedPasswords) { + return loadTimeData.valueExists('showExportPasswords') && + loadTimeData.getBoolean('showExportPasswords') && + savedPasswords.length > 0; + }, + + /** + * @private * @param {boolean} showExportPasswords * @param {boolean} showImportPasswords * @return {boolean} diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture.html b/chromium/chrome/browser/resources/settings/people_page/change_picture.html index 87ec0b1917d..f14495a8af7 100644 --- a/chromium/chrome/browser/resources/settings/people_page/change_picture.html +++ b/chromium/chrome/browser/resources/settings/people_page/change_picture.html @@ -106,7 +106,7 @@ choose-file-label="$i18n{chooseFile}" old-image-label="$i18n{oldPhoto}" profile-image-label="$i18n{profilePhoto}" - take-photo-label="$i18n{takePhoto}"> + take-photo-label="$i18n{takePhoto}" capture-video-label="$i18n{captureVideo}"> </cr-picture-list> </div> diff --git a/chromium/chrome/browser/resources/settings/people_page/change_picture.js b/chromium/chrome/browser/resources/settings/people_page/change_picture.js index 48eaa44ed61..eeca32878b8 100644 --- a/chromium/chrome/browser/resources/settings/people_page/change_picture.js +++ b/chromium/chrome/browser/resources/settings/people_page/change_picture.js @@ -81,6 +81,9 @@ Polymer({ /** @private {?CrPictureListElement} */ pictureList_: null, + /** @private {boolean} */ + oldImagePending_: false, + /** @override */ ready: function() { this.browserProxy_ = settings.ChangePictureBrowserProxyImpl.getInstance(); @@ -144,6 +147,7 @@ Polymer({ * @private */ receiveOldImage_: function(imageInfo) { + this.oldImagePending_ = false; this.pictureList_.setOldImageUrl(imageInfo.url, imageInfo.index); }, @@ -216,6 +220,7 @@ Polymer({ * @private */ onPhotoTaken_: function(event) { + this.oldImagePending_ = true; this.browserProxy_.photoTaken(event.detail.photoDataUrl); this.pictureList_.setOldImageUrl(event.detail.photoDataUrl); this.pictureList_.setFocus(); @@ -235,6 +240,9 @@ Polymer({ /** @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(); 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 index 7295e66f6a0..3cd333e51bd 100644 --- 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 @@ -51,8 +51,9 @@ cr.define('settings', function() { selectProfileImage() {} /** - * Provides the taken photo as a data URL to the C++. No response is - * expected. + * 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) {} diff --git a/chromium/chrome/browser/resources/settings/people_page/compiled_resources2.gyp b/chromium/chrome/browser/resources/settings/people_page/compiled_resources2.gyp index 3159cd12ca1..5c15adeb340 100644 --- a/chromium/chrome/browser/resources/settings/people_page/compiled_resources2.gyp +++ b/chromium/chrome/browser/resources/settings/people_page/compiled_resources2.gyp @@ -255,5 +255,19 @@ ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, + { + 'target_name': 'sync_account_control', + 'dependencies': [ + '../compiled_resources2.gyp:route', + '../prefs/compiled_resources2.gyp:prefs_behavior', + '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:icon', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:web_ui_listener_behavior', + 'profile_info_browser_proxy', + 'sync_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, ], } diff --git a/chromium/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.html b/chromium/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.html index 211d825ee32..dd65510f60f 100644 --- a/chromium/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.html +++ b/chromium/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.html @@ -21,11 +21,12 @@ hidden="[[isButtonBarHidden_(status_)]]"> <paper-spinner-lite active="[[isSpinnerActive_(status_)]]"> </paper-spinner-lite> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" hidden="[[isCancelButtonHidden_(status_)]]"> $i18n{cancel} </paper-button> - <paper-button id="turnOff" class="action-button" on-tap="onTurnOffTap_" + <paper-button id="turnOff" class="action-button" + on-click="onTurnOffTap_" disabled="[[!isTurnOffButtonEnabled_(status_)]]"> [[getTurnOffButtonText_(status_)]] </paper-button> diff --git a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html b/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html index 6c62e3e44af..c98c91b9c8a 100644 --- a/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html +++ b/chromium/chrome/browser/resources/settings/people_page/fingerprint_list.html @@ -29,7 +29,7 @@ } .body { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; } .list-item { @@ -55,14 +55,14 @@ on-change="onFingerprintLabelChanged_"> </paper-input> <button is="paper-icon-button-light" class="icon-delete-gray" - on-tap="onFingerprintDeleteTapped_"> + on-click="onFingerprintDeleteTapped_"> </button> </div> </template> </iron-list> <div class="continuation"> <paper-button id="addFingerprint" class="add-link action-button" - on-tap="openAddFingerprintDialog_"> + on-click="openAddFingerprintDialog_"> $i18n{lockScreenAddFingerprint} </paper-button> </div> 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 index 985a4aec02c..cb07d334450 100644 --- a/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html +++ b/chromium/chrome/browser/resources/settings/people_page/import_data_dialog.html @@ -104,7 +104,7 @@ importStatusEnum_.SUCCEEDED, importStatus_)]]" disabled="[[hasImportStatus_( importStatusEnum_.IN_PROGRESS, importStatus_)]]" - on-tap="closeDialog_"> + on-click="closeDialog_"> $i18n{cancel} </paper-button> <paper-button id="import" class="action-button" @@ -112,14 +112,14 @@ importStatusEnum_.SUCCEEDED, importStatus_)]]" disabled="[[shouldDisableImport_( importStatus_, noImportDataTypeSelected_)]]" - on-tap="onActionButtonTap_"> + on-click="onActionButtonTap_"> [[getActionButtonText_(selected_)]] </paper-button> <paper-button id="done" class="action-button" hidden$="[[!hasImportStatus_( importStatusEnum_.SUCCEEDED, importStatus_)]]" - on-tap="closeDialog_">$i18n{done}</paper-button> + on-click="closeDialog_">$i18n{done}</paper-button> </div> </dialog> </template> diff --git a/chromium/chrome/browser/resources/settings/people_page/lock_screen.html b/chromium/chrome/browser/resources/settings/people_page/lock_screen.html index cdaa2505cae..d12222f1f36 100644 --- a/chromium/chrome/browser/resources/settings/people_page/lock_screen.html +++ b/chromium/chrome/browser/resources/settings/people_page/lock_screen.html @@ -59,7 +59,7 @@ } #easyUnlockSettingsCollapsible { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; } .no-padding { @@ -111,7 +111,7 @@ <div id="pinPasswordSecondaryActionDiv" class="secondary-action"> <paper-button id="setupPinButton" class="secondary-button" - on-tap="onConfigurePin_"> + on-click="onConfigurePin_"> [[getSetupPinText_(hasPin)]] </paper-button> </div> @@ -140,7 +140,7 @@ <div class="separator"></div> <div class="secondary-action"> <paper-button class="secondary-button" - on-tap="onEditFingerprints_" + on-click="onEditFingerprints_" aria-label="$i18n{lockScreenEditFingerprints}" aria-descibedby="lockScreenEditFingerprintsSecondary"> $i18n{lockScreenSetupFingerprintButton} @@ -167,13 +167,13 @@ <div class="separator"></div> <template is="dom-if" if="[[!easyUnlockEnabled_]]"> <paper-button id="easyUnlockSetup" class="secondary-button" - on-tap="onEasyUnlockSetupTap_"> + on-click="onEasyUnlockSetupTap_"> $i18n{easyUnlockSetupButton} </paper-button> </template> <template is="dom-if" if="[[easyUnlockEnabled_]]"> <paper-button id="easyUnlockTurnOff" class="secondary-button" - on-tap="onEasyUnlockTurnOffTap_"> + on-click="onEasyUnlockTurnOffTap_"> $i18n{easyUnlockTurnOffButton} </paper-button> </template> diff --git a/chromium/chrome/browser/resources/settings/people_page/password_prompt_dialog.html b/chromium/chrome/browser/resources/settings/people_page/password_prompt_dialog.html index 9a7dac7a58c..68056695bfe 100644 --- a/chromium/chrome/browser/resources/settings/people_page/password_prompt_dialog.html +++ b/chromium/chrome/browser/resources/settings/people_page/password_prompt_dialog.html @@ -34,11 +34,11 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="submitPassword_" + <paper-button class="action-button" on-click="submitPassword_" disabled$="[[!enableConfirm_(password_, passwordInvalid_)]]"> $i18n{confirm} </paper-button> diff --git a/chromium/chrome/browser/resources/settings/people_page/people_page.html b/chromium/chrome/browser/resources/settings/people_page/people_page.html index 4ab0dfcd81f..d7b6822e5ec 100644 --- a/chromium/chrome/browser/resources/settings/people_page/people_page.html +++ b/chromium/chrome/browser/resources/settings/people_page/people_page.html @@ -33,6 +33,7 @@ <link rel="import" href="users_page.html"> </if> <if expr="not chromeos"> +<link rel="import" href="sync_account_control.html"> <link rel="import" href="import_data_dialog.html"> <link rel="import" href="manage_profile.html"> </if> @@ -46,9 +47,7 @@ } #profile-icon { - background-position: center; - background-repeat: no-repeat; - background-size: cover; + background: center / cover no-repeat; border-radius: 20px; flex-shrink: 0; height: 40px; @@ -102,66 +101,80 @@ <settings-animated-pages id="pages" section="people" focus-config="[[focusConfig_]]"> <neon-animatable route-path="default"> - <div id="picture-subpage-trigger" class="settings-box first two-line"> - <template is="dom-if" if="[[syncStatus]]"> - <div id="profile-icon" on-tap="onPictureTap_" actionable - style="background-image: [[getIconImageSet_(profileIconUrl_)]]"> - </div> <if expr="not chromeos"> - <div class="middle two-line no-min-width" - on-tap="onProfileNameTap_" actionable> -</if> -<if expr="chromeos"> - <div class="middle two-line no-min-width" on-tap="onPictureTap_" - actionable> + <template is="dom-if" if="[[shouldShowSyncAccountControl_(diceEnabled_, + syncStatus.syncSystemEnabled, syncStatus.signinAllowed)]]"> + <settings-sync-account-control + promo-label="$i18n{peopleSignInPrompt}" + promo-secondary-label="$i18n{peopleSignInPromptSecondary}"> + </settings-sync-account-control> + </template> + <template is="dom-if" if="[[!diceEnabled_]]"> </if> - <div class="flex text-elide"> - <span id="profile-name">[[profileName_]]</span> - <div class="secondary" hidden="[[!syncStatus.signedIn]]"> - [[syncStatus.signedInUsername]] - </div> + <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 + style="background-image: [[getIconImageSet_( + profileIconUrl_)]]"> </div> + <div class="middle two-line no-min-width" on-click="onProfileTap_" + actionable> + <div class="flex text-elide"> + <span id="profile-name">[[profileName_]]</span> + <div class="secondary" hidden="[[!syncStatus.signedIn]]"> + [[syncStatus.signedInUsername]] + </div> + </div> <if expr="not chromeos"> - <button class="subpage-arrow" is="paper-icon-button-light" - aria-label="$i18n{editPerson}" - aria-describedby="profile-name"></button> + <button class="subpage-arrow" is="paper-icon-button-light" + aria-label="$i18n{editPerson}" + aria-describedby="profile-name"></button> </if> <if expr="chromeos"> - <button class="subpage-arrow" is="paper-icon-button-light" - aria-label="$i18n{changePictureTitle}" - aria-describedby="profile-name"></button> + <button class="subpage-arrow" is="paper-icon-button-light" + aria-label="$i18n{changePictureTitle}" + aria-describedby="profile-name"></button> </if> - </div> + </div> <if expr="not chromeos"> - <template is="dom-if" if="[[showSignin_(syncStatus)]]"> - <div class="separator"></div> - <paper-button class="primary-button" on-tap="onSigninTap_" - disabled="[[syncStatus.setupInProgress]]"> - $i18n{syncSignin} - </paper-button> - </template> - <template is="dom-if" if="[[syncStatus.signedIn]]"> - <div class="separator"></div> - <paper-button id="disconnectButton" class="secondary-button" - on-tap="onDisconnectTap_" - disabled="[[syncStatus.setupInProgress]]"> - $i18n{syncDisconnect} - </paper-button> + <template is="dom-if" if="[[showSignin_(syncStatus)]]"> + <div class="separator"></div> + <paper-button class="primary-button" on-click="onSigninTap_" + disabled="[[syncStatus.setupInProgress]]"> + $i18n{syncSignin} + </paper-button> + </template> + <template is="dom-if" if="[[syncStatus.signedIn]]"> + <div class="separator"></div> + <paper-button id="disconnectButton" class="secondary-button" + on-click="onDisconnectTap_" + disabled="[[syncStatus.setupInProgress]]"> + $i18n{syncDisconnect} + </paper-button> + </template> +</if> </template> + </div> +<if expr="not chromeos"> + </template> <!-- if="[[!diceEnabled_]]" --> </if> - </template> - </div> <template is="dom-if" if="[[!syncStatus.signedIn]]"> - <div class="settings-box two-line" - hidden="[[!syncStatus.signinAllowed]]"> - <div class="start"> - $i18n{syncOverview} - <a target="_blank" href="$i18n{syncLearnMoreUrl}"> - $i18n{learnMore} - </a> +<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"> + $i18n{syncOverview} + <a target="_blank" href="$i18n{syncLearnMoreUrl}"> + $i18n{learnMore} + </a> + </div> </div> - </div> +<if expr="not chromeos"> + </template> <!-- if="[[!diceEnabled_]]" --> +</if> <div class="settings-box" hidden="[[syncStatus.signinAllowed]]"> $i18n{syncDisabledByAdministrator} </div> @@ -182,7 +195,7 @@ <template is="dom-if" if="[[isAdvancedSyncSettingsVisible_(syncStatus)]]"> - <div class="settings-box two-line" on-tap="onSyncTap_" + <div class="settings-box two-line" on-click="onSyncTap_" id="sync-status" actionable$="[[isSyncStatusActionable_( syncStatus)]]"> <div class="icon-container"> @@ -202,9 +215,20 @@ </div> </template> +<if expr="not chromeos"> + <template is="dom-if" if="[[diceEnabled_]]"> + <div class="settings-box" id="edit-profile" on-click="onProfileTap_" + actionable> + <div class="start">$i18n{profileNameAndPicture}</div> + <button class="subpage-arrow" is="paper-icon-button-light" + aria-label="$i18n{editPerson}"></button> + </div> + </template> +</if> + <if expr="chromeos"> <div id="lock-screen-subpage-trigger" class="settings-box two-line" - actionable on-tap="onConfigureLockTap_"> + actionable on-click="onConfigureLockTap_"> <div class="start"> $i18n{lockScreenTitle} <div class="secondary" id="lockScreenSecondary"> @@ -219,14 +243,14 @@ </if> <div id="manage-other-people-subpage-trigger" - class="settings-box" on-tap="onManageOtherPeople_" actionable> + class="settings-box" on-click="onManageOtherPeople_" actionable> <div class="start">$i18n{manageOtherPeople}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{manageOtherPeople}"></button> </div> <if expr="not chromeos"> - <div class="settings-box" on-tap="onImportDataTap_" actionable> + <div class="settings-box" on-click="onImportDataTap_" actionable> <div class="start">$i18n{importTitle}</div> <button id="importDataDialogTrigger" class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{importTitle}"> @@ -234,16 +258,6 @@ </div> </if> - <template is="dom-if" if="[[profileManagesSupervisedUsers_]]"> - <a id="manageSupervisedUsersContainer" - class="settings-box inherit-color no-outline" tabindex="-1" - target="_blank" href="$i18n{supervisedUsersUrl}"> - <div class="start">$i18n{manageSupervisedUsers}</div> - <button class="icon-external" is="paper-icon-button-light" - actionable aria-label="$i18n{manageSupervisedUsers}"> - </button> - </a> - </template> </neon-animatable> <template is="dom-if" route-path="/syncSetup" no-search$="[[!isAdvancedSyncSettingsVisible_(syncStatus)]]"> @@ -274,10 +288,7 @@ <settings-subpage associated-control="[[$$('#manage-other-people-subpage-trigger')]]" page-title="$i18n{manageOtherPeople}"> - <settings-users-page - prefs="{{prefs}}" - profile-manages-supervised-users= - "[[profileManagesSupervisedUsers_]]"> + <settings-users-page prefs="{{prefs}}"> </settings-users-page> </settings-subpage> </template> @@ -313,16 +324,16 @@ </div> </div> <div slot="button-container"> - <paper-button on-tap="onDisconnectCancel_" class="cancel-button"> + <paper-button on-click="onDisconnectCancel_" class="cancel-button"> $i18n{cancel} </paper-button> <paper-button id="disconnectConfirm" class="action-button" - hidden="[[syncStatus.domain]]" on-tap="onDisconnectConfirm_"> + hidden="[[syncStatus.domain]]" on-click="onDisconnectConfirm_"> $i18n{syncDisconnect} </paper-button> <paper-button id="disconnectManagedProfileConfirm" class="action-button" hidden="[[!syncStatus.domain]]" - on-tap="onDisconnectConfirm_"> + on-click="onDisconnectConfirm_"> $i18n{syncDisconnectConfirm} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/people_page/people_page.js b/chromium/chrome/browser/resources/settings/people_page/people_page.js index 60555a4e8a4..d1be1d2c850 100644 --- a/chromium/chrome/browser/resources/settings/people_page/people_page.js +++ b/chromium/chrome/browser/resources/settings/people_page/people_page.js @@ -25,6 +25,22 @@ Polymer({ 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(scottchen): 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> + /** * The current sync status, supplied by SyncBrowserProxy. * @type {?settings.SyncStatus} @@ -33,33 +49,33 @@ Polymer({ /** * The currently selected profile icon URL. May be a data URL. + * @private */ profileIconUrl_: String, /** * The current profile name. + * @private */ profileName_: String, /** - * True if the current profile manages supervised users. - */ - profileManagesSupervisedUsers_: 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, /** * True if the profile deletion warning is visible. + * @private */ deleteProfileWarningVisible_: Boolean, /** * True if the checkbox to delete the profile has been checked. + * @private */ deleteProfile_: Boolean, @@ -134,12 +150,6 @@ Polymer({ this.addWebUIListener( 'profile-info-changed', this.handleProfileInfo_.bind(this)); - profileInfoProxy.getProfileManagesSupervisedUsers().then( - this.handleProfileManagesSupervisedUsers_.bind(this)); - this.addWebUIListener( - 'profile-manages-supervised-users-changed', - this.handleProfileManagesSupervisedUsers_.bind(this)); - this.addWebUIListener( 'profile-stats-count-ready', this.handleProfileStatsCount_.bind(this)); @@ -209,15 +219,6 @@ Polymer({ }, /** - * Handler for when the profile starts or stops managing supervised users. - * @private - * @param {boolean} managesSupervisedUsers - */ - handleProfileManagesSupervisedUsers_: function(managesSupervisedUsers) { - this.profileManagesSupervisedUsers_ = managesSupervisedUsers; - }, - - /** * Handler for when the profile stats count is pushed from the browser. * @param {number} count * @private @@ -251,7 +252,7 @@ Polymer({ }, /** @private */ - onPictureTap_: function() { + onProfileTap_: function() { // <if expr="chromeos"> settings.navigateTo(settings.routes.CHANGE_PICTURE); // </if> @@ -260,13 +261,6 @@ Polymer({ // </if> }, - // <if expr="not chromeos"> - /** @private */ - onProfileNameTap_: function() { - settings.navigateTo(settings.routes.MANAGE_PROFILE); - }, - // </if> - /** @private */ onSigninTap_: function() { this.syncBrowserProxy_.startSignIn(); @@ -275,7 +269,16 @@ Polymer({ /** @private */ onDisconnectClosed_: function() { this.showDisconnectDialog_ = 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(); @@ -391,6 +394,15 @@ Polymer({ settings.navigateToPreviousRoute(); cr.ui.focusWithoutInk(assert(this.$.importDataDialogTrigger)); }, + + /** + * @return {boolean} + * @private + */ + shouldShowSyncAccountControl_: function() { + return !!this.diceEnabled_ && !!this.syncStatus.syncSystemEnabled && + !!this.syncStatus.signinAllowed; + }, // </if> /** diff --git a/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.html b/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.html deleted file mode 100644 index 65056df0516..00000000000 --- a/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.html +++ /dev/null @@ -1 +0,0 @@ -<include src="../../chromeos/quick_unlock/pin_keyboard.html"> diff --git a/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.js b/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.js deleted file mode 100644 index 4cb8d021731..00000000000 --- a/chromium/chrome/browser/resources/settings/people_page/pin_keyboard.js +++ /dev/null @@ -1,5 +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. - -// <include src="../../chromeos/quick_unlock/pin_keyboard.js"> 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 index 31f4824fd3b..65808a60de6 100644 --- 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 @@ -32,12 +32,6 @@ cr.define('settings', function() { * 'profile-stats-count-ready' WebUI listener event. */ getProfileStatsCount() {} - - /** - * Returns a Promise that's true if the profile manages supervised users. - * @return {!Promise<boolean>} - */ - getProfileManagesSupervisedUsers() {} } /** @@ -53,11 +47,6 @@ cr.define('settings', function() { getProfileStatsCount() { chrome.send('getProfileStatsCount'); } - - /** @override */ - getProfileManagesSupervisedUsers() { - return cr.sendWithPromise('getProfileManagesSupervisedUsers'); - } } cr.addSingletonGetter(ProfileInfoBrowserProxyImpl); 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 index c39f06d3012..7e005111b04 100644 --- a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html +++ b/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html @@ -72,13 +72,13 @@ </div> </div> <div slot="button-container"> - <paper-button id="addAnotherButton" on-tap="onAddAnotherFingerprint_" + <paper-button id="addAnotherButton" on-click="onAddAnotherFingerprint_" hidden$="[[hideAddAnother_(step_)]]"> $i18n{configureFingerprintAddAnotherButton} </paper-button> <paper-button id="closeButton" - class$="[[getCloseButtonClass_(step_)]]" on-tap="onClose_"> + class$="[[getCloseButtonClass_(step_)]]" on-click="onClose_"> [[getCloseButtonText_(step_)]] </paper-button> </div> 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 index 7922b7808ec..4b9ca548d58 100644 --- a/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js +++ b/chromium/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js @@ -259,7 +259,7 @@ Polymer({ */ getCloseButtonText_: function(step) { if (step == settings.FingerprintSetupStep.READY) - return this.i18n('configureFingerprintDoneButton'); + return this.i18n('done'); return this.i18n('cancel'); }, 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 index d7a74e2a102..7cffa7ec8cb 100644 --- a/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html +++ b/chromium/chrome/browser/resources/settings/people_page/setup_pin_dialog.html @@ -1,3 +1,4 @@ +<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/pin_keyboard.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/polymer.html"> @@ -6,7 +7,6 @@ <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="../i18n_setup.html"> <link rel="import" href="lock_screen_constants.html"> -<link rel="import" href="pin_keyboard.html"> <link rel="import" href="../settings_shared_css.html"> <dom-module id="settings-setup-pin-dialog"> @@ -28,6 +28,13 @@ --iron-icon-fill-color: var(--paper-grey-700); } + pin-keyboard { + --pin-keyboard-digit-button: { + font-size: 18px; + padding: 15px 21px; + }; + } + #pinKeyboardDiv { justify-content: center; } @@ -62,11 +69,11 @@ </div> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onPinSubmit_" + <paper-button class="action-button" on-click="onPinSubmit_" disabled$="[[!enableSubmit_]]"> <span>[[getContinueMessage_(isConfirmStep_)]]</span> </paper-button> 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 new file mode 100644 index 00000000000..e59e5f06258 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/people_page/sync_account_control.html @@ -0,0 +1,200 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.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-icons/notification-icons.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.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="../route.html"> +<link rel="import" href="../settings_shared_css.html"> + +<dom-module id="settings-sync-account-control"> + <template> + <style include="settings-shared"> + :host { + --sync-icon-size: 16px; + --sync-icon-border-size: 2px; + --shown-avatar-size: 40px; + } + + setting-box.middle { + /* Per spec, middle text is indented 20px in this section. */ + -webkit-margin-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 { + -webkit-margin-start: 8px; + } + + .flex { + display: flex; + flex: 1; + flex-direction: column; + } + + #avatar-container { + position: relative; + } + + #sync-icon-container { + align-items: center; + background: var(--google-blue-500); + 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; + } + + #sync-icon-container[syncing] { + background: green; + } + + #sync-icon-container iron-icon { + fill: white; + height: 12px; + margin: auto; + width: 12px; + } + + #sign-in { + min-width: 100px; + } + + #banner { + background-color: var(--google-blue-500); + display: none; + } + + #banner img { + -webkit-margin-start: 380px; + height: 100px; + margin-bottom: -12px; + margin-top: 32px; + } + + :host([showing-promo]) #banner { + display: flex; + } + + :host([showing-promo]) #promo-headers { + line-height: 1.625rem; + padding-bottom: 10px; + padding-top: 10px; + } + + :host([showing-promo]) #promo-headers #promo-title { + font-size: 1.1rem; + } + + :host([showing-promo]) #promo-headers .secondary { + font-size: 0.9rem; + } + + :host([showing-promo]) #promo-headers .separator { + display: none; + } + </style> + <div class="settings-box" id="banner"> + <img src="../images/sync_banner.svg" alt=""> + </div> + <div class="settings-box first two-line" id="promo-headers" + hidden="[[syncStatus.signedIn]]"> + <div class="start"> + <div id="promo-title">[[promoLabel]]</div> + <div class="secondary"> + [[promoSecondaryLabel]] + </div> + </div> + <div class="separator" hidden="[[shouldShowAvatarRow_]]"></div> + <paper-button class="action-button" on-click="onSigninTap_" + disabled="[[syncStatus.setupInProgress]]" id="sign-in" + hidden="[[shouldShowAvatarRow_]]"> + $i18n{peopleSignIn} + </paper-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" syncing$="[[syncStatus.signedIn]]"> + <iron-icon icon="notification:sync"></iron-icon> + </div> + </div> + <div class="middle two-line no-min-width"> + <div class="flex text-elide" id="user-info"> + <span> + [[getNameDisplay_('$i18nPolymer{syncedToName}', + shownAccount_.fullName, syncStatus.signedIn)]] + </span> + <div class="secondary">[[shownAccount_.email]]</div> + </div> + </div> + <button is="paper-icon-button-light" id="dropdown-arrow" + on-click="onMenuButtonTap_" title="$i18n{useAnotherAccount}" + class="icon-arrow-dropdown" hidden="[[syncStatus.signedIn]]"> + </button> + <div class="separator" hidden="[[syncStatus.signedIn]]"></div> + <paper-button class="action-button" on-click="onSyncButtonTap_" + hidden="[[syncStatus.signedIn]]" + disabled="[[syncStatus.setupInProgress]]"> + [[getSubstituteLabel_( + '$i18nPolymer{syncAsName}', shownAccount_.givenName)]] + </paper-button> + <paper-button class="secondary-button" on-click="onTurnOffButtonTap_" + hidden="[[!syncStatus.signedIn]]" + disabled="[[syncStatus.setupInProgress]]"> + $i18n{turnOffSync} + </paper-button> + </div> + <template is="dom-if" if="[[!syncStatus.signedIn]]" restamp> + <dialog is="cr-action-menu" id="menu" auto-reposition> + <template is="dom-repeat" items="[[storedAccounts_]]"> + <button class="dropdown-item" on-click="onAccountTap_" slot="item"> + <img class="account-icon small" alt="" + src="[[getAccountImageSrc_(item.avatarImage)]]"> + <span>[[item.email]]</span> + </button> + </template> + <button class="dropdown-item" on-click="onSigninTap_" slot="item" + disabled="[[syncStatus.setupInProgress]]" id="sign-in-item"> + <img class="account-icon small" alt="" + src="chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE"> + <span>$i18n{useAnotherAccount}</span> + </button> + </dialog> + </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 new file mode 100644 index 00000000000..8b079fe22ac --- /dev/null +++ b/chromium/chrome/browser/resources/settings/people_page/sync_account_control.js @@ -0,0 +1,222 @@ +// 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 SyncBrowserProxy. + * @type {!settings.SyncStatus} + */ + syncStatus: Object, + + /** + * 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, + }, + + promoLabel: String, + + promoSecondaryLabel: String, + + /** @private {boolean} */ + shouldShowAvatarRow_: { + type: Boolean, + value: false, + computed: 'computeShouldShowAvatarRow_(storedAccounts_, syncStatus,' + + 'storedAccounts_.length, syncStatus.signedIn)', + observer: 'onShouldShowAvatarRowChange_', + } + }, + + observers: [ + 'onShownAccountShouldChange_(storedAccounts_, syncStatus)', + ], + + /** @private {?settings.SyncBrowserProxy} */ + syncBrowserProxy_: null, + + /** @override */ + attached: function() { + this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance(); + this.syncBrowserProxy_.getSyncStatus().then( + this.handleSyncStatus_.bind(this)); + this.syncBrowserProxy_.getStoredAccounts().then( + this.handleStoredAccounts_.bind(this)); + this.addWebUIListener( + 'sync-status-changed', this.handleSyncStatus_.bind(this)); + this.addWebUIListener( + 'stored-accounts-updated', this.handleStoredAccounts_.bind(this)); + }, + + /** + * @return {boolean} + * @private + */ + computeSignedIn_: function() { + return !!this.syncStatus.signedIn; + }, + + /** @private */ + onSignedInChanged_: function() { + 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; + } + }, + + /** + * @param {string} label + * @param {string} name + * @return {string} + * @private + */ + getSubstituteLabel_: function(label, name) { + return loadTimeData.substituteString(label, name); + }, + + /** + * @param {string} label + * @param {string} name + * @return {string} + * @private + */ + getNameDisplay_: function(label, name) { + return this.syncStatus.signedIn ? + loadTimeData.substituteString(label, name) : + name; + }, + + /** + * @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'; + }, + + /** + * @param {!Array<!settings.StoredAccount>} accounts + * @private + */ + handleStoredAccounts_: function(accounts) { + this.storedAccounts_ = accounts; + }, + + /** + * Handler for when the sync state is pushed from the browser. + * @param {!settings.SyncStatus} syncStatus + * @private + */ + handleSyncStatus_: function(syncStatus) { + this.syncStatus = syncStatus; + }, + + /** + * @return {boolean} + * @private + */ + computeShouldShowAvatarRow_: function() { + return this.syncStatus.signedIn || this.storedAccounts_.length > 0; + }, + + /** @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 */ + onSyncButtonTap_: function() { + assert(this.shownAccount_); + this.syncBrowserProxy_.startSyncingWithEmail(this.shownAccount_.email); + }, + + /** @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.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 { + this.shownAccount_ = + this.storedAccounts_ ? this.storedAccounts_[0] : null; + } + } +});
\ No newline at end of file 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 index 05f345f3edf..ecb2a43ed3d 100644 --- a/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js +++ b/chromium/chrome/browser/resources/settings/people_page/sync_browser_proxy.js @@ -10,12 +10,20 @@ 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), * domain: (string|undefined), * hasError: (boolean|undefined), * hasUnrecoverableError: (boolean|undefined), * managed: (boolean|undefined), - * setupCompleted: (boolean|undefined), * setupInProgress: (boolean|undefined), * signedIn: (boolean|undefined), * signedInUsername: (string|undefined), @@ -104,6 +112,12 @@ settings.PageStatus = { }; 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"> @@ -124,6 +138,16 @@ cr.define('settings', function() { */ manageOtherPeople() {} + /** + * @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"> @@ -141,6 +165,12 @@ cr.define('settings', function() { 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. @@ -168,6 +198,12 @@ cr.define('settings', function() { setSyncEncryption(syncPrefs) {} /** + * Start syncing with an account, specified by its email. + * @param {string} email + */ + startSyncingWithEmail(email) {} + + /** * Opens the Google Activity Controls url in a new tab. */ openActivityControlsUrl() {} @@ -193,13 +229,26 @@ cr.define('settings', function() { chrome.send('SyncSetupManageOtherPeople'); } + /** @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 */ @@ -208,6 +257,11 @@ cr.define('settings', function() { } /** @override */ + getStoredAccounts() { + return cr.sendWithPromise('SyncSetupGetStoredAccounts'); + } + + /** @override */ didNavigateToSyncPage() { chrome.send('SyncSetupShowSetupUI'); } @@ -230,6 +284,11 @@ cr.define('settings', function() { } /** @override */ + startSyncingWithEmail(email) { + chrome.send('SyncSetupStartSyncingWithEmail', [email]); + } + + /** @override */ openActivityControlsUrl() { chrome.metricsPrivate.recordUserAction( 'Signin_AccountSettings_GoogleActivityControlsClicked'); diff --git a/chromium/chrome/browser/resources/settings/people_page/sync_page.html b/chromium/chrome/browser/resources/settings/people_page/sync_page.html index bea84fd3f43..5f9b6a925e1 100644 --- a/chromium/chrome/browser/resources/settings/people_page/sync_page.html +++ b/chromium/chrome/browser/resources/settings/people_page/sync_page.html @@ -88,7 +88,7 @@ on-keypress="onSubmitExistingPassphraseTap_"> </paper-input> <paper-button id="submitExistingPassphrase" - on-tap="onSubmitExistingPassphraseTap_" class="action-button" + on-click="onSubmitExistingPassphraseTap_" class="action-button" disabled="[[!existingPassphrase_]]"> $i18n{submitPassphraseButton} </paper-button> @@ -251,7 +251,7 @@ <a class="settings-box two-line inherit-color no-outline" tabindex="-1" target="_blank" href="$i18n{activityControlsUrl}" - on-tap="onActivityControlsTap_"> + on-click="onActivityControlsTap_"> <div class="start"> $i18n{personalizeGoogleServicesTitle} <div class="secondary" id="activityControlsSecondary"> @@ -294,7 +294,7 @@ <span>[[syncPrefs.fullEncryptionBody]]</span> </template> <template is="dom-if" if="[[!syncPrefs.fullEncryptionBody]]"> - <span on-tap="onLearnMoreTap_"> + <span on-click="onLearnMoreTap_"> $i18nRaw{encryptWithSyncPassphraseLabel} </span> </template> @@ -323,7 +323,7 @@ error-message="$i18n{mismatchedPassphraseError}"> </paper-input> <paper-button id="saveNewPassphrase" - on-tap="onSaveNewPassphraseTap_" class="action-button" + on-click="onSaveNewPassphraseTap_" class="action-button" disabled="[[!isSaveNewPassphraseEnabled_(passphrase_, confirmation_)]]"> $i18n{save} diff --git a/chromium/chrome/browser/resources/settings/people_page/user_list.html b/chromium/chrome/browser/resources/settings/people_page/user_list.html index 0873990c000..4ca878eef14 100644 --- a/chromium/chrome/browser/resources/settings/people_page/user_list.html +++ b/chromium/chrome/browser/resources/settings/people_page/user_list.html @@ -46,7 +46,7 @@ </template> </div> <button is="paper-icon-button-light" class="icon-clear" - on-tap="removeUser_" + on-click="removeUser_" hidden="[[shouldHideCloseButton_(disabled, item.isOwner)]]"> </button> </div> 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 index 99850568bff..fe8cd9c784b 100644 --- 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 @@ -25,10 +25,10 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button on-tap="addUser_" class="action-button" + <paper-button on-click="addUser_" class="action-button" disabled$="[[!isValid_]]"> $i18n{add} </paper-button> diff --git a/chromium/chrome/browser/resources/settings/people_page/users_page.html b/chromium/chrome/browser/resources/settings/people_page/users_page.html index ac720c8628e..bd58da4cdd0 100644 --- a/chromium/chrome/browser/resources/settings/people_page/users_page.html +++ b/chromium/chrome/browser/resources/settings/people_page/users_page.html @@ -39,13 +39,6 @@ label="$i18n{guestBrowsingLabel}" disabled="[[isEditingDisabled_(isOwner_, isWhitelistManaged_)]]"> </settings-toggle-button> - <template is="dom-if" if="[[profileManagesSupervisedUsers]]"> - <settings-toggle-button class="continuation" - pref="{{prefs.cros.accounts.supervisedUsersEnabled}}" - label="$i18n{supervisedUsersLabel}" - disabled="[[isEditingDisabled_(isOwner_, isWhitelistManaged_)]]"> - </settings-toggle-button> - </template> <settings-toggle-button class="continuation" pref="{{prefs.cros.accounts.showUserNamesOnSignIn}}" label="$i18n{showOnSigninLabel}" @@ -66,7 +59,7 @@ <div id="add-user-button" class="list-item" hidden="[[isEditingUsersDisabled_(isOwner_, isWhitelistManaged_, prefs.cros.accounts.allowGuest.value)]]"> - <a is="action-link" class="list-button" on-tap="openAddUserDialog_"> + <a is="action-link" class="list-button" on-click="openAddUserDialog_"> $i18n{addUsers} </a> </div> diff --git a/chromium/chrome/browser/resources/settings/people_page/users_page.js b/chromium/chrome/browser/resources/settings/people_page/users_page.js index 762fb6763f8..2d14833a0dc 100644 --- a/chromium/chrome/browser/resources/settings/people_page/users_page.js +++ b/chromium/chrome/browser/resources/settings/people_page/users_page.js @@ -19,15 +19,6 @@ Polymer({ notify: true, }, - /** - * True if the current profile manages supervised users. - * Set in people-page. - */ - profileManagesSupervisedUsers: { - type: Boolean, - value: false, - }, - /** @private */ isOwner_: { type: Boolean, diff --git a/chromium/chrome/browser/resources/settings/prefs/pref_util.js b/chromium/chrome/browser/resources/settings/prefs/pref_util.js index f9ce20040b3..fd5a45a3ce8 100644 --- a/chromium/chrome/browser/resources/settings/prefs/pref_util.js +++ b/chromium/chrome/browser/resources/settings/prefs/pref_util.js @@ -18,7 +18,7 @@ cr.define('Settings.PrefUtil', function() { case chrome.settingsPrivate.PrefType.BOOLEAN: return value == 'true'; case chrome.settingsPrivate.PrefType.NUMBER: - const n = parseInt(value, 10); + const n = parseFloat(value); if (isNaN(n)) { console.error( 'Argument to stringToPrefValue for number pref ' + 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 index b9cdf9e4628..d8436bfe128 100644 --- 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 @@ -51,18 +51,18 @@ <div slot="dialog-buttons"> <div> <!-- Left group --> <paper-button id="manuallyAddPrinterButton" class="secondary-button" - on-tap="switchToManualAddDialog_"> + on-click="switchToManualAddDialog_"> $i18n{manuallyAddPrinterButtonText} </paper-button> </div> <div> <!-- Right group --> <paper-button class="cancel-button secondary-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> <paper-button class="action-button" id="addPrinterButton" disabled="[[!canAddPrinter_(selectedPrinter)]]" - on-tap="switchToConfiguringDialog_"> + on-click="switchToConfiguringDialog_"> $i18n{addPrinterButtonText} </paper-button> </div> @@ -122,8 +122,7 @@ <div class="label">$i18n{printerAddress}</div> <div class="secondary"> <paper-input no-label-float id="printerAddressInput" - value="{{newPrinter.printerAddress}}" - on-input="onAddressChanged_"> + value="{{newPrinter.printerAddress}}"> </paper-input> </div> </div> @@ -159,32 +158,21 @@ </div> </div> </div> - <div class="search-printer-box" id="searchInProgress" hidden> - <paper-spinner-lite active></paper-spinner-lite> - <span class="spinner-comment">$i18n{searchingPrinter}</span> - </div> - <div class="search-printer-box printer-not-found" - id="searchNotFound" hidden> - <span>$i18n{printerNotFound}</span> - </div> - <div class="search-printer-box printer-found" id="searchFound" hidden> - <span>$i18n{printerFound}</span> - </div> </div> <div slot="dialog-buttons"> <div> <!-- Left group --> <paper-button class="secondary-button" - on-tap="switchToDiscoveryDialog_"> + on-click="switchToDiscoveryDialog_"> $i18n{discoverPrintersButtonText} </paper-button> </div> <div> <!-- Right group --> <paper-button class="cancel-button secondary-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> <paper-button id="addPrinterButton" class="action-button" - on-tap="addPressed_" + on-click="addPressed_" disabled="[[!canAddPrinter_(newPrinter.printerName, newPrinter.printerAddress)]]"> $i18n{addPrinterButtonText} @@ -233,9 +221,11 @@ <div class="label">$i18n{selectDriver}</div> <div class="secondary"> <paper-input class="browse-file-input" no-label-float readonly - value="[[getBaseName_(activePrinter.printerPPDPath)]]"> - <paper-button class="browse-button" suffix - on-tap="onBrowseFile_"> + value="[[getBaseName_(activePrinter.printerPPDPath)]]" + error-message="$i18n{selectDriverErrorMessage}" + invalid="[[invalidPPD]]"> + <paper-button class="browse-button" slot="suffix" + on-click="onBrowseFile_"> $i18n{selectDriverButtonText} </paper-button> </paper-input> @@ -248,14 +238,14 @@ </div> <div slot="dialog-buttons"> <paper-button class="cancel-button secondary-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> <paper-button class="action-button" id="addPrinterButton" disabled="[[!canAddPrinter_(activePrinter.ppdManufacturer, activePrinter.ppdModel, activePrinter.printerPPDPath)]]" - on-tap="switchToConfiguringDialog_"> + on-click="switchToConfiguringDialog_"> $i18n{addPrinterButtonText} </paper-button> </div> @@ -279,7 +269,7 @@ </div> <div slot="dialog-buttons"> <paper-button class="cancel-button secondary-button" - on-tap="onCancelConfiguringTap_"> + on-click="onCancelConfiguringTap_"> $i18n{cancel} </paper-button> </div> 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 index bd6a143ae4d..52ff88a5f5c 100644 --- 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 @@ -184,13 +184,6 @@ Polymer({ this.fire('open-configuring-printer-dialog'); }, - /** @private */ - onAddressChanged_: function() { - // TODO(xdai): Check if the printer address exists and then show the - // corresponding message after the API is ready. - // The format of address is: ip-address-or-hostname:port-number. - }, - /** * @param {!Event} event * @private diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html index 797f6fe0f76..3928af6bfcd 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html +++ b/chromium/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html @@ -23,8 +23,8 @@ selected="{{selectedPrinter}}"> </array-selector> <template is="dom-repeat" items="[[printers]]"> - <button class="list-item" on-tap="onSelect_"> - [[item.printerName]] [[item.printerModel]] + <button class="list-item" on-click="onSelect_"> + [[item.printerName]] </button> </template> </div> @@ -39,7 +39,11 @@ width: 270px; } - iron-dropdown .dropdown-content { + iron-dropdown { + height: 270px; + } + + iron-dropdown [slot='dropdown-content'] { background-color: white; box-shadow: 0 2px 6px var(--paper-grey-500); min-width: 128px; @@ -56,22 +60,23 @@ background-size: 24px; } </style> - <paper-input-container no-label-float on-tap="onTap_"> + <paper-input-container no-label-float on-click="onTap_"> <input is="iron-input" type="search" bind-value="{{selectedItem}}" - on-search="onInputValueChanged_" on-change="onChange_" incremental> - <button is="paper-icon-button-light" class="icon-search" suffix - id="searchIcon" hidden> + on-search="onInputValueChanged_" on-change="onChange_" incremental + slot="input"> + <button is="paper-icon-button-light" class="icon-search" + id="searchIcon" hidden slot="suffix"> </button> - <button is="paper-icon-button-light" class="icon-arrow-dropdown" suffix - id="dropdownIcon"> + <button is="paper-icon-button-light" class="icon-arrow-dropdown" + id="dropdownIcon" slot="suffix"> </button> </paper-input-container> <iron-dropdown horizontal-align="left" vertical-align="top" vertical-offset="35"> - <div class="dropdown-content"> + <div slot="dropdown-content"> <template is="dom-repeat" items="[[items]]" filter="[[filterItems_(searchTerm_)]]"> - <button class="list-item" on-tap="onSelect_">[[item]]</button> + <button class="list-item" on-click="onSelect_">[[item]]</button> </template> </div> </iron-dropdown> 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 index 63ebb00216d..8f0c517f723 100644 --- 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 @@ -100,8 +100,8 @@ <div class="secondary"> <paper-input class="browse-file-input" no-label-float readonly value="[[getBaseName_(activePrinter.printerPPDPath)]]"> - <paper-button class="browse-button" suffix - on-tap="onBrowseFile_"> + <paper-button class="browse-button" slot="suffix" + on-click="onBrowseFile_"> $i18n{selectDriverButtonText} </paper-button> </paper-input> @@ -111,10 +111,10 @@ </div> <div slot="dialog-buttons"> <paper-button class="cancel-button secondary-button" - on-tap="onCancelTap_"> + on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onSaveTap_"> + <paper-button class="action-button" on-click="onSaveTap_"> $i18n{editPrinterButtonText} </paper-button> </div> 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 index 029074eb08d..026ed96e938 100644 --- 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 @@ -89,7 +89,7 @@ padding: 0 24px; text-align: start; width: 100%; - @apply(--settings-actionable); + @apply --settings-actionable; } .list-item:focus { diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html index 3df538af0ea..8fe645217ad 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html +++ b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.html @@ -46,6 +46,14 @@ width: 350px; } + #noSearchResultsMessage { + color: var(--md-loading-message-color); + font-size: 16px; + font-weight: 500; + margin-top: 80px; + text-align: center; + } + #addPrinterErrorMessage { display: flex; justify-content: space-around; @@ -68,7 +76,7 @@ </div> </div> <paper-button class="primary-button" id="addPrinter" - on-tap="onAddPrinterTap_" disabled="[[!canAddPrinter_]]"> + on-click="onAddPrinterTap_" disabled="[[!canAddPrinter_]]"> $i18n{addCupsPrinter} </paper-button> </div> @@ -88,6 +96,11 @@ search-term="[[searchTerm]]"> </settings-cups-printers-list> + <div id="noSearchResultsMessage" + hidden="[[!showNoSearchResultsMessage_(searchTerm)]]"> + $i18n{noSearchResults} + </div> + <div id="message"> <div class="center" id="addPrinterDoneMessage" hidden> $i18n{printerAddedSuccessfulMessage} diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js index f5c6e55c690..055f4a807c5 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js +++ b/chromium/chrome/browser/resources/settings/printing_page/cups_printers.js @@ -186,4 +186,18 @@ Polymer({ }); }, + /** + * @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); + }); + }, + }); 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 index 8c4a1d51ac0..353742c55e2 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html +++ b/chromium/chrome/browser/resources/settings/printing_page/cups_printers_list.html @@ -15,10 +15,10 @@ </style> <dialog is="cr-action-menu"> - <button class="dropdown-item" on-tap="onEditTap_"> + <button slot="item" class="dropdown-item" on-click="onEditTap_"> $i18n{editPrinter} </button> - <button class="dropdown-item" on-tap="onRemoveTap_"> + <button slot="item" class="dropdown-item" on-click="onRemoveTap_"> $i18n{removePrinter} </button> </dialog> @@ -29,7 +29,7 @@ <div class="printer-name text-elide">[[item.printerName]]</div> <!--TODO(xdai): Add icon for enterprise CUPS printer. --> <button is="paper-icon-button-light" class="icon-more-vert" - on-tap="onOpenActionMenuTap_" title="$i18n{moreActions}"> + on-click="onOpenActionMenuTap_" title="$i18n{moreActions}"> </button> </div> </template> diff --git a/chromium/chrome/browser/resources/settings/printing_page/cups_set_manufacturer_model_behavior.js b/chromium/chrome/browser/resources/settings/printing_page/cups_set_manufacturer_model_behavior.js index 84508310b53..43f805bf3f8 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/cups_set_manufacturer_model_behavior.js +++ b/chromium/chrome/browser/resources/settings/printing_page/cups_set_manufacturer_model_behavior.js @@ -16,15 +16,16 @@ const SetManufacturerModelBehavior = { notify: true, }, - /** @type {?Array<string>} */ - manufacturerList: { - type: Array, + invalidPPD: { + type: Boolean, + value: false, }, /** @type {?Array<string>} */ - modelList: { - type: Array, - }, + manufacturerList: Array, + + /** @type {?Array<string>} */ + modelList: Array, }, observers: [ @@ -86,11 +87,12 @@ const SetManufacturerModelBehavior = { }, /** - * @param {string} path + * @param {string} path The full path to the selected PPD file * @private */ printerPPDPathChanged_: function(path) { this.set('activePrinter.printerPPDPath', path); + this.invalidPPD = !path; }, /** diff --git a/chromium/chrome/browser/resources/settings/printing_page/printing_page.html b/chromium/chrome/browser/resources/settings/printing_page/printing_page.html index 90e18fbb2e5..6513b2f9f1a 100644 --- a/chromium/chrome/browser/resources/settings/printing_page/printing_page.html +++ b/chromium/chrome/browser/resources/settings/printing_page/printing_page.html @@ -22,7 +22,7 @@ <neon-animatable route-path="default"> <if expr="chromeos"> <div id="cupsPrinters" class="settings-box first" - on-tap="onTapCupsPrinters_" actionable> + on-click="onTapCupsPrinters_" actionable> <div class="start">$i18n{cupsPrintersTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{cupsPrintersTitle}"></button> @@ -30,14 +30,14 @@ </if> <if expr="not chromeos"> <div class="settings-box first" - on-tap="onTapLocalPrinters_" actionable> + on-click="onTapLocalPrinters_" actionable> <div class="start">$i18n{localPrintersTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{localPrintersTitle}"></button> </div> </if> <div id="cloudPrinters" class="settings-box" - on-tap="onTapCloudPrinters_" actionable> + on-click="onTapCloudPrinters_" actionable> <div class="start">$i18n{cloudPrintersTitle}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{cloudPrintersTitle}"></button> diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html index 1ba4c86e60b..d0bd4cfddfd 100644 --- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.html @@ -9,7 +9,6 @@ <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="../clear_browsing_data_dialog/clear_browsing_data_dialog.html"> -<link rel="import" href="../clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.html"> <link rel="import" href="../controls/settings_toggle_button.html"> <link rel="import" href="../lifetime_browser_proxy.html"> <link rel="import" href="../route.html"> @@ -40,16 +39,9 @@ <style include="settings-shared"> </style> <template is="dom-if" if="[[showClearBrowsingDataDialog_]]" restamp> - <template is="dom-if" if="[[!tabsInCbd_]]" restamp> - <settings-clear-browsing-data-dialog prefs="{{prefs}}" - on-close="onDialogClosed_"> - </settings-clear-browsing-data-dialog> - </template> - <template is="dom-if" if="[[tabsInCbd_]]" restamp> - <settings-clear-browsing-data-dialog-tabs prefs="{{prefs}}" - on-close="onDialogClosed_"> - </settings-clear-browsing-data-dialog-tabs> - </template> + <settings-clear-browsing-data-dialog prefs="{{prefs}}" + on-close="onDialogClosed_"> + </settings-clear-browsing-data-dialog> </template> <template id="doNotTrackDialogIf" is="dom-if" if="[[showDoNotTrackDialog_]]" notify-dom-change> @@ -60,11 +52,11 @@ <div slot="body">$i18nRaw{doNotTrackDialogMessage}</div> <div slot="button-container"> <paper-button class="cancel-button" - on-tap="onDoNotTrackDialogCancel_"> + on-click="onDoNotTrackDialogCancel_"> $i18n{cancel} </paper-button> <paper-button class="action-button" - on-tap="onDoNotTrackDialogConfirm_"> + on-click="onDoNotTrackDialogConfirm_"> $i18n{confirm} </paper-button> </div> @@ -141,7 +133,7 @@ </if> <if expr="use_nss_certs or is_win or is_macosx"> <div id="manageCertificates" class="settings-box two-line" - actionable on-tap="onManageCertificatesTap_"> + actionable on-click="onManageCertificatesTap_"> <div class="start"> $i18n{manageCertificates} <div class="secondary" id="manageCertificatesSecondary"> @@ -162,7 +154,7 @@ </if> <div id="site-settings-subpage-trigger" class="settings-box two-line" actionable - on-tap="onSiteSettingsTap_"> + on-click="onSiteSettingsTap_"> <div class="start"> [[siteSettingsPageTitle_()]] <div class="secondary" id="siteSettingsSecondary"> @@ -174,7 +166,7 @@ aria-describedby="siteSettingsSecondary"></button> </div> <div class="settings-box two-line" id="clearBrowsingData" - on-tap="onClearBrowsingDataTap_" actionable> + on-click="onClearBrowsingDataTap_" actionable> <div class="start"> $i18n{clearBrowsingData} <div class="secondary" id="clearBrowsingDataSecondary"> @@ -265,7 +257,7 @@ label="$i18n{thirdPartyCookie}" sub-label="$i18n{thirdPartyCookieSublabel}"> </settings-toggle-button> - <div class="settings-box" actionable on-tap="onSiteDataTap_"> + <div class="settings-box" actionable on-click="onSiteDataTap_"> <div class="start" id="cookiesLink"> $i18n{siteSettingsCookieLink} </div> @@ -375,6 +367,21 @@ </category-setting-exceptions> </settings-subpage> </template> + <template is="dom-if" if="[[enableSensorsContentSetting_]]" no-search> + <template is="dom-if" route-path="/content/sensors" no-search> + <settings-subpage page-title="$i18n{siteSettingsSensors}"> + <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}"> + </category-setting-exceptions> + </settings-subpage> + </template> + </template> <template is="dom-if" route-path="/content/notifications" no-search> <settings-subpage page-title="$i18n{siteSettingsCategoryNotifications}"> <category-default-setting @@ -479,7 +486,7 @@ <template is="dom-if" route-path="/cookies/detail" no-search> <settings-subpage page-title="[[pageTitle]]"> <paper-button slot="subpage-title-extra" class="secondary-button" - on-tap="onRemoveAllCookiesFromSite_"> + on-click="onRemoveAllCookiesFromSite_"> $i18n{siteSettingsCookieRemoveAll} </paper-button> <site-data-details-subpage page-title="{{pageTitle}}"> diff --git a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js index e55ac46515f..abb969d0d3b 100644 --- a/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js +++ b/chromium/chrome/browser/resources/settings/privacy_page/privacy_page.js @@ -80,14 +80,6 @@ Polymer({ showClearBrowsingDataDialog_: Boolean, /** @private */ - tabsInCbd_: { - type: Boolean, - value: function() { - return loadTimeData.getBoolean('tabsInCbd'); - } - }, - - /** @private */ showDoNotTrackDialog_: { type: Boolean, value: false, @@ -128,6 +120,15 @@ Polymer({ } }, + /** @private */ + enableSensorsContentSetting_: { + type: Boolean, + readOnly: true, + value: function() { + return loadTimeData.getBoolean('enableSensorsContentSetting'); + } + }, + /** @private {!Map<string, string>} */ focusConfig_: { type: Object, @@ -282,7 +283,7 @@ Polymer({ /** @private */ onDialogClosed_: function() { - settings.navigateToPreviousRoute(); + settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA.parent); cr.ui.focusWithoutInk(assert(this.$.clearBrowsingDataTrigger)); }, @@ -337,16 +338,21 @@ Polymer({ // </if> /** - * @param {boolean} enabled Whether reporting is enabled or not. + * @param {!SberPrefState} sberPrefState SBER enabled and managed state. * @private */ - setSafeBrowsingExtendedReporting_: function(enabled) { + setSafeBrowsingExtendedReporting_: function(sberPrefState) { // Ignore the next change because it will happen when we set the pref. - this.safeBrowsingExtendedReportingPref_ = { + const pref = { key: '', type: chrome.settingsPrivate.PrefType.BOOLEAN, - value: enabled, + value: sberPrefState.enabled, }; + if (sberPrefState.managed) { + pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED; + pref.controlledBy = chrome.settingsPrivate.ControlledBy.USER_POLICY; + } + this.safeBrowsingExtendedReportingPref_ = pref; }, /** 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 index e5037cfef21..63790dfadb2 100644 --- 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 @@ -7,6 +7,9 @@ /** @typedef {{enabled: boolean, managed: boolean}} */ let MetricsReporting; +/** @typedef {{enabled: boolean, managed: boolean}} */ +let SberPrefState; + cr.define('settings', function() { /** @interface */ class PrivacyPageBrowserProxy { @@ -25,7 +28,7 @@ cr.define('settings', function() { // </if> - /** @return {!Promise<boolean>} */ + /** @return {!Promise<!SberPrefState>} */ getSafeBrowsingExtendedReporting() {} /** @param {boolean} enabled */ diff --git a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html b/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html index 1459a9afe0e..98a82fd5991 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html +++ b/chromium/chrome/browser/resources/settings/reset_page/powerwash_dialog.html @@ -22,10 +22,10 @@ </span> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" id="cancel">$i18n{cancel}</paper-button> <paper-button class="action-button" id="powerwash" - on-tap="onRestartTap_">$i18n{powerwashDialogButton}</paper-button> + on-click="onRestartTap_">$i18n{powerwashDialogButton}</paper-button> </div> </dialog> </template> diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_page.html b/chromium/chrome/browser/resources/settings/reset_page/reset_page.html index 82b9c6cf022..d45c9976996 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/reset_page.html +++ b/chromium/chrome/browser/resources/settings/reset_page/reset_page.html @@ -17,6 +17,7 @@ <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"> @@ -25,7 +26,7 @@ <settings-animated-pages id="reset-pages" section="reset"> <neon-animatable route-path="default"> <div class="settings-box first two-line" id="resetProfile" - on-tap="onShowResetProfileDialog_" actionable> + on-click="onShowResetProfileDialog_" actionable> <div class="start"> $i18n{resetTrigger} <div class="secondary" id="resetProfileSecondary"> @@ -44,7 +45,7 @@ </template> <if expr="chromeos"> <div class="settings-box two-line" id="powerwash" actionable - on-tap="onShowPowerwashDialog_" hidden="[[!allowPowerwash_]]"> + on-click="onShowPowerwashDialog_" hidden="[[!allowPowerwash_]]"> <div class="start"> $i18n{powerwashTitle} <div class="secondary" id="powerwashSecondary"> @@ -62,20 +63,28 @@ </if> <if expr="_google_chrome and is_win"> <template is="dom-if" if="[[userInitiatedCleanupsEnabled_]]" restamp> - <div class="settings-box two-line" id="chromeCleanupSubpageTrigger" - on-tap="onChromeCleanupTap_" actionable> - <div class="start"> - $i18n{resetCleanupComputerTrigger} - <div class="secondary" id="chromeCleanupSecondary"> - $i18n{resetCleanupComputerTriggerDescription} - </div> - </div> + <div class="settings-box" id="chromeCleanupSubpageTrigger" + on-click="onChromeCleanupTap_" actionable> + <div class="start">$i18n{resetCleanupComputerTrigger}</div> <button id="chromeCleanupArrow" is="paper-icon-button-light" class="subpage-arrow" aria-label="$i18n{resetCleanupComputerTrigger}" aria-describedby="chromeCleanupSecondary"></button> </div> </template> + <template is="dom-if" if="[[showIncompatibleApplications_]]" restamp> + <div class="settings-box" + id="incompatibleApplicationsSubpageTrigger" + on-click="onIncompatibleApplicationsTap_" actionable> + <div class="start"> + $i18n{incompatibleApplicationsResetCardTitle} + </div> + <button is="paper-icon-button-light" + class="subpage-arrow" + aria-label="$i18n{incompatibleApplicationsResetCardTitle}" + aria-describedby="incompatibleApplicationsSecondary"></button> + </div> + </template> </if> </neon-animatable> <if expr="_google_chrome and is_win"> @@ -89,6 +98,16 @@ </settings-subpage> </template> </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> diff --git a/chromium/chrome/browser/resources/settings/reset_page/reset_page.js b/chromium/chrome/browser/resources/settings/reset_page/reset_page.js index fb5c2229223..10082a0b8f8 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/reset_page.js +++ b/chromium/chrome/browser/resources/settings/reset_page/reset_page.js @@ -40,6 +40,14 @@ Polymer({ return loadTimeData.getBoolean('userInitiatedCleanupsEnabled'); }, }, + + /** @private */ + showIncompatibleApplications_: { + type: Boolean, + value: function() { + return loadTimeData.getBoolean('showIncompatibleApplications'); + }, + }, // </if> }, @@ -87,9 +95,15 @@ Polymer({ // </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 index 3f49fa4eb03..aed186e6fab 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html +++ b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_banner.html @@ -19,10 +19,10 @@ </span> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onOkTap_" id="ok"> + <paper-button class="cancel-button" on-click="onOkTap_" id="ok"> $i18n{ok} </paper-button> - <paper-button class="action-button" on-tap="onResetTap_" id="reset"> + <paper-button class="action-button" on-click="onResetTap_" id="reset"> $i18n{resetProfileBannerButton} </paper-button> </div> 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 index ef12529acb3..be6154e2f30 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html +++ b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.html @@ -35,11 +35,11 @@ <div slot="button-container"> <paper-spinner-lite id="resetSpinner" active="[[clearingInProgress_]]"> </paper-spinner-lite> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" id="cancel" disabled="[[clearingInProgress_]]"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onResetTap_" + <paper-button class="action-button" on-click="onResetTap_" id="reset" disabled="[[clearingInProgress_]]"> $i18n{resetPageCommit} </paper-button> 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 index 683e2692f9e..22066b37961 100644 --- a/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js +++ b/chromium/chrome/browser/resources/settings/reset_page/reset_profile_dialog.js @@ -77,7 +77,7 @@ Polymer({ }); this.$$('paper-checkbox a') - .addEventListener('tap', this.onShowReportedSettingsTap_.bind(this)); + .addEventListener('click', this.onShowReportedSettingsTap_.bind(this)); // Prevent toggling of the checkbox when hitting the "Enter" key on the // link. this.$$('paper-checkbox a').addEventListener('keydown', function(e) { diff --git a/chromium/chrome/browser/resources/settings/route.js b/chromium/chrome/browser/resources/settings/route.js index de01fd22a9c..68183214b91 100644 --- a/chromium/chrome/browser/resources/settings/route.js +++ b/chromium/chrome/browser/resources/settings/route.js @@ -36,6 +36,7 @@ * 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), @@ -73,6 +74,7 @@ * SITE_SETTINGS_HANDLERS: (undefined|!settings.Route), * SITE_SETTINGS_IMAGES: (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), @@ -317,6 +319,7 @@ cr.define('settings', function() { r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images'); 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 = @@ -388,6 +391,10 @@ cr.define('settings', function() { if (loadTimeData.getBoolean('userInitiatedCleanupsEnabled')) { r.CHROME_CLEANUP = r.RESET.createChild('/cleanup'); } + if (loadTimeData.getBoolean('showIncompatibleApplications')) { + r.INCOMPATIBLE_APPLICATIONS = + r.RESET.createChild('/incompatibleApplications'); + } // </if> } } 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 index aa5677369c5..89475dacb36 100644 --- 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 @@ -35,14 +35,16 @@ </div> <div class="keyword-column">[[engine.keyword]]</div> <button is="paper-icon-button-light" class="icon-more-vert" - on-tap="onDotsTap_" title="$i18n{moreActions}" focus-row-control + on-click="onDotsTap_" title="$i18n{moreActions}" focus-row-control focus-type="menu"> </button> <dialog is="cr-action-menu"> - <button class="dropdown-item" on-tap="onManageTap_" id="manage"> + <button slot="item" class="dropdown-item" on-click="onManageTap_" + id="manage"> $i18n{searchEnginesManageExtension} </button> - <button class="dropdown-item" on-tap="onDisableTap_" id="disable"> + <button slot="item" class="dropdown-item" on-click="onDisableTap_" + id="disable"> $i18n{disable} </button> </dialog> 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 index a2788c608ec..a8e0591c515 100644 --- 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 @@ -44,10 +44,10 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="cancel_" id="cancel"> + <paper-button class="cancel-button" on-click="cancel_" id="cancel"> $i18n{cancel}</paper-button> <paper-button id="actionButton" class="action-button" - on-tap="onActionButtonTap_"> + on-click="onActionButtonTap_"> [[actionButtonText_]] </paper-button> </div> 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 index f4b23582e1c..5f0f410817a 100644 --- 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 @@ -54,19 +54,19 @@ <div id="keyword-column"><div>[[engine.keyword]]</div></div> <div id="url-column" class="text-elide">[[engine.url]]</div> <button is="paper-icon-button-light" class="icon-more-vert" - on-tap="onDotsTap_" title="$i18n{moreActions}" focus-row-control + on-click="onDotsTap_" title="$i18n{moreActions}" focus-row-control focus-type="cr-menu-button"> </button> <dialog is="cr-action-menu"> - <button class="dropdown-item" on-tap="onMakeDefaultTap_" + <button slot="item" class="dropdown-item" on-click="onMakeDefaultTap_" hidden$="[[!engine.canBeDefault]]" id="makeDefault"> $i18n{searchEnginesMakeDefault} </button> - <button class="dropdown-item" on-tap="onEditTap_" + <button slot="item" class="dropdown-item" on-click="onEditTap_" hidden$="[[!engine.canBeEdited]]" id="edit"> $i18n{edit} </button> - <button class="dropdown-item" on-tap="onDeleteTap_" + <button slot="item" class="dropdown-item" on-click="onDeleteTap_" hidden$="[[!engine.canBeRemoved]]" id="delete"> $i18n{searchEnginesRemoveFromList} </button> 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 index 507bf4875ad..1e6af9d9737 100644 --- 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 @@ -23,7 +23,7 @@ } #outer { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; } settings-search-engine-entry { 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 index eb06d3cd345..44638d4ee43 100644 --- 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 @@ -19,7 +19,7 @@ .extension-engines, #noOtherEngines, .no-search-results { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; } settings-omnibox-extension-entry { @@ -43,7 +43,7 @@ <div class="settings-box first"> <h2 class="start">$i18n{searchEnginesOther}</h2> <paper-button class="secondary-button header-aligned-button" - on-tap="onAddSearchEngineTap_" id="addSearchEngine"> + on-click="onAddSearchEngineTap_" id="addSearchEngine"> $i18n{add} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.html b/chromium/chrome/browser/resources/settings/search_page/search_page.html index 1e9f88f1e7c..d32e97c489c 100644 --- a/chromium/chrome/browser/resources/settings/search_page/search_page.html +++ b/chromium/chrome/browser/resources/settings/search_page/search_page.html @@ -84,7 +84,7 @@ <!-- Manage search engines --> <div id="engines-subpage-trigger" class="settings-box" - on-tap="onManageSearchEnginesTap_" actionable> + on-click="onManageSearchEnginesTap_" actionable> <div class="start"> $i18n{searchEnginesManage} </div> @@ -96,7 +96,7 @@ <!-- Google Assistant --> <template is="dom-if" if="[[voiceInteractionFeatureEnabled_]]"> <div id="assistant-subpage-trigger" class="settings-box two-line" - on-tap="onGoogleAssistantTap_" actionable> + on-click="onGoogleAssistantTap_" actionable> <div class="start"> $i18n{searchGoogleAssistant} <div class="secondary"> @@ -111,7 +111,7 @@ <template is="dom-if" if="[[!assistantOn_]]"> <div class="separator"></div> <paper-button id="enable" class="secondary-button" - on-tap="onAssistantTurnOnTap_" + on-click="onAssistantTurnOnTap_" aria-label="$i18n{searchPageTitle}" aria-describedby="secondaryText"> $i18n{assistantTurnOn} diff --git a/chromium/chrome/browser/resources/settings/search_settings.js b/chromium/chrome/browser/resources/settings/search_settings.js index 684aed657e9..71971fa8246 100644 --- a/chromium/chrome/browser/resources/settings/search_settings.js +++ b/chromium/chrome/browser/resources/settings/search_settings.js @@ -17,18 +17,6 @@ cr.exportPath('settings'); settings.SearchResult; cr.define('settings', function() { - /** @type {string} */ - const WRAPPER_CSS_CLASS = 'search-highlight-wrapper'; - - /** @type {string} */ - const ORIGINAL_CONTENT_CSS_CLASS = 'search-highlight-original-content'; - - /** @type {string} */ - const HIT_CSS_CLASS = 'search-highlight-hit'; - - /** @type {string} */ - const SEARCH_BUBBLE_CSS_CLASS = 'search-bubble'; - /** * A CSS attribute indicating that a node should be ignored during searching. * @type {string} @@ -65,59 +53,8 @@ cr.define('settings', function() { * @private */ function findAndRemoveHighlights_(node) { - const wrappers = node.querySelectorAll('* /deep/ .' + WRAPPER_CSS_CLASS); - - for (let i = 0; i < wrappers.length; i++) { - const wrapper = wrappers[i]; - const originalNode = - wrapper.querySelector('.' + ORIGINAL_CONTENT_CSS_CLASS); - wrapper.parentElement.replaceChild(originalNode.firstChild, wrapper); - } - - const searchBubbles = - node.querySelectorAll('* /deep/ .' + SEARCH_BUBBLE_CSS_CLASS); - for (let j = 0; j < searchBubbles.length; j++) - searchBubbles[j].remove(); - } - - /** - * Applies the highlight UI (yellow rectangle) around all matches in |node|. - * @param {!Node} node The text node to be highlighted. |node| ends up - * being removed from the DOM tree. - * @param {!Array<string>} tokens The string tokens after splitting on the - * relevant regExp. Even indices hold text that doesn't need highlighting, - * odd indices hold the text to be highlighted. For example: - * const r = new RegExp('(foo)', 'i'); - * 'barfoobar foo bar'.split(r) => ['bar', 'foo', 'bar ', 'foo', ' bar'] - * @private - */ - function highlight_(node, tokens) { - const wrapper = document.createElement('span'); - wrapper.classList.add(WRAPPER_CSS_CLASS); - // Use existing node as placeholder to determine where to insert the - // replacement content. - node.parentNode.replaceChild(wrapper, node); - - // Keep the existing node around for when the highlights are removed. The - // existing text node might be involved in data-binding and therefore should - // not be discarded. - const span = document.createElement('span'); - span.classList.add(ORIGINAL_CONTENT_CSS_CLASS); - span.style.display = 'none'; - span.appendChild(node); - wrapper.appendChild(span); - - for (let i = 0; i < tokens.length; ++i) { - if (i % 2 == 0) { - wrapper.appendChild(document.createTextNode(tokens[i])); - } else { - const hitSpan = document.createElement('span'); - hitSpan.classList.add(HIT_CSS_CLASS); - hitSpan.style.backgroundColor = '#ffeb3b'; // --var(--paper-yellow-500) - hitSpan.textContent = tokens[i]; - wrapper.appendChild(hitSpan); - } - } + cr.search_highlight_utils.findAndRemoveHighlights(node); + cr.search_highlight_utils.findAndRemoveBubbles(node); } /** @@ -163,8 +100,10 @@ cr.define('settings', function() { // displayed within an <option>. // TODO(dpapad): highlight <select> controls with a search bubble // instead. - if (node.parentNode.nodeName != 'OPTION') - highlight_(node, textContent.split(request.regExp)); + if (node.parentNode.nodeName != 'OPTION') { + cr.search_highlight_utils.highlight( + node, textContent.split(request.regExp)); + } } // Returning early since TEXT_NODE nodes never have children. return; @@ -189,44 +128,6 @@ cr.define('settings', function() { } /** - * Highlights the HTML control that triggers a subpage, by displaying a search - * bubble. - * @param {!HTMLElement} element The element to be highlighted. - * @param {string} rawQuery The search query. - * @private - */ - function highlightAssociatedControl_(element, rawQuery) { - let searchBubble = element.querySelector('.' + SEARCH_BUBBLE_CSS_CLASS); - // If the associated control has already been highlighted due to another - // match on the same subpage, there is no need to do anything. - if (searchBubble) - return; - - searchBubble = document.createElement('div'); - searchBubble.classList.add(SEARCH_BUBBLE_CSS_CLASS); - const innards = document.createElement('div'); - innards.classList.add('search-bubble-innards', 'text-elide'); - innards.textContent = rawQuery; - searchBubble.appendChild(innards); - element.appendChild(searchBubble); - - // Dynamically position the bubble at the edge the associated control - // element. - const updatePosition = function() { - searchBubble.style.top = element.offsetTop + - (innards.classList.contains('above') ? -searchBubble.offsetHeight : - element.offsetHeight) + - 'px'; - }; - updatePosition(); - - searchBubble.addEventListener('mouseover', function() { - innards.classList.toggle('above'); - updatePosition(); - }); - } - - /** * Finds and makes visible the <settings-section> parent of |node|. * @param {!Node} node * @param {string} rawQuery @@ -253,8 +154,10 @@ cr.define('settings', function() { // Need to add the search bubble after the parent SETTINGS-SECTION has // become visible, otherwise |offsetWidth| returns zero. - if (associatedControl) - highlightAssociatedControl_(associatedControl, rawQuery); + if (associatedControl) { + cr.search_highlight_utils.highlightControlWithBubble( + associatedControl, rawQuery); + } } /** @abstract */ diff --git a/chromium/chrome/browser/resources/settings/settings.html b/chromium/chrome/browser/resources/settings/settings.html index 8732184b614..2f8a74473c8 100644 --- a/chromium/chrome/browser/resources/settings/settings.html +++ b/chromium/chrome/browser/resources/settings/settings.html @@ -10,6 +10,8 @@ html { background-color: #f1f1f1; overflow: hidden; + /* Remove 300ms delay for 'click' event, when using touch interface. */ + touch-action: manipulation; } .loading { @@ -20,6 +22,7 @@ </head> <body> <settings-ui></settings-ui> + <link rel="stylesheet" href="chrome://resources/css/md_colors.css"> <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"> diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.html b/chromium/chrome/browser/resources/settings/settings_main/settings_main.html index fcbd63786ef..31e3040c21e 100644 --- a/chromium/chrome/browser/resources/settings/settings_main/settings_main.html +++ b/chromium/chrome/browser/resources/settings/settings_main/settings_main.html @@ -2,6 +2,7 @@ <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"> 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 index 5b3e82131ca..655b3faa8a0 100644 --- a/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html +++ b/chromium/chrome/browser/resources/settings/settings_page/settings_animated_pages.html @@ -11,6 +11,7 @@ <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animated-pages.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animation-runner-behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/web-animations.html"> <link rel="import" href="../animation/fade_animations.html"> <link rel="import" href="../route.html"> diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_section.html b/chromium/chrome/browser/resources/settings/settings_page/settings_section.html index 644879a6d93..3daf5135314 100644 --- a/chromium/chrome/browser/resources/settings/settings_page/settings_section.html +++ b/chromium/chrome/browser/resources/settings/settings_page/settings_section.html @@ -20,13 +20,13 @@ } #header .title { - @apply(--cr-section-text); + @apply --cr-section-text; margin-bottom: 0; margin-top: var(--settings-page-vertical-margin); } #card { - @apply(--shadow-elevation-2dp); + @apply --shadow-elevation-2dp; background-color: white; border-radius: 2px; flex: 1; @@ -39,7 +39,7 @@ :host(.expanding) #card, :host(.collapsing) #card, :host(.expanded) #card { - @apply(--shadow-elevation-4dp); + @apply --shadow-elevation-4dp; overflow: hidden; /* A stacking context constrains sliding sub-pages to the card. */ z-index: 0; diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html index 12b26d259d6..1021acdcbe8 100644 --- a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html +++ b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage.html @@ -26,7 +26,7 @@ } #learnMore { - @apply(--cr-paper-icon-button-margin); + @apply --cr-paper-icon-button-margin; align-items: center; display: flex; height: var(--cr-icon-ripple-size); @@ -42,12 +42,12 @@ } paper-spinner-lite { - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; } h1 { flex: 1; /* Push other items to the end. */ - @apply(--cr-title-text); + @apply --cr-title-text; } settings-subpage-search { @@ -56,7 +56,7 @@ } </style> <div class="settings-box first" id="headerLine"> - <button is="paper-icon-button-light" on-tap="onTapBack_" + <button is="paper-icon-button-light" on-click="onTapBack_" aria-label="$i18n{back}" class="icon-arrow-back"> </button> <h1>[[pageTitle]]</h1> diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage_search.html b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage_search.html index 4f4245f7fec..da6272b5834 100644 --- a/chromium/chrome/browser/resources/settings/settings_page/settings_subpage_search.html +++ b/chromium/chrome/browser/resources/settings/settings_page/settings_subpage_search.html @@ -71,10 +71,10 @@ <paper-input-container no-label-float> <input id="searchInput" type="search" on-search="onSearchTermSearch" on-input="onSearchTermInput" aria-label$="[[label]]" incremental - autofocus$="[[autofocus]]" placeholder="[[label]]"> - <button suffix is="paper-icon-button-light" id="clearSearch" - class="icon-cancel" on-tap="onTapClear_" title="[[clearLabel]]" - hidden$="[[!hasSearchText]]"> + autofocus$="[[autofocus]]" placeholder="[[label]]" slot="input"> + <button is="paper-icon-button-light" id="clearSearch" + class="icon-cancel" on-click="onTapClear_" title="[[clearLabel]]" + hidden$="[[!hasSearchText]]" slot="suffix"> </button> </paper-input-container> </template> diff --git a/chromium/chrome/browser/resources/settings/settings_resources.grd b/chromium/chrome/browser/resources/settings/settings_resources.grd index 34e3036a3cc..e4122b701b2 100644 --- a/chromium/chrome/browser/resources/settings/settings_resources.grd +++ b/chromium/chrome/browser/resources/settings/settings_resources.grd @@ -325,6 +325,26 @@ file="chrome_cleanup_page/items_to_remove_list.js" type="chrome_html"/> </if> + <if expr="is_win and _google_chrome"> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_PAGE_HTML" + file="incompatible_applications_page/incompatible_applications_page.html" + type="chrome_html" /> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_PAGE_JS" + file="incompatible_applications_page/incompatible_applications_page.js" + type="chrome_html" /> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_BROWSER_PROXY_HTML" + file="incompatible_applications_page/incompatible_applications_browser_proxy.html" + type="chrome_html" /> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_BROWSER_PROXY_JS" + file="incompatible_applications_page/incompatible_applications_browser_proxy.js" + type="chrome_html" /> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_INCOMPATIBLE_APPLICATION_ITEM_HTML" + file="incompatible_applications_page/incompatible_application_item.html" + type="chrome_html" /> + <structure name="IDR_SETTINGS_INCOMPATIBLE_APPLICATIONS_INCOMPATIBLE_APPLICATION_ITEM_JS" + file="incompatible_applications_page/incompatible_application_item.js" + type="chrome_html" /> + </if> <structure name="IDR_SETTINGS_CLEAR_BROWSING_DATA_BROWSER_PROXY_HTML" file="clear_browsing_data_dialog/clear_browsing_data_browser_proxy.html" type="chrome_html" /> @@ -337,12 +357,6 @@ <structure name="IDR_SETTINGS_CLEAR_BROWSING_DATA_DIALOG_JS" file="clear_browsing_data_dialog/clear_browsing_data_dialog.js" type="chrome_html" /> - <structure name="IDR_SETTINGS_CLEAR_BROWSING_DATA_DIALOG_TABS_HTML" - file="clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.html" - type="chrome_html" /> - <structure name="IDR_SETTINGS_CLEAR_BROWSING_DATA_DIALOG_TABS_JS" - file="clear_browsing_data_dialog/clear_browsing_data_dialog_tabs.js" - type="chrome_html" /> <structure name="IDR_SETTINGS_HISTORY_DELETION_DIALOG_HTML" file="clear_browsing_data_dialog/history_deletion_dialog.html" type="chrome_html" /> @@ -707,6 +721,14 @@ preprocess="true" allowexternalscript="true" /> <if expr="not chromeos"> + <structure name="IDR_SETTINGS_PEOPLE_PAGE_SYNC_ACCOUNT_CONTROL_HTML" + file="people_page/sync_account_control.html" + type="chrome_html" + flattenhtml="true" + allowexternalscript="true" /> + <structure name="IDR_SETTINGS_PEOPLE_PAGE_SYNC_ACCOUNT_CONTROL_JS" + file="people_page/sync_account_control.js" + type="chrome_html" /> <structure name="IDR_SETTINGS_PEOPLE_PAGE_IMPORT_DATA_DIALOG_HTML" file="people_page/import_data_dialog.html" type="chrome_html" /> @@ -1258,11 +1280,6 @@ type="chrome_html" preprocess="true" allowexternalscript="true" /> - <structure name="IDR_SETTINGS_PEOPLE_PIN_KEYBOARD_HTML" - file="people_page/pin_keyboard.html" - type="chrome_html" - preprocess="true" - allowexternalscript="true"/> <structure name="IDR_SETTINGS_PEOPLE_LOCK_SCREEN_JS" file="people_page/lock_screen.js" type="chrome_html" /> @@ -1319,10 +1336,6 @@ <structure name="IDR_SETTINGS_PEOPLE_FINGERPRINT_BROWSER_PROXY_HTML" file="people_page/fingerprint_browser_proxy.html" type="chrome_html" /> - <structure name="IDR_SETTINGS_KEYBOARD_PIN_JS" - file="people_page/pin_keyboard.js" - type="chrome_html" - preprocess="true" /> <structure name="IDR_SETTINGS_USERS_PAGE_ADD_USER_DIALOG_JS" file="people_page/users_add_user_dialog.js" type="chrome_html" /> diff --git a/chromium/chrome/browser/resources/settings/settings_shared_css.html b/chromium/chrome/browser/resources/settings/settings_shared_css.html index 36f43725cd7..2c89c7a5a36 100644 --- a/chromium/chrome/browser/resources/settings/settings_shared_css.html +++ b/chromium/chrome/browser/resources/settings/settings_shared_css.html @@ -2,6 +2,7 @@ <link rel="import" href="chrome://resources/cr_elements/paper_checkbox_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/paper_input_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/paper_toggle_style_css.html"> +<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"> @@ -11,7 +12,7 @@ <!-- Common styles for Material Design settings. --> <dom-module id="settings-shared"> <template> - <style include="settings-icons paper-button-style paper-checkbox-style paper-input-style paper-toggle-style cr-shared-style"> + <style include="settings-icons paper-button-style paper-checkbox-style paper-input-style paper-toggle-style 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] { @@ -89,8 +90,9 @@ -webkit-margin-start: 16px; } - /* Adjust the margin between the separator and the first button. */ - .separator + paper-button { + /* Adjust the margin between the separator and the first button. Exclude + * .action-button since it has a background thus is visually different. */ + .separator + paper-button:not(.action-button) { -webkit-margin-start: calc(var(--cr-button-edge-spacing) * -1); } @@ -105,7 +107,7 @@ } paper-toggle-button { - @apply(--settings-actionable); + @apply --settings-actionable; height: var(--settings-row-min-height); user-select: none; /* Prevents text selection while dragging. */ width: 36px; @@ -154,7 +156,7 @@ /* See also: .no-min-width below. */ .text-elide { - @apply(--settings-text-elide); + @apply --cr-text-elide; } /* By default, flexbox children have min-width calculated to be the width @@ -174,7 +176,7 @@ * outside of a settings-box. A list-frame is likely to follow a * settings box. */ .list-frame { - @apply(--settings-list-frame-padding); + @apply --settings-list-frame-padding; align-items: center; display: block; } @@ -230,7 +232,7 @@ /* A settings-box is a horizontal row of text or controls within a * setting section (page or subpage). */ .settings-box { - @apply(--cr-section); + @apply --cr-section; } .settings-box.two-line { @@ -273,7 +275,7 @@ /* The lower line of text in a two-line row. */ .secondary { - @apply(--cr-secondary-text); + @apply --cr-secondary-text; } /* The |:empty| CSS selector only works when there is no whitespace. @@ -358,44 +360,6 @@ width: 16px; } - .search-bubble { - /* RGB value matches var(--paper-yellow-500). */ - --search-bubble-color: rgba(255, 235, 59, 0.9); - position: absolute; - z-index: 1; - } - - .search-bubble-innards { - align-items: center; - background-color: var(--search-bubble-color); - border-radius: 2px; - max-width: 100px; - min-width: 64px; - padding: 4px 10px; - text-align: center; - } - - /* Provides the arrow which points at the anchor element. */ - .search-bubble-innards::after { - background-color: var(--search-bubble-color); - content: ''; - height: 10px; - left: calc(50% - 5px); - position: absolute; - top: -5px; - transform: rotate(-45deg); - width: 10px; - z-index: -1; - } - - /* Turns the arrow direction downwards, when the bubble is placed above - * the anchor element */ - .search-bubble-innards.above::after { - bottom: -5px; - top: auto; - transform: rotate(-135deg); - } - .column-header { color: var(--paper-grey-600); font-weight: 500; diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html index 7aca114c1af..9031db844ab 100644 --- a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html +++ b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html @@ -24,7 +24,7 @@ <template> <style include="settings-shared"> :host { - @apply(--layout-fit); + @apply --layout-fit; color: var(--primary-text-color); display: flex; flex-direction: column; @@ -38,7 +38,7 @@ } cr-toolbar { - @apply(--layout-center); + @apply --layout-center; --iron-icon-fill-color: white; background-color: var(--google-blue-700); color: white; diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js index 097280993a4..4b8c62678ec 100644 --- a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js +++ b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js @@ -118,6 +118,8 @@ Polymer({ loadTimeData.getString('networkListItemConnectingTo'), networkListItemInitializing: loadTimeData.getString('networkListItemInitializing'), + networkListItemScanning: + loadTimeData.getString('networkListItemScanning'), networkListItemNotConnected: loadTimeData.getString('networkListItemNotConnected'), networkListItemNoNetwork: diff --git a/chromium/chrome/browser/resources/settings/settings_vars_css.html b/chromium/chrome/browser/resources/settings/settings_vars_css.html index 595e689d955..5e9062ead38 100644 --- a/chromium/chrome/browser/resources/settings/settings_vars_css.html +++ b/chromium/chrome/browser/resources/settings/settings_vars_css.html @@ -37,12 +37,6 @@ --settings-row-three-line-min-height: var(--cr-section-three-line-min-height); - --settings-text-elide: { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - }; - --settings-separator-height: var(--cr-separator-height); --settings-separator-line: var(--cr-separator-line); 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 index 94157bee5f4..3aad9d10c78 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html +++ b/chromium/chrome/browser/resources/settings/site_settings/add_site_dialog.html @@ -35,10 +35,10 @@ </paper-checkbox> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_"> + <paper-button class="cancel-button" on-click="onCancelTap_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" id="add" on-tap="onSubmit_" + <paper-button class="action-button" id="add" on-click="onSubmit_" disabled> $i18n{add} </paper-button> diff --git a/chromium/chrome/browser/resources/settings/site_settings/all_sites.html b/chromium/chrome/browser/resources/settings/site_settings/all_sites.html index 47fa36b5184..2891cc77aa8 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/all_sites.html +++ b/chromium/chrome/browser/resources/settings/site_settings/all_sites.html @@ -18,7 +18,7 @@ <div class="list-frame menu-content vertical-list" id="listContainer"> <template is="dom-repeat" items="[[sites]]"> <div class="list-item"> - <div class="layout horizontal center flex" on-tap="onOriginTap_" + <div class="layout horizontal center flex" on-click="onOriginTap_" actionable> <div class="favicon-image" style$="[[computeSiteIcon(item.origin)]]"> 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 index 220b76dad6f..9933ccaaabe 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js +++ b/chromium/chrome/browser/resources/settings/site_settings/category_default_setting.js @@ -97,6 +97,7 @@ Polymer({ case settings.ContentSettingsTypes.IMAGES: case settings.ContentSettingsTypes.JAVASCRIPT: case settings.ContentSettingsTypes.SOUND: + case settings.ContentSettingsTypes.SENSORS: case settings.ContentSettingsTypes.POPUPS: case settings.ContentSettingsTypes.PROTOCOL_HANDLERS: diff --git a/chromium/chrome/browser/resources/settings/site_settings/constants.js b/chromium/chrome/browser/resources/settings/site_settings/constants.js index c6a690d271f..f878c560619 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/constants.js +++ b/chromium/chrome/browser/resources/settings/site_settings/constants.js @@ -30,9 +30,10 @@ settings.ContentSettingsTypes = { MIDI_DEVICES: 'midi-sysex', USB_DEVICES: 'usb-chooser-data', ZOOM_LEVELS: 'zoom-levels', - PROTECTED_CONTENT: 'protectedContent', + PROTECTED_CONTENT: 'protected-content', ADS: 'ads', CLIPBOARD: 'clipboard', + SENSORS: 'sensors', }; /** 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 index f6ab56fa94a..25da836f905 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html +++ b/chromium/chrome/browser/resources/settings/site_settings/edit_exception_dialog.html @@ -19,10 +19,10 @@ </paper-input> </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCancelTap_" + <paper-button class="cancel-button" on-click="onCancelTap_" id="cancel">$i18n{cancel}</paper-button> <paper-button id="actionButton" class="action-button" - on-tap="onActionButtonTap_" disabled="[[invalid_]]"> + on-click="onActionButtonTap_" disabled="[[invalid_]]"> $i18n{edit} </paper-button> </div> diff --git a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html index a37356e6dda..b734faea7df 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html +++ b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.html @@ -44,34 +44,55 @@ <div class="middle" > <div class="protocol-host">[[item.host]]</div> <div class="secondary protocol-default" - hidden$="[[!isDefault_(index, protocol.default_handler)]]"> + hidden$="[[!item.is_default]]"> $i18n{handlerIsDefault} </div> </div> - <button is="paper-icon-button-light" on-tap="showMenu_" + <button is="paper-icon-button-light" on-click="showMenu_" class="icon-more-vert" title="$i18n{moreActions}"> </button> </div> - </template> </div> </template> <dialog is="cr-action-menu"> - <button class="dropdown-item" on-tap="onDefaultTap_" id="defaultButton" - hidden$="[[isModelDefault_(actionMenuModel_)]]"> + <button slot="item" class="dropdown-item" on-click="onDefaultClick_" + id="defaultButton" hidden$="[[actionMenuModel_.is_default]]"> $i18n{handlerSetDefault} </button> - <button class="dropdown-item" on-tap="onRemoveTap_" id="removeButton"> + <button slot="item" class="dropdown-item" on-click="onRemoveClick_" + id="removeButton"> $i18n{handlerRemove} </button> </dialog> + <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"> + <div class="favicon-image" style$="[[computeSiteIcon(item.host)]]"> + </div> + <div class="middle" > + <div class="protocol-host">[[item.host]]</div> + <div class="secondary protocol-protocol">[[item.protocol]]</div> + </div> + + <button is="paper-icon-button-light" on-click="onRemoveIgnored_" + class="icon-clear" title="$i18n{moreActions}" + id="removeIgnoredButton"> + </button> + </div> + </template> + </div> + </template> + <if expr="chromeos"> <template is="dom-if" if="[[settingsAppAvailable_]]"> <div class="settings-box first" - on-tap="onManageAndroidAppsTap_" actionable> + on-click="onManageAndroidAppsClick_" actionable> <div class="start"> <div>$i18n{androidAppsManageAppLinks}</div> </div> diff --git a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js index 6ad35cec77e..17667e90278 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js +++ b/chromium/chrome/browser/resources/settings/site_settings/protocol_handlers.js @@ -19,16 +19,14 @@ const MenuActions = { /** * @typedef {{host: string, + * is_default: boolean, * protocol: string, * spec: string}} */ let HandlerEntry; /** - * @typedef {{default_handler: number, - * handlers: !Array<!HandlerEntry>, - * has_policy_recommendations: boolean, - * is_default_handler_set_by_user: boolean, + * @typedef {{handlers: !Array<!HandlerEntry>, * protocol: string}} */ let ProtocolEntry; @@ -52,7 +50,7 @@ Polymer({ /** * The targetted object for menu operations. - * @private {?Object} + * @private {?HandlerEntry} */ actionMenuModel_: Object, @@ -60,6 +58,12 @@ Polymer({ toggleOffLabel: String, toggleOnLabel: String, + /** + * Array of ignored (blocked) protocols. + * @type {!Array<!HandlerEntry>} + */ + ignoredProtocols: Array, + // <if expr="chromeos"> /** @private */ settingsAppAvailable_: { @@ -114,17 +118,6 @@ Polymer({ }, /** - * Returns whether the given index matches the default handler. - * @param {number} index The index to evaluate. - * @param {number} defaultHandler The default handler index. - * @return {boolean} Whether the item is default. - * @private - */ - isDefault_: function(index, defaultHandler) { - return defaultHandler == index; - }, - - /** * Updates the main toggle to set it enabled/disabled. * @param {boolean} enabled The state to set. * @private @@ -144,13 +137,21 @@ Polymer({ /** * Updates the list of ignored protocol handlers. - * @param {!Array<!ProtocolEntry>} args The new (ignored) protocol handler - * list. + * @param {!Array<!HandlerEntry>} ignoredProtocols The new (ignored) protocol + * handler list. * @private */ - setIgnoredProtocolHandlers_: function(args) { - // TODO(finnur): Figure this out. Have yet to be able to trigger the C++ - // side to send this. + setIgnoredProtocolHandlers_: function(ignoredProtocols) { + this.ignoredProtocols = ignoredProtocols; + }, + + /** + * Closes action menu and resets action menu model + * @private + */ + closeActionMenu_: function() { + this.$$('dialog[is=cr-action-menu]').close(); + this.actionMenuModel_ = null; }, /** @@ -165,45 +166,38 @@ Polymer({ * The handler for when "Set Default" is selected in the action menu. * @private */ - onDefaultTap_: function() { - const item = this.actionMenuModel_.item; - - this.$$('dialog[is=cr-action-menu]').close(); - this.actionMenuModel_ = null; + 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 */ - onRemoveTap_: function() { - const item = this.actionMenuModel_.item; - - this.$$('dialog[is=cr-action-menu]').close(); - this.actionMenuModel_ = null; + onRemoveClick_: function() { + const item = this.actionMenuModel_; this.browserProxy.removeProtocolHandler(item.protocol, item.spec); + this.closeActionMenu_(); }, /** - * Checks whether or not the selected actionMenuModel is the default handler - * for its protocol. - * @return {boolean} if actionMenuModel_ is default handler of its protocol. + * Handler for removing handlers that were blocked + * @private */ - isModelDefault_: function() { - return !!this.actionMenuModel_ && - (this.actionMenuModel_.index == - this.actionMenuModel_.protocol.default_handler); + 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: !{protocol: HandlerEntry, item: ProtocolEntry, - * index: number}}} event + * @param {!{model: !{item: HandlerEntry}}} event * @private */ showMenu_: function(event) { - this.actionMenuModel_ = event.model; + this.actionMenuModel_ = event.model.item; /** @type {!CrActionMenuElement} */ (this.$$('dialog[is=cr-action-menu]')) .showAt( /** @type {!Element} */ ( @@ -215,7 +209,7 @@ Polymer({ * Opens an activity to handle App links (preferred apps). * @private */ - onManageAndroidAppsTap_: function() { + 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 index c50d94123c3..a92719b376e 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/site_data.html +++ b/chromium/chrome/browser/resources/settings/site_settings/site_data.html @@ -25,7 +25,7 @@ } paper-spinner-lite { - @apply(--cr-icon-height-width); + @apply --cr-icon-height-width; opacity: 0; transition-delay: 1s; } @@ -46,7 +46,7 @@ <paper-spinner-lite active="[[isLoading_]]"></paper-spinner-lite> <paper-button class="secondary-button" disabled$="[[isLoading_]]" id="removeShowingSites" - on-tap="onRemoveShowingSitesTap_" hidden$="[[!sites.length]]"> + on-click="onRemoveShowingSitesTap_" hidden$="[[!sites.length]]"> [[computeRemoveLabel_(filter)]] </paper-button> </div> @@ -54,7 +54,7 @@ scroll-target="[[subpageScrollTarget]]"> <template> <div class="settings-box two-line site-item" first$="[[!index]]" - on-tap="onSiteTap_" actionable> + on-click="onSiteTap_" actionable> <div class="favicon-image" style$="background-image: [[favicon_(item.site)]]"> </div> @@ -67,7 +67,7 @@ <div class="separator"></div> <button is="paper-icon-button-light" class="icon-delete-gray" title$="[[i18n('siteSettingsCookieRemoveSite', item.site)]]" - on-tap="onRemoveSiteTap_"> + on-click="onRemoveSiteTap_"> </button> </div> </template> @@ -81,10 +81,10 @@ </div> <div slot="body">$i18n{siteSettingsCookieRemoveMultipleConfirmation}</div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCloseDialog_"> + <paper-button class="cancel-button" on-click="onCloseDialog_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onConfirmDelete_"> + <paper-button class="action-button" on-click="onConfirmDelete_"> $i18n{siteSettingsCookiesClearAll} </paper-button> </div> 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 index 958e28f8cb5..aedab0ae6d3 100644 --- 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 @@ -29,7 +29,7 @@ </cr-expand-button> <div class="separator"></div> <button is="paper-icon-button-light" data-id-path$="[[item.idPath]]" - class="icon-clear" on-tap="onRemove_"> + class="icon-clear" on-click="onRemove_"> </button> </div> <iron-collapse class="list-frame vertical-list" diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details.html b/chromium/chrome/browser/resources/settings/site_settings/site_details.html index 9a10a9ebf6b..b7f1908390e 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/site_details.html +++ b/chromium/chrome/browser/resources/settings/site_settings/site_details.html @@ -44,10 +44,10 @@ $i18n{siteSettingsSiteResetConfirmation} </div> <div slot="button-container"> - <paper-button class="cancel-button" on-tap="onCloseDialog_"> + <paper-button class="cancel-button" on-click="onCloseDialog_"> $i18n{cancel} </paper-button> - <paper-button class="action-button" on-tap="onClearAndReset_"> + <paper-button class="action-button" on-click="onClearAndReset_"> $i18n{siteSettingsSiteResetAll} </paper-button> </div> @@ -66,7 +66,8 @@ <div class="list-item" id="storage" hidden$="[[!storedData_]]"> <div class="start">[[storedData_]]</div> <button is="paper-icon-button-light" class="icon-delete-gray" - on-tap="onConfirmClearStorage_" alt="$i18n{siteSettingsDelete}"> + on-click="onConfirmClearStorage_" + aria-label="$i18n{siteSettingsDelete}"> </button> </div> </div> @@ -89,6 +90,12 @@ icon="settings:mic" id="mic" label="$i18n{siteSettingsMic}"> </site-details-permission> + <site-details-permission + category="{{ContentSettingsTypes.SENSORS}}" + icon="settings:sensors" id="sensors" + label="$i18n{siteSettingsSensors}" + hidden$="[[!enableSensorsContentSetting_]]"> + </site-details-permission> <site-details-permission category="{{ContentSettingsTypes.NOTIFICATIONS}}" icon="settings:notifications" id="notifications" label="$i18n{siteSettingsNotifications}"> @@ -132,12 +139,6 @@ id="midiDevices" label="$i18n{siteSettingsMidiDevices}"> </site-details-permission> <site-details-permission - category="{{ContentSettingsTypes.CLIPBOARD}}" - icon="settings:clipboard" id="clipboard" - label="$i18n{siteSettingsClipboard}" - hidden$="[[!enableClipboardContentSetting_]]"> - </site-details-permission> - <site-details-permission category="{{ContentSettingsTypes.UNSANDBOXED_PLUGINS}}" icon="cr:extension" id="unsandboxedPlugins" label="$i18n{siteSettingsUnsandboxedPlugins}"> @@ -149,10 +150,16 @@ label="$i18n{siteSettingsProtectedContentIdentifiers}"> </site-details-permission> </if> + <site-details-permission + category="{{ContentSettingsTypes.CLIPBOARD}}" + icon="settings:clipboard" id="clipboard" + label="$i18n{siteSettingsClipboard}" + hidden$="[[!enableClipboardContentSetting_]]"> + </site-details-permission> </div> <div id="clearAndReset" class="settings-box" - on-tap="onConfirmClearSettings_" actionable> + on-click="onConfirmClearSettings_" actionable> <div class="start"> $i18n{siteSettingsReset} </div> diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_details.js b/chromium/chrome/browser/resources/settings/site_settings/site_details.js index 6d4f9c7c7db..73bd087ea10 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/site_details.js +++ b/chromium/chrome/browser/resources/settings/site_settings/site_details.js @@ -74,6 +74,15 @@ Polymer({ }, }, + /** @private */ + enableSensorsContentSetting_: { + type: Boolean, + readOnly: true, + value: function() { + return loadTimeData.getBoolean('enableSensorsContentSetting'); + }, + }, + /** * The type of storage for the origin. * @private @@ -236,6 +245,8 @@ Polymer({ onClearAndReset_: function() { this.browserProxy.setOriginPermissions( this.origin, this.getCategoryList_(), settings.ContentSetting.DEFAULT); + if (this.getCategoryList_().includes(settings.ContentSettingsTypes.PLUGINS)) + this.browserProxy.clearFlashPref(this.origin); if (this.storedData_ != '') this.onClearStorage_(); diff --git a/chromium/chrome/browser/resources/settings/site_settings/site_list.html b/chromium/chrome/browser/resources/settings/site_settings/site_list.html index 4f0141e4d32..9090d7df259 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/site_list.html +++ b/chromium/chrome/browser/resources/settings/site_settings/site_list.html @@ -30,29 +30,31 @@ <h2 class="start">[[categoryHeader]]</h2> <paper-button id="addSite" class="secondary-button header-aligned-button" - hidden="[[readOnlyList]]" on-tap="onAddSiteTap_"> + hidden="[[readOnlyList]]" on-click="onAddSiteTap_"> $i18n{add} </paper-button> </div> <dialog is="cr-action-menu"> - <button class="dropdown-item" id="allow" - on-tap="onAllowTap_" hidden$="[[!showAllowAction_]]"> + <button slot="item" class="dropdown-item" id="allow" + on-click="onAllowTap_" hidden$="[[!showAllowAction_]]"> $i18n{siteSettingsActionAllow} </button> - <button class="dropdown-item" id="block" - on-tap="onBlockTap_" hidden$="[[!showBlockAction_]]"> + <button slot="item" class="dropdown-item" id="block" + on-click="onBlockTap_" hidden$="[[!showBlockAction_]]"> $i18n{siteSettingsActionBlock} </button> - <button class="dropdown-item" id="sessionOnly" - on-tap="onSessionOnlyTap_" + <button slot="item" class="dropdown-item" id="sessionOnly" + on-click="onSessionOnlyTap_" hidden$="[[!showSessionOnlyActionForSite_(actionMenuSite_)]]"> $i18n{siteSettingsActionSessionOnly} </button> - <button class="dropdown-item" id="edit" on-tap="onEditTap_"> + <button slot="item" class="dropdown-item" id="edit" + on-click="onEditTap_"> $i18n{edit} </button> - <button class="dropdown-item" id="reset" on-tap="onResetTap_"> + <button slot="item" class="dropdown-item" id="reset" + on-click="onResetTap_"> $i18n{siteSettingsActionReset} </button> </dialog> @@ -64,7 +66,7 @@ <template is="dom-repeat" items="[[sites]]"> <div class="list-item"> <div class="settings-row" - actionable$="[[enableSiteSettings_]]" on-tap="onOriginTap_"> + actionable$="[[enableSiteSettings_]]" on-click="onOriginTap_"> <div class="favicon-image" style$="[[computeSiteIcon(item.origin)]]"> </div> @@ -76,7 +78,7 @@ id="siteDescription">[[computeSiteDescription_(item)]]</div> </div> <template is="dom-if" if="[[enableSiteSettings_]]"> - <div on-tap="onOriginTap_" actionable> + <div on-click="onOriginTap_" actionable> <button class="subpage-arrow" is="paper-icon-button-light" aria-label$="[[item.displayName]]" aria-describedby="siteDescription"></button> @@ -90,12 +92,12 @@ </cr-policy-pref-indicator> </template> <button is="paper-icon-button-light" id="resetSite" - class="icon-delete-gray" on-tap="onResetButtonTap_" + class="icon-delete-gray" on-click="onResetButtonTap_" hidden="[[shouldHideResetButton_(item, readOnlyList)]]" - alt="$i18n{siteSettingsActionReset}"> + aria-label="$i18n{siteSettingsActionReset}"> </button> <button is="paper-icon-button-light" id="actionMenuButton" - class="icon-more-vert" on-tap="onShowActionMenuTap_" + class="icon-more-vert" on-click="onShowActionMenuTap_" hidden="[[shouldHideActionMenu_(item, readOnlyList)]]" title="$i18n{moreActions}"> </button> 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 index 81a019ec3c4..7974cfdf2a1 100644 --- 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 @@ -137,6 +137,13 @@ cr.define('settings', function() { 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. @@ -307,6 +314,11 @@ cr.define('settings', function() { } /** @override */ + clearFlashPref(origin) { + chrome.send('clearFlashPref', [origin]); + } + + /** @override */ resetCategoryPermissionForPattern( primaryPattern, secondaryPattern, contentType, incognito) { chrome.send( @@ -362,12 +374,12 @@ cr.define('settings', function() { /** @override */ setProtocolDefault(protocol, url) { - chrome.send('setDefault', [[protocol, url]]); + chrome.send('setDefault', [protocol, url]); } /** @override */ removeProtocolHandler(protocol, url) { - chrome.send('removeHandler', [[protocol, url]]); + chrome.send('removeHandler', [protocol, url]); } /** @override */ diff --git a/chromium/chrome/browser/resources/settings/site_settings/usb_devices.html b/chromium/chrome/browser/resources/settings/site_settings/usb_devices.html index 0d49492c635..9b4e5e046d2 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/usb_devices.html +++ b/chromium/chrome/browser/resources/settings/site_settings/usb_devices.html @@ -33,7 +33,7 @@ style$="[[computeSiteIcon(item.origin)]]"></div> <div class="middle">[[item.origin]]</div> - <button is="paper-icon-button-light" on-tap="showMenu_" + <button is="paper-icon-button-light" on-click="showMenu_" class="icon-more-vert" title="$i18n{moreActions}"> </button> </div> @@ -41,7 +41,8 @@ </template> <dialog is="cr-action-menu"> - <button id="removeButton" class="dropdown-item" on-tap="onRemoveTap_"> + <button id="removeButton" slot="item" class="dropdown-item" + on-click="onRemoveTap_"> $i18n{handlerRemove} </button> </dialog> diff --git a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html b/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html index e651cd9ad88..facdc237e3e 100644 --- a/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html +++ b/chromium/chrome/browser/resources/settings/site_settings/zoom_levels.html @@ -36,7 +36,7 @@ <div class="zoom-label">[[item.zoom]]</div> <div> <button is="paper-icon-button-light" class="icon-clear" - on-tap="removeZoomLevel_" + on-click="removeZoomLevel_" title="$i18n{siteSettingsRemoveZoomLevel}"></button> </div> </div> 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 index c523b2f8536..37210d136c7 100644 --- 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 @@ -18,7 +18,7 @@ </style> <template is="dom-if" if="[[enableSiteSettings_]]"> <div class="settings-box first" category$="[[ALL_SITES]]" - data-route="SITE_SETTINGS_ALL" on-tap="onTapNavigate_" actionable> + data-route="SITE_SETTINGS_ALL" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:list"></iron-icon> <div class="middle">$i18n{siteSettingsCategoryAllSites}</div> <button class="subpage-arrow" is="paper-icon-button-light" @@ -29,7 +29,7 @@ </template> <div id="cookies" class="settings-box two-line first" category$="[[ContentSettingsTypes.COOKIES]]" - data-route="SITE_SETTINGS_COOKIES" on-tap="onTapNavigate_" actionable> + data-route="SITE_SETTINGS_COOKIES" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:cookie"></iron-icon> <div class="middle"> $i18n{siteSettingsCookies} @@ -47,7 +47,8 @@ </div> <div id="location" class="settings-box two-line" category$="[[ContentSettingsTypes.GEOLOCATION]]" - data-route="SITE_SETTINGS_LOCATION" on-tap="onTapNavigate_" actionable> + data-route="SITE_SETTINGS_LOCATION" on-click="onTapNavigate_" + actionable> <iron-icon icon="settings:location-on"></iron-icon> <div class="middle"> $i18n{siteSettingsLocation} @@ -65,7 +66,7 @@ <div id="camera" class="settings-box two-line" category$="[[ContentSettingsTypes.CAMERA]]" data-route="SITE_SETTINGS_CAMERA" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="settings:videocam"></iron-icon> <div class="middle"> $i18n{siteSettingsCamera} @@ -82,7 +83,7 @@ </div> <div id="microphone" class="settings-box two-line" category$="[[ContentSettingsTypes.MIC]]" - data-route="SITE_SETTINGS_MICROPHONE" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_MICROPHONE" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:mic"></iron-icon> <div class="middle"> @@ -98,9 +99,29 @@ aria-label="$i18n{siteSettingsMic}" aria-describedby="micSecondary"></button> </div> + <template is="dom-if" if="[[enableSensorsContentSetting_]]"> + <div id="sensors" class="settings-box two-line" + category$="[[ContentSettingsTypes.SENSORS]]" + data-route="SITE_SETTINGS_SENSORS" on-click="onTapNavigate_" + actionable> + <iron-icon icon="settings:sensors"></iron-icon> + <div class="middle"> + $i18n{siteSettingsSensors} + <div class="secondary" id="sensorsSecondary"> + [[defaultSettingLabel_( + default_.sensors, + '$i18nPolymer{siteSettingsSensorsAllow}', + '$i18nPolymer{siteSettingsSensorsBlock}')]] + </div> + </div> + <button class="subpage-arrow" is="paper-icon-button-light" + aria-label="$i18n{siteSettingsSensors}" + aria-describedby="sensorsSecondary"></button> + </div> + </template> <div id="notifications" class="settings-box two-line" category$="[[ContentSettingsTypes.NOTIFICATIONS]]" - data-route="SITE_SETTINGS_NOTIFICATIONS" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_NOTIFICATIONS" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:notifications"></iron-icon> <div class="middle"> @@ -118,7 +139,7 @@ </div> <div id="javascript" class="settings-box two-line" category$="[[ContentSettingsTypes.JAVASCRIPT]]" - data-route="SITE_SETTINGS_JAVASCRIPT" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_JAVASCRIPT" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:code"></iron-icon> <div class="middle"> @@ -136,7 +157,7 @@ </div> <div id="flash" class="settings-box two-line" category$="[[ContentSettingsTypes.PLUGINS]]" - data-route="SITE_SETTINGS_FLASH" on-tap="onTapNavigate_" actionable> + data-route="SITE_SETTINGS_FLASH" on-click="onTapNavigate_" actionable> <iron-icon icon="cr:extension"></iron-icon> <div class="middle"> $i18n{siteSettingsFlash} @@ -153,7 +174,7 @@ </div> <div id="images" class="settings-box two-line" category$="[[ContentSettingsTypes.IMAGES]]" - data-route="SITE_SETTINGS_IMAGES" on-tap="onTapNavigate_" actionable> + data-route="SITE_SETTINGS_IMAGES" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:photo"></iron-icon> <div class="middle"> $i18n{siteSettingsImages} @@ -170,7 +191,7 @@ </div> <div id="popups" category$="[[ContentSettingsTypes.POPUPS]]" class="settings-box two-line" data-route="SITE_SETTINGS_POPUPS" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="cr:open-in-new"></iron-icon> <div class="middle"> $i18n{siteSettingsPopups} @@ -188,7 +209,7 @@ <template is="dom-if" if="[[enableSafeBrowsingSubresourceFilter_]]"> <div id="ads" class="settings-box two-line" category$="[[ContentSettingsTypes.ADS]]" - data-route="SITE_SETTINGS_ADS" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_ADS" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:ads"></iron-icon> <div class="middle"> @@ -207,7 +228,7 @@ </template> <div id="background-sync" class="settings-box two-line" category$="[[ContentSettingsTypes.BACKGROUND_SYNC]]" - data-route="SITE_SETTINGS_BACKGROUND_SYNC" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_BACKGROUND_SYNC" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:sync"></iron-icon> <div class="middle"> @@ -226,7 +247,7 @@ <template is="dom-if" if="[[enableSoundContentSetting_]]"> <div id="sound" class="settings-box two-line" category$="[[ContentSettingsTypes.SOUND]]" - data-route="SITE_SETTINGS_SOUND" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_SOUND" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:volume-up"></iron-icon> <div class="middle"> @@ -246,7 +267,7 @@ <div id="automatic-downloads" class="settings-box two-line" category$="[[ContentSettingsTypes.AUTOMATIC_DOWNLOADS]]" data-route="SITE_SETTINGS_AUTOMATIC_DOWNLOADS" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="cr:file-download"></iron-icon> <div class="middle"> $i18n{siteSettingsAutomaticDownloads} @@ -264,7 +285,7 @@ <div id="unsandboxed-plugins" class="settings-box two-line" category$="[[ContentSettingsTypes.UNSANDBOXED_PLUGINS]]" data-route="SITE_SETTINGS_UNSANDBOXED_PLUGINS" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="cr:extension"></iron-icon> <div class="middle"> $i18n{siteSettingsUnsandboxedPlugins} @@ -283,7 +304,7 @@ <div id="protocol-handlers" class="settings-box two-line" category$="[[ContentSettingsTypes.PROTOCOL_HANDLERS]]" data-route="SITE_SETTINGS_HANDLERS" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="settings:protocol-handler"></iron-icon> <div class="middle"> $i18n{siteSettingsHandlers} @@ -302,7 +323,7 @@ <div id="midi-devices" class="settings-box two-line" category$="[[ContentSettingsTypes.MIDI_DEVICES]]" data-route="SITE_SETTINGS_MIDI_DEVICES" - on-tap="onTapNavigate_" actionable> + on-click="onTapNavigate_" actionable> <iron-icon icon="settings:midi"></iron-icon> <div class="middle"> $i18n{siteSettingsMidiDevices} @@ -317,29 +338,9 @@ aria-label="$i18n{siteSettingsMidiDevices}" aria-describedby="midiDevicesSecondary"></button> </div> - <template is="dom-if" if="[[enableClipboardContentSetting_]]"> - <div id="clipboard" class="settings-box two-line" - category$="[[ContentSettingsTypes.CLIPBOARD]]" - data-route="SITE_SETTINGS_CLIPBOARD" on-tap="onTapNavigate_" - actionable> - <iron-icon icon="settings:clipboard"></iron-icon> - <div class="middle"> - $i18n{siteSettingsClipboard} - <div class="secondary" id="clipboardSecondary"> - [[defaultSettingLabel_( - default_.clipboard, - '$i18nPolymer{siteSettingsAskBeforeAccessing}', - '$i18nPolymer{siteSettingsBlocked}')]] - </div> - </div> - <button class="subpage-arrow" is="paper-icon-button-light" - aria-label="$i18n{siteSettingsClipboard}" - aria-describedby="clipboardSecondary"></button> - </div> - </template> <div id="zoom-levels" class="settings-box" category$="[[ContentSettingsTypes.ZOOM_LEVELS]]" - data-route="SITE_SETTINGS_ZOOM_LEVELS" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_ZOOM_LEVELS" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:zoom-in"></iron-icon> <div class="middle">$i18n{siteSettingsZoomLevels}</div> @@ -348,7 +349,7 @@ </div> <div id="usb-devices" class="settings-box" category$="[[ContentSettingsTypes.USB_DEVICES]]" - data-route="SITE_SETTINGS_USB_DEVICES" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_USB_DEVICES" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:usb"></iron-icon> <div class="middle">$i18n{siteSettingsUsbDevices}</div> @@ -356,7 +357,7 @@ aria-label="$i18n{siteSettingsUsbDevices}"></button> </div> <div id="pdf-documents" class="settings-box" - data-route="SITE_SETTINGS_PDF_DOCUMENTS" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_PDF_DOCUMENTS" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:pdf"></iron-icon> <div class="middle">$i18n{siteSettingsPdfDocuments}</div> @@ -364,13 +365,33 @@ aria-label="$i18n{siteSettingsPdfDocuments}"></button> </div> <div id="protected-content" class="settings-box" - data-route="SITE_SETTINGS_PROTECTED_CONTENT" on-tap="onTapNavigate_" + data-route="SITE_SETTINGS_PROTECTED_CONTENT" on-click="onTapNavigate_" actionable> <iron-icon icon="settings:security"></iron-icon> <div class="middle">$i18n{siteSettingsProtectedContent}</div> <button class="subpage-arrow" is="paper-icon-button-light" aria-label="$i18n{siteSettingsProtectedContent}"></button> </div> + <template is="dom-if" if="[[enableClipboardContentSetting_]]"> + <div id="clipboard" class="settings-box two-line" + category$="[[ContentSettingsTypes.CLIPBOARD]]" + data-route="SITE_SETTINGS_CLIPBOARD" on-click="onTapNavigate_" + actionable> + <iron-icon icon="settings:clipboard"></iron-icon> + <div class="middle"> + $i18n{siteSettingsClipboard} + <div class="secondary" id="clipboardSecondary"> + [[defaultSettingLabel_( + default_.clipboard, + '$i18nPolymer{siteSettingsAskBeforeAccessing}', + '$i18nPolymer{siteSettingsBlocked}')]] + </div> + </div> + <button class="subpage-arrow" is="paper-icon-button-light" + aria-label="$i18n{siteSettingsClipboard}" + aria-describedby="clipboardSecondary"></button> + </div> + </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 index 0b1ce9dc1de..5a3b98d12af 100644 --- 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 @@ -67,6 +67,15 @@ Polymer({ } }, + /** @private */ + enableSensorsContentSetting_: { + type: Boolean, + readOnly: true, + value: function() { + return loadTimeData.getBoolean('enableSensorsContentSetting'); + } + }, + /** @type {!Map<string, string>} */ focusConfig: { type: Object, @@ -105,6 +114,7 @@ Polymer({ [R.SITE_SETTINGS_PDF_DOCUMENTS, 'pdf-documents'], [R.SITE_SETTINGS_PROTECTED_CONTENT, 'protected-content'], [R.SITE_SETTINGS_CLIPBOARD, 'clipboard'], + [R.SITE_SETTINGS_SENSORS, 'sensors'], ].forEach(pair => { const route = pair[0]; const id = pair[1]; diff --git a/chromium/chrome/browser/resources/settings/system_page/system_page.html b/chromium/chrome/browser/resources/settings/system_page/system_page.html index 77bc47816f3..fb4ed5031ee 100644 --- a/chromium/chrome/browser/resources/settings/system_page/system_page.html +++ b/chromium/chrome/browser/resources/settings/system_page/system_page.html @@ -29,7 +29,7 @@ </paper-button> </template> </settings-toggle-button> - <div id="proxy" class="settings-box" on-tap="onProxyTap_" + <div id="proxy" class="settings-box" on-click="onProxyTap_" actionable$="[[!isProxyEnforcedByPolicy_]]"> <div class="start">$i18n{proxySettingsLabel}</div> <button is="paper-icon-button-light" class="icon-external" diff --git a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google.png b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google.png Binary files differindex 3c38ca006b5..f7602a57b40 100644 --- a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google.png +++ b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google.png diff --git a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google_2x.png b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google_2x.png Binary files differindex 3634aa48a93..76d098584c9 100644 --- a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google_2x.png +++ b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/images/ic_google_2x.png diff --git a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.html b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.html index 41dac3c5e3b..644c00a0c8f 100644 --- a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.html +++ b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.html @@ -81,9 +81,9 @@ } #personalize-logo { - fill: var(--google-blue-700); /* Need the following rules to adjust for white spacing in the svg. */ -webkit-margin-end: 14px; + fill: var(--google-blue-700); height: 18px; width: 18px; } @@ -107,37 +107,52 @@ url(./images/ic_google_2x.png) 2x); } </style> + + <!-- + Use the 'consent-description' attribute to annotate all the UI elements + that are part of the text the user reads before consenting to the Sync + data collection . Similarly, use 'consent-confirmation' on UI elements on + which user clicks to indicate consent. + --> + <div id="illustration-container"></div> - <h1 id="heading">$i18n{syncConfirmationTitle}</h1> + <h1 id="heading" consent-description>$i18n{syncConfirmationTitle}</h1> <div class="message-container"> <!-- Container needed to contain the icon in a green circle. --> <div id="sync-logo-container" class="logo"> <iron-icon icon="notification:sync" class="logo"> </iron-icon> </div> - <div>$i18n{syncConfirmationChromeSyncBody}</div> + <div consent-description>$i18n{syncConfirmationChromeSyncBody}</div> </div> <div class="message-container"> <iron-icon icon="image:assistant" id="personalize-logo" class="logo"> </iron-icon> - <div>$i18n{syncConfirmationPersonalizeServicesBody}</div> + <div consent-description> + $i18n{syncConfirmationPersonalizeServicesBody} + </div> </div> <div class="message-container"> <div id="googleg-logo" class="logo"></div> - <div>$i18n{syncConfirmationGoogleServicesBody}</div> + <div consent-description>$i18n{syncConfirmationGoogleServicesBody}</div> </div> <div class="footer"> <div class="message-container"> <iron-icon icon="icons:settings" class="logo"></iron-icon> - <div>$i18nRaw{syncConfirmationSyncSettingsLinkBody}</div> + <div consent-description consent-confirmation> + $i18nRaw{syncConfirmationSyncSettingsLinkBody} + </div> </div> <div class="message-container"> <div class="logo"><!-- Spacer to line up with other texts --></div> - <div>$i18n{syncConfirmationSyncSettingsDescription}</div> + <div consent-description> + $i18n{syncConfirmationSyncSettingsDescription} + </div> </div> </div> <div class="action-container"> - <paper-button class="primary-action" id="confirmButton" on-tap="onConfirm_"> + <paper-button class="primary-action" id="confirmButton" + on-tap="onConfirm_" consent-confirmation> $i18n{syncConfirmationConfirmLabel} </paper-button> <paper-button class="secondary-action" id="undoButton" on-tap="onUndo_"> diff --git a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.js b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.js index bb45993114c..fe19b8f3dd4 100644 --- a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.js +++ b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_app.js @@ -33,8 +33,9 @@ Polymer({ }, /** @private */ - onConfirm_: function() { - this.syncConfirmationBrowserProxy_.confirm(); + onConfirm_: function(e) { + this.syncConfirmationBrowserProxy_.confirm( + this.getConsentDescription_(), this.getConsentConfirmation_(e.path)); }, /** @private */ @@ -43,15 +44,41 @@ Polymer({ }, /** @private */ - onGoToSettings_: function() { - this.syncConfirmationBrowserProxy_.goToSettings(); + onGoToSettings_: function(e) { + this.syncConfirmationBrowserProxy_.goToSettings( + this.getConsentDescription_(), this.getConsentConfirmation_(e.path)); }, /** @private */ onKeyDown_: function(e) { if (e.key == 'Enter' && !/^(A|PAPER-BUTTON)$/.test(e.path[0].tagName)) { - this.onConfirm_(); + this.onConfirm_(e); e.preventDefault(); } }, + + /** + * @param {!Array<!HTMLElement>} path Path of the click event. Must contain + * a consent confirmation element. + * @return {string} The text of the consent confirmation element. + * @private + */ + getConsentConfirmation_: function(path) { + for (var element of path) { + if (element.hasAttribute('consent-confirmation')) + return element.innerHTML.trim(); + } + assertNotReached('No consent confirmation element found.'); + return ''; + }, + + /** @return {!Array<string>} Text of the consent description elements. */ + getConsentDescription_: function() { + var consentDescription = + Array.from(this.shadowRoot.querySelectorAll('[consent-description]')) + .filter(element => element.clientWidth * element.clientHeight > 0) + .map(element => element.innerHTML.trim()); + assert(consentDescription); + return consentDescription; + } }); diff --git a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_browser_proxy.js b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_browser_proxy.js index 304c3024888..f6d3b1096be 100644 --- a/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_browser_proxy.js +++ b/chromium/chrome/browser/resources/signin/dice_sync_confirmation/sync_confirmation_browser_proxy.js @@ -11,9 +11,27 @@ cr.define('sync.confirmation', function() { /** @interface */ class SyncConfirmationBrowserProxy { - confirm() {} + /** + * Called when the user confirms the Sync Confirmation dialog. + * @param {!Array<string>} description Strings that the user was presented + * with in the UI. + * @param {string} confirmation Text of the element that the user + * clicked on. + */ + confirm(description, confirmation) {} + + /** Called when the user undoes the Sync confirmation. */ undo() {} - goToSettings() {} + + /** + * Called when the user clicks on the Settings link in + * the Sync Confirmation dialog. + * @param {!Array<string>} description Strings that the user was presented + * with in the UI. + * @param {string} confirmation Text of the element that the user + * clicked on. + */ + goToSettings(description, confirmation) {} /** @param {!Array<number>} height */ initializedWithSize(height) {} @@ -22,8 +40,8 @@ cr.define('sync.confirmation', function() { /** @implements {sync.confirmation.SyncConfirmationBrowserProxy} */ class SyncConfirmationBrowserProxyImpl { /** @override */ - confirm() { - chrome.send('confirm'); + confirm(description, confirmation) { + chrome.send('confirm', [description, confirmation]); } /** @override */ @@ -32,8 +50,8 @@ cr.define('sync.confirmation', function() { } /** @override */ - goToSettings() { - chrome.send('goToSettings'); + goToSettings(description, confirmation) { + chrome.send('goToSettings', [description, confirmation]); } /** @override */ diff --git a/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.html b/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.html index 99a1e1122d7..74acb08a2dd 100644 --- a/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.html +++ b/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.html @@ -18,8 +18,16 @@ </style> </head> <body> + <!-- + Use the 'consent-description' attribute to annotate all the UI elements + that are part of the text the user reads before consenting to the Sync + data collection . Similarly, use 'consent-confirmation' on UI elements on + which user clicks to indicate consent. + --> <div class="container"> - <div class="top-title-bar">$i18n{syncConfirmationTitle}</div> + <div class="top-title-bar" consent-description> + $i18n{syncConfirmationTitle} + </div> <div class="details" id="syncConfirmationDetails"> <div id="picture-container"> <div id="illustration"> @@ -55,8 +63,12 @@ --> <div id="chrome-logo" class="logo"></div> <div> - <div class="title">$i18n{syncConfirmationChromeSyncTitle}</div> - <div class="body text">$i18n{syncConfirmationChromeSyncBody}</div> + <div class="title" consent-description> + $i18n{syncConfirmationChromeSyncTitle} + </div> + <div class="body text" consent-description> + $i18n{syncConfirmationChromeSyncBody} + </div> </div> </div> <div class="message-container"> @@ -66,23 +78,28 @@ --> <div id="googleg-logo" class="logo"></div> <div> - <div class="title"> + <div class="title" consent-description> $i18n{syncConfirmationPersonalizeServicesTitle} </div> - <div class="body text"> + <div class="body text" consent-description> $i18n{syncConfirmationPersonalizeServicesBody} </div> </div> </div> <div class="message-container"> - <div class="body">$i18nRaw{syncConfirmationSyncSettingsLinkBody}</div> + <div class="body" consent-description consent-confirmation> + $i18nRaw{syncConfirmationSyncSettingsLinkBody} + </div> </div> </div> <div class="details" id="syncDisabledDetails"> - <div class="body text">$i18n{syncDisabledConfirmationDetails}</div> + <div class="body text" consent-description> + $i18n{syncDisabledConfirmationDetails} + </div> </div> <div class="action-container"> - <paper-button class="primary-action" id="confirmButton"> + <paper-button class="primary-action" id="confirmButton" + consent-confirmation> $i18n{syncConfirmationConfirmLabel} </paper-button> <paper-button class="secondary-action" id="undoButton"> diff --git a/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js b/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js index 17eb3bfa71c..72d29205331 100644 --- a/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js +++ b/chromium/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js @@ -5,8 +5,35 @@ cr.define('sync.confirmation', function() { 'use strict'; + /** + * @param {!Array<!HTMLElement>} path Path of the click event. Must contain + * a consent confirmation element. + * @return {string} The text of the consent confirmation element. + * @private + */ + function getConsentConfirmation(path) { + var consentConfirmation; + for (var element of path) { + if (element.hasAttribute('consent-confirmation')) + return element.innerHTML.trim(); + } + assertNotReached('No consent confirmation element found.'); + return ''; + } + + /** @return {!Array<string>} Text of the consent description elements. */ + function getConsentDescription() { + var consentDescription = + Array.from(document.querySelectorAll('[consent-description]')) + .filter(element => element.clientWidth * element.clientHeight > 0) + .map(element => element.innerHTML.trim()); + assert(consentDescription); + return consentDescription; + } + function onConfirm(e) { - chrome.send('confirm'); + chrome.send( + 'confirm', [getConsentDescription(), getConsentConfirmation(e.path)]); } function onUndo(e) { @@ -14,7 +41,9 @@ cr.define('sync.confirmation', function() { } function onGoToSettings(e) { - chrome.send('goToSettings'); + chrome.send( + 'goToSettings', + [getConsentDescription(), getConsentConfirmation(e.path)]); } function initialize() { diff --git a/chromium/chrome/browser/resources/snippets_internals.html b/chromium/chrome/browser/resources/snippets_internals.html index 27ee59e3a95..6b3cb663e44 100644 --- a/chromium/chrome/browser/resources/snippets_internals.html +++ b/chromium/chrome/browser/resources/snippets_internals.html @@ -90,11 +90,10 @@ found in the LICENSE file. </div> <div id="snippets"> - <h2>NTPSnippetsService</h2> + <h2>ContentSuggestionsService</h2> <div class="forms"> <div> - <button id="submit-download" type="button">Add snippets</button> - <span id="remote-status" class="detail"></span> + <button id="submit-download" type="button">Reload suggestions</button> </div> <div> <button id="debug-log-dump" type="button">Dump the debug log</button> @@ -107,27 +106,36 @@ found in the LICENSE file. </div> </div> - <div id="last-json" class="hidden"> - <h2>Last JSON from Server</h2> - <a id="last-json-button">Show the last JSON >></a> - <div id="last-json-container" class="hidden"> - <div id="last-json-text"></div> - <button id="last-json-dump" type="button">Dump the last JSON</button> - </div> - </div> - <div id="remote-content-suggestions"> <h2>Remote content suggestions</h2> + <table class="section-details"> + <tr> + <td class="name">Last Fetch Status + <td id="remote-status" class="value"> + <tr> + <td class="name">Last Fetch Type + <td id="remote-authenticated" class="value"> + <tr> + <td class="name">Last Background Fetch Time: + <td id="last-background-fetch-time-label" class="value"> + </table> <div> - <span>Last Background Fetch Time: </span> - <span id="last-background-fetch-time-label"></span> + <button id="background-fetch-button" type="button"> + Fetch remote suggestions in the background in 2 seconds + </button> + </div> + <div> + <button id="push-dummy-suggestion-10-seconds-button" type="button"> + Push dummy suggestion in 10 seconds + </button> + </div> + <div> + <button id="last-json-button" type="button">Show the last JSON</button> + </div> + <div id="last-json-container" class="hidden"> + <div id="last-json-text"></div> + <button id="last-json-dump" type="button">Dump the last JSON</button> </div> - <button id="background-fetch-button" type="button"> - Fetch remote suggestions in the background in 2 seconds - </button> - <button id="push-dummy-suggestion-10-seconds-button" type="button"> - Push dummy suggestion in 10 seconds - </button> </div> <div id="notifications"> diff --git a/chromium/chrome/browser/resources/vr/assets/PRESUBMIT.py b/chromium/chrome/browser/resources/vr/assets/PRESUBMIT.py index 5d740312570..c290f6d7b3d 100644 --- a/chromium/chrome/browser/resources/vr/assets/PRESUBMIT.py +++ b/chromium/chrome/browser/resources/vr/assets/PRESUBMIT.py @@ -12,10 +12,11 @@ def IsNewer(old_version, new_version): old_version.minor < new_version.minor))) -def CheckVersion(input_api, output_api): +def CheckVersionAndAssetParity(input_api, output_api): """Checks that - the version was upraded if assets files were changed, - - the version was not downgraded. + - the version was not downgraded, + - both the google_chrome and the chromium assets have the same files. """ sys.path.append(input_api.PresubmitLocalPath()) import parse_version @@ -24,9 +25,21 @@ def CheckVersion(input_api, output_api): new_version = None changed_assets = False changed_version = False + changed_asset_files = {'google_chrome': [], 'chromium': []} for file in input_api.AffectedFiles(): basename = input_api.os_path.basename(file.LocalPath()) extension = input_api.os_path.splitext(basename)[1][1:].strip().lower() + basename_without_extension = input_api.os_path.splitext(basename)[ + 0].strip().lower() + if extension == 'sha1': + basename_without_extension = input_api.os_path.splitext( + basename_without_extension)[0] + dirname = input_api.os_path.basename( + input_api.os_path.dirname(file.LocalPath())) + action = file.Action() + if (dirname in changed_asset_files and extension in {'sha1', 'png'} and + action in {'A', 'D'}): + changed_asset_files[dirname].append((action, basename_without_extension)) if (extension == 'sha1' or basename == 'vr_assets_component_files.json'): changed_assets = True if (basename == 'VERSION'): @@ -38,6 +51,15 @@ def CheckVersion(input_api, output_api): input_api.os_path.dirname(input_api.AffectedFiles()[0].LocalPath()), 'VERSION') + if changed_asset_files['google_chrome'] != changed_asset_files['chromium']: + return [ + output_api.PresubmitError( + 'Must have same asset files for %s in \'%s\'.' % + (changed_asset_files.keys(), + input_api.os_path.dirname( + input_api.AffectedFiles()[0].LocalPath()))) + ] + if changed_version and (not old_version or not new_version): return [ output_api.PresubmitError( @@ -61,8 +83,8 @@ def CheckVersion(input_api, output_api): def CheckChangeOnUpload(input_api, output_api): - return CheckVersion(input_api, output_api) + return CheckVersionAndAssetParity(input_api, output_api) def CheckChangeOnCommit(input_api, output_api): - return CheckVersion(input_api, output_api) + return CheckVersionAndAssetParity(input_api, output_api) diff --git a/chromium/chrome/browser/resources/vr/assets/VERSION b/chromium/chrome/browser/resources/vr/assets/VERSION index 1dea3031bc3..75fb8d39112 100644 --- a/chromium/chrome/browser/resources/vr/assets/VERSION +++ b/chromium/chrome/browser/resources/vr/assets/VERSION @@ -1,2 +1,2 @@ MAJOR=1 -MINOR=2
\ No newline at end of file +MINOR=3
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/chromium/background.png b/chromium/chrome/browser/resources/vr/assets/chromium/background.png Binary files differnew file mode 100644 index 00000000000..0fb3bfc35fc --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/chromium/background.png diff --git a/chromium/chrome/browser/resources/vr/assets/chromium/fullscreen_gradient.png b/chromium/chrome/browser/resources/vr/assets/chromium/fullscreen_gradient.png Binary files differnew file mode 100644 index 00000000000..04c5d88e646 --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/chromium/fullscreen_gradient.png diff --git a/chromium/chrome/browser/resources/vr/assets/chromium/incognito_gradient.png b/chromium/chrome/browser/resources/vr/assets/chromium/incognito_gradient.png Binary files differnew file mode 100644 index 00000000000..4fb17499405 --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/chromium/incognito_gradient.png diff --git a/chromium/chrome/browser/resources/vr/assets/chromium/normal_gradient.png b/chromium/chrome/browser/resources/vr/assets/chromium/normal_gradient.png Binary files differnew file mode 100644 index 00000000000..0fb3bfc35fc --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/chromium/normal_gradient.png diff --git a/chromium/chrome/browser/resources/vr/assets/fullscreen_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/fullscreen_gradient.png.sha1 deleted file mode 100644 index 8286e261102..00000000000 --- a/chromium/chrome/browser/resources/vr/assets/fullscreen_gradient.png.sha1 +++ /dev/null @@ -1 +0,0 @@ -4945728b62a01aa712b5b0bacbf10d5a1a95bd80
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/background.png.sha1 b/chromium/chrome/browser/resources/vr/assets/google_chrome/background.png.sha1 index d3427cb5c7b..d3427cb5c7b 100644 --- a/chromium/chrome/browser/resources/vr/assets/background.png.sha1 +++ b/chromium/chrome/browser/resources/vr/assets/google_chrome/background.png.sha1 diff --git a/chromium/chrome/browser/resources/vr/assets/google_chrome/fullscreen_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/google_chrome/fullscreen_gradient.png.sha1 new file mode 100644 index 00000000000..201ca21adc8 --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/google_chrome/fullscreen_gradient.png.sha1 @@ -0,0 +1 @@ +be61cbcdc981d5592455d3b7a14b97e14d3acac4
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/google_chrome/incognito_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/google_chrome/incognito_gradient.png.sha1 new file mode 100644 index 00000000000..1282cf18759 --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/google_chrome/incognito_gradient.png.sha1 @@ -0,0 +1 @@ +6b0e506b19b79ec1be4963a9bd92ab8b92ccca1c
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/google_chrome/normal_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/google_chrome/normal_gradient.png.sha1 new file mode 100644 index 00000000000..30a79ad3c93 --- /dev/null +++ b/chromium/chrome/browser/resources/vr/assets/google_chrome/normal_gradient.png.sha1 @@ -0,0 +1 @@ +bbe1145ae7778d7d50b02c54fd1bf4b62bbc04db
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/incognito_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/incognito_gradient.png.sha1 deleted file mode 100644 index 88f59906b07..00000000000 --- a/chromium/chrome/browser/resources/vr/assets/incognito_gradient.png.sha1 +++ /dev/null @@ -1 +0,0 @@ -a80f18117d1e8820404d50cdf49ba6bf6c3c69f0
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/normal_gradient.png.sha1 b/chromium/chrome/browser/resources/vr/assets/normal_gradient.png.sha1 deleted file mode 100644 index 425681fc101..00000000000 --- a/chromium/chrome/browser/resources/vr/assets/normal_gradient.png.sha1 +++ /dev/null @@ -1 +0,0 @@ -9888994181e2fa6cf6b510c02f21e183ad8efc46
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr/assets/push_assets_component.py b/chromium/chrome/browser/resources/vr/assets/push_assets_component.py index b259a6da904..b47f275ad0d 100755 --- a/chromium/chrome/browser/resources/vr/assets/push_assets_component.py +++ b/chromium/chrome/browser/resources/vr/assets/push_assets_component.py @@ -42,8 +42,8 @@ def main(): assets_dir = os.path.dirname(os.path.abspath(__file__)) files = [] - with open( - os.path.join(assets_dir, 'vr_assets_component_files.json')) as json_file: + with open(os.path.join(assets_dir, + 'vr_assets_component_files.json')) as json_file: files = json.load(json_file) version = None @@ -61,16 +61,20 @@ def main(): zip_path = os.path.join(zip_dir, 'vr-assets.zip') os.makedirs(zip_dir) + zip_files = [] with zipfile.ZipFile(zip_path, 'w') as zip: for file in files: file_path = os.path.join(assets_dir, file) zip.write(file_path, os.path.basename(file_path), zipfile.ZIP_DEFLATED) + for info in zip.infolist(): + zip_files.append(info.filename) # Upload component. command = ['gsutil', 'cp', '-nR', '.', DEST_BUCKET] PrintInfo('Going to run the following command', [' '.join(command)]) PrintInfo('In directory', [temp_dir]) PrintInfo('Which pushes the following file', [zip_path]) + PrintInfo('Which contains the files', zip_files) if raw_input('\nAre you sure (y/N) ').lower() != 'y': print 'aborting' diff --git a/chromium/chrome/browser/resources/vr/assets/vr_assets_component_files.json b/chromium/chrome/browser/resources/vr/assets/vr_assets_component_files.json index 7601c295253..f58922a30a3 100644 --- a/chromium/chrome/browser/resources/vr/assets/vr_assets_component_files.json +++ b/chromium/chrome/browser/resources/vr/assets/vr_assets_component_files.json @@ -1,6 +1,6 @@ [ - "background.png", - "fullscreen_gradient.png", - "incognito_gradient.png", - "normal_gradient.png" + "google_chrome/background.png", + "google_chrome/fullscreen_gradient.png", + "google_chrome/incognito_gradient.png", + "google_chrome/normal_gradient.png" ]
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/vr_shell/OWNERS b/chromium/chrome/browser/resources/vr_shell/OWNERS deleted file mode 100644 index 0fa23cfe0db..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -file://chrome/browser/android/vr_shell/OWNERS - -# COMPONENT: UI>Browser>VR diff --git a/chromium/chrome/browser/resources/vr_shell/ddcontroller.glb b/chromium/chrome/browser/resources/vr_shell/ddcontroller.glb Binary files differdeleted file mode 100644 index fa28d6b1962..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/ddcontroller.glb +++ /dev/null diff --git a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_app.png b/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_app.png Binary files differdeleted file mode 100644 index 77594782ad3..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_app.png +++ /dev/null diff --git a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_idle.png b/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_idle.png Binary files differdeleted file mode 100644 index 5928f21896c..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_idle.png +++ /dev/null diff --git a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_system.png b/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_system.png Binary files differdeleted file mode 100644 index d4bae158510..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_system.png +++ /dev/null diff --git a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_touchpad.png b/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_touchpad.png Binary files differdeleted file mode 100644 index 7be802ee886..00000000000 --- a/chromium/chrome/browser/resources/vr_shell/tex/ddcontroller_touchpad.png +++ /dev/null diff --git a/chromium/chrome/browser/resources/vr_shell_resources.grd b/chromium/chrome/browser/resources/vr_shell_resources.grd deleted file mode 100644 index 06de759f2f6..00000000000 --- a/chromium/chrome/browser/resources/vr_shell_resources.grd +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<grit latest_public_release="0" current_release="1" output_all_resource_defines="false"> - <outputs> - <output filename="grit/vr_shell_resources.h" type="rc_header"> - <emit emit_type='prepend'></emit> - </output> - <output filename="vr_shell_resources.pak" type="data_package" /> - </outputs> - <release seq="1"> - <includes> - <!-- TODO(vollick): add conditionals for test-specific generic assets (see crbug.com/743687) --> - <include name="IDR_VR_SHELL_DDCONTROLLER_MODEL" file="vr_shell\ddcontroller.glb" type="BINDATA" /> - <include name="IDR_VR_SHELL_DDCONTROLLER_IDLE_TEXTURE" file="vr_shell\tex\ddcontroller_idle.png" type="BINDATA" /> - <include name="IDR_VR_SHELL_DDCONTROLLER_APP_PATCH" file="vr_shell\tex\ddcontroller_app.png" type="BINDATA" /> - <include name="IDR_VR_SHELL_DDCONTROLLER_TOUCHPAD_PATCH" file="vr_shell\tex\ddcontroller_touchpad.png" type="BINDATA" /> - <include name="IDR_VR_SHELL_DDCONTROLLER_SYSTEM_PATCH" file="vr_shell\tex\ddcontroller_system.png" type="BINDATA" /> - </includes> - </release> -</grit> diff --git a/chromium/chrome/browser/safe_browsing/BUILD.gn b/chromium/chrome/browser/safe_browsing/BUILD.gn index d870ac227ab..98c6062015f 100644 --- a/chromium/chrome/browser/safe_browsing/BUILD.gn +++ b/chromium/chrome/browser/safe_browsing/BUILD.gn @@ -105,6 +105,8 @@ static_library("safe_browsing") { "test_safe_browsing_blocking_page_quiet.h", "test_safe_browsing_service.cc", "test_safe_browsing_service.h", + "trigger_creator.cc", + "trigger_creator.h", "ui_manager.cc", "ui_manager.h", ] @@ -115,9 +117,12 @@ static_library("safe_browsing") { "//components/safe_browsing:safe_browsing", "//components/safe_browsing/browser:browser", "//components/safe_browsing/common:common", + "//components/safe_browsing/common:safe_browsing_prefs", "//components/safe_browsing/db:metadata_proto", "//components/safe_browsing/db:whitelist_checker_client", "//components/safe_browsing/password_protection", + "//components/safe_browsing/triggers:ad_sampler_trigger", + "//components/safe_browsing/triggers:trigger_throttler", "//components/safe_browsing/triggers:triggers", ] if (safe_browsing_mode == 1) { @@ -227,18 +232,13 @@ static_library("safe_browsing") { "services_delegate_impl.h", "signature_evaluator_mac.h", "signature_evaluator_mac.mm", - "trigger_creator.cc", - "trigger_creator.h", ] deps += [ "//chrome/services/file_util/public/cpp", "//components/content_settings/core/browser:browser", + "//components/language/core/common:common", "//components/prefs:prefs", - "//components/safe_browsing/common:safe_browsing_prefs", "//components/safe_browsing/db:db", - "//components/safe_browsing/triggers:ad_sampler_trigger", - "//components/safe_browsing/triggers:trigger_throttler", - "//components/safe_browsing/triggers:triggers", "//components/security_interstitials/content:security_interstitial_page", "//content/public/browser:browser", "//net:net", diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc index 0aafa6af519..23bb639702c 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc @@ -18,7 +18,7 @@ SpellCheckHostChromeImpl::SpellCheckHostChromeImpl( const service_manager::Identity& renderer_identity) - : renderer_identity_(renderer_identity) {} + : renderer_identity_(renderer_identity), weak_factory_(this) {} SpellCheckHostChromeImpl::~SpellCheckHostChromeImpl() = default; @@ -27,7 +27,7 @@ void SpellCheckHostChromeImpl::Create( spellcheck::mojom::SpellCheckHostRequest request, const service_manager::BindSourceInfo& source_info) { mojo::MakeStrongBinding( - base::MakeUnique<SpellCheckHostChromeImpl>(source_info.identity), + std::make_unique<SpellCheckHostChromeImpl>(source_info.identity), std::move(request)); } @@ -61,6 +61,7 @@ void SpellCheckHostChromeImpl::NotifyChecked(const base::string16& word, spellcheck->GetMetrics()->RecordCheckedWordStats(word, misspelled); } +#if !BUILDFLAG(USE_BROWSER_SPELLCHECKER) void SpellCheckHostChromeImpl::CallSpellingService( const base::string16& text, CallSpellingServiceCallback callback) { @@ -72,7 +73,6 @@ void SpellCheckHostChromeImpl::CallSpellingService( return; } -#if !BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Checks the user profile and sends a JSON-RPC request to the Spelling // service if a user enables the "Ask Google for suggestions" option. When // a response is received (including an error) from the remote Spelling @@ -83,13 +83,9 @@ void SpellCheckHostChromeImpl::CallSpellingService( client_.RequestTextCheck( context, SpellingServiceClient::SPELLCHECK, text, base::BindOnce(&SpellCheckHostChromeImpl::CallSpellingServiceDone, - base::Unretained(this), base::Passed(&callback))); -#else - std::move(callback).Run(false, std::vector<SpellCheckResult>()); -#endif + weak_factory_.GetWeakPtr(), std::move(callback))); } -#if !BUILDFLAG(USE_BROWSER_SPELLCHECKER) void SpellCheckHostChromeImpl::CallSpellingServiceDone( CallSpellingServiceCallback callback, bool success, diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h index 1e712fcdbb3..e0d5c7c9393 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h @@ -5,22 +5,18 @@ #ifndef CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_HOST_CHROME_IMPL_H_ #define CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_HOST_CHROME_IMPL_H_ -#include "base/macros.h" +#include "build/build_config.h" +#include "components/spellcheck/browser/spell_check_host_impl.h" #include "components/spellcheck/browser/spelling_service_client.h" -#include "components/spellcheck/common/spellcheck.mojom.h" -#include "components/spellcheck/spellcheck_build_features.h" #include "services/service_manager/public/cpp/bind_source_info.h" -#if !BUILDFLAG(ENABLE_SPELLCHECK) -#error "Spellcheck should be enabled." -#endif - class SpellcheckCustomDictionary; class SpellcheckService; struct SpellCheckResult; -class SpellCheckHostChromeImpl : public spellcheck::mojom::SpellCheckHost { +// Implementation of SpellCheckHost involving Chrome-only features. +class SpellCheckHostChromeImpl : public SpellCheckHostImpl { public: explicit SpellCheckHostChromeImpl( const service_manager::Identity& renderer_identity); @@ -31,14 +27,16 @@ class SpellCheckHostChromeImpl : public spellcheck::mojom::SpellCheckHost { private: friend class TestSpellCheckHostChromeImpl; + friend class SpellCheckHostChromeImplMacTest; - // spellcheck::mojom::SpellCheckHost: + // SpellCheckHostImpl: void RequestDictionary() override; void NotifyChecked(const base::string16& word, bool misspelled) override; + +#if !BUILDFLAG(USE_BROWSER_SPELLCHECKER) void CallSpellingService(const base::string16& text, CallSpellingServiceCallback callback) override; -#if !BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Invoked when the remote Spelling service has finished checking the // text of a CallSpellingService request. void CallSpellingServiceDone( @@ -55,6 +53,28 @@ class SpellCheckHostChromeImpl : public spellcheck::mojom::SpellCheckHost { const std::vector<SpellCheckResult>& service_results); #endif +#if defined(OS_MACOSX) + // Non-Mac (i.e., Android) implementations of the following APIs are in the + // base class SpellCheckHostImpl. + void CheckSpelling(const base::string16& word, + int route_id, + CheckSpellingCallback callback) override; + void FillSuggestionList(const base::string16& word, + FillSuggestionListCallback callback) override; + void RequestTextCheck(const base::string16& text, + int route_id, + RequestTextCheckCallback callback) override; + + // Exposed to tests only. + static void CombineResultsForTesting( + std::vector<SpellCheckResult>* remote_results, + const std::vector<SpellCheckResult>& local_results); + + int ToDocumentTag(int route_id); + void RetireDocumentTag(int route_id); + std::map<int, int> tag_map_; +#endif // defined(OS_MACOSX) + // Returns the SpellcheckService of our |render_process_id_|. The return // is null if the render process is being shut down. virtual SpellcheckService* GetSpellcheckService() const; @@ -65,6 +85,8 @@ class SpellCheckHostChromeImpl : public spellcheck::mojom::SpellCheckHost { // A JSON-RPC client that calls the remote Spelling service. SpellingServiceClient client_; + base::WeakPtrFactory<SpellCheckHostChromeImpl> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(SpellCheckHostChromeImpl); }; diff --git a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac.cc index 5666e75af54..316a6b45352 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac.cc @@ -2,48 +2,70 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/spellcheck/browser/spellcheck_message_filter_platform.h" - -#include <algorithm> -#include <functional> +#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" #include "base/barrier_closure.h" #include "base/bind.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_service.h" #include "components/spellcheck/browser/spellcheck_platform.h" -#include "components/spellcheck/browser/spelling_service_client.h" -#include "components/spellcheck/common/spellcheck_messages.h" -#include "components/spellcheck/common/spellcheck_result.h" #include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" +#include "services/service_manager/public/cpp/identity.h" using content::BrowserThread; using content::BrowserContext; namespace { -bool CompareLocation(const SpellCheckResult& r1, - const SpellCheckResult& r2) { +bool CompareLocation(const SpellCheckResult& r1, const SpellCheckResult& r2) { return r1.location < r2.location; } +// Adjusts remote_results by examining local_results. Any result that's both +// local and remote stays type SPELLING, all others are flagged GRAMMAR. +// (This is needed to force gray underline for remote-only results.) +void CombineResults(std::vector<SpellCheckResult>* remote_results, + const std::vector<SpellCheckResult>& local_results) { + std::vector<SpellCheckResult>::const_iterator local_iter( + local_results.begin()); + std::vector<SpellCheckResult>::iterator remote_iter; + + for (remote_iter = remote_results->begin(); + remote_iter != remote_results->end(); ++remote_iter) { + // Discard all local results occurring before remote result. + while (local_iter != local_results.end() && + local_iter->location < remote_iter->location) { + local_iter++; + } + + // Unless local and remote result coincide, result is GRAMMAR. + remote_iter->decoration = SpellCheckResult::GRAMMAR; + if (local_iter != local_results.end() && + local_iter->location == remote_iter->location && + local_iter->length == remote_iter->length) { + remote_iter->decoration = SpellCheckResult::SPELLING; + } + } +} + } // namespace class SpellingRequest { public: - SpellingRequest(SpellingServiceClient* client, - content::BrowserMessageFilter* destination, - int render_process_id); + using RequestTextCheckCallback = + spellcheck::mojom::SpellCheckHost::RequestTextCheckCallback; - void RequestCheck(const base::string16& text, - int route_id, - int identifier, - int document_tag); + SpellingRequest(SpellingServiceClient* client, + const base::string16& text, + const service_manager::Identity& renderer_identity, + int document_tag, + RequestTextCheckCallback callback); private: // Request server-side checking for |text_|. - void RequestRemoteCheck(); + void RequestRemoteCheck(SpellingServiceClient* client); // Request a check for |text_| from local spell checker. void RequestLocalCheck(); @@ -66,57 +88,42 @@ class SpellingRequest { base::RepeatingClosure completion_barrier_; bool remote_success_; - SpellingServiceClient* client_; // Owned by |destination|. - content::BrowserMessageFilter* destination_; // ref-counted. - int render_process_id_; - base::string16 text_; - int route_id_; - int identifier_; + const service_manager::Identity renderer_identity_; int document_tag_; + RequestTextCheckCallback callback_; }; -SpellingRequest::SpellingRequest(SpellingServiceClient* client, - content::BrowserMessageFilter* destination, - int render_process_id) +SpellingRequest::SpellingRequest( + SpellingServiceClient* client, + const base::string16& text, + const service_manager::Identity& renderer_identity, + int document_tag, + RequestTextCheckCallback callback) : remote_success_(false), - client_(client), - destination_(destination), - render_process_id_(render_process_id), - route_id_(-1), - identifier_(-1), - document_tag_(-1) { - destination_->AddRef(); -} - -void SpellingRequest::RequestCheck(const base::string16& text, - int route_id, - int identifier, - int document_tag) { - DCHECK(!text.empty()); + text_(text), + renderer_identity_(renderer_identity), + document_tag_(document_tag), + callback_(std::move(callback)) { + DCHECK(!text_.empty()); DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - text_ = text; - route_id_ = route_id; - identifier_ = identifier; - document_tag_ = document_tag; - // Send the remote query out. The barrier owns |this|, ensuring it is deleted // after completion. completion_barrier_ = BarrierClosure( 2, base::BindOnce(&SpellingRequest::OnCheckCompleted, base::Owned(this))); - RequestRemoteCheck(); + RequestRemoteCheck(client); RequestLocalCheck(); } -void SpellingRequest::RequestRemoteCheck() { +void SpellingRequest::RequestRemoteCheck(SpellingServiceClient* client) { BrowserContext* context = NULL; content::RenderProcessHost* host = - content::RenderProcessHost::FromID(render_process_id_); + content::RenderProcessHost::FromRendererIdentity(renderer_identity_); if (host) context = host->GetBrowserContext(); - client_->RequestTextCheck( + client->RequestTextCheck( context, SpellingServiceClient::SPELLCHECK, text_, base::BindOnce(&SpellingRequest::OnRemoteCheckCompleted, base::Unretained(this))); @@ -131,22 +138,18 @@ void SpellingRequest::RequestLocalCheck() { void SpellingRequest::OnCheckCompleted() { // Final completion can happen on any thread - don't DCHECK thread. - const std::vector<SpellCheckResult>* check_results = &local_results_; + std::vector<SpellCheckResult>* check_results = &local_results_; if (remote_success_) { std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation); std::sort(local_results_.begin(), local_results_.end(), CompareLocation); - SpellCheckMessageFilterPlatform::CombineResults(&remote_results_, - local_results_); + CombineResults(&remote_results_, local_results_); check_results = &remote_results_; } - destination_->Send( - new SpellCheckMsg_RespondTextCheck( - route_id_, - identifier_, - text_, - *check_results)); - destination_->Release(); + // |callback_| must be run on UI thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce(std::move(callback_), std::move(*check_results))); // Object is self-managed - at this point, its life span is over. // No need to delete, since the OnCheckCompleted callback owns |this|. @@ -169,81 +172,33 @@ void SpellingRequest::OnLocalCheckCompleted( completion_barrier_.Run(); } - -SpellCheckMessageFilterPlatform::SpellCheckMessageFilterPlatform( - int render_process_id) - : BrowserMessageFilter(SpellCheckMsgStart), - render_process_id_(render_process_id), - client_(new SpellingServiceClient) { -} - -void SpellCheckMessageFilterPlatform::OverrideThreadForMessage( - const IPC::Message& message, BrowserThread::ID* thread) { - if (message.type() == SpellCheckHostMsg_RequestTextCheck::ID) - *thread = BrowserThread::UI; -} - -bool SpellCheckMessageFilterPlatform::OnMessageReceived( - const IPC::Message& message) { - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(SpellCheckMessageFilterPlatform, message) - IPC_MESSAGE_HANDLER(SpellCheckHostMsg_CheckSpelling, - OnCheckSpelling) - IPC_MESSAGE_HANDLER(SpellCheckHostMsg_FillSuggestionList, - OnFillSuggestionList) - IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RequestTextCheck, - OnRequestTextCheck) - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - return handled; -} - // static -void SpellCheckMessageFilterPlatform::CombineResults( +void SpellCheckHostChromeImpl::CombineResultsForTesting( std::vector<SpellCheckResult>* remote_results, const std::vector<SpellCheckResult>& local_results) { - std::vector<SpellCheckResult>::const_iterator local_iter( - local_results.begin()); - std::vector<SpellCheckResult>::iterator remote_iter; - - for (remote_iter = remote_results->begin(); - remote_iter != remote_results->end(); - ++remote_iter) { - // Discard all local results occurring before remote result. - while (local_iter != local_results.end() && - local_iter->location < remote_iter->location) { - local_iter++; - } - - // Unless local and remote result coincide, result is GRAMMAR. - remote_iter->decoration = SpellCheckResult::GRAMMAR; - if (local_iter != local_results.end() && - local_iter->location == remote_iter->location && - local_iter->length == remote_iter->length) { - remote_iter->decoration = SpellCheckResult::SPELLING; - } - } + CombineResults(remote_results, local_results); } -SpellCheckMessageFilterPlatform::~SpellCheckMessageFilterPlatform() {} - -void SpellCheckMessageFilterPlatform::OnCheckSpelling( - const base::string16& word, - int route_id, - bool* correct) { - *correct = spellcheck_platform::CheckSpelling(word, ToDocumentTag(route_id)); +void SpellCheckHostChromeImpl::CheckSpelling(const base::string16& word, + int route_id, + CheckSpellingCallback callback) { + bool correct = + spellcheck_platform::CheckSpelling(word, ToDocumentTag(route_id)); + std::move(callback).Run(correct); } -void SpellCheckMessageFilterPlatform::OnFillSuggestionList( +void SpellCheckHostChromeImpl::FillSuggestionList( const base::string16& word, - std::vector<base::string16>* suggestions) { - spellcheck_platform::FillSuggestionList(word, suggestions); + FillSuggestionListCallback callback) { + std::vector<base::string16> suggestions; + spellcheck_platform::FillSuggestionList(word, &suggestions); + std::move(callback).Run(suggestions); } -void SpellCheckMessageFilterPlatform::OnRequestTextCheck( +void SpellCheckHostChromeImpl::RequestTextCheck( + const base::string16& text, int route_id, - int identifier, - const base::string16& text) { + RequestTextCheckCallback callback) { DCHECK(!text.empty()); DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -251,18 +206,14 @@ void SpellCheckMessageFilterPlatform::OnRequestTextCheck( // language code for text breaking to the renderer. (Text breaking is required // for the context menu to show spelling suggestions.) Initialization must // happen on UI thread. - content::RenderProcessHost* host = - content::RenderProcessHost::FromID(render_process_id_); - if (host) - SpellcheckServiceFactory::GetForRenderer(host->GetChildIdentity()); + GetSpellcheckService(); // SpellingRequest self-destructs. - SpellingRequest* request = - new SpellingRequest(client_.get(), this, render_process_id_); - request->RequestCheck(text, route_id, identifier, ToDocumentTag(route_id)); + new SpellingRequest(&client_, text, renderer_identity_, + ToDocumentTag(route_id), std::move(callback)); } -int SpellCheckMessageFilterPlatform::ToDocumentTag(int route_id) { +int SpellCheckHostChromeImpl::ToDocumentTag(int route_id) { if (!tag_map_.count(route_id)) tag_map_[route_id] = spellcheck_platform::GetDocumentTag(); return tag_map_[route_id]; @@ -271,7 +222,7 @@ int SpellCheckMessageFilterPlatform::ToDocumentTag(int route_id) { // TODO(groby): We are currently not notified of retired tags. We need // to track destruction of RenderViewHosts on the browser process side // to update our mappings when a document goes away. -void SpellCheckMessageFilterPlatform::RetireDocumentTag(int route_id) { +void SpellCheckHostChromeImpl::RetireDocumentTag(int route_id) { spellcheck_platform::CloseDocumentWithTag(ToDocumentTag(route_id)); tag_map_.erase(route_id); } diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc new file mode 100644 index 00000000000..03d2bab0daf --- /dev/null +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc @@ -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. + +#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" + +#include "base/memory/ptr_util.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.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/test/mock_render_process_host.h" + +class SpellCheckHostChromeImplMacBrowserTest : public InProcessBrowserTest { + public: + void SetUpOnMainThread() override { + content::BrowserContext* context = browser()->profile(); + renderer_.reset(new content::MockRenderProcessHost(context)); + + service_manager::BindSourceInfo source_info; + source_info.identity = renderer_->GetChildIdentity(); + SpellCheckHostChromeImpl::Create(mojo::MakeRequest(&spell_check_host_), + source_info); + } + + void TearDownOnMainThread() override { renderer_.reset(); } + + void LogResult(const std::vector<SpellCheckResult>& result) { + received_result_ = true; + result_ = result; + if (quit_) + std::move(quit_).Run(); + } + + void RunUntilResultReceived() { + if (received_result_) + return; + base::RunLoop run_loop; + quit_ = run_loop.QuitClosure(); + run_loop.Run(); + } + + protected: + std::unique_ptr<content::MockRenderProcessHost> renderer_; + spellcheck::mojom::SpellCheckHostPtr spell_check_host_; + + bool received_result_ = false; + std::vector<SpellCheckResult> result_; + base::OnceClosure quit_; +}; + +// Uses browsertest to setup chrome threads. +IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplMacBrowserTest, + SpellCheckReturnMessage) { + spell_check_host_->RequestTextCheck( + base::UTF8ToUTF16("zz."), 123, + base::BindOnce(&SpellCheckHostChromeImplMacBrowserTest::LogResult, + base::Unretained(this))); + RunUntilResultReceived(); + + ASSERT_EQ(1U, result_.size()); + EXPECT_EQ(result_[0].location, 0); + EXPECT_EQ(result_[0].length, 2); + EXPECT_EQ(result_[0].decoration, SpellCheckResult::SPELLING); +} diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_unittest.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_unittest.cc new file mode 100644 index 00000000000..2eecc346490 --- /dev/null +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_unittest.cc @@ -0,0 +1,46 @@ +// 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/spellchecker/spell_check_host_chrome_impl.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +class SpellCheckHostChromeImplMacTest : public ::testing::Test { + protected: + void CombineResults( + std::vector<SpellCheckResult>* remote_results, + const std::vector<SpellCheckResult>& local_results) const { + SpellCheckHostChromeImpl::CombineResultsForTesting(remote_results, + local_results); + } +}; + +TEST_F(SpellCheckHostChromeImplMacTest, CombineResults) { + std::vector<SpellCheckResult> local_results; + std::vector<SpellCheckResult> remote_results; + base::string16 remote_suggestion = base::ASCIIToUTF16("remote"); + base::string16 local_suggestion = base::ASCIIToUTF16("local"); + + // Remote-only result - must be flagged as GRAMMAR after combine + remote_results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, 0, 5)); + + // Local-only result - must be discarded after combine + local_results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, 10, 5)); + + // local & remote result - must be flagged SPELLING, uses remote suggestion. + SpellCheckResult result(SpellCheckResult::SPELLING, 20, 5, local_suggestion); + local_results.push_back(result); + result.replacements[0] = remote_suggestion; + remote_results.push_back(result); + + CombineResults(&remote_results, local_results); + + ASSERT_EQ(2U, remote_results.size()); + EXPECT_EQ(SpellCheckResult::GRAMMAR, remote_results[0].decoration); + EXPECT_EQ(0, remote_results[0].location); + EXPECT_EQ(SpellCheckResult::SPELLING, remote_results[1].decoration); + EXPECT_EQ(20, remote_results[1].location); + EXPECT_EQ(remote_suggestion, remote_results[1].replacements[0]); +} diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_unittest.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_unittest.cc index e991fddc5e1..7e978c228ee 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_unittest.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_unittest.cc @@ -22,7 +22,7 @@ class TestSpellCheckHostChromeImpl { public: TestSpellCheckHostChromeImpl() - : spellcheck_(base::MakeUnique<SpellcheckService>(&testing_profile_)) {} + : spellcheck_(std::make_unique<SpellcheckService>(&testing_profile_)) {} SpellcheckCustomDictionary& GetCustomDictionary() const { EXPECT_NE(nullptr, spellcheck_.get()); diff --git a/chromium/chrome/browser/spellchecker/spell_check_panel_host_impl.cc b/chromium/chrome/browser/spellchecker/spell_check_panel_host_impl.cc index 591b90567eb..558e744efa2 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_panel_host_impl.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_panel_host_impl.cc @@ -17,7 +17,7 @@ SpellCheckPanelHostImpl::~SpellCheckPanelHostImpl() = default; // static void SpellCheckPanelHostImpl::Create( spellcheck::mojom::SpellCheckPanelHostRequest request) { - mojo::MakeStrongBinding(base::MakeUnique<SpellCheckPanelHostImpl>(), + mojo::MakeStrongBinding(std::make_unique<SpellCheckPanelHostImpl>(), std::move(request)); } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc index 24a9f621704..5ad42fc6183 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc @@ -87,7 +87,7 @@ ChecksumStatus LoadFile(const base::FilePath& file_path, bool IsValidWord(const std::string& word) { std::string tmp; return !word.empty() && - word.size() <= spellcheck::MAX_CUSTOM_DICTIONARY_WORD_BYTES && + word.size() <= spellcheck::kMaxCustomDictionaryWordBytes && base::IsStringUTF8(word) && base::TRIM_NONE == base::TrimWhitespaceASCII(word, base::TRIM_ALL, &tmp); @@ -340,7 +340,7 @@ syncer::SyncDataList SpellcheckCustomDictionary::GetAllSyncData( syncer::SyncDataList data; size_t i = 0; for (const auto& word : words_) { - if (i++ >= spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS) + if (i++ >= spellcheck::kMaxSyncableDictionaryWords) break; sync_pb::EntitySpecifics specifics; specifics.mutable_dictionary()->set_word(word); @@ -435,7 +435,7 @@ void SpellcheckCustomDictionary::OnLoaded( // Save cleaned up data only after startup. fix_invalid_file_.Reset( base::BindOnce(&SpellcheckCustomDictionary::FixInvalidFile, - weak_ptr_factory_.GetWeakPtr(), base::Passed(&result))); + weak_ptr_factory_.GetWeakPtr(), std::move(result))); BrowserThread::PostAfterStartupTask( FROM_HERE, BrowserThread::GetTaskRunnerForThread(BrowserThread::UI), fix_invalid_file_.callback()); @@ -458,7 +458,7 @@ void SpellcheckCustomDictionary::FixInvalidFile( task_runner_->PostTask( FROM_HERE, base::BindOnce(&SavePassedWordsToDictionaryFileReliably, - custom_dictionary_path_, base::Passed(&load_file_result))); + custom_dictionary_path_, std::move(load_file_result))); } void SpellcheckCustomDictionary::Save( @@ -468,8 +468,7 @@ void SpellcheckCustomDictionary::Save( task_runner_->PostTask( FROM_HERE, base::BindOnce(&SpellcheckCustomDictionary::UpdateDictionaryFile, - base::Passed(&dictionary_change), - custom_dictionary_path_)); + std::move(dictionary_change), custom_dictionary_path_)); } syncer::SyncError SpellcheckCustomDictionary::Sync( @@ -483,7 +482,7 @@ syncer::SyncError SpellcheckCustomDictionary::Sync( int server_size = static_cast<int>(words_.size()) - static_cast<int>(dictionary_change.to_add().size()); int max_upload_size = - std::max(0, static_cast<int>(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS) - + std::max(0, static_cast<int>(spellcheck::kMaxSyncableDictionaryWords) - server_size); int upload_size = std::min( static_cast<int>(dictionary_change.to_add().size()), @@ -518,7 +517,7 @@ syncer::SyncError SpellcheckCustomDictionary::Sync( // Turn off syncing of this dictionary if the server already has the maximum // number of words. - if (words_.size() > spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS) + if (words_.size() > spellcheck::kMaxSyncableDictionaryWords) StopSyncing(syncer::DICTIONARY); return error; diff --git a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary_unittest.cc index c488d189d6d..b60ebfd25a6 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary_unittest.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary_unittest.cc @@ -58,7 +58,7 @@ syncer::SyncDataList GetAllSyncDataNoLimit( static std::unique_ptr<KeyedService> BuildSpellcheckService( content::BrowserContext* profile) { - return base::MakeUnique<SpellcheckService>(static_cast<Profile*>(profile)); + return std::make_unique<SpellcheckService>(static_cast<Profile*>(profile)); } class SpellcheckCustomDictionaryTest : public testing::Test { @@ -325,31 +325,31 @@ TEST_F(SpellcheckCustomDictionaryTest, GetAllSyncDataHasLimit) { &profile_)->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS - 1; i++) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords - 1; i++) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*dictionary, change); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS - 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords - 1, dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS - 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords - 1, dictionary->GetAllSyncData(syncer::DICTIONARY).size()); dictionary->AddWord("baz"); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, dictionary->GetAllSyncData(syncer::DICTIONARY).size()); dictionary->AddWord("bar"); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, dictionary->GetAllSyncData(syncer::DICTIONARY).size()); dictionary->AddWord("snafoo"); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 2, dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, dictionary->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -438,13 +438,13 @@ TEST_F(SpellcheckCustomDictionaryTest, MergeDataAndStartSyncing) { spellcheck_service2->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords / 2; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); SpellcheckCustomDictionary::Change change2; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords / 2; ++i) { change2.AddWord("bar" + base::NumberToString(i)); } Apply(*custom_dictionary2, change2); @@ -535,7 +535,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigBeforeSyncing) { spellcheck_service2->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords + 1; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); @@ -555,14 +555,14 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigBeforeSyncing) { EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -581,7 +581,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigAndServerFull) { SpellcheckCustomDictionary::Change change; SpellcheckCustomDictionary::Change change2; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords; ++i) { change.AddWord("foo" + base::NumberToString(i)); change2.AddWord("bar" + base::NumberToString(i)); } @@ -589,9 +589,9 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigAndServerFull) { Apply(*custom_dictionary, change); Apply(*custom_dictionary2, change2); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); int error_counter = 0; @@ -609,14 +609,14 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigAndServerFull) { EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS * 2 + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords * 2 + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -635,16 +635,16 @@ TEST_F(SpellcheckCustomDictionaryTest, ServerTooBig) { SpellcheckCustomDictionary::Change change; SpellcheckCustomDictionary::Change change2; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords + 1; ++i) { change.AddWord("foo" + base::NumberToString(i)); change2.AddWord("bar" + base::NumberToString(i)); } Apply(*custom_dictionary, change); Apply(*custom_dictionary2, change2); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary2->GetWords().size()); int error_counter = 0; @@ -662,14 +662,14 @@ TEST_F(SpellcheckCustomDictionaryTest, ServerTooBig) { EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS * 2 + 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords * 2 + 2, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -687,7 +687,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigToStartSyncing) { spellcheck_service2->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS - 1; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords - 1; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); @@ -710,14 +710,14 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigToStartSyncing) { EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -735,7 +735,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigToContiueSyncing) { spellcheck_service2->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS - 1; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords - 1; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); @@ -763,14 +763,14 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionaryTooBigToContiueSyncing) { EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -849,21 +849,21 @@ TEST_F(SpellcheckCustomDictionaryTest, LoadAfterSyncStartTooBigToSync) { std::unique_ptr<std::set<std::string>> custom_words( new std::set<std::string>); - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords; ++i) { custom_words->insert(custom_words->end(), "foo" + base::NumberToString(i)); } OnLoaded(*custom_dictionary, std::move(custom_words)); EXPECT_EQ(0, error_counter); EXPECT_FALSE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS + 1, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords + 1, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -881,7 +881,7 @@ TEST_F(SpellcheckCustomDictionaryTest, LoadDuplicatesAfterSync) { spellcheck_service2->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords / 2; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); @@ -902,18 +902,18 @@ TEST_F(SpellcheckCustomDictionaryTest, LoadDuplicatesAfterSync) { EXPECT_TRUE(custom_dictionary->IsSyncing()); OnLoaded(*custom_dictionary, - base::MakeUnique<std::set<std::string>>(change.to_add())); + std::make_unique<std::set<std::string>>(change.to_add())); EXPECT_EQ(0, error_counter); EXPECT_TRUE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords / 2, custom_dictionary->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords / 2, custom_dictionary2->GetWords().size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords / 2, custom_dictionary->GetAllSyncData(syncer::DICTIONARY).size()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS / 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords / 2, custom_dictionary2->GetAllSyncData(syncer::DICTIONARY).size()); } @@ -1054,7 +1054,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionarySyncLimit) { spellcheck_service->GetCustomDictionary(); SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords; ++i) { change.AddWord("foo" + base::NumberToString(i)); } Apply(*custom_dictionary, change); @@ -1074,12 +1074,12 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionarySyncLimit) { .IsSet()); EXPECT_EQ(0, error_counter); EXPECT_TRUE(custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, custom_dictionary->GetWords().size()); } // The sync server now has the maximum number of words. - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, server_custom_dictionary->GetWords().size()); // Associate the sync server with a client that also has the maximum number of @@ -1099,7 +1099,7 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionarySyncLimit) { // Add the maximum number of words to the client. These words are all // different from those on the server. SpellcheckCustomDictionary::Change change; - for (size_t i = 0; i < spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS; ++i) { + for (size_t i = 0; i < spellcheck::kMaxSyncableDictionaryWords; ++i) { change.AddWord("bar" + base::NumberToString(i)); } Apply(*client_custom_dictionary, change); @@ -1120,13 +1120,13 @@ TEST_F(SpellcheckCustomDictionaryTest, DictionarySyncLimit) { .IsSet()); EXPECT_EQ(0, error_counter); EXPECT_FALSE(client_custom_dictionary->IsSyncing()); - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS * 2, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords * 2, client_custom_dictionary->GetWords().size()); } // The sync server should not receive more words, because it has the maximum // number of words already. - EXPECT_EQ(spellcheck::MAX_SYNCABLE_DICTIONARY_WORDS, + EXPECT_EQ(spellcheck::kMaxSyncableDictionaryWords, server_custom_dictionary->GetWords().size()); } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc index f02ff11f850..20054d12bc5 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc @@ -69,9 +69,9 @@ KeyedService* SpellcheckServiceFactory::BuildServiceInstanceFor( void SpellcheckServiceFactory::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* user_prefs) { user_prefs->RegisterListPref(spellcheck::prefs::kSpellCheckDictionaries, - base::MakeUnique<base::ListValue>()); + std::make_unique<base::ListValue>()); user_prefs->RegisterListPref(spellcheck::prefs::kSpellCheckForcedDictionaries, - base::MakeUnique<base::ListValue>()); + std::make_unique<base::ListValue>()); // Continue registering kSpellCheckDictionary for preference migration. // TODO(estade): remove: crbug.com/751275 user_prefs->RegisterStringPref( diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc index 3a10b007355..cca9f376dfc 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc @@ -106,9 +106,12 @@ SpellcheckHunspellDictionary::SpellcheckHunspellDictionary( language_(language), use_browser_spellchecker_(false), request_context_getter_(request_context_getter), +#if !defined(OS_ANDROID) spellcheck_service_(spellcheck_service), +#endif download_status_(DOWNLOAD_NONE), - weak_ptr_factory_(this) {} + weak_ptr_factory_(this) { +} SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() { if (dictionary_file_.file.IsValid()) { @@ -227,7 +230,7 @@ void SpellcheckHunspellDictionary::OnURLFetchComplete( base::PostTaskAndReplyWithResult( task_runner_.get(), FROM_HERE, - base::BindOnce(&SaveDictionaryData, base::Passed(&data), + base::BindOnce(&SaveDictionaryData, std::move(data), dictionary_file_.path), base::BindOnce(&SpellcheckHunspellDictionary::SaveDictionaryDataComplete, weak_ptr_factory_.GetWeakPtr())); @@ -287,18 +290,19 @@ void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) { request_context_getter_ = NULL; } -// The default_dictionary_file can either come from the standard list of -// hunspell dictionaries (determined in InitializeDictionaryLocation), or it -// can be passed in via an extension. In either case, the file is checked for -// existence so that it's not re-downloaded. -// For systemwide installations on Windows, the default directory may not -// have permissions for download. In that case, the alternate directory for -// download is chrome::DIR_USER_DATA. -// +#if !defined(OS_ANDROID) // static SpellcheckHunspellDictionary::DictionaryFile SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { base::AssertBlockingAllowed(); + + // The default_dictionary_file can either come from the standard list of + // hunspell dictionaries (determined in InitializeDictionaryLocation), or it + // can be passed in via an extension. In either case, the file is checked for + // existence so that it's not re-downloaded. + // For systemwide installations on Windows, the default directory may not + // have permissions for download. In that case, the alternate directory for + // download is chrome::DIR_USER_DATA. DictionaryFile dictionary; #if defined(OS_WIN) @@ -313,13 +317,12 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { dictionary.path = path; #else dictionary.path = path; -#endif +#endif // defined(OS_WIN) // Read the dictionary file and scan its data to check for corruption. The // scoping closes the memory-mapped file before it is opened or deleted. bool bdict_is_valid = false; -#if !defined(OS_ANDROID) { base::MemoryMappedFile map; bdict_is_valid = @@ -328,7 +331,6 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { hunspell::BDict::Verify(reinterpret_cast<const char*>(map.data()), map.length()); } -#endif if (bdict_is_valid) { dictionary.file.Initialize(dictionary.path, @@ -340,15 +342,15 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { return dictionary; } -// The default place where the spellcheck dictionary resides is -// chrome::DIR_APP_DICTIONARIES. -// // static SpellcheckHunspellDictionary::DictionaryFile SpellcheckHunspellDictionary::InitializeDictionaryLocation( const std::string& language) { base::AssertBlockingAllowed(); + // The default place where the spellcheck dictionary resides is + // chrome::DIR_APP_DICTIONARIES. + // // Initialize the BDICT path. Initialization should be on the blocking // sequence because it checks if there is a "Dictionaries" directory and // create it. @@ -384,6 +386,7 @@ void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete( InformListenersOfInitialization(); } +#endif // !defined(OS_ANDROID) void SpellcheckHunspellDictionary::SaveDictionaryDataComplete( bool dictionary_saved) { diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h index a9a193e0e00..020284e76b7 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h +++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h @@ -15,6 +15,7 @@ #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/sequenced_task_runner.h" +#include "build/build_config.h" #include "components/spellcheck/browser/spellcheck_dictionary.h" #include "net/url_request/url_fetcher_delegate.h" @@ -122,6 +123,7 @@ class SpellcheckHunspellDictionary // Attempt to download the dictionary. void DownloadDictionary(GURL url); +#if !defined(OS_ANDROID) // Figures out the location for the dictionary, verifies its contents, and // opens it. static DictionaryFile OpenDictionaryFile(const base::FilePath& path); @@ -133,6 +135,7 @@ class SpellcheckHunspellDictionary // The reply point for PostTaskAndReplyWithResult, called after the dictionary // file has been initialized. void InitializeDictionaryLocationComplete(DictionaryFile file); +#endif // The reply point for PostTaskAndReplyWithResult, called after the dictionary // file has been saved. @@ -145,10 +148,10 @@ class SpellcheckHunspellDictionary void InformListenersOfDownloadFailure(); // Task runner where the file operations takes place. - scoped_refptr<base::SequencedTaskRunner> task_runner_; + scoped_refptr<base::SequencedTaskRunner> const task_runner_; // The language of the dictionary file. - std::string language_; + const std::string language_; // Whether to use the platform spellchecker instead of Hunspell. bool use_browser_spellchecker_; @@ -160,7 +163,9 @@ class SpellcheckHunspellDictionary // Used for downloading the dictionary file. std::unique_ptr<net::URLFetcher> fetcher_; - SpellcheckService* spellcheck_service_; +#if !defined(OS_ANDROID) + SpellcheckService* const spellcheck_service_; +#endif // Observers of Hunspell dictionary events. base::ObserverList<Observer> observers_; diff --git a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handler.cc b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handler.cc index 5e7d5d2ac98..187eb3b1eb9 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handler.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handler.cc @@ -24,9 +24,7 @@ bool SpellcheckLanguagePolicyHandler::CheckPolicySettings( const policy::PolicyMap& policies, policy::PolicyErrorMap* errors) { const base::Value* value = nullptr; - if (!CheckAndGetValue(policies, errors, &value)) - return false; - return true; + return CheckAndGetValue(policies, errors, &value); } void SpellcheckLanguagePolicyHandler::ApplyPolicySettings( @@ -35,10 +33,8 @@ void SpellcheckLanguagePolicyHandler::ApplyPolicySettings( // Ignore this policy if the SpellcheckEnabled policy disables spellcheck. const base::Value* spellcheck_enabled_value = policies.GetValue(policy::key::kSpellcheckEnabled); - if (spellcheck_enabled_value && - spellcheck_enabled_value->GetBool() == false) { + if (spellcheck_enabled_value && spellcheck_enabled_value->GetBool() == false) return; - } const base::Value* value = policies.GetValue(policy_name()); if (!value) @@ -47,12 +43,11 @@ void SpellcheckLanguagePolicyHandler::ApplyPolicySettings( const base::Value::ListStorage& languages = value->GetList(); std::unique_ptr<base::ListValue> forced_language_list = - base::MakeUnique<base::ListValue>(); + std::make_unique<base::ListValue>(); for (const base::Value& language : languages) { std::string current_language = spellcheck::GetCorrespondingSpellCheckLanguage( - base::TrimWhitespaceASCII(language.GetString(), base::TRIM_ALL) - .as_string()); + base::TrimWhitespaceASCII(language.GetString(), base::TRIM_ALL)); if (!current_language.empty()) { forced_language_list->GetList().push_back(base::Value(current_language)); } else { @@ -61,7 +56,7 @@ void SpellcheckLanguagePolicyHandler::ApplyPolicySettings( } prefs->SetValue(spellcheck::prefs::kSpellCheckEnable, - base::MakeUnique<base::Value>(true)); + std::make_unique<base::Value>(true)); prefs->SetValue(spellcheck::prefs::kSpellCheckForcedDictionaries, std::move(forced_language_list)); } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_browsertest.cc b/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_browsertest.cc deleted file mode 100644 index 6db6c0684c6..00000000000 --- a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_browsertest.cc +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. 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/spellcheck/browser/spellcheck_message_filter_platform.h" - -#include <memory> -#include <tuple> -#include <vector> - -#include "base/command_line.h" -#include "base/memory/ptr_util.h" -#include "base/run_loop.h" -#include "base/single_thread_task_runner.h" -#include "base/stl_util.h" -#include "base/strings/utf_string_conversions.h" -#include "base/threading/thread_task_runner_handle.h" -#include "chrome/test/base/in_process_browser_test.h" -#include "components/spellcheck/common/spellcheck_messages.h" -#include "components/spellcheck/common/spellcheck_result.h" -#include "testing/gtest/include/gtest/gtest.h" - -// Fake filter for testing, which stores sent messages and -// allows verification by the test case. -class TestingSpellCheckMessageFilter : public SpellCheckMessageFilterPlatform { - public: - explicit TestingSpellCheckMessageFilter(base::OnceClosure&& quit_closure) - : SpellCheckMessageFilterPlatform(0), - quit_closure_(std::move(quit_closure)) {} - - bool Send(IPC::Message* message) override { - sent_messages_.push_back(base::WrapUnique(message)); - main_thread_task_runner_->PostTask(FROM_HERE, std::move(quit_closure_)); - return true; - } - - std::vector<std::unique_ptr<IPC::Message>> sent_messages_; - const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_ = - base::ThreadTaskRunnerHandle::Get(); - base::OnceClosure quit_closure_; - - private: - ~TestingSpellCheckMessageFilter() override {} -}; - -typedef InProcessBrowserTest SpellCheckMessageFilterPlatformMacBrowserTest; - -// Uses browsertest to setup chrome threads. -IN_PROC_BROWSER_TEST_F(SpellCheckMessageFilterPlatformMacBrowserTest, - SpellCheckReturnMessage) { - base::RunLoop run_loop; - scoped_refptr<TestingSpellCheckMessageFilter> target( - new TestingSpellCheckMessageFilter(run_loop.QuitWhenIdleClosure())); - - SpellCheckHostMsg_RequestTextCheck to_be_received(123, 456, - base::UTF8ToUTF16("zz.")); - target->OnMessageReceived(to_be_received); - - run_loop.Run(); - ASSERT_EQ(1U, target->sent_messages_.size()); - - SpellCheckMsg_RespondTextCheck::Param params; - bool ok = SpellCheckMsg_RespondTextCheck::Read( - target->sent_messages_[0].get(), ¶ms); - EXPECT_TRUE(ok); - - std::vector<SpellCheckResult> sent_results = std::get<2>(params); - ASSERT_EQ(1U, sent_results.size()); - EXPECT_EQ(sent_results[0].location, 0); - EXPECT_EQ(sent_results[0].length, 2); - EXPECT_EQ(sent_results[0].decoration, SpellCheckResult::SPELLING); -} diff --git a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc deleted file mode 100644 index 1ad7b2b13a0..00000000000 --- a/chromium/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc +++ /dev/null @@ -1,68 +0,0 @@ -// 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 "components/spellcheck/browser/spellcheck_message_filter_platform.h" - -#include <stddef.h> -#include <stdint.h> - -#include "base/macros.h" -#include "base/strings/utf_string_conversions.h" -#include "components/spellcheck/common/spellcheck_messages.h" -#include "components/spellcheck/common/spellcheck_result.h" -#include "content/public/browser/browser_thread.h" -#include "ipc/ipc_message.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -TEST(SpellcheckMessageFilterPlatformMacTest, CombineResults) { - std::vector<SpellCheckResult> local_results; - std::vector<SpellCheckResult> remote_results; - base::string16 remote_suggestion = base::ASCIIToUTF16("remote"); - base::string16 local_suggestion = base::ASCIIToUTF16("local"); - - // Remote-only result - must be flagged as GRAMMAR after combine - remote_results.push_back( - SpellCheckResult(SpellCheckResult::SPELLING, 0, 5)); - - // Local-only result - must be discarded after combine - local_results.push_back( - SpellCheckResult(SpellCheckResult::SPELLING, 10, 5)); - - // local & remote result - must be flagged SPELLING, uses remote suggestion. - SpellCheckResult result(SpellCheckResult::SPELLING, 20, 5, local_suggestion); - local_results.push_back(result); - result.replacements[0] = remote_suggestion; - remote_results.push_back(result); - - SpellCheckMessageFilterPlatform::CombineResults(&remote_results, - local_results); - - ASSERT_EQ(2U, remote_results.size()); - EXPECT_EQ(SpellCheckResult::GRAMMAR, remote_results[0].decoration); - EXPECT_EQ(0, remote_results[0].location); - EXPECT_EQ(SpellCheckResult::SPELLING, remote_results[1].decoration); - EXPECT_EQ(20, remote_results[1].location); - EXPECT_EQ(remote_suggestion, remote_results[1].replacements[0]); -} - -TEST(SpellCheckMessageFilterPlatformMacTest, TestOverrideThread) { - static const uint32_t kSpellcheckMessages[] = { - SpellCheckHostMsg_RequestTextCheck::ID, - }; - scoped_refptr<SpellCheckMessageFilterPlatform> filter( - new SpellCheckMessageFilterPlatform(0)); - content::BrowserThread::ID thread; - IPC::Message message; - for (size_t i = 0; i < arraysize(kSpellcheckMessages); ++i) { - message.SetHeaderValues( - 0, kSpellcheckMessages[i], IPC::Message::PRIORITY_NORMAL); - thread = content::BrowserThread::IO; - filter->OverrideThreadForMessage(message, &thread); - EXPECT_EQ(content::BrowserThread::UI, thread); - } -} - -} // namespace diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service.cc b/chromium/chrome/browser/spellchecker/spellcheck_service.cc index 2b1399033b6..f83bd7d56d1 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_service.cc @@ -13,8 +13,10 @@ #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "build/build_config.h" +#include "chrome/browser/chrome_service.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" +#include "chrome/common/constants.mojom.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_member.h" #include "components/prefs/pref_service.h" @@ -168,7 +170,7 @@ bool SpellcheckService::SignalStatusEvent( } void SpellcheckService::StartRecordingMetrics(bool spellcheck_enabled) { - metrics_.reset(new SpellCheckHostMetrics()); + metrics_ = std::make_unique<SpellCheckHostMetrics>(); metrics_->RecordEnabledStats(spellcheck_enabled); OnUseSpellingServiceChanged(); } @@ -202,8 +204,10 @@ void SpellcheckService::InitForRenderer( } spellcheck::mojom::SpellCheckerPtr spellchecker; - content::BindInterface( - content::RenderProcessHost::FromRendererIdentity(renderer_identity), + ChromeService::GetInstance()->connector()->BindInterface( + service_manager::Identity(chrome::mojom::kRendererServiceName, + renderer_identity.user_id(), + renderer_identity.instance()), &spellchecker); spellchecker->Initialize(std::move(dictionaries), custom_words, enable); } @@ -236,7 +240,7 @@ void SpellcheckService::LoadHunspellDictionaries() { for (const auto& dictionary : dictionaries) { hunspell_dictionaries_.push_back( - base::MakeUnique<SpellcheckHunspellDictionary>( + std::make_unique<SpellcheckHunspellDictionary>( dictionary, content::BrowserContext::GetDefaultStoragePartition(context_) ->GetURLRequestContext(), @@ -287,8 +291,14 @@ void SpellcheckService::OnCustomDictionaryChanged( const std::vector<std::string> deletions(change.to_remove().begin(), change.to_remove().end()); while (!process_hosts.IsAtEnd()) { + service_manager::Identity renderer_identity = + process_hosts.GetCurrentValue()->GetChildIdentity(); spellcheck::mojom::SpellCheckerPtr spellchecker; - content::BindInterface(process_hosts.GetCurrentValue(), &spellchecker); + ChromeService::GetInstance()->connector()->BindInterface( + service_manager::Identity(chrome::mojom::kRendererServiceName, + renderer_identity.user_id(), + renderer_identity.instance()), + &spellchecker); spellchecker->CustomDictionaryChanged(additions, deletions); process_hosts.Advance(); } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc index 854b69cccdb..6f310786d47 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc @@ -18,12 +18,14 @@ #include "base/test/histogram_tester.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" +#include "chrome/browser/chrome_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_service.h" #include "chrome/browser/ui/browser.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/constants.mojom.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "components/prefs/pref_service.h" @@ -74,13 +76,19 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest, base::SPLIT_WANT_NONEMPTY)); prefs_->Set(spellcheck::prefs::kSpellCheckDictionaries, dictionaries_value); + service_manager::Identity renderer_identity = renderer_->GetChildIdentity(); SpellcheckService* spellcheck = - SpellcheckServiceFactory::GetForRenderer(renderer_->GetChildIdentity()); + SpellcheckServiceFactory::GetForRenderer(renderer_identity); ASSERT_NE(nullptr, spellcheck); - // Override |renderer_| requests for the spellcheck::mojom::SpellChecker + // Override requests for the spellcheck::mojom::SpellChecker // interface so we can test the SpellChecker request flow. - renderer_->OverrideBinderForTesting( + service_manager::Connector::TestApi test_api( + ChromeService::GetInstance()->connector()); + test_api.OverrideBinderForTesting( + service_manager::Identity(chrome::mojom::kRendererServiceName, + renderer_identity.user_id(), + renderer_identity.instance()), spellcheck::mojom::SpellChecker::Name_, base::BindRepeating(&SpellcheckServiceBrowserTest::Bind, base::Unretained(this))); diff --git a/chromium/chrome/browser/ui/BUILD.gn b/chromium/chrome/browser/ui/BUILD.gn index 1a43eba4852..e89ff1f97be 100644 --- a/chromium/chrome/browser/ui/BUILD.gn +++ b/chromium/chrome/browser/ui/BUILD.gn @@ -8,7 +8,7 @@ import("//build/config/ui.gni") import("//build/split_static_library.gni") import("//chrome/common/features.gni") import("//components/nacl/features.gni") -import("//components/offline_pages/features/features.gni") +import("//components/offline_pages/buildflags/features.gni") import("//components/signin/features.gni") import("//extensions/features/features.gni") import("//media/media_options.gni") @@ -38,6 +38,579 @@ split_static_library("ui") { split_count = 1 } + # The cocoa browser (ie, primary ui) sources. These are in a separate + # variable temporarily to ease the work on https://crbug.com/804950 and + # https://crbug.com/802257. + # TODO(ellyjones): Remove this variable once 804950 and 802257 are fixed. + if (is_mac) { + cocoa_browser_sources = [ + "cocoa/animatable_image.h", + "cocoa/animatable_image.mm", + "cocoa/animatable_view.h", + "cocoa/animatable_view.mm", + "cocoa/app_menu/app_menu_button_cell.h", + "cocoa/app_menu/app_menu_button_cell.mm", + "cocoa/app_menu/app_menu_controller.h", + "cocoa/app_menu/app_menu_controller.mm", + "cocoa/app_menu/menu_tracked_button.h", + "cocoa/app_menu/menu_tracked_button.mm", + "cocoa/app_menu/menu_tracked_root_view.h", + "cocoa/app_menu/menu_tracked_root_view.mm", + "cocoa/app_menu/recent_tabs_menu_model_delegate.h", + "cocoa/app_menu/recent_tabs_menu_model_delegate.mm", + "cocoa/apps/chrome_app_window_client_views_cocoa.mm", + "cocoa/apps/native_app_window_cocoa.h", + "cocoa/apps/native_app_window_cocoa.mm", + "cocoa/autofill/autofill_bubble_controller.h", + "cocoa/autofill/autofill_bubble_controller.mm", + "cocoa/autofill/autofill_dialog_constants.h", + "cocoa/autofill/autofill_popup_base_view_cocoa.h", + "cocoa/autofill/autofill_popup_base_view_cocoa.mm", + "cocoa/autofill/autofill_popup_view_bridge.h", + "cocoa/autofill/autofill_popup_view_bridge.mm", + "cocoa/autofill/autofill_popup_view_cocoa.h", + "cocoa/autofill/autofill_popup_view_cocoa.mm", + "cocoa/autofill/autofill_tooltip_controller.h", + "cocoa/autofill/autofill_tooltip_controller.mm", + "cocoa/autofill/credit_card_autofill_touch_bar_controller.h", + "cocoa/autofill/credit_card_autofill_touch_bar_controller.mm", + "cocoa/autofill/password_generation_popup_view_bridge.h", + "cocoa/autofill/password_generation_popup_view_bridge.mm", + "cocoa/autofill/password_generation_popup_view_cocoa.h", + "cocoa/autofill/password_generation_popup_view_cocoa.mm", + "cocoa/autofill/save_card_bubble_view_views.h", + "cocoa/autofill/save_card_bubble_view_views.mm", + "cocoa/background_gradient_view.h", + "cocoa/background_gradient_view.mm", + "cocoa/base_bubble_controller.h", + "cocoa/base_bubble_controller.mm", + "cocoa/bookmarks/bookmark_all_tabs_controller.h", + "cocoa/bookmarks/bookmark_all_tabs_controller.mm", + "cocoa/bookmarks/bookmark_bar_bridge.h", + "cocoa/bookmarks/bookmark_bar_bridge.mm", + "cocoa/bookmarks/bookmark_bar_constants.h", + "cocoa/bookmarks/bookmark_bar_controller.h", + "cocoa/bookmarks/bookmark_bar_controller.mm", + "cocoa/bookmarks/bookmark_bar_folder_button_cell.h", + "cocoa/bookmarks/bookmark_bar_folder_button_cell.mm", + "cocoa/bookmarks/bookmark_bar_folder_controller.h", + "cocoa/bookmarks/bookmark_bar_folder_controller.mm", + "cocoa/bookmarks/bookmark_bar_folder_hover_state.h", + "cocoa/bookmarks/bookmark_bar_folder_hover_state.mm", + "cocoa/bookmarks/bookmark_bar_folder_view.h", + "cocoa/bookmarks/bookmark_bar_folder_view.mm", + "cocoa/bookmarks/bookmark_bar_folder_window.h", + "cocoa/bookmarks/bookmark_bar_folder_window.mm", + "cocoa/bookmarks/bookmark_bar_state.h", + "cocoa/bookmarks/bookmark_bar_toolbar_view.h", + "cocoa/bookmarks/bookmark_bar_toolbar_view.mm", + "cocoa/bookmarks/bookmark_bar_view_cocoa.h", + "cocoa/bookmarks/bookmark_bar_view_cocoa.mm", + "cocoa/bookmarks/bookmark_bubble_controller.h", + "cocoa/bookmarks/bookmark_bubble_controller.mm", + "cocoa/bookmarks/bookmark_bubble_observer_cocoa.h", + "cocoa/bookmarks/bookmark_bubble_observer_cocoa.mm", + "cocoa/bookmarks/bookmark_button.h", + "cocoa/bookmarks/bookmark_button.mm", + "cocoa/bookmarks/bookmark_button_cell.h", + "cocoa/bookmarks/bookmark_button_cell.mm", + "cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h", + "cocoa/bookmarks/bookmark_context_menu_cocoa_controller.mm", + "cocoa/bookmarks/bookmark_drag_drop_cocoa.mm", + "cocoa/bookmarks/bookmark_editor_base_controller.h", + "cocoa/bookmarks/bookmark_editor_base_controller.mm", + "cocoa/bookmarks/bookmark_editor_controller.h", + "cocoa/bookmarks/bookmark_editor_controller.mm", + "cocoa/bookmarks/bookmark_folder_target.h", + "cocoa/bookmarks/bookmark_folder_target.mm", + "cocoa/bookmarks/bookmark_model_observer_for_cocoa.h", + "cocoa/bookmarks/bookmark_model_observer_for_cocoa.mm", + "cocoa/bookmarks/bookmark_name_folder_controller.h", + "cocoa/bookmarks/bookmark_name_folder_controller.mm", + "cocoa/bookmarks/bookmark_tree_browser_cell.h", + "cocoa/bookmarks/bookmark_tree_browser_cell.mm", + "cocoa/browser/exclusive_access_controller_views.h", + "cocoa/browser/exclusive_access_controller_views.mm", + "cocoa/browser/zoom_bubble_controller.h", + "cocoa/browser/zoom_bubble_controller.mm", + "cocoa/browser_dialogs_views_mac.cc", + "cocoa/browser_dialogs_views_mac.h", + "cocoa/browser_window_cocoa.h", + "cocoa/browser_window_cocoa.mm", + "cocoa/browser_window_cocoa_views_mac.mm", + "cocoa/browser_window_controller.h", + "cocoa/browser_window_controller.mm", + "cocoa/browser_window_controller_private.h", + "cocoa/browser_window_controller_private.mm", + "cocoa/browser_window_factory_cocoa.mm", + "cocoa/browser_window_fullscreen_transition.h", + "cocoa/browser_window_fullscreen_transition.mm", + "cocoa/browser_window_layout.h", + "cocoa/browser_window_layout.mm", + "cocoa/browser_window_touch_bar.h", + "cocoa/browser_window_touch_bar.mm", + "cocoa/browser_window_utils.h", + "cocoa/browser_window_utils.mm", + "cocoa/bubble_anchor_helper.h", + "cocoa/bubble_anchor_helper.mm", + "cocoa/bubble_anchor_helper_views.h", + "cocoa/bubble_anchor_helper_views.mm", + "cocoa/bubble_anchor_util_views_mac.mm", + "cocoa/bubble_combobox.h", + "cocoa/bubble_combobox.mm", + "cocoa/bubble_sync_promo_controller.h", + "cocoa/bubble_sync_promo_controller.mm", + "cocoa/bubble_view.h", + "cocoa/bubble_view.mm", + "cocoa/certificate_viewer_mac_cocoa.h", + "cocoa/certificate_viewer_mac_cocoa.mm", + "cocoa/chrome_browser_window.h", + "cocoa/chrome_browser_window.mm", + "cocoa/chrome_event_processing_window.h", + "cocoa/chrome_event_processing_window.mm", + "cocoa/clickhold_button_cell.h", + "cocoa/clickhold_button_cell.mm", + "cocoa/constrained_web_dialog_delegate_mac.mm", + "cocoa/constrained_window/constrained_window_alert.h", + "cocoa/constrained_window/constrained_window_alert.mm", + "cocoa/constrained_window/constrained_window_button.h", + "cocoa/constrained_window/constrained_window_button.mm", + "cocoa/constrained_window/constrained_window_control_utils.h", + "cocoa/constrained_window/constrained_window_control_utils.mm", + "cocoa/constrained_window/constrained_window_custom_sheet.h", + "cocoa/constrained_window/constrained_window_custom_sheet.mm", + "cocoa/constrained_window/constrained_window_custom_window.h", + "cocoa/constrained_window/constrained_window_custom_window.mm", + "cocoa/constrained_window/constrained_window_mac.h", + "cocoa/constrained_window/constrained_window_mac.mm", + "cocoa/constrained_window/constrained_window_sheet.h", + "cocoa/constrained_window/constrained_window_sheet_controller.h", + "cocoa/constrained_window/constrained_window_sheet_controller.mm", + "cocoa/constrained_window/constrained_window_sheet_info.h", + "cocoa/constrained_window/constrained_window_sheet_info.mm", + "cocoa/constrained_window/constrained_window_web_dialog_sheet.h", + "cocoa/constrained_window/constrained_window_web_dialog_sheet.mm", + "cocoa/content_settings/blocked_plugin_bubble_controller.h", + "cocoa/content_settings/blocked_plugin_bubble_controller.mm", + "cocoa/content_settings/collected_cookies_mac.h", + "cocoa/content_settings/collected_cookies_mac.mm", + "cocoa/content_settings/content_setting_bubble_cocoa.h", + "cocoa/content_settings/content_setting_bubble_cocoa.mm", + "cocoa/content_settings/cookie_details.h", + "cocoa/content_settings/cookie_details.mm", + "cocoa/content_settings/cookie_details_view_controller.h", + "cocoa/content_settings/cookie_details_view_controller.mm", + "cocoa/content_settings/cookie_tree_node.h", + "cocoa/content_settings/cookie_tree_node.mm", + "cocoa/content_settings/cookies_tree_controller_bridge.h", + "cocoa/content_settings/cookies_tree_controller_bridge.mm", + "cocoa/create_native_web_modal_manager_cocoa.mm", + "cocoa/dev_tools_controller.h", + "cocoa/dev_tools_controller.mm", + "cocoa/device_chooser_content_view_cocoa.h", + "cocoa/device_chooser_content_view_cocoa.mm", + "cocoa/dialog_text_field_editor.h", + "cocoa/dialog_text_field_editor.mm", + "cocoa/download/background_theme.h", + "cocoa/download/background_theme.mm", + "cocoa/download/download_danger_prompt_impl.cc", + "cocoa/download/download_item_button.h", + "cocoa/download/download_item_button.mm", + "cocoa/download/download_item_cell.h", + "cocoa/download/download_item_cell.mm", + "cocoa/download/download_item_controller.h", + "cocoa/download/download_item_controller.mm", + "cocoa/download/download_item_mac.h", + "cocoa/download/download_item_mac.mm", + "cocoa/download/download_item_view_protocol.h", + "cocoa/download/download_shelf_context_menu_controller.h", + "cocoa/download/download_shelf_context_menu_controller.mm", + "cocoa/download/download_shelf_controller.h", + "cocoa/download/download_shelf_controller.mm", + "cocoa/download/download_shelf_mac.h", + "cocoa/download/download_shelf_mac.mm", + "cocoa/download/download_shelf_view_cocoa.h", + "cocoa/download/download_shelf_view_cocoa.mm", + "cocoa/download/download_show_all_button.h", + "cocoa/download/download_show_all_button.mm", + "cocoa/download/download_show_all_cell.h", + "cocoa/download/download_show_all_cell.mm", + "cocoa/download/download_started_animation_mac.mm", + "cocoa/download/md_download_item_progress_indicator.h", + "cocoa/download/md_download_item_progress_indicator.mm", + "cocoa/download/md_download_item_view.h", + "cocoa/download/md_download_item_view.mm", + "cocoa/download/md_download_item_view_testing.h", + "cocoa/drag_util.h", + "cocoa/drag_util.mm", + "cocoa/draggable_button.h", + "cocoa/draggable_button.mm", + "cocoa/draggable_button_mixin.h", + "cocoa/draggable_button_mixin.mm", + "cocoa/extensions/browser_action_button.h", + "cocoa/extensions/browser_action_button.mm", + "cocoa/extensions/browser_actions_container_view.h", + "cocoa/extensions/browser_actions_container_view.mm", + "cocoa/extensions/browser_actions_controller.h", + "cocoa/extensions/browser_actions_controller.mm", + "cocoa/extensions/chooser_dialog_cocoa.h", + "cocoa/extensions/chooser_dialog_cocoa.mm", + "cocoa/extensions/chooser_dialog_cocoa_controller.h", + "cocoa/extensions/chooser_dialog_cocoa_controller.mm", + "cocoa/extensions/extension_action_platform_delegate_cocoa.h", + "cocoa/extensions/extension_action_platform_delegate_cocoa.mm", + "cocoa/extensions/extension_install_dialog_controller.h", + "cocoa/extensions/extension_install_dialog_controller.mm", + "cocoa/extensions/extension_install_view_controller.h", + "cocoa/extensions/extension_install_view_controller.mm", + "cocoa/extensions/extension_installed_bubble_controller.h", + "cocoa/extensions/extension_installed_bubble_controller.mm", + "cocoa/extensions/extension_keybinding_registry_cocoa.h", + "cocoa/extensions/extension_keybinding_registry_cocoa.mm", + "cocoa/extensions/extension_popup_controller.h", + "cocoa/extensions/extension_popup_controller.mm", + "cocoa/extensions/extension_popup_views_mac.h", + "cocoa/extensions/extension_popup_views_mac.mm", + "cocoa/extensions/extension_uninstall_dialog_cocoa.mm", + "cocoa/extensions/extension_view_mac.h", + "cocoa/extensions/extension_view_mac.mm", + "cocoa/extensions/media_galleries_dialog_cocoa.h", + "cocoa/extensions/media_galleries_dialog_cocoa.mm", + "cocoa/extensions/media_gallery_list_entry_view.h", + "cocoa/extensions/media_gallery_list_entry_view.mm", + "cocoa/extensions/toolbar_actions_bar_bubble_mac.h", + "cocoa/extensions/toolbar_actions_bar_bubble_mac.mm", + "cocoa/extensions/toolbar_actions_bar_bubble_views_presenter.h", + "cocoa/extensions/toolbar_actions_bar_bubble_views_presenter.mm", + "cocoa/extensions/windowed_install_dialog_controller.h", + "cocoa/extensions/windowed_install_dialog_controller.mm", + "cocoa/external_protocol_dialog.h", + "cocoa/external_protocol_dialog_cocoa.mm", + "cocoa/external_protocol_dialog_views_mac.mm", + "cocoa/fast_resize_view.h", + "cocoa/fast_resize_view.mm", + "cocoa/find_bar/find_bar_bridge.h", + "cocoa/find_bar/find_bar_bridge.mm", + "cocoa/find_bar/find_bar_cocoa_controller.h", + "cocoa/find_bar/find_bar_cocoa_controller.mm", + "cocoa/find_bar/find_bar_text_field.h", + "cocoa/find_bar/find_bar_text_field.mm", + "cocoa/find_bar/find_bar_text_field_cell.h", + "cocoa/find_bar/find_bar_text_field_cell.mm", + "cocoa/find_bar/find_bar_view_cocoa.h", + "cocoa/find_bar/find_bar_view_cocoa.mm", + "cocoa/floating_bar_backing_view.h", + "cocoa/floating_bar_backing_view.mm", + "cocoa/framed_browser_window.h", + "cocoa/framed_browser_window.mm", + "cocoa/fullscreen/fullscreen_menubar_tracker.h", + "cocoa/fullscreen/fullscreen_menubar_tracker.mm", + "cocoa/fullscreen/fullscreen_toolbar_animation_controller.h", + "cocoa/fullscreen/fullscreen_toolbar_animation_controller.mm", + "cocoa/fullscreen/fullscreen_toolbar_controller.h", + "cocoa/fullscreen/fullscreen_toolbar_controller.mm", + "cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.h", + "cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.mm", + "cocoa/fullscreen/fullscreen_toolbar_visibility_lock_controller.h", + "cocoa/fullscreen/fullscreen_toolbar_visibility_lock_controller.mm", + "cocoa/fullscreen/immersive_fullscreen_controller.h", + "cocoa/fullscreen/immersive_fullscreen_controller.mm", + "cocoa/fullscreen_placeholder_view.h", + "cocoa/fullscreen_placeholder_view.mm", + "cocoa/fullscreen_window.h", + "cocoa/fullscreen_window.mm", + "cocoa/global_error_bubble_controller.h", + "cocoa/global_error_bubble_controller.mm", + "cocoa/global_error_bubble_controller_views.mm", + "cocoa/gradient_button_cell.h", + "cocoa/gradient_button_cell.mm", + "cocoa/harmony_button.h", + "cocoa/harmony_button.mm", + "cocoa/has_weak_browser_pointer.h", + "cocoa/hover_close_button.h", + "cocoa/hover_close_button.mm", + "cocoa/hung_renderer_controller.h", + "cocoa/hung_renderer_controller.mm", + "cocoa/image_button_cell.h", + "cocoa/image_button_cell.mm", + "cocoa/importer/import_lock_dialog_cocoa.mm", + "cocoa/info_bubble_view.h", + "cocoa/info_bubble_view.mm", + "cocoa/info_bubble_window.h", + "cocoa/info_bubble_window.mm", + "cocoa/infobars/after_translate_infobar_controller.h", + "cocoa/infobars/after_translate_infobar_controller.mm", + "cocoa/infobars/alternate_nav_infobar_controller.h", + "cocoa/infobars/alternate_nav_infobar_controller.mm", + "cocoa/infobars/before_translate_infobar_controller.h", + "cocoa/infobars/before_translate_infobar_controller.mm", + "cocoa/infobars/confirm_infobar_controller.h", + "cocoa/infobars/confirm_infobar_controller.mm", + "cocoa/infobars/infobar_cocoa.h", + "cocoa/infobars/infobar_cocoa.mm", + "cocoa/infobars/infobar_container_cocoa.h", + "cocoa/infobars/infobar_container_cocoa.mm", + "cocoa/infobars/infobar_container_controller.h", + "cocoa/infobars/infobar_container_controller.mm", + "cocoa/infobars/infobar_controller.h", + "cocoa/infobars/infobar_controller.mm", + "cocoa/infobars/infobar_gradient_view.h", + "cocoa/infobars/infobar_gradient_view.mm", + "cocoa/infobars/infobar_utilities.h", + "cocoa/infobars/infobar_utilities.mm", + "cocoa/infobars/translate_infobar_base.h", + "cocoa/infobars/translate_infobar_base.mm", + "cocoa/infobars/translate_message_infobar_controller.h", + "cocoa/infobars/translate_message_infobar_controller.mm", + "cocoa/javascript_app_modal_dialog_cocoa.h", + "cocoa/javascript_app_modal_dialog_cocoa.mm", + "cocoa/location_bar/autocomplete_text_field.h", + "cocoa/location_bar/autocomplete_text_field.mm", + "cocoa/location_bar/autocomplete_text_field_cell.h", + "cocoa/location_bar/autocomplete_text_field_cell.mm", + "cocoa/location_bar/autocomplete_text_field_editor.h", + "cocoa/location_bar/autocomplete_text_field_editor.mm", + "cocoa/location_bar/bubble_decoration.h", + "cocoa/location_bar/bubble_decoration.mm", + "cocoa/location_bar/content_setting_decoration.h", + "cocoa/location_bar/content_setting_decoration.mm", + "cocoa/location_bar/image_decoration.h", + "cocoa/location_bar/image_decoration.mm", + "cocoa/location_bar/keyword_hint_decoration.h", + "cocoa/location_bar/keyword_hint_decoration.mm", + "cocoa/location_bar/location_bar_decoration.h", + "cocoa/location_bar/location_bar_decoration.mm", + "cocoa/location_bar/location_bar_view_mac.h", + "cocoa/location_bar/location_bar_view_mac.mm", + "cocoa/location_bar/manage_passwords_decoration.h", + "cocoa/location_bar/manage_passwords_decoration.mm", + "cocoa/location_bar/page_info_bubble_decoration.h", + "cocoa/location_bar/page_info_bubble_decoration.mm", + "cocoa/location_bar/save_credit_card_decoration.h", + "cocoa/location_bar/save_credit_card_decoration.mm", + "cocoa/location_bar/selected_keyword_decoration.h", + "cocoa/location_bar/selected_keyword_decoration.mm", + "cocoa/location_bar/star_decoration.h", + "cocoa/location_bar/star_decoration.mm", + "cocoa/location_bar/translate_decoration.h", + "cocoa/location_bar/translate_decoration.mm", + "cocoa/location_bar/zoom_decoration.h", + "cocoa/location_bar/zoom_decoration.mm", + "cocoa/login_handler_cocoa.h", + "cocoa/login_handler_cocoa.mm", + "cocoa/main_menu_item.h", + "cocoa/menu_button.h", + "cocoa/menu_button.mm", + "cocoa/multi_key_equivalent_button.h", + "cocoa/multi_key_equivalent_button.mm", + "cocoa/new_tab_button.h", + "cocoa/new_tab_button_cocoa.mm", + "cocoa/nsview_additions.h", + "cocoa/nsview_additions.mm", + "cocoa/omnibox/omnibox_popup_cell.h", + "cocoa/omnibox/omnibox_popup_cell.mm", + "cocoa/omnibox/omnibox_popup_matrix.h", + "cocoa/omnibox/omnibox_popup_matrix.mm", + "cocoa/omnibox/omnibox_popup_separator_view.h", + "cocoa/omnibox/omnibox_popup_separator_view.mm", + "cocoa/omnibox/omnibox_popup_view_mac.h", + "cocoa/omnibox/omnibox_popup_view_mac.mm", + "cocoa/omnibox/omnibox_view_mac.h", + "cocoa/omnibox/omnibox_view_mac.mm", + "cocoa/omnibox_decoration_bubble_controller.h", + "cocoa/omnibox_decoration_bubble_controller.mm", + "cocoa/one_click_signin_dialog_controller.h", + "cocoa/one_click_signin_dialog_controller.mm", + "cocoa/one_click_signin_view_controller.h", + "cocoa/one_click_signin_view_controller.mm", + "cocoa/page_info/page_info_bubble_controller.h", + "cocoa/page_info/page_info_bubble_controller.mm", + "cocoa/page_info/page_info_utils_cocoa.h", + "cocoa/page_info/page_info_utils_cocoa.mm", + "cocoa/page_info/permission_selector_button.h", + "cocoa/page_info/permission_selector_button.mm", + "cocoa/page_info/split_block_button.h", + "cocoa/page_info/split_block_button.mm", + "cocoa/password_reuse_warning_dialog_cocoa.h", + "cocoa/password_reuse_warning_dialog_cocoa.mm", + "cocoa/password_reuse_warning_view_controller.h", + "cocoa/password_reuse_warning_view_controller.mm", + "cocoa/passwords/account_avatar_fetcher_manager.h", + "cocoa/passwords/account_avatar_fetcher_manager.mm", + "cocoa/passwords/account_chooser_view_controller.h", + "cocoa/passwords/account_chooser_view_controller.mm", + "cocoa/passwords/auto_signin_view_controller.h", + "cocoa/passwords/auto_signin_view_controller.mm", + "cocoa/passwords/autosignin_prompt_view_controller.h", + "cocoa/passwords/autosignin_prompt_view_controller.mm", + "cocoa/passwords/base_passwords_content_view_controller.h", + "cocoa/passwords/base_passwords_content_view_controller.mm", + "cocoa/passwords/confirmation_password_saved_view_controller.h", + "cocoa/passwords/confirmation_password_saved_view_controller.mm", + "cocoa/passwords/credential_item_button.h", + "cocoa/passwords/credential_item_button.mm", + "cocoa/passwords/credentials_selection_view_cocoa.h", + "cocoa/passwords/credentials_selection_view_cocoa.mm", + "cocoa/passwords/manage_passwords_view_controller.h", + "cocoa/passwords/manage_passwords_view_controller.mm", + "cocoa/passwords/password_item_views.h", + "cocoa/passwords/password_prompt_bridge_interface.h", + "cocoa/passwords/password_prompt_view_bridge.h", + "cocoa/passwords/password_prompt_view_bridge.mm", + "cocoa/passwords/password_prompt_views_mac.mm", + "cocoa/passwords/passwords_bubble_cocoa.h", + "cocoa/passwords/passwords_bubble_cocoa.mm", + "cocoa/passwords/passwords_bubble_controller.h", + "cocoa/passwords/passwords_bubble_controller.mm", + "cocoa/passwords/passwords_bubble_utils.h", + "cocoa/passwords/passwords_bubble_utils.mm", + "cocoa/passwords/passwords_list_view_controller.h", + "cocoa/passwords/passwords_list_view_controller.mm", + "cocoa/passwords/pending_password_view_controller.h", + "cocoa/passwords/pending_password_view_controller.mm", + "cocoa/passwords/save_pending_password_view_controller.h", + "cocoa/passwords/save_pending_password_view_controller.mm", + "cocoa/passwords/signin_promo_view_controller.h", + "cocoa/passwords/signin_promo_view_controller.mm", + "cocoa/passwords/update_pending_password_view_controller.h", + "cocoa/passwords/update_pending_password_view_controller.mm", + "cocoa/permission_bubble/chooser_bubble_ui_cocoa.h", + "cocoa/permission_bubble/chooser_bubble_ui_cocoa.mm", + "cocoa/permission_bubble/chooser_bubble_ui_views_mac.mm", + "cocoa/permission_bubble/permission_prompt_impl_views_mac.mm", + "cocoa/profiles/avatar_base_controller.h", + "cocoa/profiles/avatar_base_controller.mm", + "cocoa/profiles/avatar_button.h", + "cocoa/profiles/avatar_button_cocoa.mm", + "cocoa/profiles/avatar_button_controller.h", + "cocoa/profiles/avatar_button_controller.mm", + "cocoa/profiles/avatar_icon_controller.h", + "cocoa/profiles/avatar_icon_controller.mm", + "cocoa/profiles/profile_chooser_bridge_views.h", + "cocoa/profiles/profile_chooser_bridge_views.mm", + "cocoa/profiles/profile_chooser_controller.h", + "cocoa/profiles/profile_chooser_controller.mm", + "cocoa/profiles/profile_signin_confirmation_dialog_cocoa.h", + "cocoa/profiles/profile_signin_confirmation_dialog_cocoa.mm", + "cocoa/profiles/profile_signin_confirmation_view_controller.h", + "cocoa/profiles/profile_signin_confirmation_view_controller.mm", + "cocoa/profiles/signin_view_controller_delegate_mac.h", + "cocoa/profiles/signin_view_controller_delegate_mac.mm", + "cocoa/profiles/user_manager_mac.h", + "cocoa/profiles/user_manager_mac.mm", + "cocoa/rect_path_utils.h", + "cocoa/rect_path_utils.mm", + "cocoa/restart_browser.h", + "cocoa/restart_browser.mm", + "cocoa/screen_capture_notification_ui_cocoa.h", + "cocoa/screen_capture_notification_ui_cocoa.mm", + "cocoa/separate_fullscreen_window.h", + "cocoa/separate_fullscreen_window.mm", + "cocoa/session_crashed_bubble.mm", + "cocoa/simple_message_box_bridge_views.mm", + "cocoa/simple_message_box_cocoa.h", + "cocoa/simple_message_box_cocoa.mm", + "cocoa/single_web_contents_dialog_manager_cocoa.h", + "cocoa/single_web_contents_dialog_manager_cocoa.mm", + "cocoa/spinner_view.h", + "cocoa/spinner_view.mm", + "cocoa/ssl_client_certificate_selector_cocoa.h", + "cocoa/ssl_client_certificate_selector_cocoa.mm", + "cocoa/status_bubble_mac.h", + "cocoa/status_bubble_mac.mm", + "cocoa/styled_text_field.h", + "cocoa/styled_text_field.mm", + "cocoa/styled_text_field_cell.h", + "cocoa/styled_text_field_cell.mm", + "cocoa/subresource_filter/subresource_filter_bubble_controller.h", + "cocoa/subresource_filter/subresource_filter_bubble_controller.mm", + "cocoa/tab_contents/favicon_util_mac.h", + "cocoa/tab_contents/favicon_util_mac.mm", + "cocoa/tab_contents/overlayable_contents_controller.h", + "cocoa/tab_contents/overlayable_contents_controller.mm", + "cocoa/tab_contents/sad_tab_mac.mm", + "cocoa/tab_contents/sad_tab_view_cocoa.h", + "cocoa/tab_contents/sad_tab_view_cocoa.mm", + "cocoa/tab_contents/tab_contents_controller.h", + "cocoa/tab_contents/tab_contents_controller.mm", + "cocoa/tab_dialogs_cocoa.h", + "cocoa/tab_dialogs_cocoa.mm", + "cocoa/tab_dialogs_views_mac.h", + "cocoa/tab_dialogs_views_mac.mm", + "cocoa/tab_modal_confirm_dialog_mac.h", + "cocoa/tab_modal_confirm_dialog_mac.mm", + "cocoa/tabbed_browser_window.h", + "cocoa/tabbed_browser_window.mm", + "cocoa/tabs/alert_indicator_button_cocoa.h", + "cocoa/tabs/alert_indicator_button_cocoa.mm", + "cocoa/tabs/tab_controller.h", + "cocoa/tabs/tab_controller.mm", + "cocoa/tabs/tab_controller_target.h", + "cocoa/tabs/tab_favicon_view.h", + "cocoa/tabs/tab_favicon_view.mm", + "cocoa/tabs/tab_spinner_view.h", + "cocoa/tabs/tab_spinner_view.mm", + "cocoa/tabs/tab_strip_background_view.h", + "cocoa/tabs/tab_strip_background_view.mm", + "cocoa/tabs/tab_strip_controller.h", + "cocoa/tabs/tab_strip_controller.mm", + "cocoa/tabs/tab_strip_drag_controller.h", + "cocoa/tabs/tab_strip_drag_controller.mm", + "cocoa/tabs/tab_strip_model_observer_bridge.h", + "cocoa/tabs/tab_strip_model_observer_bridge.mm", + "cocoa/tabs/tab_strip_view.h", + "cocoa/tabs/tab_strip_view.mm", + "cocoa/tabs/tab_view.h", + "cocoa/tabs/tab_view.mm", + "cocoa/tabs/tab_window_controller.h", + "cocoa/tabs/tab_window_controller.mm", + "cocoa/themed_window.h", + "cocoa/themed_window.mm", + "cocoa/toolbar/app_toolbar_button.h", + "cocoa/toolbar/app_toolbar_button.mm", + "cocoa/toolbar/app_toolbar_button_cell.h", + "cocoa/toolbar/app_toolbar_button_cell.mm", + "cocoa/toolbar/back_forward_menu_controller.h", + "cocoa/toolbar/back_forward_menu_controller.mm", + "cocoa/toolbar/media_router_action_platform_delegate_cocoa.h", + "cocoa/toolbar/media_router_action_platform_delegate_cocoa.mm", + "cocoa/toolbar/reload_button_cocoa.h", + "cocoa/toolbar/reload_button_cocoa.mm", + "cocoa/toolbar/toolbar_button_cocoa.h", + "cocoa/toolbar/toolbar_button_cocoa.mm", + "cocoa/toolbar/toolbar_controller.h", + "cocoa/toolbar/toolbar_controller.mm", + "cocoa/toolbar/toolbar_view_cocoa.h", + "cocoa/toolbar/toolbar_view_cocoa.mm", + "cocoa/translate/translate_bubble_bridge_views.h", + "cocoa/translate/translate_bubble_bridge_views.mm", + "cocoa/translate/translate_bubble_controller.h", + "cocoa/translate/translate_bubble_controller.mm", + "cocoa/url_drop_target.h", + "cocoa/url_drop_target.mm", + "cocoa/vertical_gradient_view.h", + "cocoa/vertical_gradient_view.mm", + "cocoa/view_id_util.h", + "cocoa/view_id_util.mm", + "cocoa/view_resizer.h", + "cocoa/web_contents_modal_dialog_manager_views_mac.h", + "cocoa/web_contents_modal_dialog_manager_views_mac.mm", + "cocoa/web_textfield_touch_bar_controller.h", + "cocoa/web_textfield_touch_bar_controller.mm", + + # TODO(estade): this class should be deleted in favor of a combobox model. + # See crbug.com/590850 + "content_settings/content_setting_media_menu_model.cc", + "content_settings/content_setting_media_menu_model.h", + "javascript_dialogs/javascript_dialog_cocoa.h", + "javascript_dialogs/javascript_dialog_cocoa.mm", + "javascript_dialogs/javascript_dialog_mac.cc", + "proximity_auth/proximity_auth_error_bubble_stub.cc", + "startup/session_crashed_infobar_delegate.cc", + "startup/session_crashed_infobar_delegate.h", + ] + } + sources = [ "accelerator_utils.h", "app_list/app_list_util.cc", @@ -49,6 +622,8 @@ split_static_library("ui") { "autofill/autofill_popup_controller.h", "autofill/autofill_popup_controller_impl.cc", "autofill/autofill_popup_controller_impl.h", + "autofill/autofill_popup_controller_impl_mac.h", + "autofill/autofill_popup_controller_impl_mac.mm", "autofill/autofill_popup_layout_model.cc", "autofill/autofill_popup_layout_model.h", "autofill/autofill_popup_view.h", @@ -342,7 +917,7 @@ split_static_library("ui") { deps = [ "//base", "//base:i18n", - "//base/allocator:features", + "//base/allocator:buildflags", "//cc/paint", "//chrome:extra_resources", "//chrome:resources", @@ -375,6 +950,7 @@ split_static_library("ui") { "//components/browsing_data/core", "//components/bubble:bubble", "//components/certificate_reporting:cert_logger_proto", + "//components/consent_auditor/", "//components/content_settings/core/browser", "//components/crx_file", "//components/data_reduction_proxy/core/browser", @@ -387,7 +963,7 @@ split_static_library("ui") { "//components/encrypted_messages:encrypted_message_proto", "//components/favicon/content", "//components/favicon/core", - "//components/feature_engagement:features", + "//components/feature_engagement:buildflags", "//components/feedback", "//components/flags_ui", "//components/gcm_driver", @@ -399,11 +975,12 @@ split_static_library("ui") { "//components/invalidation/impl", "//components/keyed_service/content", "//components/keyed_service/core", + "//components/language/core/common", "//components/navigation_metrics", "//components/net_log", "//components/ntp_snippets", "//components/ntp_tiles", - "//components/offline_pages/features:features", + "//components/offline_pages/buildflags", "//components/omnibox/browser", "//components/onc", "//components/password_manager/content/browser", @@ -442,7 +1019,6 @@ split_static_library("ui") { "//components/strings", "//components/subresource_filter/content/browser", "//components/subresource_filter/core/browser:browser", - "//components/suggestions/proto", "//components/supervised_user_error_page", "//components/sync", "//components/sync_preferences", @@ -481,9 +1057,9 @@ split_static_library("ui") { "//skia", "//storage/browser", "//storage/common", - "//third_party/WebKit/common:blink_common", "//third_party/WebKit/public:features", "//third_party/WebKit/public:resources", + "//third_party/WebKit/public/common", "//third_party/adobe/flash:flapper_version_h", "//third_party/brotli:dec", "//third_party/cacheinvalidation", @@ -588,6 +1164,8 @@ split_static_library("ui") { "android/infobars/infobar_android.h", "android/infobars/infobar_container_android.cc", "android/infobars/infobar_container_android.h", + "android/infobars/installable_ambient_badge_infobar.cc", + "android/infobars/installable_ambient_badge_infobar.h", "android/infobars/instant_apps_infobar.cc", "android/infobars/instant_apps_infobar.h", "android/infobars/near_oom_infobar.cc", @@ -670,7 +1248,7 @@ split_static_library("ui") { "//crypto:platform", "//device/usb/mojo", "//device/usb/public/cpp", - "//device/usb/public/interfaces", + "//device/usb/public/mojom", "//device/vr/features", "//ui/android", ] @@ -784,8 +1362,6 @@ split_static_library("ui") { "fast_unload_controller.h", "find_bar/find_bar_controller.cc", "find_bar/find_bar_controller.h", - "first_run_bubble_presenter.cc", - "first_run_bubble_presenter.h", "global_error/global_error.cc", "global_error/global_error.h", "global_error/global_error_bubble_view_base.h", @@ -793,6 +1369,8 @@ split_static_library("ui") { "global_error/global_error_service.h", "global_error/global_error_service_factory.cc", "global_error/global_error_service_factory.h", + "hung_renderer/hung_renderer_core.cc", + "hung_renderer/hung_renderer_core.h", "infobar_container_delegate.cc", "infobar_container_delegate.h", "javascript_dialogs/javascript_dialog_views.cc", @@ -805,6 +1383,7 @@ split_static_library("ui") { "media_router/presentation_receiver_window_controller.cc", "media_router/presentation_receiver_window_controller.h", "media_router/presentation_receiver_window_delegate.h", + "media_router/presentation_receiver_window_factory_mac.cc", "native_window_tracker.h", "omnibox/alternate_nav_infobar_delegate.cc", "omnibox/alternate_nav_infobar_delegate.h", @@ -855,10 +1434,8 @@ split_static_library("ui") { "scoped_tabbed_browser_displayer.h", "search/instant_controller.cc", "search/instant_controller.h", - "search/new_tab_page_interceptor_service.cc", - "search/new_tab_page_interceptor_service.h", - "search/new_tab_page_interceptor_service_factory.cc", - "search/new_tab_page_interceptor_service_factory.h", + "search/new_tab_page_navigation_throttle.cc", + "search/new_tab_page_navigation_throttle.h", "search/ntp_user_data_logger.cc", "search/ntp_user_data_logger.h", "search/search_ipc_router.cc", @@ -907,9 +1484,6 @@ split_static_library("ui") { "tabs/tab_change_type.h", "tabs/tab_menu_model.cc", "tabs/tab_menu_model.h", - "tabs/tab_metrics_logger.h", - "tabs/tab_metrics_logger_impl.cc", - "tabs/tab_metrics_logger_impl.h", "tabs/tab_network_state.cc", "tabs/tab_network_state.h", "tabs/tab_strip_model.cc", @@ -966,8 +1540,6 @@ split_static_library("ui") { "unload_controller.h", "unload_controller_web_contents_delegate.cc", "unload_controller_web_contents_delegate.h", - "user_manager.cc", - "user_manager.h", "webui/app_launcher_login_handler.cc", "webui/app_launcher_login_handler.h", "webui/bookmarks_ui.cc", @@ -1033,6 +1605,7 @@ split_static_library("ui") { "webui/media_router/media_sink_with_cast_modes.h", "webui/media_router/query_result_manager.cc", "webui/media_router/query_result_manager.h", + "webui/media_router/web_contents_display_observer.h", "webui/ntp/app_icon_webui_handler.cc", "webui/ntp/app_icon_webui_handler.h", "webui/ntp/app_launcher_handler.cc", @@ -1055,8 +1628,6 @@ split_static_library("ui") { "webui/policy_tool_ui.h", "webui/policy_tool_ui_handler.cc", "webui/policy_tool_ui_handler.h", - "webui/profile_helper.cc", - "webui/profile_helper.h", "webui/profile_info_watcher.cc", "webui/profile_info_watcher.h", "webui/set_as_default_browser_ui_win.cc", @@ -1153,8 +1724,8 @@ split_static_library("ui") { "//chrome/browser:theme_properties", "//chrome/browser/media/router", "//chrome/browser/profile_resetter:profile_reset_report_proto", - "//chrome/browser/ui/tabs:tab_metrics_event_proto", - "//chrome/common:features", + "//chrome/browser/resource_coordinator:tab_metrics_event_proto", + "//chrome/common:buildflags", "//chrome/common:search_mojom", "//components/feedback/proto", "//components/keep_alive_registry", @@ -1166,8 +1737,9 @@ split_static_library("ui") { "//components/web_modal", "//components/zoom", "//device/bluetooth", - "//mash/public/interfaces", - "//services/device/public/interfaces", + "//mash/public/mojom", + "//services/device/public/cpp:device_features", + "//services/device/public/mojom", "//services/metrics/public/cpp:metrics_cpp", "//third_party/libaddressinput", "//third_party/libaddressinput:strings", @@ -1222,6 +1794,7 @@ split_static_library("ui") { "ash/chrome_new_window_client.h", "ash/chrome_screenshot_grabber.cc", "ash/chrome_screenshot_grabber.h", + "ash/chrome_screenshot_grabber_test_observer.h", "ash/chrome_shell_content_state.cc", "ash/chrome_shell_content_state.h", "ash/chrome_shell_content_state_chromeos.cc", @@ -1231,6 +1804,8 @@ split_static_library("ui") { "ash/ime_controller_client.h", "ash/keyboard_ui_service.cc", "ash/keyboard_ui_service.h", + "ash/ksv/keyboard_shortcut_viewer_util.cc", + "ash/ksv/keyboard_shortcut_viewer_util.h", "ash/launcher/app_shortcut_launcher_item_controller.cc", "ash/launcher/app_shortcut_launcher_item_controller.h", "ash/launcher/app_window_launcher_controller.cc", @@ -1337,8 +1912,6 @@ split_static_library("ui") { "views/frame/browser_frame_header_ash.h", "views/frame/browser_non_client_frame_view_ash.cc", "views/frame/browser_non_client_frame_view_ash.h", - "views/frame/hosted_app_frame_header_ash.cc", - "views/frame/hosted_app_frame_header_ash.h", "views/frame/immersive_context_mus.cc", "views/frame/immersive_context_mus.h", "views/frame/immersive_handler_factory_mus.cc", @@ -1538,13 +2111,16 @@ split_static_library("ui") { deps += [ "//ash", "//ash:ash_with_content", + "//ash/components/shortcut_viewer:ksv", "//ash/public/cpp", + "//ash/public/cpp/vector_icons", "//ash/resources/vector_icons", "//ash/strings", "//chrome/browser/chromeos", "//chromeos:cryptohome_proto", "//chromeos/components/tether", "//components/arc", + "//components/consent_auditor:consent_auditor", "//components/cryptauth", "//components/drive:drive_chromeos", "//components/exo", @@ -1553,7 +2129,8 @@ split_static_library("ui") { "//components/session_manager/core", "//components/user_manager", "//services/data_decoder/public/cpp", - "//services/device/public/interfaces", + "//services/device/public/cpp:device_features", + "//services/device/public/mojom", "//services/ui/public/cpp", "//services/ui/public/interfaces", "//ui/app_list/presenter", @@ -1612,17 +2189,22 @@ split_static_library("ui") { "sync/one_click_signin_sync_observer.h", "sync/one_click_signin_sync_starter.cc", "sync/one_click_signin_sync_starter.h", + "user_manager.cc", + "user_manager.h", "views/close_bubble_on_tab_activation_helper.cc", "views/close_bubble_on_tab_activation_helper.h", "views/external_protocol_dialog.cc", "views/external_protocol_dialog.h", - "views/profiles/avatar_button_style.h", "views/profiles/badged_profile_photo.cc", "views/profiles/badged_profile_photo.h", + "views/profiles/dice_accounts_menu.cc", + "views/profiles/dice_accounts_menu.h", "views/profiles/profile_chooser_view.cc", "views/profiles/profile_chooser_view.h", "webui/app_launcher_page_ui.cc", "webui/app_launcher_page_ui.h", + "webui/profile_helper.cc", + "webui/profile_helper.h", "webui/settings/settings_default_browser_handler.cc", "webui/settings/settings_default_browser_handler.h", "webui/settings/system_handler.cc", @@ -1665,20 +2247,19 @@ split_static_library("ui") { if (!is_mac || mac_views_browser) { sources += [ - "views/frame/avatar_button_manager.cc", - "views/frame/avatar_button_manager.h", "views/profiles/avatar_button.cc", "views/profiles/avatar_button.h", + "views/profiles/user_manager_view.cc", + "views/profiles/user_manager_view.h", ] - deps += [ "//ui/views:features" ] } if (enable_dice_support) { sources += [ "webui/signin/dice_turn_sync_on_helper.cc", "webui/signin/dice_turn_sync_on_helper.h", - "webui/signin/signin_dice_internals_handler.cc", - "webui/signin/signin_dice_internals_handler.h", + "webui/signin/dice_turn_sync_on_helper_delegate_impl.cc", + "webui/signin/dice_turn_sync_on_helper_delegate_impl.h", ] } } @@ -1758,6 +2339,7 @@ split_static_library("ui") { "cocoa/bookmarks/bookmark_menu_cocoa_controller.mm", "cocoa/browser_window_command_handler.h", "cocoa/browser_window_command_handler.mm", + "cocoa/browser_window_views_mac.h", "cocoa/chrome_command_dispatcher_delegate.h", "cocoa/chrome_command_dispatcher_delegate.mm", "cocoa/chrome_style.cc", @@ -1848,6 +2430,9 @@ split_static_library("ui") { "webui/settings_utils_mac.mm", "window_sizer/window_sizer_mac.mm", ] + + sources += cocoa_browser_sources + deps += [ "//chrome/app/nibs:localizer_table", "//chrome/browser/apps/app_shim", @@ -1872,6 +2457,8 @@ split_static_library("ui") { "views/dropdown_bar_host_mac.mm", "views/frame/browser_frame_mac.h", "views/frame/browser_frame_mac.mm", + "views/frame/browser_native_widget_window_mac.h", + "views/frame/browser_native_widget_window_mac.mm", "views/frame/browser_non_client_frame_view_factory_mac.mm", "views/frame/browser_non_client_frame_view_mac.h", "views/frame/browser_non_client_frame_view_mac.mm", @@ -1882,586 +2469,49 @@ split_static_library("ui") { "views/tabs/window_finder_mac.mm", ] deps += [ "//extensions/components/native_app_window" ] - } else { - sources += [ - "cocoa/animatable_image.h", - "cocoa/animatable_image.mm", - "cocoa/animatable_view.h", - "cocoa/animatable_view.mm", - "cocoa/app_menu/app_menu_button_cell.h", - "cocoa/app_menu/app_menu_button_cell.mm", - "cocoa/app_menu/app_menu_controller.h", - "cocoa/app_menu/app_menu_controller.mm", - "cocoa/app_menu/menu_tracked_button.h", - "cocoa/app_menu/menu_tracked_button.mm", - "cocoa/app_menu/menu_tracked_root_view.h", - "cocoa/app_menu/menu_tracked_root_view.mm", - "cocoa/app_menu/recent_tabs_menu_model_delegate.h", - "cocoa/app_menu/recent_tabs_menu_model_delegate.mm", - "cocoa/apps/chrome_app_window_client_views_cocoa.mm", - "cocoa/apps/native_app_window_cocoa.h", - "cocoa/apps/native_app_window_cocoa.mm", - "cocoa/autofill/autofill_bubble_controller.h", - "cocoa/autofill/autofill_bubble_controller.mm", - "cocoa/autofill/autofill_dialog_constants.h", - "cocoa/autofill/autofill_input_field.h", - "cocoa/autofill/autofill_layout.h", - "cocoa/autofill/autofill_pop_up_button.h", - "cocoa/autofill/autofill_pop_up_button.mm", - "cocoa/autofill/autofill_popup_base_view_cocoa.h", - "cocoa/autofill/autofill_popup_base_view_cocoa.mm", - "cocoa/autofill/autofill_popup_view_bridge.h", - "cocoa/autofill/autofill_popup_view_bridge.mm", - "cocoa/autofill/autofill_popup_view_cocoa.h", - "cocoa/autofill/autofill_popup_view_cocoa.mm", - "cocoa/autofill/autofill_textfield.h", - "cocoa/autofill/autofill_textfield.mm", - "cocoa/autofill/autofill_tooltip_controller.h", - "cocoa/autofill/autofill_tooltip_controller.mm", - "cocoa/autofill/card_unmask_prompt_view_bridge.h", - "cocoa/autofill/card_unmask_prompt_view_bridge.mm", - "cocoa/autofill/card_unmask_prompt_view_views.mm", - "cocoa/autofill/layout_view.h", - "cocoa/autofill/layout_view.mm", - "cocoa/autofill/password_generation_popup_view_bridge.h", - "cocoa/autofill/password_generation_popup_view_bridge.mm", - "cocoa/autofill/password_generation_popup_view_cocoa.h", - "cocoa/autofill/password_generation_popup_view_cocoa.mm", - "cocoa/autofill/save_card_bubble_view_bridge.h", - "cocoa/autofill/save_card_bubble_view_bridge.mm", - "cocoa/autofill/save_card_bubble_view_views.h", - "cocoa/autofill/save_card_bubble_view_views.mm", - "cocoa/autofill/simple_grid_layout.h", - "cocoa/autofill/simple_grid_layout.mm", - "cocoa/background_gradient_view.h", - "cocoa/background_gradient_view.mm", - "cocoa/base_bubble_controller.h", - "cocoa/base_bubble_controller.mm", + + # Truly cocoa-browser-specific sources. These are secondary UI pieces that + # are obsolete before mac_views_browser will ever ship, so they aren't + # linked in at all. + sources -= [ "cocoa/bookmarks/bookmark_all_tabs_controller.h", "cocoa/bookmarks/bookmark_all_tabs_controller.mm", - "cocoa/bookmarks/bookmark_bar_bridge.h", - "cocoa/bookmarks/bookmark_bar_bridge.mm", - "cocoa/bookmarks/bookmark_bar_constants.h", - "cocoa/bookmarks/bookmark_bar_controller.h", - "cocoa/bookmarks/bookmark_bar_controller.mm", - "cocoa/bookmarks/bookmark_bar_folder_button_cell.h", - "cocoa/bookmarks/bookmark_bar_folder_button_cell.mm", - "cocoa/bookmarks/bookmark_bar_folder_controller.h", - "cocoa/bookmarks/bookmark_bar_folder_controller.mm", - "cocoa/bookmarks/bookmark_bar_folder_hover_state.h", - "cocoa/bookmarks/bookmark_bar_folder_hover_state.mm", - "cocoa/bookmarks/bookmark_bar_folder_view.h", - "cocoa/bookmarks/bookmark_bar_folder_view.mm", - "cocoa/bookmarks/bookmark_bar_folder_window.h", - "cocoa/bookmarks/bookmark_bar_folder_window.mm", - "cocoa/bookmarks/bookmark_bar_state.h", - "cocoa/bookmarks/bookmark_bar_toolbar_view.h", - "cocoa/bookmarks/bookmark_bar_toolbar_view.mm", - "cocoa/bookmarks/bookmark_bar_view_cocoa.h", - "cocoa/bookmarks/bookmark_bar_view_cocoa.mm", - "cocoa/bookmarks/bookmark_bubble_controller.h", - "cocoa/bookmarks/bookmark_bubble_controller.mm", - "cocoa/bookmarks/bookmark_bubble_observer_cocoa.h", - "cocoa/bookmarks/bookmark_bubble_observer_cocoa.mm", - "cocoa/bookmarks/bookmark_button.h", - "cocoa/bookmarks/bookmark_button.mm", - "cocoa/bookmarks/bookmark_button_cell.h", - "cocoa/bookmarks/bookmark_button_cell.mm", - "cocoa/bookmarks/bookmark_context_menu_cocoa_controller.h", - "cocoa/bookmarks/bookmark_context_menu_cocoa_controller.mm", - "cocoa/bookmarks/bookmark_drag_drop_cocoa.mm", "cocoa/bookmarks/bookmark_editor_base_controller.h", "cocoa/bookmarks/bookmark_editor_base_controller.mm", "cocoa/bookmarks/bookmark_editor_controller.h", "cocoa/bookmarks/bookmark_editor_controller.mm", - "cocoa/bookmarks/bookmark_folder_target.h", - "cocoa/bookmarks/bookmark_folder_target.mm", - "cocoa/bookmarks/bookmark_model_observer_for_cocoa.h", - "cocoa/bookmarks/bookmark_model_observer_for_cocoa.mm", - "cocoa/bookmarks/bookmark_name_folder_controller.h", - "cocoa/bookmarks/bookmark_name_folder_controller.mm", - "cocoa/bookmarks/bookmark_tree_browser_cell.h", - "cocoa/bookmarks/bookmark_tree_browser_cell.mm", - "cocoa/browser/exclusive_access_controller_views.h", - "cocoa/browser/exclusive_access_controller_views.mm", - "cocoa/browser/zoom_bubble_controller.h", - "cocoa/browser/zoom_bubble_controller.mm", - "cocoa/browser_dialogs_views_mac.cc", - "cocoa/browser_dialogs_views_mac.h", - "cocoa/browser_window_cocoa.h", - "cocoa/browser_window_cocoa.mm", - "cocoa/browser_window_controller.h", - "cocoa/browser_window_controller.mm", - "cocoa/browser_window_controller_private.h", - "cocoa/browser_window_controller_private.mm", - "cocoa/browser_window_factory_cocoa.mm", - "cocoa/browser_window_fullscreen_transition.h", - "cocoa/browser_window_fullscreen_transition.mm", - "cocoa/browser_window_layout.h", - "cocoa/browser_window_layout.mm", - "cocoa/browser_window_touch_bar.h", - "cocoa/browser_window_touch_bar.mm", - "cocoa/browser_window_utils.h", - "cocoa/browser_window_utils.mm", - "cocoa/bubble_anchor_helper.h", - "cocoa/bubble_anchor_helper.mm", - "cocoa/bubble_anchor_helper_views.h", - "cocoa/bubble_anchor_helper_views.mm", - "cocoa/bubble_anchor_util_views_mac.mm", - "cocoa/bubble_combobox.h", - "cocoa/bubble_combobox.mm", - "cocoa/bubble_sync_promo_controller.h", - "cocoa/bubble_sync_promo_controller.mm", - "cocoa/bubble_view.h", - "cocoa/bubble_view.mm", - "cocoa/certificate_viewer_mac_cocoa.h", - "cocoa/certificate_viewer_mac_cocoa.mm", - "cocoa/chrome_browser_window.h", - "cocoa/chrome_browser_window.mm", - "cocoa/chrome_event_processing_window.h", - "cocoa/chrome_event_processing_window.mm", - "cocoa/clickhold_button_cell.h", - "cocoa/clickhold_button_cell.mm", - "cocoa/confirm_bubble_cocoa.h", - "cocoa/confirm_bubble_cocoa.mm", - "cocoa/confirm_bubble_controller.h", - "cocoa/confirm_bubble_controller.mm", - "cocoa/confirm_bubble_views_mac.mm", "cocoa/constrained_web_dialog_delegate_mac.mm", - "cocoa/constrained_window/constrained_window_alert.h", - "cocoa/constrained_window/constrained_window_alert.mm", - "cocoa/constrained_window/constrained_window_button.h", - "cocoa/constrained_window/constrained_window_button.mm", - "cocoa/constrained_window/constrained_window_control_utils.h", - "cocoa/constrained_window/constrained_window_control_utils.mm", - "cocoa/constrained_window/constrained_window_custom_sheet.h", - "cocoa/constrained_window/constrained_window_custom_sheet.mm", - "cocoa/constrained_window/constrained_window_custom_window.h", - "cocoa/constrained_window/constrained_window_custom_window.mm", - "cocoa/constrained_window/constrained_window_mac.h", - "cocoa/constrained_window/constrained_window_mac.mm", - "cocoa/constrained_window/constrained_window_sheet.h", - "cocoa/constrained_window/constrained_window_sheet_controller.h", - "cocoa/constrained_window/constrained_window_sheet_controller.mm", - "cocoa/constrained_window/constrained_window_sheet_info.h", - "cocoa/constrained_window/constrained_window_sheet_info.mm", - "cocoa/constrained_window/constrained_window_web_dialog_sheet.h", - "cocoa/constrained_window/constrained_window_web_dialog_sheet.mm", - "cocoa/content_settings/blocked_plugin_bubble_controller.h", - "cocoa/content_settings/blocked_plugin_bubble_controller.mm", - "cocoa/content_settings/collected_cookies_mac.h", - "cocoa/content_settings/collected_cookies_mac.mm", - "cocoa/content_settings/content_setting_bubble_cocoa.h", - "cocoa/content_settings/content_setting_bubble_cocoa.mm", - "cocoa/content_settings/cookie_details.h", - "cocoa/content_settings/cookie_details.mm", - "cocoa/content_settings/cookie_details_view_controller.h", - "cocoa/content_settings/cookie_details_view_controller.mm", - "cocoa/content_settings/cookie_tree_node.h", - "cocoa/content_settings/cookie_tree_node.mm", - "cocoa/content_settings/cookies_tree_controller_bridge.h", - "cocoa/content_settings/cookies_tree_controller_bridge.mm", - "cocoa/create_native_web_modal_manager_cocoa.mm", - "cocoa/dev_tools_controller.h", - "cocoa/dev_tools_controller.mm", - "cocoa/device_chooser_content_view_cocoa.h", - "cocoa/device_chooser_content_view_cocoa.mm", - "cocoa/dialog_text_field_editor.h", - "cocoa/dialog_text_field_editor.mm", - "cocoa/download/background_theme.h", - "cocoa/download/background_theme.mm", "cocoa/download/download_danger_prompt_impl.cc", - "cocoa/download/download_item_button.h", - "cocoa/download/download_item_button.mm", - "cocoa/download/download_item_cell.h", - "cocoa/download/download_item_cell.mm", - "cocoa/download/download_item_controller.h", - "cocoa/download/download_item_controller.mm", - "cocoa/download/download_item_mac.h", - "cocoa/download/download_item_mac.mm", - "cocoa/download/download_item_view_protocol.h", - "cocoa/download/download_shelf_context_menu_controller.h", - "cocoa/download/download_shelf_context_menu_controller.mm", - "cocoa/download/download_shelf_controller.h", - "cocoa/download/download_shelf_controller.mm", - "cocoa/download/download_shelf_mac.h", - "cocoa/download/download_shelf_mac.mm", - "cocoa/download/download_shelf_view_cocoa.h", - "cocoa/download/download_shelf_view_cocoa.mm", - "cocoa/download/download_show_all_button.h", - "cocoa/download/download_show_all_button.mm", - "cocoa/download/download_show_all_cell.h", - "cocoa/download/download_show_all_cell.mm", - "cocoa/download/download_started_animation_mac.mm", - "cocoa/download/md_download_item_progress_indicator.h", - "cocoa/download/md_download_item_progress_indicator.mm", - "cocoa/download/md_download_item_view.h", - "cocoa/download/md_download_item_view.mm", - "cocoa/download/md_download_item_view_testing.h", - "cocoa/drag_util.h", - "cocoa/drag_util.mm", - "cocoa/draggable_button.h", - "cocoa/draggable_button.mm", - "cocoa/draggable_button_mixin.h", - "cocoa/draggable_button_mixin.mm", - "cocoa/extensions/browser_action_button.h", - "cocoa/extensions/browser_action_button.mm", - "cocoa/extensions/browser_actions_container_view.h", - "cocoa/extensions/browser_actions_container_view.mm", - "cocoa/extensions/browser_actions_controller.h", - "cocoa/extensions/browser_actions_controller.mm", "cocoa/extensions/chooser_dialog_cocoa.h", "cocoa/extensions/chooser_dialog_cocoa.mm", "cocoa/extensions/chooser_dialog_cocoa_controller.h", "cocoa/extensions/chooser_dialog_cocoa_controller.mm", - "cocoa/extensions/extension_action_platform_delegate_cocoa.h", - "cocoa/extensions/extension_action_platform_delegate_cocoa.mm", - "cocoa/extensions/extension_install_dialog_controller.h", - "cocoa/extensions/extension_install_dialog_controller.mm", - "cocoa/extensions/extension_install_view_controller.h", - "cocoa/extensions/extension_install_view_controller.mm", "cocoa/extensions/extension_installed_bubble_controller.h", "cocoa/extensions/extension_installed_bubble_controller.mm", - "cocoa/extensions/extension_keybinding_registry_cocoa.h", - "cocoa/extensions/extension_keybinding_registry_cocoa.mm", - "cocoa/extensions/extension_popup_controller.h", - "cocoa/extensions/extension_popup_controller.mm", "cocoa/extensions/extension_uninstall_dialog_cocoa.mm", - "cocoa/extensions/extension_view_mac.h", - "cocoa/extensions/extension_view_mac.mm", - "cocoa/extensions/media_galleries_dialog_cocoa.h", - "cocoa/extensions/media_galleries_dialog_cocoa.mm", - "cocoa/extensions/media_gallery_list_entry_view.h", - "cocoa/extensions/media_gallery_list_entry_view.mm", - "cocoa/extensions/toolbar_actions_bar_bubble_mac.h", - "cocoa/extensions/toolbar_actions_bar_bubble_mac.mm", - "cocoa/extensions/toolbar_actions_bar_bubble_views_presenter.h", - "cocoa/extensions/toolbar_actions_bar_bubble_views_presenter.mm", - "cocoa/extensions/windowed_install_dialog_controller.h", - "cocoa/extensions/windowed_install_dialog_controller.mm", - "cocoa/external_protocol_dialog.h", - "cocoa/external_protocol_dialog_cocoa.mm", "cocoa/external_protocol_dialog_views_mac.mm", - "cocoa/fast_resize_view.h", - "cocoa/fast_resize_view.mm", - "cocoa/find_bar/find_bar_bridge.h", - "cocoa/find_bar/find_bar_bridge.mm", - "cocoa/find_bar/find_bar_cocoa_controller.h", - "cocoa/find_bar/find_bar_cocoa_controller.mm", - "cocoa/find_bar/find_bar_text_field.h", - "cocoa/find_bar/find_bar_text_field.mm", - "cocoa/find_bar/find_bar_text_field_cell.h", - "cocoa/find_bar/find_bar_text_field_cell.mm", - "cocoa/find_bar/find_bar_view_cocoa.h", - "cocoa/find_bar/find_bar_view_cocoa.mm", - "cocoa/first_run_bubble_controller.h", - "cocoa/first_run_bubble_controller.mm", - "cocoa/floating_bar_backing_view.h", - "cocoa/floating_bar_backing_view.mm", - "cocoa/framed_browser_window.h", - "cocoa/framed_browser_window.mm", - "cocoa/fullscreen/fullscreen_menubar_tracker.h", - "cocoa/fullscreen/fullscreen_menubar_tracker.mm", - "cocoa/fullscreen/fullscreen_toolbar_animation_controller.h", - "cocoa/fullscreen/fullscreen_toolbar_animation_controller.mm", - "cocoa/fullscreen/fullscreen_toolbar_controller.h", - "cocoa/fullscreen/fullscreen_toolbar_controller.mm", - "cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.h", - "cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.mm", - "cocoa/fullscreen/fullscreen_toolbar_visibility_lock_controller.h", - "cocoa/fullscreen/fullscreen_toolbar_visibility_lock_controller.mm", - "cocoa/fullscreen/immersive_fullscreen_controller.h", - "cocoa/fullscreen/immersive_fullscreen_controller.mm", - "cocoa/fullscreen_placeholder_view.h", - "cocoa/fullscreen_placeholder_view.mm", - "cocoa/fullscreen_window.h", - "cocoa/fullscreen_window.mm", "cocoa/global_error_bubble_controller.h", "cocoa/global_error_bubble_controller.mm", - "cocoa/global_error_bubble_controller_views.mm", - "cocoa/gradient_button_cell.h", - "cocoa/gradient_button_cell.mm", - "cocoa/harmony_button.h", - "cocoa/harmony_button.mm", - "cocoa/has_weak_browser_pointer.h", - "cocoa/hover_close_button.h", - "cocoa/hover_close_button.mm", - "cocoa/hung_renderer_controller.h", - "cocoa/hung_renderer_controller.mm", - "cocoa/image_button_cell.h", - "cocoa/image_button_cell.mm", "cocoa/importer/import_lock_dialog_cocoa.mm", - "cocoa/info_bubble_view.h", - "cocoa/info_bubble_view.mm", - "cocoa/info_bubble_window.h", - "cocoa/info_bubble_window.mm", - "cocoa/infobars/after_translate_infobar_controller.h", - "cocoa/infobars/after_translate_infobar_controller.mm", - "cocoa/infobars/alternate_nav_infobar_controller.h", - "cocoa/infobars/alternate_nav_infobar_controller.mm", - "cocoa/infobars/before_translate_infobar_controller.h", - "cocoa/infobars/before_translate_infobar_controller.mm", - "cocoa/infobars/confirm_infobar_controller.h", - "cocoa/infobars/confirm_infobar_controller.mm", - "cocoa/infobars/infobar_cocoa.h", - "cocoa/infobars/infobar_cocoa.mm", - "cocoa/infobars/infobar_container_cocoa.h", - "cocoa/infobars/infobar_container_cocoa.mm", - "cocoa/infobars/infobar_container_controller.h", - "cocoa/infobars/infobar_container_controller.mm", - "cocoa/infobars/infobar_controller.h", - "cocoa/infobars/infobar_controller.mm", - "cocoa/infobars/infobar_gradient_view.h", - "cocoa/infobars/infobar_gradient_view.mm", - "cocoa/infobars/infobar_utilities.h", - "cocoa/infobars/infobar_utilities.mm", - "cocoa/infobars/translate_infobar_base.h", - "cocoa/infobars/translate_infobar_base.mm", - "cocoa/infobars/translate_message_infobar_controller.h", - "cocoa/infobars/translate_message_infobar_controller.mm", - "cocoa/javascript_app_modal_dialog_cocoa.h", - "cocoa/javascript_app_modal_dialog_cocoa.mm", - "cocoa/location_bar/autocomplete_text_field.h", - "cocoa/location_bar/autocomplete_text_field.mm", - "cocoa/location_bar/autocomplete_text_field_cell.h", - "cocoa/location_bar/autocomplete_text_field_cell.mm", - "cocoa/location_bar/autocomplete_text_field_editor.h", - "cocoa/location_bar/autocomplete_text_field_editor.mm", - "cocoa/location_bar/bubble_decoration.h", - "cocoa/location_bar/bubble_decoration.mm", - "cocoa/location_bar/content_setting_decoration.h", - "cocoa/location_bar/content_setting_decoration.mm", - "cocoa/location_bar/image_decoration.h", - "cocoa/location_bar/image_decoration.mm", - "cocoa/location_bar/keyword_hint_decoration.h", - "cocoa/location_bar/keyword_hint_decoration.mm", - "cocoa/location_bar/location_bar_decoration.h", - "cocoa/location_bar/location_bar_decoration.mm", - "cocoa/location_bar/location_bar_view_mac.h", - "cocoa/location_bar/location_bar_view_mac.mm", - "cocoa/location_bar/manage_passwords_decoration.h", - "cocoa/location_bar/manage_passwords_decoration.mm", - "cocoa/location_bar/page_info_bubble_decoration.h", - "cocoa/location_bar/page_info_bubble_decoration.mm", - "cocoa/location_bar/save_credit_card_decoration.h", - "cocoa/location_bar/save_credit_card_decoration.mm", - "cocoa/location_bar/selected_keyword_decoration.h", - "cocoa/location_bar/selected_keyword_decoration.mm", - "cocoa/location_bar/star_decoration.h", - "cocoa/location_bar/star_decoration.mm", - "cocoa/location_bar/translate_decoration.h", - "cocoa/location_bar/translate_decoration.mm", - "cocoa/location_bar/zoom_decoration.h", - "cocoa/location_bar/zoom_decoration.mm", "cocoa/login_handler_cocoa.h", "cocoa/login_handler_cocoa.mm", - "cocoa/main_menu_item.h", - "cocoa/menu_button.h", - "cocoa/menu_button.mm", - "cocoa/multi_key_equivalent_button.h", - "cocoa/multi_key_equivalent_button.mm", - "cocoa/new_tab_button.h", - "cocoa/new_tab_button.mm", - "cocoa/nsview_additions.h", - "cocoa/nsview_additions.mm", - "cocoa/omnibox/omnibox_popup_cell.h", - "cocoa/omnibox/omnibox_popup_cell.mm", - "cocoa/omnibox/omnibox_popup_matrix.h", - "cocoa/omnibox/omnibox_popup_matrix.mm", - "cocoa/omnibox/omnibox_popup_separator_view.h", - "cocoa/omnibox/omnibox_popup_separator_view.mm", - "cocoa/omnibox/omnibox_popup_view_mac.h", - "cocoa/omnibox/omnibox_popup_view_mac.mm", - "cocoa/omnibox/omnibox_view_mac.h", - "cocoa/omnibox/omnibox_view_mac.mm", - "cocoa/omnibox_decoration_bubble_controller.h", - "cocoa/omnibox_decoration_bubble_controller.mm", - "cocoa/one_click_signin_dialog_controller.h", - "cocoa/one_click_signin_dialog_controller.mm", - "cocoa/one_click_signin_view_controller.h", - "cocoa/one_click_signin_view_controller.mm", "cocoa/page_info/page_info_bubble_controller.h", "cocoa/page_info/page_info_bubble_controller.mm", - "cocoa/page_info/page_info_utils_cocoa.h", - "cocoa/page_info/page_info_utils_cocoa.mm", - "cocoa/page_info/permission_selector_button.h", - "cocoa/page_info/permission_selector_button.mm", - "cocoa/page_info/split_block_button.h", - "cocoa/page_info/split_block_button.mm", "cocoa/password_reuse_warning_dialog_cocoa.h", "cocoa/password_reuse_warning_dialog_cocoa.mm", "cocoa/password_reuse_warning_view_controller.h", "cocoa/password_reuse_warning_view_controller.mm", - "cocoa/passwords/account_avatar_fetcher_manager.h", - "cocoa/passwords/account_avatar_fetcher_manager.mm", - "cocoa/passwords/account_chooser_view_controller.h", - "cocoa/passwords/account_chooser_view_controller.mm", - "cocoa/passwords/auto_signin_view_controller.h", - "cocoa/passwords/auto_signin_view_controller.mm", - "cocoa/passwords/autosignin_prompt_view_controller.h", - "cocoa/passwords/autosignin_prompt_view_controller.mm", - "cocoa/passwords/base_passwords_content_view_controller.h", - "cocoa/passwords/base_passwords_content_view_controller.mm", - "cocoa/passwords/confirmation_password_saved_view_controller.h", - "cocoa/passwords/confirmation_password_saved_view_controller.mm", - "cocoa/passwords/credential_item_button.h", - "cocoa/passwords/credential_item_button.mm", - "cocoa/passwords/credentials_selection_view_cocoa.h", - "cocoa/passwords/credentials_selection_view_cocoa.mm", - "cocoa/passwords/manage_passwords_view_controller.h", - "cocoa/passwords/manage_passwords_view_controller.mm", - "cocoa/passwords/password_item_views.h", - "cocoa/passwords/password_prompt_bridge_interface.h", - "cocoa/passwords/password_prompt_view_bridge.h", - "cocoa/passwords/password_prompt_view_bridge.mm", - "cocoa/passwords/passwords_bubble_cocoa.h", - "cocoa/passwords/passwords_bubble_cocoa.mm", - "cocoa/passwords/passwords_bubble_controller.h", - "cocoa/passwords/passwords_bubble_controller.mm", - "cocoa/passwords/passwords_bubble_utils.h", - "cocoa/passwords/passwords_bubble_utils.mm", - "cocoa/passwords/passwords_list_view_controller.h", - "cocoa/passwords/passwords_list_view_controller.mm", - "cocoa/passwords/pending_password_view_controller.h", - "cocoa/passwords/pending_password_view_controller.mm", - "cocoa/passwords/save_pending_password_view_controller.h", - "cocoa/passwords/save_pending_password_view_controller.mm", - "cocoa/passwords/signin_promo_view_controller.h", - "cocoa/passwords/signin_promo_view_controller.mm", - "cocoa/passwords/update_pending_password_view_controller.h", - "cocoa/passwords/update_pending_password_view_controller.mm", - "cocoa/permission_bubble/chooser_bubble_ui_cocoa.h", - "cocoa/permission_bubble/chooser_bubble_ui_cocoa.mm", + "cocoa/passwords/password_prompt_views_mac.mm", "cocoa/permission_bubble/chooser_bubble_ui_views_mac.mm", - "cocoa/permission_bubble/permission_prompt_impl_views_mac.mm", - "cocoa/profiles/avatar_base_controller.h", - "cocoa/profiles/avatar_base_controller.mm", - "cocoa/profiles/avatar_button.h", - "cocoa/profiles/avatar_button.mm", - "cocoa/profiles/avatar_button_controller.h", - "cocoa/profiles/avatar_button_controller.mm", - "cocoa/profiles/avatar_icon_controller.h", - "cocoa/profiles/avatar_icon_controller.mm", - "cocoa/profiles/profile_chooser_bridge_views.h", - "cocoa/profiles/profile_chooser_bridge_views.mm", - "cocoa/profiles/profile_chooser_controller.h", - "cocoa/profiles/profile_chooser_controller.mm", - "cocoa/profiles/profile_signin_confirmation_dialog_cocoa.h", - "cocoa/profiles/profile_signin_confirmation_dialog_cocoa.mm", - "cocoa/profiles/profile_signin_confirmation_view_controller.h", - "cocoa/profiles/profile_signin_confirmation_view_controller.mm", - "cocoa/profiles/signin_view_controller_delegate_mac.h", - "cocoa/profiles/signin_view_controller_delegate_mac.mm", - "cocoa/profiles/user_manager_mac.h", - "cocoa/profiles/user_manager_mac.mm", - "cocoa/rect_path_utils.h", - "cocoa/rect_path_utils.mm", - "cocoa/restart_browser.h", - "cocoa/restart_browser.mm", - "cocoa/screen_capture_notification_ui_cocoa.h", - "cocoa/screen_capture_notification_ui_cocoa.mm", - "cocoa/separate_fullscreen_window.h", - "cocoa/separate_fullscreen_window.mm", "cocoa/session_crashed_bubble.mm", "cocoa/simple_message_box_bridge_views.mm", "cocoa/simple_message_box_cocoa.h", "cocoa/simple_message_box_cocoa.mm", - "cocoa/single_web_contents_dialog_manager_cocoa.h", - "cocoa/single_web_contents_dialog_manager_cocoa.mm", - "cocoa/spinner_view.h", - "cocoa/spinner_view.mm", - "cocoa/sprite_view.h", - "cocoa/sprite_view.mm", - "cocoa/ssl_client_certificate_selector_cocoa.h", - "cocoa/ssl_client_certificate_selector_cocoa.mm", - "cocoa/status_bubble_mac.h", - "cocoa/status_bubble_mac.mm", - "cocoa/styled_text_field.h", - "cocoa/styled_text_field.mm", - "cocoa/styled_text_field_cell.h", - "cocoa/styled_text_field_cell.mm", - "cocoa/subresource_filter/subresource_filter_bubble_controller.h", - "cocoa/subresource_filter/subresource_filter_bubble_controller.mm", - "cocoa/tab_contents/favicon_util_mac.h", - "cocoa/tab_contents/favicon_util_mac.mm", - "cocoa/tab_contents/overlayable_contents_controller.h", - "cocoa/tab_contents/overlayable_contents_controller.mm", - "cocoa/tab_contents/sad_tab_mac.mm", - "cocoa/tab_contents/sad_tab_view_cocoa.h", - "cocoa/tab_contents/sad_tab_view_cocoa.mm", - "cocoa/tab_contents/tab_contents_controller.h", - "cocoa/tab_contents/tab_contents_controller.mm", "cocoa/tab_dialogs_cocoa.h", "cocoa/tab_dialogs_cocoa.mm", - "cocoa/tab_dialogs_views_mac.h", - "cocoa/tab_dialogs_views_mac.mm", - "cocoa/tab_modal_confirm_dialog_mac.h", - "cocoa/tab_modal_confirm_dialog_mac.mm", - "cocoa/tabbed_browser_window.h", - "cocoa/tabbed_browser_window.mm", - "cocoa/tabs/alert_indicator_button_cocoa.h", - "cocoa/tabs/alert_indicator_button_cocoa.mm", - "cocoa/tabs/tab_controller.h", - "cocoa/tabs/tab_controller.mm", - "cocoa/tabs/tab_controller_target.h", - "cocoa/tabs/tab_strip_background_view.h", - "cocoa/tabs/tab_strip_background_view.mm", - "cocoa/tabs/tab_strip_controller.h", - "cocoa/tabs/tab_strip_controller.mm", - "cocoa/tabs/tab_strip_drag_controller.h", - "cocoa/tabs/tab_strip_drag_controller.mm", - "cocoa/tabs/tab_strip_model_observer_bridge.h", - "cocoa/tabs/tab_strip_model_observer_bridge.mm", - "cocoa/tabs/tab_strip_view.h", - "cocoa/tabs/tab_strip_view.mm", - "cocoa/tabs/tab_view.h", - "cocoa/tabs/tab_view.mm", - "cocoa/tabs/tab_window_controller.h", - "cocoa/tabs/tab_window_controller.mm", - "cocoa/themed_window.h", - "cocoa/themed_window.mm", - "cocoa/toolbar/app_toolbar_button.h", - "cocoa/toolbar/app_toolbar_button.mm", - "cocoa/toolbar/app_toolbar_button_cell.h", - "cocoa/toolbar/app_toolbar_button_cell.mm", - "cocoa/toolbar/back_forward_menu_controller.h", - "cocoa/toolbar/back_forward_menu_controller.mm", - "cocoa/toolbar/media_router_action_platform_delegate_cocoa.h", - "cocoa/toolbar/media_router_action_platform_delegate_cocoa.mm", - "cocoa/toolbar/reload_button_cocoa.h", - "cocoa/toolbar/reload_button_cocoa.mm", - "cocoa/toolbar/toolbar_button_cocoa.h", - "cocoa/toolbar/toolbar_button_cocoa.mm", - "cocoa/toolbar/toolbar_controller.h", - "cocoa/toolbar/toolbar_controller.mm", - "cocoa/toolbar/toolbar_view_cocoa.h", - "cocoa/toolbar/toolbar_view_cocoa.mm", - "cocoa/translate/translate_bubble_bridge_views.h", - "cocoa/translate/translate_bubble_bridge_views.mm", - "cocoa/translate/translate_bubble_controller.h", - "cocoa/translate/translate_bubble_controller.mm", - "cocoa/url_drop_target.h", - "cocoa/url_drop_target.mm", - "cocoa/vertical_gradient_view.h", - "cocoa/vertical_gradient_view.mm", - "cocoa/view_id_util.h", - "cocoa/view_id_util.mm", - "cocoa/view_resizer.h", - "cocoa/web_contents_modal_dialog_manager_views_mac.h", - "cocoa/web_contents_modal_dialog_manager_views_mac.mm", - "cocoa/web_textfield_touch_bar_controller.h", - "cocoa/web_textfield_touch_bar_controller.mm", - - # TODO(estade): this class should be deleted in favor of a combobox model. - # See crbug.com/590850 - "content_settings/content_setting_media_menu_model.cc", - "content_settings/content_setting_media_menu_model.h", "javascript_dialogs/javascript_dialog_cocoa.h", "javascript_dialogs/javascript_dialog_cocoa.mm", "javascript_dialogs/javascript_dialog_mac.cc", - "proximity_auth/proximity_auth_error_bubble_stub.cc", - "startup/session_crashed_infobar_delegate.cc", - "startup/session_crashed_infobar_delegate.h", ] } } else { # non-Mac. @@ -2567,6 +2617,8 @@ split_static_library("ui") { sources += [ "webui/help/version_updater_win.cc", "webui/help/version_updater_win.h", + "webui/settings/incompatible_applications_handler_win.cc", + "webui/settings/incompatible_applications_handler_win.h", ] deps += [ "//google_update" ] } else { @@ -2678,6 +2730,8 @@ split_static_library("ui") { # This test header is included because it contains forward declarations # needed for "friend" statements for use in tests. "translate/translate_bubble_test_utils.h", + "views/accessibility/non_accessible_image_view.cc", + "views/accessibility/non_accessible_image_view.h", "views/apps/app_info_dialog/app_info_dialog_container.cc", "views/apps/app_info_dialog/app_info_dialog_container.h", "views/apps/app_info_dialog/app_info_dialog_views.cc", @@ -2744,8 +2798,8 @@ split_static_library("ui") { "views/extensions/pwa_confirmation_view.h", "views/extensions/web_app_info_image_source.cc", "views/extensions/web_app_info_image_source.h", - "views/first_run_bubble.cc", - "views/first_run_bubble.h", + "views/folder_upload_confirmation_view.cc", + "views/folder_upload_confirmation_view.h", "views/fullscreen_control/fullscreen_control_host.cc", "views/fullscreen_control/fullscreen_control_host.h", "views/fullscreen_control/fullscreen_control_popup.cc", @@ -2782,8 +2836,6 @@ split_static_library("ui") { "views/page_info/chosen_object_view.cc", "views/page_info/chosen_object_view.h", "views/page_info/chosen_object_view_observer.h", - "views/page_info/non_accessible_image_view.cc", - "views/page_info/non_accessible_image_view.h", "views/page_info/page_info_bubble_view.cc", "views/page_info/page_info_bubble_view.h", "views/page_info/page_info_bubble_view_base.cc", @@ -2799,22 +2851,20 @@ split_static_library("ui") { "views/passwords/credentials_item_view.h", "views/passwords/credentials_selection_view.cc", "views/passwords/credentials_selection_view.h", - "views/passwords/manage_password_auto_sign_in_view.cc", - "views/passwords/manage_password_auto_sign_in_view.h", - "views/passwords/manage_password_items_view.cc", - "views/passwords/manage_password_items_view.h", - "views/passwords/manage_password_pending_view.cc", - "views/passwords/manage_password_pending_view.h", - "views/passwords/manage_password_save_confirmation_view.cc", - "views/passwords/manage_password_save_confirmation_view.h", - "views/passwords/manage_password_sign_in_promo_view.cc", - "views/passwords/manage_password_sign_in_promo_view.h", - "views/passwords/manage_password_update_pending_view.cc", - "views/passwords/manage_password_update_pending_view.h", - "views/passwords/manage_passwords_bubble_delegate_view_base.cc", - "views/passwords/manage_passwords_bubble_delegate_view_base.h", - "views/passwords/manage_passwords_bubble_view.cc", - "views/passwords/manage_passwords_bubble_view.h", + "views/passwords/password_auto_sign_in_view.cc", + "views/passwords/password_auto_sign_in_view.h", + "views/passwords/password_bubble_view_base.cc", + "views/passwords/password_bubble_view_base.h", + "views/passwords/password_items_view.cc", + "views/passwords/password_items_view.h", + "views/passwords/password_pending_view.cc", + "views/passwords/password_pending_view.h", + "views/passwords/password_save_confirmation_view.cc", + "views/passwords/password_save_confirmation_view.h", + "views/passwords/password_sign_in_promo_view.cc", + "views/passwords/password_sign_in_promo_view.h", + "views/passwords/password_update_pending_view.cc", + "views/passwords/password_update_pending_view.h", "views/payments/contact_info_editor_view_controller.cc", "views/payments/contact_info_editor_view_controller.h", "views/payments/credit_card_editor_view_controller.cc", @@ -2923,6 +2973,15 @@ split_static_library("ui") { deps += [ "//ui/views/mus" ] } + if (enable_dice_support) { + sources += [ + "views/sync/dice_bubble_sync_promo_view.cc", + "views/sync/dice_bubble_sync_promo_view.h", + "views/sync/dice_signin_button_view.cc", + "views/sync/dice_signin_button_view.h", + ] + } + if (!is_mac || mac_views_browser) { sources += [ "javascript_dialogs/javascript_dialog.cc", @@ -2934,7 +2993,6 @@ split_static_library("ui") { "views/autofill/autofill_popup_view_native_views.h", "views/autofill/autofill_popup_view_views.cc", "views/autofill/autofill_popup_view_views.h", - "views/autofill/card_unmask_prompt_views_shim.cc", "views/autofill/password_generation_popup_view_views.cc", "views/autofill/password_generation_popup_view_views.h", "views/autofill/save_card_icon_view.cc", @@ -2985,6 +3043,8 @@ split_static_library("ui") { "views/find_bar_host.h", "views/find_bar_view.cc", "views/find_bar_view.h", + "views/frame/avatar_button_manager.cc", + "views/frame/avatar_button_manager.h", "views/frame/browser_frame.cc", "views/frame/browser_frame.h", "views/frame/browser_non_client_frame_view.cc", @@ -2993,6 +3053,7 @@ split_static_library("ui") { "views/frame/browser_root_view.h", "views/frame/browser_view.cc", "views/frame/browser_view.h", + "views/frame/browser_view_button_provider.h", "views/frame/browser_view_layout.cc", "views/frame/browser_view_layout.h", "views/frame/browser_view_layout_delegate.h", @@ -3065,20 +3126,25 @@ split_static_library("ui") { "views/media_router/presentation_receiver_window_frame.h", "views/media_router/presentation_receiver_window_view.cc", "views/media_router/presentation_receiver_window_view.h", + "views/media_router/web_contents_display_observer_view.cc", + "views/media_router/web_contents_display_observer_view.h", "views/omnibox/omnibox_popup_contents_view.cc", "views/omnibox/omnibox_popup_contents_view.h", "views/omnibox/omnibox_result_view.cc", "views/omnibox/omnibox_result_view.h", + "views/omnibox/omnibox_tab_switch_button.cc", + "views/omnibox/omnibox_tab_switch_button.h", "views/omnibox/omnibox_view_views.cc", "views/omnibox/omnibox_view_views.h", + "views/omnibox/rounded_omnibox_results_frame.cc", + "views/omnibox/rounded_omnibox_results_frame.h", "views/passwords/manage_passwords_icon_views.cc", "views/passwords/manage_passwords_icon_views.h", "views/permission_bubble/chooser_bubble_ui_views.cc", "views/permission_bubble/permission_prompt_impl_views.cc", + "views/profiles/avatar_button_style.h", "views/profiles/profile_indicator_icon.cc", "views/profiles/profile_indicator_icon.h", - "views/profiles/user_manager_view.cc", - "views/profiles/user_manager_view.h", "views/proximity_auth/proximity_auth_error_bubble_view.cc", "views/proximity_auth/proximity_auth_error_bubble_view.h", "views/sad_tab_view.cc", @@ -3150,8 +3216,24 @@ split_static_library("ui") { "views/translate/translate_icon_view.h", "views/webshare/webshare_target_picker_view.cc", "views/webshare/webshare_target_picker_view.h", + "views_mode_controller.cc", + "views_mode_controller.h", ] + deps += [ "//ui/views:features" ] + + if (is_mac) { + # This Mac-specific file is being removed in this case because it's only + # needed when Views isn't used on Mac. + sources -= + [ "media_router/presentation_receiver_window_factory_mac.cc" ] + + # This source file is compiled out on Mac because there is a + # Mac-specific implementation of it that works on mac_views_browser + # builds as well: + sources -= [ "views/permission_bubble/permission_prompt_impl_views.cc" ] + } + if (!is_chromeos) { sources += [ "views/frame/opaque_browser_frame_view.cc", @@ -3233,6 +3315,23 @@ split_static_library("ui") { ] } } + + if (!is_chromeos) { + sources += [ + "views/relaunch_notification/relaunch_notification_controller.cc", + "views/relaunch_notification/relaunch_notification_controller.h", + "views/relaunch_notification/relaunch_recommended_bubble_view.cc", + "views/relaunch_notification/relaunch_recommended_bubble_view.h", + "views/relaunch_notification/relaunch_required_dialog_view.cc", + "views/relaunch_notification/relaunch_required_dialog_view.h", + ] + if (is_mac) { + sources += [ + "views/relaunch_notification/get_app_menu_anchor_point.h", + "views/relaunch_notification/get_app_menu_anchor_point.mm", + ] + } + } } if (use_aura) { @@ -3316,11 +3415,14 @@ split_static_library("ui") { "app_list/app_context_menu.cc", "app_list/app_context_menu.h", "app_list/app_context_menu_delegate.h", + "app_list/app_list_client_impl.cc", + "app_list/app_list_client_impl.h", "app_list/app_list_controller_delegate.cc", "app_list/app_list_controller_delegate.h", "app_list/app_list_model_builder.cc", "app_list/app_list_model_builder.h", "app_list/app_list_model_updater.h", + "app_list/app_list_model_updater_delegate.h", "app_list/app_list_service.cc", "app_list/app_list_service.h", "app_list/app_list_service_impl.cc", @@ -3335,7 +3437,6 @@ split_static_library("ui") { "app_list/chrome_app_list_item.h", "app_list/chrome_app_list_model_updater.cc", "app_list/chrome_app_list_model_updater.h", - "app_list/chrome_app_list_model_updater_delegate.h", "app_list/extension_app_context_menu.cc", "app_list/extension_app_context_menu.h", "app_list/extension_app_item.cc", @@ -3393,27 +3494,12 @@ split_static_library("ui") { "app_list/search/search_util.h", "app_list/search/search_webstore_result.cc", "app_list/search/search_webstore_result.h", - "app_list/search/suggestions/suggestions_search_provider.cc", - "app_list/search/suggestions/suggestions_search_provider.h", - "app_list/search/suggestions/url_suggestion_result.cc", - "app_list/search/suggestions/url_suggestion_result.h", "app_list/search/webstore/webstore_installer.cc", "app_list/search/webstore/webstore_installer.h", "app_list/search/webstore/webstore_provider.cc", "app_list/search/webstore/webstore_provider.h", "app_list/search/webstore/webstore_result.cc", "app_list/search/webstore/webstore_result.h", - "app_list/speech_auth_helper.cc", - "app_list/speech_auth_helper.h", - "app_list/start_page_observer.h", - "app_list/start_page_service.cc", - "app_list/start_page_service.h", - "app_list/start_page_service_factory.cc", - "app_list/start_page_service_factory.h", - "webui/app_list/start_page_handler.cc", - "webui/app_list/start_page_handler.h", - "webui/app_list/start_page_ui.cc", - "webui/app_list/start_page_ui.h", ] deps += [ "//ui/app_list", @@ -3454,10 +3540,22 @@ split_static_library("ui") { "app_list/arc/arc_pai_starter.h", "app_list/arc/arc_playstore_app_context_menu.cc", "app_list/arc/arc_playstore_app_context_menu.h", + "app_list/arc/arc_usb_host_permission_manager.cc", + "app_list/arc/arc_usb_host_permission_manager.h", + "app_list/arc/arc_usb_host_permission_manager_factory.cc", + "app_list/arc/arc_usb_host_permission_manager_factory.h", "app_list/arc/arc_vpn_provider_manager.cc", "app_list/arc/arc_vpn_provider_manager.h", "app_list/arc/arc_vpn_provider_manager_factory.cc", "app_list/arc/arc_vpn_provider_manager_factory.h", + "app_list/crostini/crostini_app_item.cc", + "app_list/crostini/crostini_app_item.h", + "app_list/crostini/crostini_app_model_builder.cc", + "app_list/crostini/crostini_app_model_builder.h", + "app_list/crostini/crostini_installer_view.cc", + "app_list/crostini/crostini_installer_view.h", + "app_list/crostini/crostini_util.cc", + "app_list/crostini/crostini_util.h", "app_list/search/arc_app_result.cc", "app_list/search/arc_app_result.h", "ash/launcher/arc_app_deferred_launcher_controller.cc", @@ -3542,6 +3640,10 @@ split_static_library("ui") { "extensions/installation_error_infobar_delegate.h", "extensions/settings_api_bubble_helpers.cc", "extensions/settings_api_bubble_helpers.h", + "views/extensions/extension_popup.cc", + "views/extensions/extension_popup.h", + "views/extensions/extension_view_views.cc", + "views/extensions/extension_view_views.h", "webui/extensions/extension_basic_info.cc", "webui/extensions/extension_basic_info.h", "webui/extensions/extension_icon_source.cc", @@ -3561,10 +3663,6 @@ split_static_library("ui") { "views/extensions/extension_dialog.h", "views/extensions/extension_dialog_observer.cc", "views/extensions/extension_dialog_observer.h", - "views/extensions/extension_popup.cc", - "views/extensions/extension_popup.h", - "views/extensions/extension_view_views.cc", - "views/extensions/extension_view_views.h", "views/extensions/media_galleries_dialog_views.cc", "views/extensions/media_galleries_dialog_views.h", "views/extensions/media_gallery_checkbox_view.cc", @@ -3658,6 +3756,9 @@ split_static_library("ui") { "webui/local_discovery/local_discovery_ui_handler.cc", "webui/local_discovery/local_discovery_ui_handler.h", ] + if (enable_print_preview && !is_chromeos) { + deps += [ "//chrome/common:service_process_mojom" ] + } } if (enable_webrtc) { @@ -3665,6 +3766,7 @@ split_static_library("ui") { "webui/media/webrtc_logs_ui.cc", "webui/media/webrtc_logs_ui.h", ] + deps += [ "//components/webrtc_logging/browser" ] } if (safe_browsing_mode == 1) { @@ -3756,7 +3858,9 @@ static_library("test_support") { ] if (toolkit_views) { + deps += [ "//ui/views:test_support" ] sources += [ + "extensions/browser_action_test_util.h", "views/payments/test_chrome_payment_request_delegate.cc", "views/payments/test_chrome_payment_request_delegate.h", ] @@ -3770,7 +3874,7 @@ static_library("test_support") { } } else { sources += [ - "cocoa/extensions/browser_action_test_util_mac.mm", + "cocoa/extensions/browser_action_test_util_views_mac.mm", "cocoa/find_bar/find_bar_host_unittest_util_cocoa.mm", ] } @@ -3794,6 +3898,7 @@ static_library("test_support") { ] deps += [ "//chrome/test:test_support_ui", + "//components/signin/core/browser", "//components/zoom", ] } diff --git a/chromium/chrome/browser/ui/libgtkui/BUILD.gn b/chromium/chrome/browser/ui/libgtkui/BUILD.gn index 505d87c67be..cc7663cbe6c 100644 --- a/chromium/chrome/browser/ui/libgtkui/BUILD.gn +++ b/chromium/chrome/browser/ui/libgtkui/BUILD.gn @@ -90,7 +90,7 @@ template("libgtkui") { "//base:i18n", "//base/third_party/dynamic_annotations", "//cc/paint", - "//chrome/common:features", + "//chrome/common:buildflags", "//chrome:extra_resources", "//chrome:resources", "//chrome:strings", diff --git a/chromium/chrome/browser/ui/webui/OWNERS b/chromium/chrome/browser/ui/webui/OWNERS index 4dbbf3fcf06..211ccfc88f3 100644 --- a/chromium/chrome/browser/ui/webui/OWNERS +++ b/chromium/chrome/browser/ui/webui/OWNERS @@ -10,6 +10,8 @@ per-file inspect_ui*=pfeldman@chromium.org per-file md_history_ui*=calamity@chromium.org +per-file memory_internals_ui*=erikchen@chromium.org + per-file net_export_ui.*=file://net/OWNERS per-file ntp_tiles_internals_ui.*=file://components/ntp_tiles/OWNERS diff --git a/chromium/chrome/browser/ui/webui/about_ui.cc b/chromium/chrome/browser/ui/webui/about_ui.cc index 8c97750329e..808d6e76497 100644 --- a/chromium/chrome/browser/ui/webui/about_ui.cc +++ b/chromium/chrome/browser/ui/webui/about_ui.cc @@ -51,6 +51,7 @@ #include "components/about_ui/credit_utils.h" #include "components/grit/components_resources.h" #include "components/strings/grit/components_locale_settings.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -325,6 +326,25 @@ std::string ChromeURLs() { return html; } +std::string HelpCenterContent() { + std::string html; + AppendHeader(&html, 0, l10n_util::GetStringUTF8(IDS_CONNECTION_HELP_TITLE)); + html += + "<meta name=\"viewport\" content=\"initial-scale=1, minimum-scale=1, " + "width=device-width\">\n"; + webui::AppendWebUiCssTextDefaults(&html); + html += "<style>"; + html += l10n_util::GetStringUTF8(IDR_SECURITY_INTERSTITIAL_COMMON_CSS); + html += l10n_util::GetStringUTF8(IDR_SECURITY_INTERSTITIAL_CORE_CSS); + html += "</style>"; + AppendBody(&html); + html += "<div class=\"interstitial-wrapper\">\n"; + html += l10n_util::GetStringUTF8(IDS_CONNECTION_HELP_HTML); + html += "</div>\n"; + AppendFooter(&html); + return html; +} + // AboutDnsHandler bounces the request back to the IO thread to collect // the DNS information. class AboutDnsHandler : public base::RefCountedThreadSafe<AboutDnsHandler> { @@ -425,6 +445,8 @@ void AboutUIHTMLSource::StartDataRequest( // Add your data source here, in alphabetical order. if (source_name_ == chrome::kChromeUIChromeURLsHost) { response = ChromeURLs(); + } else if (source_name_ == chrome::kChromeUIConnectionHelpHost) { + response = HelpCenterContent(); } else if (source_name_ == chrome::kChromeUICreditsHost) { int idr = IDR_ABOUT_UI_CREDITS_HTML; if (path == kCreditsJsPath) diff --git a/chromium/chrome/browser/ui/webui/app_list/OWNERS b/chromium/chrome/browser/ui/webui/app_list/OWNERS deleted file mode 100644 index ed54f09c40c..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -calamity@chromium.org -khmel@chromium.org diff --git a/chromium/chrome/browser/ui/webui/app_list/start_page_browsertest.js b/chromium/chrome/browser/ui/webui/app_list/start_page_browsertest.js deleted file mode 100644 index 84869c4f540..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/start_page_browsertest.js +++ /dev/null @@ -1,161 +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. - -/** - * TestFixture for kiosk app settings WebUI testing. - * @extends {testing.Test} - * @constructor - */ -function AppListStartPageWebUITest() {} - -/** - * Mock of audioContext. - * @constructor - */ -function mockAudioContext() { - this.sampleRate = 44100; /* some dummy number */ -} - -mockAudioContext.prototype = { - createMediaStreamSource: function(stream) { - return {connect: function(audioProc) {}, - disconnect: function() {}}; - }, - createScriptProcessor: function(bufSize, in_channels, out_channels) { - return {connect: function(destination) {}, - disconnect: function() {}}; - } -}; - -AppListStartPageWebUITest.prototype = { - __proto__: testing.Test.prototype, - - /** - * Browser to app launcher start page. - */ - browsePreload: 'chrome://app-list/', - - /** - * Placeholder for mock speech recognizer. - */ - speechRecognizer: null, - - /** - * Sends the speech recognition result. - * - * @param {string} result The testing result. - * @param {boolean} isFinal Whether the result is final or not. - */ - sendSpeechResult: function(result, isFinal) { - var speechEvent = new Event('test'); - // Each result contains a list of alternatives and 'isFinal' flag. - var speechResult = [{transcript: result}]; - speechResult.isFinal = isFinal; - speechEvent.results = [speechResult]; - this.speechRecognizer.onresult(speechEvent); - }, - - /** - * Registers the webkitSpeechRecognition mock for test. - * @private - */ - registerMockSpeechRecognition_: function() { - var owner = this; - function mockSpeechRecognition() { - this.inSpeech_ = false; - owner.speechRecognizer = this; - } - - mockSpeechRecognition.prototype = { - start: function() { - this.onstart(); - }, - - abort: function() { - if (this.inSpeech_) - this.onspeechend(); - this.onerror(new Error()); - this.onend(); - } - }, - - window.webkitSpeechRecognition = mockSpeechRecognition; - }, - - /** - * Mock of webkitGetUserMedia for start page. - * - * @private - * @param {object} constraint The constraint parameter. - * @param {Function} success The success callback. - * @param {Function} error The error callback. - */ - mockGetUserMedia_: function(constraint, success, error) { - function getAudioTracks() { - } - assertTrue(constraint.audio); - assertNotEquals(null, error, 'error callback must not be null'); - var audioTracks = []; - for (var i = 0; i < 2; ++i) { - audioTracks.push(this.audioTrackMocks[i].proxy()); - } - success({getAudioTracks: function() { return audioTracks; }}); - }, - - /** @override */ - preLoad: function() { - this.makeAndRegisterMockHandler(['initialize', - 'launchApp', - 'setSpeechRecognitionState', - 'speechResult']); - this.mockHandler.stubs().initialize(); - this.mockHandler.stubs().launchApp(ANYTHING); - - this.registerMockSpeechRecognition_(); - window.AudioContext = mockAudioContext; - navigator.webkitGetUserMedia = this.mockGetUserMedia_.bind(this); - this.audioTrackMocks = [mock(MediaStreamTrack), mock(MediaStreamTrack)]; - } -}; - -TEST_F('AppListStartPageWebUITest', 'Basic', function() { - assertEquals(this.browsePreload, document.location.href); -}); - -TEST_F('AppListStartPageWebUITest', 'LoadDoodle', function() { - var doodleData = { - 'ddljson': { - 'transparent_large_image': { - 'url': 'doodle.png' - }, - 'alt_text': 'Doodle alt text', - 'target_url': '/target.html' - } - }; - - assertEquals(null, $('doodle')); - - // Load the doodle with a target url and alt text. - appList.startPage.onAppListDoodleUpdated(doodleData, - 'http://example.com/x/'); - assertNotEquals(null, $('doodle')); - assertEquals('http://example.com/x/doodle.png', $('doodle_image').src); - assertEquals(doodleData.ddljson.alt_text, $('doodle_image').title); - assertEquals('http://example.com/target.html', $('doodle_link').href); - - // Reload the doodle without a target url and alt text. - doodleData.ddljson.alt_text = undefined; - doodleData.ddljson.target_url = undefined; - appList.startPage.onAppListDoodleUpdated(doodleData, - 'http://example.com/x/'); - assertNotEquals(null, $('doodle')); - assertEquals('http://example.com/x/doodle.png', $('doodle_image').src); - assertEquals('', $('doodle_image').title); - assertEquals(null, $('doodle_link')); - - - appList.startPage.onAppListDoodleUpdated({}, - 'http://example.com/'); - assertEquals(null, $('doodle')); -}); diff --git a/chromium/chrome/browser/ui/webui/app_list/start_page_handler.cc b/chromium/chrome/browser/ui/webui/app_list/start_page_handler.cc deleted file mode 100644 index d8fd5b02f2b..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/start_page_handler.cc +++ /dev/null @@ -1,112 +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. - -#include "chrome/browser/ui/webui/app_list/start_page_handler.h" - -#include <memory> -#include <string> - -#include "base/bind.h" -#include "base/metrics/histogram_macros.h" -#include "base/values.h" -#include "base/version.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" -#include "chrome/browser/ui/app_list/app_list_service.h" -#include "chrome/browser/ui/app_list/start_page_service.h" -#include "chrome/browser/ui/webui/extensions/extension_icon_source.h" -#include "chrome/common/pref_names.h" -#include "components/update_client/update_query_params.h" -#include "content/public/browser/web_ui.h" -#include "extensions/browser/extension_registry.h" -#include "extensions/browser/extension_system.h" -#include "extensions/common/constants.h" -#include "extensions/common/extension.h" -#include "extensions/common/extension_icon_set.h" -#include "ui/app_list/app_list_switches.h" -#include "ui/events/event_constants.h" - -namespace app_list { - -namespace { - -const char kAppListDoodleActionHistogram[] = "Apps.AppListDoodleAction"; - -// Interactions a user has with the app list doodle. This enum must not have its -// order altered as it is used in histograms. -enum DoodleAction { - DOODLE_SHOWN = 0, - DOODLE_CLICKED, - // Add values here. - - DOODLE_ACTION_LAST, -}; - -} // namespace - -StartPageHandler::StartPageHandler() { -} - -StartPageHandler::~StartPageHandler() { -} - -void StartPageHandler::RegisterMessages() { - web_ui()->RegisterMessageCallback( - "appListShown", base::Bind(&StartPageHandler::HandleAppListShown, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "doodleClicked", base::Bind(&StartPageHandler::HandleDoodleClicked, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "initialize", - base::Bind(&StartPageHandler::HandleInitialize, base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "launchApp", - base::Bind(&StartPageHandler::HandleLaunchApp, base::Unretained(this))); -} - -void StartPageHandler::HandleAppListShown(const base::ListValue* args) { - bool doodle_shown = false; - if (args->GetBoolean(0, &doodle_shown) && doodle_shown) { - UMA_HISTOGRAM_ENUMERATION(kAppListDoodleActionHistogram, DOODLE_SHOWN, - DOODLE_ACTION_LAST); - } -} - -void StartPageHandler::HandleDoodleClicked(const base::ListValue* args) { - UMA_HISTOGRAM_ENUMERATION(kAppListDoodleActionHistogram, DOODLE_CLICKED, - DOODLE_ACTION_LAST); -} - -void StartPageHandler::HandleInitialize(const base::ListValue* args) { - Profile* profile = Profile::FromWebUI(web_ui()); - StartPageService* service = StartPageService::Get(profile); - if (!service) - return; - - service->WebUILoaded(); -} - -void StartPageHandler::HandleLaunchApp(const base::ListValue* args) { - std::string app_id; - CHECK(args->GetString(0, &app_id)); - - Profile* profile = Profile::FromWebUI(web_ui()); - const extensions::Extension* app = - extensions::ExtensionRegistry::Get(profile) - ->GetExtensionById(app_id, extensions::ExtensionRegistry::EVERYTHING); - if (!app) { - NOTREACHED(); - return; - } - - AppListControllerDelegate* controller = - AppListService::Get()->GetControllerDelegate(); - controller->ActivateApp(profile, - app, - AppListControllerDelegate::LAUNCH_FROM_APP_LIST, - ui::EF_NONE); -} - -} // namespace app_list diff --git a/chromium/chrome/browser/ui/webui/app_list/start_page_handler.h b/chromium/chrome/browser/ui/webui/app_list/start_page_handler.h deleted file mode 100644 index 516413a3270..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/start_page_handler.h +++ /dev/null @@ -1,42 +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. - -#ifndef CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_HANDLER_H_ -#define CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_HANDLER_H_ - -#include "base/compiler_specific.h" -#include "base/macros.h" -#include "components/prefs/pref_change_registrar.h" -#include "content/public/browser/web_ui_message_handler.h" - -namespace base { -class ListValue; -} - -namespace app_list { - -// Handler for the app launcher start page. -class StartPageHandler : public content::WebUIMessageHandler { - public: - StartPageHandler(); - ~StartPageHandler() override; - - private: - // content::WebUIMessageHandler overrides: - void RegisterMessages() override; - - // JS callbacks. - void HandleAppListShown(const base::ListValue* args); - void HandleDoodleClicked(const base::ListValue* args); - void HandleInitialize(const base::ListValue* args); - void HandleLaunchApp(const base::ListValue* args); - - PrefChangeRegistrar pref_change_registrar_; - - DISALLOW_COPY_AND_ASSIGN(StartPageHandler); -}; - -} // namespace app_list - -#endif // CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_HANDLER_H_ diff --git a/chromium/chrome/browser/ui/webui/app_list/start_page_ui.cc b/chromium/chrome/browser/ui/webui/app_list/start_page_ui.cc deleted file mode 100644 index a5160c2ab5f..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/start_page_ui.cc +++ /dev/null @@ -1,48 +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. - -#include "chrome/browser/ui/webui/app_list/start_page_ui.h" - -#include <memory> - -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/memory/ref_counted_memory.h" -#include "base/sys_info.h" -#include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/webui/app_list/start_page_handler.h" -#include "chrome/common/extensions/extension_constants.h" -#include "chrome/common/url_constants.h" -#include "chrome/grit/browser_resources.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/web_ui.h" -#include "content/public/browser/web_ui_data_source.h" -#include "extensions/browser/extension_system.h" -#include "extensions/common/extension.h" - -namespace app_list { - -StartPageUI::StartPageUI(content::WebUI* web_ui) - : content::WebUIController(web_ui) { - web_ui->AddMessageHandler(std::make_unique<StartPageHandler>()); - InitDataSource(); -} - -StartPageUI::~StartPageUI() {} - -void StartPageUI::InitDataSource() { - std::unique_ptr<content::WebUIDataSource> source( - content::WebUIDataSource::Create(chrome::kChromeUIAppListStartPageHost)); - - source->SetJsonPath("strings.js"); - - source->AddResourcePath("start_page.css", IDR_APP_LIST_START_PAGE_CSS); - source->AddResourcePath("start_page.js", IDR_APP_LIST_START_PAGE_JS); - source->SetDefaultResource(IDR_APP_LIST_START_PAGE_HTML); - - content::WebUIDataSource::Add(Profile::FromWebUI(web_ui()), source.release()); -} - -} // namespace app_list diff --git a/chromium/chrome/browser/ui/webui/app_list/start_page_ui.h b/chromium/chrome/browser/ui/webui/app_list/start_page_ui.h deleted file mode 100644 index 04089144dd3..00000000000 --- a/chromium/chrome/browser/ui/webui/app_list/start_page_ui.h +++ /dev/null @@ -1,28 +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. - -#ifndef CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_UI_H_ -#define CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_UI_H_ - -#include "base/macros.h" -#include "content/public/browser/web_ui_controller.h" - -namespace app_list { - -// StartPageUI for the app launcher start page. -class StartPageUI : public content::WebUIController { - public: - explicit StartPageUI(content::WebUI* web_ui); - ~StartPageUI() override; - - private: - // Initializes the data source used for this webui. - void InitDataSource(); - - DISALLOW_COPY_AND_ASSIGN(StartPageUI); -}; - -} // namespace app_list - -#endif // CHROME_BROWSER_UI_WEBUI_APP_LIST_START_PAGE_UI_H_ diff --git a/chromium/chrome/browser/ui/webui/bluetooth_internals/BUILD.gn b/chromium/chrome/browser/ui/webui/bluetooth_internals/BUILD.gn index 7aa5cd60035..f3e7c4bd926 100644 --- a/chromium/chrome/browser/ui/webui/bluetooth_internals/BUILD.gn +++ b/chromium/chrome/browser/ui/webui/bluetooth_internals/BUILD.gn @@ -30,6 +30,6 @@ mojom("mojo_bindings") { ] deps = [ - "//device/bluetooth/public/interfaces:deprecated_experimental_interfaces", + "//device/bluetooth/public/mojom:deprecated_experimental_interfaces", ] } diff --git a/chromium/chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals.mojom b/chromium/chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals.mojom index c1730c3500c..e5b8e22777b 100644 --- a/chromium/chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals.mojom +++ b/chromium/chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals.mojom @@ -4,7 +4,7 @@ module mojom; -import "device/bluetooth/public/interfaces/adapter.mojom"; +import "device/bluetooth/public/mojom/adapter.mojom"; interface BluetoothInternalsHandler { // Gets an Adapter interface. Returns null if Bluetooth is not supported. diff --git a/chromium/chrome/browser/ui/webui/browsing_history_handler.cc b/chromium/chrome/browser/ui/webui/browsing_history_handler.cc index 7666d2d1077..da08cc49277 100644 --- a/chromium/chrome/browser/ui/webui/browsing_history_handler.cc +++ b/chromium/chrome/browser/ui/webui/browsing_history_handler.cc @@ -27,8 +27,8 @@ #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/webui/favicon_source.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_features.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_utils.h" @@ -230,7 +230,8 @@ std::unique_ptr<base::DictionaryValue> HistoryEntryToValue( } // namespace BrowsingHistoryHandler::BrowsingHistoryHandler() - : clock_(new base::DefaultClock()), browsing_history_service_(nullptr) {} + : clock_(base::DefaultClock::GetInstance()), + browsing_history_service_(nullptr) {} BrowsingHistoryHandler::~BrowsingHistoryHandler() {} @@ -242,6 +243,9 @@ void BrowsingHistoryHandler::RegisterMessages() { ProfileSyncServiceFactory::GetSyncServiceForBrowserContext(profile); browsing_history_service_ = std::make_unique<BrowsingHistoryService>( this, local_history, sync_service); + // Make sure BrowsingDataRemoverDelegate is initialized and listening + // for history deletions. + profile->GetBrowsingDataRemoverDelegate(); // Create our favicon data source. content::URLDataSource::Add(profile, new FaviconSource(profile)); @@ -363,9 +367,8 @@ void BrowsingHistoryHandler::OnQueryComplete( // Convert the result vector into a ListValue. base::ListValue results_value; for (const BrowsingHistoryService::HistoryEntry& entry : results) { - std::unique_ptr<base::Value> value( - HistoryEntryToValue(entry, bookmark_model, supervised_user_service, - sync_service, clock_.get())); + std::unique_ptr<base::Value> value(HistoryEntryToValue( + entry, bookmark_model, supervised_user_service, sync_service, clock_)); results_value.Append(std::move(value)); } diff --git a/chromium/chrome/browser/ui/webui/browsing_history_handler.h b/chromium/chrome/browser/ui/webui/browsing_history_handler.h index 0a300973273..eef6585d166 100644 --- a/chromium/chrome/browser/ui/webui/browsing_history_handler.h +++ b/chromium/chrome/browser/ui/webui/browsing_history_handler.h @@ -59,10 +59,9 @@ class BrowsingHistoryHandler : public content::WebUIMessageHandler, // ProfileBasedBrowsingHistoryDriver implementation. Profile* GetProfile() override; - // For tests. - void set_clock(std::unique_ptr<base::Clock> clock) { - clock_ = std::move(clock); - } + // For tests. This does not take the ownership of the clock. |clock| must + // outlive the BrowsingHistoryHandler instance. + void set_clock(base::Clock* clock) { clock_ = clock; } private: FRIEND_TEST_ALL_PREFIXES(BrowsingHistoryHandlerTest, @@ -70,7 +69,7 @@ class BrowsingHistoryHandler : public content::WebUIMessageHandler, FRIEND_TEST_ALL_PREFIXES(BrowsingHistoryHandlerTest, MdTruncatesTitles); // The clock used to vend times. - std::unique_ptr<base::Clock> clock_; + base::Clock* clock_; std::unique_ptr<history::BrowsingHistoryService> browsing_history_service_; diff --git a/chromium/chrome/browser/ui/webui/browsing_history_handler_unittest.cc b/chromium/chrome/browser/ui/webui/browsing_history_handler_unittest.cc index ae9c92fa2ec..fb5013eaff2 100644 --- a/chromium/chrome/browser/ui/webui/browsing_history_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/browsing_history_handler_unittest.cc @@ -57,8 +57,6 @@ base::Time PretendNow() { return out_time; } -void IgnoreBoolAndDoNothing(bool ignored_argument) {} - class TestSyncService : public browser_sync::TestProfileSyncService { public: explicit TestSyncService(Profile* profile) @@ -84,17 +82,16 @@ class TestSyncService : public browser_sync::TestProfileSyncService { class BrowsingHistoryHandlerWithWebUIForTesting : public BrowsingHistoryHandler { public: - explicit BrowsingHistoryHandlerWithWebUIForTesting(content::WebUI* web_ui) - : test_clock_(new base::SimpleTestClock()) { - set_clock(base::WrapUnique(test_clock_)); + explicit BrowsingHistoryHandlerWithWebUIForTesting(content::WebUI* web_ui) { + set_clock(&test_clock_); set_web_ui(web_ui); - test_clock_->SetNow(PretendNow()); + test_clock_.SetNow(PretendNow()); } - base::SimpleTestClock* test_clock() { return test_clock_; } + base::SimpleTestClock* test_clock() { return &test_clock_; } private: - base::SimpleTestClock* test_clock_; + base::SimpleTestClock test_clock_; }; } // namespace @@ -167,7 +164,7 @@ class BrowsingHistoryHandlerTest : public ::testing::Test { // Tests that BrowsingHistoryHandler is informed about WebHistoryService // deletions. TEST_F(BrowsingHistoryHandlerTest, ObservingWebHistoryDeletions) { - base::Callback<void(bool)> callback = base::Bind(&IgnoreBoolAndDoNothing); + base::Callback<void(bool)> callback = base::DoNothing(); // BrowsingHistoryHandler is informed about WebHistoryService history // deletions. diff --git a/chromium/chrome/browser/ui/webui/certificate_manager_localized_strings_provider.cc b/chromium/chrome/browser/ui/webui/certificate_manager_localized_strings_provider.cc index 88a3a34ecf0..301701382d3 100644 --- a/chromium/chrome/browser/ui/webui/certificate_manager_localized_strings_provider.cc +++ b/chromium/chrome/browser/ui/webui/certificate_manager_localized_strings_provider.cc @@ -39,7 +39,6 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_CERTIFICATE_MANAGER_IMPORT_AND_BIND}, {"certificateManagerExport", IDS_SETTINGS_CERTIFICATE_MANAGER_EXPORT}, {"certificateManagerDelete", IDS_SETTINGS_CERTIFICATE_MANAGER_DELETE}, - {"certificateManagerDone", IDS_SETTINGS_CERTIFICATE_MANAGER_DONE}, {"certificateManagerUntrusted", IDS_SETTINGS_CERTIFICATE_MANAGER_UNTRUSTED}, // CA trust edit dialog. diff --git a/chromium/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chromium/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc index 1edbf61e3a7..8b87a09ec96 100644 --- a/chromium/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc +++ b/chromium/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc @@ -9,6 +9,7 @@ #include <vector> #include "base/bind.h" +#include "base/feature_list.h" #include "base/location.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" @@ -62,8 +63,8 @@ #include "chrome/browser/ui/webui/usb_internals/usb_internals_ui.h" #include "chrome/browser/ui/webui/user_actions/user_actions_ui.h" #include "chrome/browser/ui/webui/version_ui.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_features.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "components/dom_distiller/core/dom_distiller_constants.h" @@ -75,7 +76,7 @@ #include "components/favicon_base/favicon_util.h" #include "components/favicon_base/select_favicon_frames.h" #include "components/history/core/browser/history_types.h" -#include "components/nacl/common/features.h" +#include "components/nacl/common/buildflags.h" #include "components/prefs/pref_service.h" #include "components/safe_browsing/web_ui/constants.h" #include "components/safe_browsing/web_ui/safe_browsing_ui.h" @@ -192,13 +193,10 @@ #include "chrome/browser/ui/webui/local_discovery/local_discovery_ui.h" #endif -#if BUILDFLAG(ENABLE_APP_LIST) -#include "chrome/browser/ui/webui/app_list/start_page_ui.h" -#endif - #if BUILDFLAG(ENABLE_EXTENSIONS) #include "chrome/browser/extensions/extension_web_ui.h" #include "chrome/browser/ui/webui/extensions/extensions_ui.h" +#include "chrome/common/chrome_features.h" #include "chrome/common/extensions/extension_constants.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" @@ -279,6 +277,10 @@ WebUIController* NewWebUI<WelcomeWin10UI>(WebUI* web_ui, const GURL& url) { #endif // defined(OS_WIN) bool IsAboutUI(const GURL& url) { + if (base::FeatureList::IsEnabled(features::kBundledConnectionHelpFeature) && + url.host_piece() == chrome::kChromeUIConnectionHelpHost) { + return true; + } return (url.host_piece() == chrome::kChromeUIChromeURLsHost || url.host_piece() == chrome::kChromeUICreditsHost || url.host_piece() == chrome::kChromeUIDNSHost @@ -328,10 +330,7 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, return &NewWebUI<chromeos::DeviceLogUI>; if (url.host_piece() == chrome::kChromeUIDomainReliabilityInternalsHost) return &NewWebUI<DomainReliabilityInternalsUI>; - // TODO(dtrainor): Remove the OffTheRecord check once crbug.com/766363 is - // fixed. - if (url.host_piece() == chrome::kChromeUIDownloadInternalsHost && - !profile->IsOffTheRecord()) + if (url.host_piece() == chrome::kChromeUIDownloadInternalsHost) return &NewWebUI<DownloadInternalsUI>; if (url.host_piece() == chrome::kChromeUIFlagsHost) return &NewWebUI<FlagsUI>; @@ -359,11 +358,11 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, return &NewWebUI<PasswordManagerInternalsUI>; if (url.host_piece() == chrome::kChromeUIPredictorsHost) return &NewWebUI<PredictorsUI>; - if (url.host() == chrome::kChromeUIQuotaInternalsHost) + if (url.host_piece() == chrome::kChromeUIQuotaInternalsHost) return &NewWebUI<QuotaInternalsUI>; - if (url.host() == safe_browsing::kChromeUISafeBrowsingHost) + if (url.host_piece() == safe_browsing::kChromeUISafeBrowsingHost) return &NewWebUI<safe_browsing::SafeBrowsingUI>; - if (url.host() == chrome::kChromeUISignInInternalsHost) + if (url.host_piece() == chrome::kChromeUISignInInternalsHost) return &NewWebUI<SignInInternalsUI>; if (url.host_piece() == chrome::kChromeUISuggestionsHost) return &NewWebUI<suggestions::SuggestionsUI>; @@ -546,10 +545,6 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, } #endif -#if BUILDFLAG(ENABLE_APP_LIST) - if (url.host_piece() == chrome::kChromeUIAppListStartPageHost) - return &NewWebUI<app_list::StartPageUI>; -#endif #if BUILDFLAG(ENABLE_EXTENSIONS) if (url.host_piece() == chrome::kChromeUIExtensionsFrameHost) return &NewWebUI<extensions::ExtensionsUI>; @@ -672,7 +667,7 @@ void ChromeWebUIControllerFactory::GetFaviconForURL( // All extensions but the bookmark manager get their favicon from the icons // part of the manifest. if (url.SchemeIs(extensions::kExtensionScheme) && - url.host() != extension_misc::kBookmarkManagerId) { + url.host_piece() != extension_misc::kBookmarkManagerId) { ExtensionWebUI::GetFaviconForURL(profile, url, callback); return; } diff --git a/chromium/chrome/browser/ui/webui/chromeos/DEPS b/chromium/chrome/browser/ui/webui/chromeos/DEPS index 20c74c9134c..2bc7e71f953 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/DEPS +++ b/chromium/chrome/browser/ui/webui/chromeos/DEPS @@ -2,7 +2,7 @@ include_rules = [ "+components/login", "+components/user_manager", "+media/audio/sounds", - "+services/device/public/interfaces", + "+services/device/public/mojom", ] specific_include_rules = { diff --git a/chromium/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc b/chromium/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc index 675d6a2ca46..5ee6b3b64bf 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc @@ -733,7 +733,7 @@ void DriveInternalsWebUIHandler::UpdateCacheContentsSection( debug_info_collector->IterateFileCache( base::Bind(&DriveInternalsWebUIHandler::UpdateCacheEntry, weak_ptr_factory_.GetWeakPtr()), - base::Bind(&base::DoNothing)); + base::DoNothing()); } void DriveInternalsWebUIHandler::UpdateEventLogSection() { diff --git a/chromium/chrome/browser/ui/webui/chromeos/image_source.cc b/chromium/chrome/browser/ui/webui/chromeos/image_source.cc index 514536325e9..67ea4411f91 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/image_source.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/image_source.cc @@ -17,7 +17,6 @@ #include "base/single_thread_task_runner.h" #include "base/task_scheduler/post_task.h" #include "base/task_scheduler/task_scheduler.h" -#include "base/threading/sequenced_worker_pool.h" #include "base/threading/thread_task_runner_handle.h" #include "chrome/browser/chromeos/login/users/avatar/user_image_loader.h" #include "chrome/common/url_constants.h" diff --git a/chromium/chrome/browser/ui/webui/chromeos/keyboard_overlay_ui.cc b/chromium/chrome/browser/ui/webui/chromeos/keyboard_overlay_ui.cc index 39421136d31..d9eb8871542 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/keyboard_overlay_ui.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/keyboard_overlay_ui.cc @@ -8,7 +8,7 @@ #include <memory> -#include "ash/public/cpp/ash_switches.h" +#include "ash/public/cpp/ash_features.h" #include "ash/shell.h" #include "base/bind.h" #include "base/bind_helpers.h" @@ -25,6 +25,7 @@ #include "chrome/grit/generated_resources.h" #include "chromeos/chromeos_switches.h" #include "components/prefs/pref_service.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/page_navigator.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_delegate.h" @@ -76,7 +77,7 @@ struct I18nContentToMessage { IDS_KEYBOARD_OVERLAY_SYSTEM_MENU_KEY_LABEL}, {"keyboardOverlayLauncherKeyLabel", IDS_KEYBOARD_OVERLAY_LAUNCHER_KEY_LABEL}, - {"keyboardOverlayLearnMore", IDS_KEYBOARD_OVERLAY_LEARN_MORE}, + {"keyboardOverlayLearnMore", IDS_LEARN_MORE}, {"keyboardOverlayTitle", IDS_KEYBOARD_OVERLAY_TITLE}, {"keyboardOverlayEscKeyLabel", IDS_KEYBOARD_OVERLAY_ESC_KEY_LABEL}, {"keyboardOverlayBackKeyLabel", IDS_KEYBOARD_OVERLAY_BACK_KEY_LABEL}, @@ -212,14 +213,8 @@ struct I18nContentToMessage { {"keyboardOverlayMirrorMonitors", IDS_KEYBOARD_OVERLAY_MIRROR_MONITORS}, // TODO(warx): keyboard overlay name for move window between displays // shortcuts need to be updated when new keyboard shortcuts helper is there. - {"keyboardOverlayMoveWindowToAboveDisplay", - IDS_KEYBOARD_OVERLAY_MOVE_WINDOW_TO_ABOVE_DISPLAY}, - {"keyboardOverlayMoveWindowToBelowDisplay", - IDS_KEYBOARD_OVERLAY_MOVE_WINDOW_TO_BELOW_DISPLAY}, - {"keyboardOverlayMoveWindowToLeftDisplay", - IDS_KEYBOARD_OVERLAY_MOVE_WINDOW_TO_LEFT_DISPLAY}, - {"keyboardOverlayMoveWindowToRightDisplay", - IDS_KEYBOARD_OVERLAY_MOVE_WINDOW_TO_RIGHT_DISPLAY}, + {"keyboardOverlayMoveActiveWindowBetweenDisplays", + IDS_KEYBOARD_OVERLAY_MOVE_ACTIVE_WINDOW_BETWEEN_DISPLAYS}, {"keyboardOverlayNewIncognitoWindow", IDS_KEYBOARD_OVERLAY_NEW_INCOGNITO_WINDOW}, {"keyboardOverlayNewTab", IDS_KEYBOARD_OVERLAY_NEW_TAB}, @@ -342,7 +337,7 @@ content::WebUIDataSource* CreateKeyboardOverlayUIHTMLSource(Profile* profile) { source->AddBoolean("voiceInteractionEnabled", chromeos::switches::IsVoiceInteractionEnabled()); source->AddBoolean("displayMoveWindowAccelsEnabled", - ash::switches::IsDisplayMoveWindowAccelsEnabled()); + ash::features::IsDisplayMoveWindowAccelsEnabled()); source->AddBoolean("keyboardOverlayUsesLayout2", ui::DeviceUsesKeyboardLayout2()); ash::Shell* shell = ash::Shell::Get(); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.cc index 6e1171adf37..684dfbfeecf 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.cc @@ -8,6 +8,7 @@ #include "chrome/browser/chromeos/login/oobe_screen.h" #include "chrome/browser/chromeos/login/screens/core_oobe_view.h" +#include "chrome/browser/chromeos/login/ui/login_display_host.h" #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" #include "chrome/grit/generated_resources.h" #include "chromeos/login/auth/authpolicy_login_helper.h" @@ -77,9 +78,7 @@ void ActiveDirectoryPasswordChangeScreenHandler::HandleCancel() { } void ActiveDirectoryPasswordChangeScreenHandler::ShowScreen( - const std::string& username, - SigninScreenHandlerDelegate* delegate) { - delegate_ = delegate; + const std::string& username) { base::DictionaryValue data; data.SetString(kUsernameKey, username); ShowScreenWithData(OobeScreen::SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE, @@ -105,16 +104,16 @@ void ActiveDirectoryPasswordChangeScreenHandler::OnAuthFinished( !account_info.account_id().empty()); const AccountId account_id = user_manager::known_user::GetAccountId( username, account_info.account_id(), AccountType::ACTIVE_DIRECTORY); - DCHECK(delegate_); - delegate_->SetDisplayAndGivenName(account_info.display_name(), - account_info.given_name()); + DCHECK(LoginDisplayHost::default_host()); + LoginDisplayHost::default_host()->SetDisplayAndGivenName( + account_info.display_name(), account_info.given_name()); UserContext user_context(account_id); user_context.SetKey(key); user_context.SetAuthFlow(UserContext::AUTH_FLOW_ACTIVE_DIRECTORY); user_context.SetIsUsingOAuth(false); user_context.SetUserType( user_manager::UserType::USER_TYPE_ACTIVE_DIRECTORY); - delegate_->CompleteLogin(user_context); + LoginDisplayHost::default_host()->CompleteLogin(user_context); break; } case authpolicy::ERROR_BAD_PASSWORD: @@ -132,7 +131,7 @@ void ActiveDirectoryPasswordChangeScreenHandler::OnAuthFinished( break; default: NOTREACHED() << "Unhandled error: " << error; - ShowScreen(username, delegate_); + ShowScreen(username); core_oobe_view_->ShowSignInError( 0, l10n_util::GetStringUTF8(IDS_AD_AUTH_UNKNOWN_ERROR), std::string(), HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h index 83b651b80f6..e53f21e9054 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h @@ -19,7 +19,6 @@ namespace chromeos { class CoreOobeView; class Key; -class SigninScreenHandlerDelegate; // A class that handles WebUI hooks in Active Directory password change screen. class ActiveDirectoryPasswordChangeScreenHandler : public BaseScreenHandler { @@ -42,10 +41,8 @@ class ActiveDirectoryPasswordChangeScreenHandler : public BaseScreenHandler { const std::string& new_password); void HandleCancel(); - // Shows the password change screen for |username|. Uses |delegate| to - // compelete authentication process. - void ShowScreen(const std::string& username, - SigninScreenHandlerDelegate* delegate); + // Shows the password change screen for |username|. + void ShowScreen(const std::string& username); private: // Shows the screen with the error message corresponding to |error|. @@ -63,9 +60,6 @@ class ActiveDirectoryPasswordChangeScreenHandler : public BaseScreenHandler { // password on the Active Directory server. std::unique_ptr<AuthPolicyLoginHelper> authpolicy_login_helper_; - // Non-owned. Used to complete authentication process. - SigninScreenHandlerDelegate* delegate_ = nullptr; - // Non-owned. Used to display signin error. CoreOobeView* core_oobe_view_ = nullptr; diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc index 9930d3a2e99..42a867d7dfc 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc @@ -4,11 +4,13 @@ #include "chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.h" -#include "base/command_line.h" #include "base/i18n/timezone.h" +#include "chrome/browser/chromeos/arc/arc_support_host.h" +#include "chrome/browser/chromeos/arc/arc_util.h" #include "chrome/browser/chromeos/arc/optin/arc_optin_preference_handler.h" #include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen_view_observer.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" +#include "chrome/browser/consent_auditor/consent_auditor_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/grit/generated_resources.h" @@ -17,6 +19,7 @@ #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "components/arc/arc_prefs.h" +#include "components/consent_auditor/consent_auditor.h" #include "components/login/localized_values_builder.h" #include "components/prefs/pref_service.h" #include "content/public/browser/web_contents.h" @@ -72,11 +75,6 @@ void ArcTermsOfServiceScreenHandler::OnCurrentScreenChanged( if (new_screen != OobeScreen::SCREEN_GAIA_SIGNIN) return; - const base::CommandLine* command_line = - base::CommandLine::ForCurrentProcess(); - if (!command_line->HasSwitch(chromeos::switches::kEnableArcOOBEOptIn)) - return; - MaybeLoadPlayStoreToS(false); StartNetworkAndTimeZoneObserving(); } @@ -102,17 +100,28 @@ void ArcTermsOfServiceScreenHandler::DeclareLocalizedValues( builder->Add("arcTermsOfServiceRetryButton", IDS_ARC_OOBE_TERMS_BUTTON_RETRY); builder->Add("arcTermsOfServiceAcceptButton", IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT); + builder->Add("arcTermsOfServiceNextButton", + IDS_ARC_OPT_IN_DIALOG_BUTTON_NEXT); builder->Add("arcPolicyLink", IDS_ARC_OPT_IN_PRIVACY_POLICY_LINK); builder->Add("arcTextBackupRestore", IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE); builder->Add("arcTextLocationService", IDS_ARC_OPT_IN_LOCATION_SETTING); + builder->Add("arcTextPaiService", IDS_ARC_OPT_IN_PAI); + builder->Add("arcTextGoogleServiceConfirmation", + IDS_ARC_OPT_IN_GOOGLE_SERVICE_CONFIRMATION); builder->Add("arcLearnMoreStatistics", IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS); builder->Add("arcLearnMoreLocationService", IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES); builder->Add("arcLearnMoreBackupAndRestore", IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE); + builder->Add("arcLearnMorePaiService", IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE); builder->Add("arcOverlayClose", IDS_ARC_OOBE_TERMS_POPUP_HELP_CLOSE_BUTTON); } +void ArcTermsOfServiceScreenHandler::SendArcManagedStatus(Profile* profile) { + CallJS("setArcManaged", + arc::IsArcPlayStoreEnabledPreferenceManagedForProfile(profile)); +} + void ArcTermsOfServiceScreenHandler::OnMetricsModeChanged(bool enabled, bool managed) { const Profile* const profile = ProfileManager::GetActiveUserProfile(); @@ -130,23 +139,29 @@ void ArcTermsOfServiceScreenHandler::OnMetricsModeChanged(bool enabled, // managed flag. const bool owner_profile = !owner.is_valid() || user->GetAccountId() == owner; - if (owner_profile && !managed) { + if (owner_profile && !managed && !enabled) { CallJS("setMetricsMode", base::string16(), false); } else { - int message_id = enabled ? - IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_ENABLED : - IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED; + int message_id; + if (owner_profile && !managed) { + message_id = IDS_ARC_OOBE_TERMS_DIALOG_METRICS_ENABLED; + } else { + message_id = enabled ? IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_ENABLED + : IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED; + } CallJS("setMetricsMode", l10n_util::GetStringUTF16(message_id), true); } } void ArcTermsOfServiceScreenHandler::OnBackupAndRestoreModeChanged( bool enabled, bool managed) { + backup_restore_managed_ = managed; CallJS("setBackupAndRestoreMode", enabled, managed); } void ArcTermsOfServiceScreenHandler::OnLocationServicesModeChanged( bool enabled, bool managed) { + location_services_managed_ = managed; CallJS("setLocationServicesMode", enabled, managed); } @@ -210,6 +225,7 @@ void ArcTermsOfServiceScreenHandler::DoShow() { ShowScreen(kScreenId); + SendArcManagedStatus(profile); MaybeLoadPlayStoreToS(true); StartNetworkAndTimeZoneObserving(); @@ -235,12 +251,40 @@ void ArcTermsOfServiceScreenHandler::HandleSkip() { void ArcTermsOfServiceScreenHandler::HandleAccept( bool enable_backup_restore, - bool enable_location_services) { + bool enable_location_services, + const std::string& tos_content) { if (!NeedDispatchEventOnAction()) return; - pref_handler_->EnableBackupRestore(enable_backup_restore); pref_handler_->EnableLocationService(enable_location_services); + + consent_auditor::ConsentAuditor* consent_auditor = + ConsentAuditorFactory::GetForProfile( + ProfileManager::GetPrimaryUserProfile()); + + // Record acceptance of Play ToS. + consent_auditor->RecordGaiaConsent( + consent_auditor::Feature::PLAY_STORE, + ArcSupportHost::ComputePlayToSConsentIds(tos_content), + IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT, consent_auditor::ConsentStatus::GIVEN); + + // If the user - not policy - chose Backup and Restore, record consent. + if (enable_backup_restore && !backup_restore_managed_) { + consent_auditor->RecordGaiaConsent( + consent_auditor::Feature::BACKUP_AND_RESTORE, + {IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE}, + IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT, + consent_auditor::ConsentStatus::GIVEN); + } + + // If the user - not policy - chose Location Services, record consent. + if (enable_location_services && !location_services_managed_) { + consent_auditor->RecordGaiaConsent( + consent_auditor::Feature::GOOGLE_LOCATION_SERVICE, + {IDS_ARC_OPT_IN_LOCATION_SETTING}, IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT, + consent_auditor::ConsentStatus::GIVEN); + } + for (auto& observer : observer_list_) observer.OnAccept(); } diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.h index b626fb1fc7b..bac38856df3 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.h @@ -17,6 +17,8 @@ #include "chromeos/network/network_state_handler_observer.h" #include "chromeos/settings/timezone_settings.h" +class Profile; + namespace arc { class ArcOptInPreferenceHandler; } @@ -66,13 +68,17 @@ class ArcTermsOfServiceScreenHandler void DoShow(); void HandleSkip(); void HandleAccept(bool enable_backup_restore, - bool enable_location_services); + bool enable_location_services, + const std::string& tos_content); // Loads Play Store ToS content in case default network exists. If // |ignore_network_state| is set then network state is not checked. void MaybeLoadPlayStoreToS(bool ignore_network_state); void StartNetworkAndTimeZoneObserving(); + // Sends if Arc enable status is manged to screen. + void SendArcManagedStatus(Profile* profile); + bool NeedDispatchEventOnAction(); // arc::ArcOptInPreferenceHandlerObserver: @@ -91,6 +97,10 @@ class ArcTermsOfServiceScreenHandler // To filter out duplicate notifications from html. bool action_taken_ = false; + // To track if optional features are managed preferences. + bool backup_restore_managed_ = false; + bool location_services_managed_ = false; + std::unique_ptr<arc::ArcOptInPreferenceHandler> pref_handler_; DISALLOW_COPY_AND_ASSIGN(ArcTermsOfServiceScreenHandler); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc index df9f8651a17..1164b909f83 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc @@ -37,6 +37,7 @@ #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "chromeos/chromeos_constants.h" +#include "chromeos/chromeos_switches.h" #include "components/login/base_screen_handler_utils.h" #include "components/login/localized_values_builder.h" #include "components/prefs/pref_service.h" @@ -194,6 +195,7 @@ void CoreOobeHandler::RegisterMessages() { &CoreOobeHandler::HandleSetOobeBootstrappingSlave); AddRawCallback("getPrimaryDisplayNameForTesting", &CoreOobeHandler::HandleGetPrimaryDisplayNameForTesting); + AddCallback("setupDemoMode", &CoreOobeHandler::HandleSetupDemoMode); } void CoreOobeHandler::ShowSignInError( @@ -211,34 +213,7 @@ void CoreOobeHandler::ShowTpmError() { } void CoreOobeHandler::ShowDeviceResetScreen() { - // Powerwash is generally not available on enterprise devices. First, check - // the common case of a correctly enrolled device. - if (g_browser_process->platform_part() - ->browser_policy_connector_chromeos() - ->IsEnterpriseManaged()) { - // Powerwash not allowed, except if allowed by the admin specifically for - // the purpose of installing a TPM firmware update. - tpm_firmware_update::ShouldOfferUpdateViaPowerwash( - base::Bind([](bool offer_update) { - if (offer_update) { - // Force the TPM firmware update option to be enabled. - g_browser_process->local_state()->SetBoolean( - prefs::kFactoryResetTPMFirmwareUpdateRequested, true); - LaunchResetScreen(); - } - })); - return; - } - - // Devices that are still in OOBE may be subject to forced re-enrollment (FRE) - // and thus pending for enterprise management. These should not be allowed to - // powerwash either. Note that taking consumer device ownership has the side - // effect of dropping the FRE requirement if it was previously in effect. - const AutoEnrollmentController::FRERequirement requirement = - AutoEnrollmentController::GetFRERequirement(); - if (requirement != AutoEnrollmentController::EXPLICITLY_REQUIRED) { - LaunchResetScreen(); - } + LaunchResetScreen(); } void CoreOobeHandler::ShowEnableDebuggingScreen() { @@ -402,7 +377,35 @@ void CoreOobeHandler::HandleSkipToUpdateForTesting() { } void CoreOobeHandler::HandleToggleResetScreen() { - ShowDeviceResetScreen(); + // Powerwash is generally not available on enterprise devices. First, check + // the common case of a correctly enrolled device. + if (g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->IsEnterpriseManaged()) { + // Powerwash is only available if allowed by the admin specifically for the + // purpose of installing a TPM firmware update. + tpm_firmware_update::ShouldOfferUpdateViaPowerwash( + base::BindOnce([](bool offer_update) { + if (offer_update) { + // Force the TPM firmware update option to be enabled. + g_browser_process->local_state()->SetBoolean( + prefs::kFactoryResetTPMFirmwareUpdateRequested, true); + LaunchResetScreen(); + } + }), + base::TimeDelta()); + return; + } + + // Devices that are still in OOBE may be subject to forced re-enrollment (FRE) + // and thus pending for enterprise management. These should not be allowed to + // powerwash either. Note that taking consumer device ownership has the side + // effect of dropping the FRE requirement if it was previously in effect. + const AutoEnrollmentController::FRERequirement requirement = + AutoEnrollmentController::GetFRERequirement(); + if (requirement != AutoEnrollmentController::EXPLICITLY_REQUIRED) { + LaunchResetScreen(); + } } void CoreOobeHandler::HandleEnableDebuggingScreen() { @@ -575,6 +578,15 @@ void CoreOobeHandler::HandleGetPrimaryDisplayNameForTesting( ResolveJavascriptCallback(*callback_id, base::Value(display_name)); } +void CoreOobeHandler::HandleSetupDemoMode() { + const bool is_demo_mode_enabled = + base::CommandLine::ForCurrentProcess()->HasSwitch( + chromeos::switches::kEnableDemoMode); + if (is_demo_mode_enabled) { + NOTIMPLEMENTED(); + } +} + void CoreOobeHandler::InitDemoModeDetection() { demo_mode_detector_.InitDetection(); } diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h index 2579735ea1f..795c016df47 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h @@ -130,6 +130,7 @@ class CoreOobeHandler : public BaseWebUIHandler, void HandleHeaderBarVisible(); void HandleSetOobeBootstrappingSlave(); void HandleGetPrimaryDisplayNameForTesting(const base::ListValue* args); + void HandleSetupDemoMode(); // When keyboard_utils.js arrow key down event is reached, raise it // to tab/shift-tab event. diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc index 07a8fc05c1f..3f3a9c1e27a 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc @@ -39,8 +39,8 @@ #include "content/public/browser/browser_thread.h" #include "content/public/common/service_manager_connection.h" #include "mojo/public/cpp/bindings/interface_request.h" -#include "services/device/public/interfaces/constants.mojom.h" -#include "services/device/public/interfaces/wake_lock_provider.mojom.h" +#include "services/device/public/mojom/constants.mojom.h" +#include "services/device/public/mojom/wake_lock_provider.mojom.h" #include "services/service_manager/public/cpp/connector.h" #include "third_party/cros_system_api/dbus/service_constants.h" #include "ui/base/text/bytes_formatting.h" @@ -255,7 +255,7 @@ namespace chromeos { EncryptionMigrationScreenHandler::EncryptionMigrationScreenHandler() : BaseScreenHandler(kScreenId), - tick_clock_(std::make_unique<base::DefaultTickClock>()), + tick_clock_(base::DefaultTickClock::GetInstance()), weak_ptr_factory_(this) { set_call_js_prefix(kJsScreenPath); free_disk_space_fetcher_ = base::Bind(&base::SysInfo::AmountOfFreeDiskSpace, @@ -386,8 +386,8 @@ void EncryptionMigrationScreenHandler::SetFreeDiskSpaceFetcherForTesting( } void EncryptionMigrationScreenHandler::SetTickClockForTesting( - std::unique_ptr<base::TickClock> tick_clock) { - tick_clock_ = std::move(tick_clock); + base::TickClock* tick_clock) { + tick_clock_ = tick_clock; } void EncryptionMigrationScreenHandler::RegisterMessages() { @@ -581,11 +581,13 @@ void EncryptionMigrationScreenHandler::StartMigration() { void EncryptionMigrationScreenHandler::OnMountExistingVault( base::Optional<cryptohome::BaseReply> reply) { - if (cryptohome::BaseReplyToMountError(reply) != - cryptohome::MOUNT_ERROR_NONE) { + cryptohome::MountError return_code = + cryptohome::MountExReplyToMountError(reply); + if (return_code != cryptohome::MOUNT_ERROR_NONE) { RecordMigrationResultMountFailure(IsResumingIncompleteMigration(), IsArcKiosk()); UpdateUIState(UIState::MIGRATION_FAILED); + LOG(ERROR) << "Mount existing vault failed. Error: " << return_code; return; } diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h index e117c206705..48ce6f487b4 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h @@ -19,7 +19,7 @@ #include "chromeos/dbus/cryptohome_client.h" #include "chromeos/dbus/power_manager_client.h" #include "chromeos/login/auth/user_context.h" -#include "services/device/public/interfaces/wake_lock.mojom.h" +#include "services/device/public/mojom/wake_lock.mojom.h" #include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h" namespace base { @@ -64,7 +64,9 @@ class EncryptionMigrationScreenHandler : public EncryptionMigrationScreenView, FreeDiskSpaceFetcher free_disk_space_fetcher); // Testing only: Sets the tick clock used to measure elapsed time during // migration. - void SetTickClockForTesting(std::unique_ptr<base::TickClock> tick_clock); + // This doesn't toke the ownership of the clock. |tick_clock| must outlive the + // EncryptionMigrationScreenHandler instance. + void SetTickClockForTesting(base::TickClock* tick_clock); virtual device::mojom::WakeLock* GetWakeLock(); @@ -180,7 +182,7 @@ class EncryptionMigrationScreenHandler : public EncryptionMigrationScreenView, std::unique_ptr<LoginFeedback> login_feedback_; // Used to measure elapsed time during migration. - std::unique_ptr<base::TickClock> tick_clock_; + base::TickClock* tick_clock_; FreeDiskSpaceFetcher free_disk_space_fetcher_; diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler_unittest.cc b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler_unittest.cc index bc669e2c0ca..1e60a10482e 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler_unittest.cc @@ -69,9 +69,7 @@ class TestEncryptionMigrationScreenHandler SetFreeDiskSpaceFetcherForTesting(base::BindRepeating( &TestEncryptionMigrationScreenHandler::FreeDiskSpaceFetcher, base::Unretained(this))); - auto tick_clock = std::make_unique<base::SimpleTestTickClock>(); - testing_tick_clock_ = tick_clock.get(); - SetTickClockForTesting(std::move(tick_clock)); + SetTickClockForTesting(&testing_tick_clock_); } // Sets the testing WebUI. @@ -87,7 +85,7 @@ class TestEncryptionMigrationScreenHandler // Returns the SimpleTestTickClock used to simulate time elapsed during // migration. base::SimpleTestTickClock* testing_tick_clock() { - return testing_tick_clock_; + return &testing_tick_clock_; } FakeWakeLock* fake_wake_lock() { return &fake_wake_lock_; } @@ -102,9 +100,8 @@ class TestEncryptionMigrationScreenHandler FakeWakeLock fake_wake_lock_; - // Non-owned pointer. Tick clock used to simulate time elapsed during - // migration. This is actually owned by the base class. - base::SimpleTestTickClock* testing_tick_clock_; + // Tick clock used to simulate time elapsed during migration. + base::SimpleTestTickClock testing_tick_clock_; int64_t free_disk_space_; }; @@ -235,6 +232,9 @@ TEST_F(EncryptionMigrationScreenHandlerTest, MinimalMigration) { EXPECT_TRUE(fake_cryptohome_client_->minimal_migration()); EXPECT_EQ(cryptohome::Identification(user_context_.GetAccountId()), fake_cryptohome_client_->get_id_for_disk_migrated_to_dircrypto()); + EXPECT_EQ( + user_context_.GetKey()->GetSecret(), + fake_cryptohome_client_->get_secret_for_last_mount_authentication()); } // Tests handling of a resumed minimal migration run. This should behave the @@ -256,6 +256,9 @@ TEST_F(EncryptionMigrationScreenHandlerTest, ResumeMinimalMigration) { EXPECT_TRUE(fake_cryptohome_client_->minimal_migration()); EXPECT_EQ(cryptohome::Identification(user_context_.GetAccountId()), fake_cryptohome_client_->get_id_for_disk_migrated_to_dircrypto()); + EXPECT_EQ( + user_context_.GetKey()->GetSecret(), + fake_cryptohome_client_->get_secret_for_last_mount_authentication()); } // Tests handling of a minimal migration run that takes a long time to finish. @@ -279,6 +282,9 @@ TEST_F(EncryptionMigrationScreenHandlerTest, MinimalMigrationSlow) { EXPECT_TRUE(fake_cryptohome_client_->minimal_migration()); EXPECT_EQ(cryptohome::Identification(user_context_.GetAccountId()), fake_cryptohome_client_->get_id_for_disk_migrated_to_dircrypto()); + EXPECT_EQ( + user_context_.GetKey()->GetSecret(), + fake_cryptohome_client_->get_secret_for_last_mount_authentication()); } // Tests handling of a minimal migration run that fails. @@ -304,6 +310,9 @@ TEST_F(EncryptionMigrationScreenHandlerTest, MinimalMigrationFails) { EXPECT_TRUE(fake_cryptohome_client_->minimal_migration()); EXPECT_EQ(cryptohome::Identification(user_context_.GetAccountId()), fake_cryptohome_client_->get_id_for_disk_migrated_to_dircrypto()); + EXPECT_EQ( + user_context_.GetKey()->GetSecret(), + fake_cryptohome_client_->get_secret_for_last_mount_authentication()); } } // namespace chromeos diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc index 654ddbb4409..8ecb85b0cf4 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc @@ -112,6 +112,37 @@ std::string GetEnterpriseDisplayDomain() { return connector->GetEnterpriseDisplayDomain(); } +constexpr struct { + int title_id; + int subtitle_id; + authpolicy::KerberosEncryptionTypes encryption_types; +} kEncryptionTypes[] = { + {IDS_AD_ENCRYPTION_STRONG_TITLE, IDS_AD_ENCRYPTION_STRONG_SUBTITLE, + authpolicy::KerberosEncryptionTypes::ENC_TYPES_STRONG}, + {IDS_AD_ENCRYPTION_ALL_TITLE, IDS_AD_ENCRYPTION_ALL_SUBTITLE, + authpolicy::KerberosEncryptionTypes::ENC_TYPES_ALL}, + {IDS_AD_ENCRYPTION_LEGACY_TITLE, IDS_AD_ENCRYPTION_LEGACY_SUBTITLE, + authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY}}; + +std::unique_ptr<base::ListValue> GetEncryptionTypesList() { + const authpolicy::KerberosEncryptionTypes default_types = + authpolicy::KerberosEncryptionTypes::ENC_TYPES_STRONG; + auto encryption_list = std::make_unique<base::ListValue>(); + for (const auto& enc_types : kEncryptionTypes) { + auto enc_option = std::make_unique<base::DictionaryValue>(); + enc_option->SetKey( + "title", base::Value(l10n_util::GetStringUTF16(enc_types.title_id))); + enc_option->SetKey( + "subtitle", + base::Value(l10n_util::GetStringUTF16(enc_types.subtitle_id))); + enc_option->SetKey("value", base::Value(enc_types.encryption_types)); + enc_option->SetKey( + "selected", base::Value(default_types == enc_types.encryption_types)); + encryption_list->Append(std::move(enc_option)); + } + return encryption_list; +} + } // namespace // EnrollmentScreenHandler, public ------------------------------ @@ -438,6 +469,12 @@ void EnrollmentScreenHandler::DeclareLocalizedValues( IDS_ENTERPRISE_ENROLLMENT_KIOSK_LICENSE_TYPE); builder->Add("licenseCountTemplate", IDS_ENTERPRISE_ENROLLMENT_LICENSES_REMAINING_TEMPLATE); + builder->Add("selectEncryption", IDS_AD_ENCRYPTION_SELECTION_SELECT); +} + +void EnrollmentScreenHandler::GetAdditionalParameters( + base::DictionaryValue* parameters) { + parameters->Set("encryptionTypesList", GetEncryptionTypesList()); } bool EnrollmentScreenHandler::IsOnEnrollmentScreen() const { @@ -574,13 +611,14 @@ void EnrollmentScreenHandler::HandleCompleteLogin( void EnrollmentScreenHandler::HandleAdCompleteLogin( const std::string& machine_name, const std::string& distinguished_name, + int encryption_types, const std::string& user_name, const std::string& password) { observe_network_failure_ = false; DCHECK(controller_); DCHECK(authpolicy_login_helper_); authpolicy_login_helper_->JoinAdDomain( - machine_name, distinguished_name, user_name, password, + machine_name, distinguished_name, encryption_types, user_name, password, base::BindOnce(&EnrollmentScreenHandler::HandleAdDomainJoin, weak_ptr_factory_.GetWeakPtr(), machine_name, user_name)); } @@ -639,6 +677,9 @@ void EnrollmentScreenHandler::HandleAdDomainJoin( case authpolicy::ERROR_SETTING_OU_FAILED: ShowError(IDS_AD_OU_SETTING_FAILED, true); return; + case authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE: + ShowError(IDS_AD_NOT_SUPPORTED_ENCRYPTION, true); + return; #if !defined(ARCH_CPU_X86_64) // Currently, the Active Directory integration is only supported on x86_64 // systems. (see https://crbug.com/676602) diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h index e97c73e7e4d..93bfb3f7289 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h @@ -72,6 +72,7 @@ class EnrollmentScreenHandler void Initialize() override; void DeclareLocalizedValues( ::login::LocalizedValuesBuilder* builder) override; + void GetAdditionalParameters(base::DictionaryValue* parameters) override; // Implements NetworkStateInformer::NetworkStateInformerObserver void UpdateState(NetworkError::ErrorReason reason) override; @@ -84,6 +85,7 @@ class EnrollmentScreenHandler const std::string& auth_code); void HandleAdCompleteLogin(const std::string& machine_name, const std::string& distinguished_name, + int encryption_types, const std::string& user_name, const std::string& password); void HandleRetry(); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc index e5e0153399f..b54c7d66bc8 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc @@ -21,16 +21,20 @@ #include "chrome/browser/browser_shutdown.h" #include "chrome/browser/chromeos/language_preferences.h" #include "chrome/browser/chromeos/login/lock_screen_utils.h" +#include "chrome/browser/chromeos/login/reauth_stats.h" #include "chrome/browser/chromeos/login/screens/network_error.h" #include "chrome/browser/chromeos/login/signin_partition_manager.h" +#include "chrome/browser/chromeos/login/ui/login_display_host.h" +#include "chrome/browser/chromeos/login/ui/login_display_host_webui.h" #include "chrome/browser/chromeos/login/ui/user_adding_screen.h" #include "chrome/browser/chromeos/login/users/chrome_user_manager.h" #include "chrome/browser/chromeos/net/network_portal_detector_impl.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" -#include "chrome/browser/chromeos/policy/untrusted_authority_certs_cache.h" +#include "chrome/browser/chromeos/policy/temp_certs_cache_nss.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/io_thread.h" +#include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h" @@ -280,7 +284,9 @@ void GaiaScreenHandler::MaybePreloadAuthExtension() { if (!network_portal_detector_) { NetworkPortalDetectorImpl* detector = new NetworkPortalDetectorImpl( - g_browser_process->system_request_context(), false); + g_browser_process->system_network_context_manager() + ->GetURLLoaderFactory(), + false); detector->set_portal_test_url(GURL(kRestrictiveProxyURL)); network_portal_detector_.reset(detector); network_portal_detector_->AddObserver(this); @@ -345,6 +351,11 @@ void GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent( UpdateAuthParams(¶ms, IsRestrictiveProxy()); + int user_count = LoginDisplayHost::default_host() + ? LoginDisplayHost::default_host()->GetUsers().size() + : 0; + params.SetInteger("userCount", user_count); + GaiaScreenMode screen_mode = GetGaiaScreenMode(context.email, context.use_offline); params.SetInteger("screenMode", screen_mode); @@ -450,7 +461,7 @@ void GaiaScreenHandler::DeclareLocalizedValues( builder->Add("whitelistErrorEnterprise", IDS_ENTERPRISE_LOGIN_ERROR_WHITELIST); builder->Add("tryAgainButton", IDS_WHITELIST_ERROR_TRY_AGAIN_BUTTON); - builder->Add("learnMoreButton", IDS_WHITELIST_ERROR_LEARN_MORE_BUTTON); + builder->Add("learnMoreButton", IDS_LEARN_MORE); builder->Add("gaiaLoading", IDS_LOGIN_GAIA_LOADING_MESSAGE); // Strings used by the SAML fatal error dialog. @@ -529,6 +540,11 @@ void GaiaScreenHandler::RegisterMessages() { &GaiaScreenHandler::HandleCompleteAdAuthentication); AddCallback("cancelAdAuthentication", &GaiaScreenHandler::HandleCancelActiveDirectoryAuth); + AddRawCallback("showAddUser", &GaiaScreenHandler::HandleShowAddUser); + AddCallback("updateGaiaDialogSize", + &GaiaScreenHandler::HandleUpdateGaiaDialogSize); + AddCallback("updateGaiaDialogVisibility", + &GaiaScreenHandler::HandleUpdateGaiaDialogVisibility); // Allow UMA metrics collection from JS. web_ui()->AddMessageHandler(std::make_unique<MetricsHandler>()); @@ -553,9 +569,12 @@ void GaiaScreenHandler::OnPortalDetectionCompleted( } void GaiaScreenHandler::HandleIdentifierEntered(const std::string& user_email) { - if (!Delegate()->IsUserWhitelisted(user_manager::known_user::GetAccountId( - user_email, std::string() /* id */, AccountType::UNKNOWN))) + if (LoginDisplayHost::default_host() && + !LoginDisplayHost::default_host()->IsUserWhitelisted( + user_manager::known_user::GetAccountId( + user_email, std::string() /* id */, AccountType::UNKNOWN))) { ShowWhitelistCheckFailedError(); + } } void GaiaScreenHandler::HandleAuthExtensionLoaded() { @@ -626,24 +645,24 @@ void GaiaScreenHandler::DoAdAuth( switch (error) { case authpolicy::ERROR_NONE: { DCHECK(account_info.has_account_id() && - !account_info.account_id().empty()); + !account_info.account_id().empty() && + LoginDisplayHost::default_host()); const AccountId account_id(GetAccountId( username, account_info.account_id(), AccountType::ACTIVE_DIRECTORY)); - Delegate()->SetDisplayAndGivenName(account_info.display_name(), - account_info.given_name()); + LoginDisplayHost::default_host()->SetDisplayAndGivenName( + account_info.display_name(), account_info.given_name()); UserContext user_context(account_id); user_context.SetKey(key); user_context.SetAuthFlow(UserContext::AUTH_FLOW_ACTIVE_DIRECTORY); user_context.SetIsUsingOAuth(false); user_context.SetUserType( user_manager::UserType::USER_TYPE_ACTIVE_DIRECTORY); - Delegate()->CompleteLogin(user_context); + LoginDisplayHost::default_host()->CompleteLogin(user_context); break; } case authpolicy::ERROR_PASSWORD_EXPIRED: DCHECK(active_directory_password_change_screen_handler_); - active_directory_password_change_screen_handler_->ShowScreen(username, - Delegate()); + active_directory_password_change_screen_handler_->ShowScreen(username); break; case authpolicy::ERROR_PARSE_UPN_FAILED: case authpolicy::ERROR_BAD_USER_NAME: @@ -667,7 +686,9 @@ void GaiaScreenHandler::DoAdAuth( void GaiaScreenHandler::HandleCompleteAdAuthentication( const std::string& username, const std::string& password) { - Delegate()->SetDisplayEmail(username); + if (LoginDisplayHost::default_host()) + LoginDisplayHost::default_host()->SetDisplayEmail(username); + set_populated_email(username); DCHECK(authpolicy_login_helper_); authpolicy_login_helper_->AuthenticateUser( @@ -688,22 +709,29 @@ void GaiaScreenHandler::HandleCompleteAuthentication( const std::string& auth_code, bool using_saml, const std::string& gaps_cookie) { - if (!Delegate()) + if (!LoginDisplayHost::default_host()) return; DCHECK(!email.empty()); DCHECK(!gaia_id.empty()); const std::string sanitized_email = gaia::SanitizeEmail(email); - Delegate()->SetDisplayEmail(sanitized_email); + LoginDisplayHost::default_host()->SetDisplayEmail(sanitized_email); UserContext user_context(GetAccountId(email, gaia_id, AccountType::GOOGLE)); user_context.SetKey(Key(password)); + // Only save the password for enterprise users. See https://crbug.com/386606. + const bool is_enterprise_managed = g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->IsEnterpriseManaged(); + if (is_enterprise_managed) { + user_context.SetPasswordKey(Key(password)); + } user_context.SetAuthCode(auth_code); user_context.SetAuthFlow(using_saml ? UserContext::AUTH_FLOW_GAIA_WITH_SAML : UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML); user_context.SetGAPSCookie(gaps_cookie); - Delegate()->CompleteLogin(user_context); + LoginDisplayHost::default_host()->CompleteLogin(user_context); } void GaiaScreenHandler::HandleCompleteLogin(const std::string& gaia_id, @@ -748,15 +776,48 @@ void GaiaScreenHandler::HandleGaiaUIReady() { if (test_expects_complete_login_) SubmitLoginFormForTest(); - if (Delegate()) - Delegate()->OnGaiaScreenReady(); + + if (LoginDisplayHost::default_host()) + LoginDisplayHost::default_host()->OnGaiaScreenReady(); +} + +void GaiaScreenHandler::HandleUpdateGaiaDialogSize(int width, int height) { + if (LoginDisplayHost::default_host()) + LoginDisplayHost::default_host()->UpdateGaiaDialogSize(width, height); +} + +void GaiaScreenHandler::HandleUpdateGaiaDialogVisibility(bool visible) { + if (LoginDisplayHost::default_host()) + LoginDisplayHost::default_host()->UpdateGaiaDialogVisibility(visible); +} + +void GaiaScreenHandler::HandleShowAddUser(const base::ListValue* args) { + // TODO(xiaoyinh): Add trace event for gaia webui in views login screen. + TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI", + LoginDisplayHostWebUI::kShowLoginWebUIid, + "ShowAddUser"); + + std::string email; + // |args| can be null if it's OOBE. + if (args) + args->GetString(0, &email); + set_populated_email(email); + if (!email.empty()) + SendReauthReason(AccountId::FromUserEmail(email)); + OnShowAddUser(); +} + +void GaiaScreenHandler::OnShowAddUser() { + signin_screen_handler_->is_account_picker_showing_first_time_ = false; + lock_screen_utils::EnforcePolicyInputMethods(std::string()); + ShowGaiaAsync(); } void GaiaScreenHandler::DoCompleteLogin(const std::string& gaia_id, const std::string& typed_email, const std::string& password, bool using_saml) { - if (!Delegate()) + if (!LoginDisplayHost::default_host()) return; if (using_saml && !using_saml_api_) @@ -765,14 +826,14 @@ void GaiaScreenHandler::DoCompleteLogin(const std::string& gaia_id, DCHECK(!typed_email.empty()); DCHECK(!gaia_id.empty()); const std::string sanitized_email = gaia::SanitizeEmail(typed_email); - Delegate()->SetDisplayEmail(sanitized_email); + LoginDisplayHost::default_host()->SetDisplayEmail(sanitized_email); UserContext user_context( GetAccountId(typed_email, gaia_id, AccountType::GOOGLE)); user_context.SetKey(Key(password)); user_context.SetAuthFlow(using_saml ? UserContext::AUTH_FLOW_GAIA_WITH_SAML : UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML); - Delegate()->CompleteLogin(user_context); + LoginDisplayHost::default_host()->CompleteLogin(user_context); if (test_expects_complete_login_) { VLOG(2) << "Complete test login for " << typed_email @@ -837,7 +898,7 @@ void GaiaScreenHandler::ShowSigninScreenForTest(const std::string& username, if (frame_state() == GaiaScreenHandler::FRAME_STATE_LOADED) { SubmitLoginFormForTest(); } else if (frame_state() != GaiaScreenHandler::FRAME_STATE_LOADING) { - signin_screen_handler_->OnShowAddUser(); + OnShowAddUser(); } } @@ -886,10 +947,9 @@ void GaiaScreenHandler::CancelShowGaiaAsync() { } void GaiaScreenHandler::ShowGaiaScreenIfReady() { - if (!dns_cleared_ || - !cookies_cleared_ || + if (!dns_cleared_ || !cookies_cleared_ || !show_when_dns_and_cookies_cleared_ || - !Delegate()) { + !LoginDisplayHost::default_host()) { return; } @@ -903,10 +963,11 @@ void GaiaScreenHandler::ShowGaiaScreenIfReady() { // Note that LoadAuthExtension clears |populated_email_|. if (populated_email_.empty()) { - Delegate()->LoadSigninWallpaper(); + LoginDisplayHost::default_host()->LoadSigninWallpaper(); } else { - Delegate()->LoadWallpaper(user_manager::known_user::GetAccountId( - populated_email_, std::string() /* id */, AccountType::UNKNOWN)); + LoginDisplayHost::default_host()->LoadWallpaper( + user_manager::known_user::GetAccountId( + populated_email_, std::string() /* id */, AccountType::UNKNOWN)); } input_method::InputMethodManager* imm = @@ -953,8 +1014,8 @@ void GaiaScreenHandler::ShowGaiaScreenIfReady() { // When the WebUI is destroyed, |untrusted_authority_certs_cache_| will go // out of scope and the certificates will not be held in memory anymore. untrusted_authority_certs_cache_ = - std::make_unique<policy::UntrustedAuthorityCertsCache>( - policy::UntrustedAuthorityCertsCache:: + std::make_unique<policy::TempCertsCacheNSS>( + policy::TempCertsCacheNSS:: GetUntrustedAuthoritiesFromDeviceOncPolicy()); } @@ -1027,10 +1088,6 @@ void GaiaScreenHandler::UpdateState(NetworkError::ErrorReason reason) { signin_screen_handler_->UpdateState(reason); } -SigninScreenHandlerDelegate* GaiaScreenHandler::Delegate() { - return signin_screen_handler_->delegate_; -} - bool GaiaScreenHandler::IsRestrictiveProxy() const { return !disable_restrictive_proxy_check_for_test_ && !IsOnline(captive_portal_status_); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h index 749c2de039e..f4c4c5150bc 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h @@ -21,7 +21,7 @@ class AccountId; namespace policy { -class UntrustedAuthorityCertsCache; +class TempCertsCacheNSS; } namespace chromeos { @@ -29,7 +29,6 @@ namespace chromeos { class ActiveDirectoryPasswordChangeScreenHandler; class Key; class SigninScreenHandler; -class SigninScreenHandlerDelegate; // A class that handles WebUI hooks in Gaia screen. class GaiaScreenHandler : public BaseScreenHandler, @@ -53,9 +52,10 @@ class GaiaScreenHandler : public BaseScreenHandler, // GaiaView: void MaybePreloadAuthExtension() override; void DisableRestrictiveProxyCheckForTest() override; + void ShowGaiaAsync() override; private: - // TODO (antrim@): remove this dependency. + // TODO (xiaoyinh): remove this dependency. friend class SigninScreenHandler; struct GaiaContext; @@ -127,6 +127,10 @@ class GaiaScreenHandler : public BaseScreenHandler, void HandleIdentifierEntered(const std::string& account_identifier); void HandleAuthExtensionLoaded(); + void HandleUpdateGaiaDialogSize(int width, int height); + void HandleUpdateGaiaDialogVisibility(bool visible); + void HandleShowAddUser(const base::ListValue* args); + void OnShowAddUser(); // Really handles the complete login message. void DoCompleteLogin(const std::string& gaia_id, @@ -163,13 +167,6 @@ class GaiaScreenHandler : public BaseScreenHandler, // principals API was used during SAML login. void SetSAMLPrincipalsAPIUsed(bool api_used); - // Show the sign-in screen. Depending on internal state, the screen will - // either be shown immediately or after an asynchronous clean-up process that - // cleans DNS cache and cookies. In the latter case, the request to show the - // screen can be canceled by calling CancelShowGaiaAsync() while the clean-up - // is in progress. - void ShowGaiaAsync(); - // Cancels the request to show the sign-in screen while the asynchronous // clean-up process that precedes the screen showing is in progress. void CancelShowGaiaAsync(); @@ -194,8 +191,6 @@ class GaiaScreenHandler : public BaseScreenHandler, // Are we on a restrictive proxy? bool IsRestrictiveProxy() const; - SigninScreenHandlerDelegate* Delegate(); - // Returns temporary unused device Id. std::string GetTemporaryDeviceId(); @@ -283,8 +278,7 @@ class GaiaScreenHandler : public BaseScreenHandler, // Makes untrusted authority certificates from device policy available for // client certificate discovery. - std::unique_ptr<policy::UntrustedAuthorityCertsCache> - untrusted_authority_certs_cache_; + std::unique_ptr<policy::TempCertsCacheNSS> untrusted_authority_certs_cache_; base::WeakPtrFactory<GaiaScreenHandler> weak_factory_; diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.cc index 9b1091eeacb..11d88571163 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.cc @@ -103,8 +103,19 @@ void NetworkDropdownHandler::HandleLaunchAddWiFiNetworkDialog() { void NetworkDropdownHandler::HandleShowNetworkDetails( const base::ListValue* args) { - std::string guid; - args->GetString(0, &guid); + std::string type, guid; + args->GetString(0, &type); + args->GetString(1, &guid); + if (type == ::onc::network_type::kCellular) { + // Make sure Cellular is enabled. + NetworkStateHandler* handler = + NetworkHandler::Get()->network_state_handler(); + if (handler->GetTechnologyState(NetworkTypePattern::Cellular()) != + NetworkStateHandler::TECHNOLOGY_ENABLED) { + handler->SetTechnologyEnabled(NetworkTypePattern::Cellular(), true, + network_handler::ErrorCallback()); + } + } InternetDetailDialog::ShowDialog(guid); } diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc index 8e783732f12..8225c73c1ea 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/network_screen_handler.cc @@ -147,8 +147,6 @@ void NetworkScreenHandler::DeclareLocalizedValues( builder->Add("networkScreenGreeting", IDS_WELCOME_SCREEN_GREETING); builder->Add("networkScreenTitle", IDS_WELCOME_SCREEN_TITLE); - builder->Add("networkScreenAccessibleTitle", - IDS_NETWORK_SCREEN_ACCESSIBLE_TITLE); builder->Add("selectLanguage", IDS_LANGUAGE_SELECTION_SELECT); builder->Add("selectKeyboard", IDS_KEYBOARD_SELECTION_SELECT); builder->Add("selectNetwork", IDS_NETWORK_SELECTION_SELECT); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/network_state_informer.cc b/chromium/chrome/browser/ui/webui/chromeos/login/network_state_informer.cc index e235b926c97..5bf72ab80fd 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/network_state_informer.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/network_state_informer.cc @@ -17,7 +17,7 @@ #include "chromeos/network/proxy/ui_proxy_config_service.h" #include "components/proxy_config/proxy_config_dictionary.h" #include "components/proxy_config/proxy_prefs.h" -#include "net/proxy/proxy_config.h" +#include "net/proxy_resolution/proxy_config.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc index 93b5b1f5a49..62fe9bd2e68 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc @@ -23,8 +23,7 @@ namespace chromeos { namespace { bool TouchSupportAvailable(const display::Display& display) { - return display.touch_support() == - display::Display::TouchSupport::TOUCH_SUPPORT_AVAILABLE; + return display.touch_support() == display::Display::TouchSupport::AVAILABLE; } // TODO(felixe): More context at crbug.com/738885 diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc index 55379f6367d..e63d5176a4f 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser_unittest.cc @@ -97,7 +97,7 @@ TEST_F(OobeDisplayChooserTest, DontSwitchFromTouch) { display::ManagedDisplayInfo::CreateFromSpecWithID("0+0-3000x2000", 1)); display_info.push_back( display::ManagedDisplayInfo::CreateFromSpecWithID("3000+0-800x600", 2)); - display_info[0].set_touch_support(display::Display::TOUCH_SUPPORT_AVAILABLE); + display_info[0].set_touch_support(display::Display::TouchSupport::AVAILABLE); display_manager()->OnNativeDisplaysChanged(display_info); base::RunLoop().RunUntilIdle(); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc index b55575d7d73..87a5e5e2571 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc @@ -96,7 +96,8 @@ const char* kKnownDisplayTypes[] = {OobeUI::kOobeDisplay, OobeUI::kLockDisplay, OobeUI::kUserAddingDisplay, OobeUI::kAppLaunchSplashDisplay, - OobeUI::kArcKioskSplashDisplay}; + OobeUI::kArcKioskSplashDisplay, + OobeUI::kGaiaSigninDisplay}; const char kStringsJSPath[] = "strings.js"; const char kLockJSPath[] = "lock.js"; @@ -108,10 +109,6 @@ const char kCustomElementsJSPath[] = "custom_elements.js"; const char kCustomElementsUserPodHTMLPath[] = "custom_elements_user_pod.html"; // Paths for deferred resource loading. -const char kCustomElementsPinKeyboardHTMLPath[] = - "custom_elements/pin_keyboard.html"; -const char kCustomElementsPinKeyboardJSPath[] = - "custom_elements/pin_keyboard.js"; const char kEnrollmentHTMLPath[] = "enrollment.html"; const char kEnrollmentCSSPath[] = "enrollment.css"; const char kEnrollmentJSPath[] = "enrollment.js"; @@ -143,42 +140,23 @@ content::WebUIDataSource* CreateOobeUIDataSource( IDR_CUSTOM_ELEMENTS_OOBE_HTML); source->AddResourcePath(kCustomElementsJSPath, IDR_CUSTOM_ELEMENTS_OOBE_JS); } else if (display_type == OobeUI::kLockDisplay) { - if (command_line->HasSwitch(chromeos::switches::kShowNonMdLogin)) { - source->SetDefaultResource(IDR_LOCK_HTML); - source->AddResourcePath(kLockJSPath, IDR_LOCK_JS); - source->AddResourcePath(kCustomElementsPinKeyboardHTMLPath, - IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_HTML); - source->AddResourcePath(kCustomElementsPinKeyboardJSPath, - IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_JS); - } else { - source->SetDefaultResource(IDR_MD_LOCK_HTML); - source->AddResourcePath(kLockJSPath, IDR_MD_LOCK_JS); - source->AddResourcePath(kCustomElementsPinKeyboardHTMLPath, - IDR_MD_CUSTOM_ELEMENTS_PIN_KEYBOARD_HTML); - source->AddResourcePath(kCustomElementsPinKeyboardJSPath, - IDR_MD_CUSTOM_ELEMENTS_PIN_KEYBOARD_JS); - } + // TODO(crbug.com/810170): Remove the resource files associated with + // kShowNonMdLogin switch (IDR_LOCK_HTML/JS and IDR_LOGIN_HTML/JS and the + // files those use). + source->SetDefaultResource(IDR_MD_LOCK_HTML); + source->AddResourcePath(kLockJSPath, IDR_MD_LOCK_JS); source->AddResourcePath(kCustomElementsHTMLPath, IDR_CUSTOM_ELEMENTS_LOCK_HTML); source->AddResourcePath(kCustomElementsJSPath, IDR_CUSTOM_ELEMENTS_LOCK_JS); source->AddResourcePath(kCustomElementsUserPodHTMLPath, IDR_CUSTOM_ELEMENTS_USER_POD_HTML); } else { - if (command_line->HasSwitch(chromeos::switches::kShowNonMdLogin)) { - source->SetDefaultResource(IDR_LOGIN_HTML); - source->AddResourcePath(kLoginJSPath, IDR_LOGIN_JS); - } else { - source->SetDefaultResource(IDR_MD_LOGIN_HTML); - source->AddResourcePath(kLoginJSPath, IDR_MD_LOGIN_JS); - } + source->SetDefaultResource(IDR_MD_LOGIN_HTML); + source->AddResourcePath(kLoginJSPath, IDR_MD_LOGIN_JS); source->AddResourcePath(kCustomElementsHTMLPath, IDR_CUSTOM_ELEMENTS_LOGIN_HTML); source->AddResourcePath(kCustomElementsJSPath, IDR_CUSTOM_ELEMENTS_LOGIN_JS); - source->AddResourcePath(kCustomElementsPinKeyboardHTMLPath, - IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_HTML); - source->AddResourcePath(kCustomElementsPinKeyboardJSPath, - IDR_CUSTOM_ELEMENTS_PIN_KEYBOARD_JS); source->AddResourcePath(kCustomElementsUserPodHTMLPath, IDR_CUSTOM_ELEMENTS_USER_POD_HTML); } @@ -245,6 +223,7 @@ const char OobeUI::kLockDisplay[] = "lock"; const char OobeUI::kUserAddingDisplay[] = "user-adding"; const char OobeUI::kAppLaunchSplashDisplay[] = "app-launch-splash"; const char OobeUI::kArcKioskSplashDisplay[] = "arc-kiosk-splash"; +const char OobeUI::kGaiaSigninDisplay[] = "gaia-signin"; OobeUI::OobeUI(content::WebUI* web_ui, const GURL& url) : WebUIController(web_ui) { diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.h b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.h index 0f97d1acbf9..4a1eb7d2094 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/oobe_ui.h @@ -79,6 +79,7 @@ class OobeUI : public content::WebUIController, static const char kUserAddingDisplay[]; static const char kAppLaunchSplashDisplay[]; static const char kArcKioskSplashDisplay[]; + static const char kGaiaSigninDisplay[]; class Observer { public: diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc index 07b957a72b2..aebb3b11c28 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc @@ -56,7 +56,6 @@ #include "chrome/browser/chromeos/login/ui/login_display_webui.h" #include "chrome/browser/chromeos/login/ui/login_feedback.h" #include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h" -#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h" #include "chrome/browser/chromeos/login/wizard_controller.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/device_local_account.h" @@ -92,6 +91,7 @@ #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/proximity_auth/screenlock_bridge.h" +#include "components/strings/grit/components_strings.h" #include "components/user_manager/known_user.h" #include "components/user_manager/user.h" #include "components/user_manager/user_manager.h" @@ -418,7 +418,7 @@ void SigninScreenHandler::DeclareLocalizedValues( IDS_LOGIN_PUBLIC_ACCOUNT_ENTER_ACCESSIBLE_NAME); builder->Add("publicAccountMonitoringWarning", IDS_LOGIN_PUBLIC_ACCOUNT_MONITORING_WARNING); - builder->Add("publicAccountLearnMore", IDS_LOGIN_PUBLIC_ACCOUNT_LEARN_MORE); + builder->Add("publicAccountLearnMore", IDS_LEARN_MORE); builder->Add("publicAccountMonitoringInfo", IDS_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO); builder->Add("publicAccountMonitoringInfoItem1", @@ -494,7 +494,6 @@ void SigninScreenHandler::RegisterMessages() { &SigninScreenHandler::HandleLaunchPublicSession); AddRawCallback("offlineLogin", &SigninScreenHandler::HandleOfflineLogin); AddCallback("rebootSystem", &SigninScreenHandler::HandleRebootSystem); - AddRawCallback("showAddUser", &SigninScreenHandler::HandleShowAddUser); AddCallback("shutdownSystem", &SigninScreenHandler::HandleShutdownSystem); AddCallback("removeUser", &SigninScreenHandler::HandleRemoveUser); AddCallback("toggleEnrollmentScreen", @@ -622,7 +621,7 @@ void SigninScreenHandler::ShowImpl() { if (oobe_ui_) { // Shows new user sign-in for OOBE. - OnShowAddUser(); + gaia_screen_handler_->OnShowAddUser(); } else { // Populates account picker. Animation is turned off for now until we // figure out how to make it fast enough. This will call LoadUsers. @@ -987,7 +986,7 @@ void SigninScreenHandler::OnUserRemoved(const AccountId& account_id, bool last_user_removed) { CallJS("login.AccountPickerScreen.removeUser", account_id); if (last_user_removed) - OnShowAddUser(); + gaia_screen_handler_->OnShowAddUser(); } void SigninScreenHandler::OnUserImageChanged(const user_manager::User& user) { @@ -1018,7 +1017,7 @@ void SigninScreenHandler::OnPreferencesChanged() { !delegate_->IsShowUsers()) { // We are at the account picker screen and the POD setting has changed // to be disabled. We need to show the add user page. - HandleShowAddUser(nullptr); + gaia_screen_handler_->HandleShowAddUser(nullptr); return; } @@ -1182,6 +1181,13 @@ void SigninScreenHandler::HandleAuthenticateUser(const AccountId& account_id, UserContext user_context(account_id); user_context.SetKey(Key(password)); + // Only save the password for enterprise users. See https://crbug.com/386606. + const bool is_enterprise_managed = g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->IsEnterpriseManaged(); + if (is_enterprise_managed) { + user_context.SetPasswordKey(Key(password)); + } user_context.SetIsUsingPin(authenticated_by_pin); const user_manager::User* user = user_manager::UserManager::Get()->FindUser(account_id); @@ -1269,20 +1275,6 @@ void SigninScreenHandler::HandleRemoveUser(const AccountId& account_id) { UpdateAddButtonStatus(); } -void SigninScreenHandler::HandleShowAddUser(const base::ListValue* args) { - TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI", - LoginDisplayHostWebUI::kShowLoginWebUIid, - "ShowAddUser"); - std::string email; - // |args| can be null if it's OOBE. - if (args) - args->GetString(0, &email); - gaia_screen_handler_->set_populated_email(email); - if (!email.empty()) - SendReauthReason(AccountId::FromUserEmail(email)); - OnShowAddUser(); -} - void SigninScreenHandler::HandleToggleEnrollmentScreen() { if (delegate_) delegate_->ShowEnterpriseEnrollmentScreen(); @@ -1433,7 +1425,7 @@ void SigninScreenHandler::HandleLoginUIStateChanged(const std::string& source, // On slow devices, the wallpaper animation is not shown initially, so we // must explicitly load the wallpaper. This is also the case for the // account-picker and gaia-signin UI states. - delegate_->LoadSigninWallpaper(); + LoginDisplayHost::default_host()->LoadSigninWallpaper(); HandleToggleKioskAutolaunchScreen(); return; } @@ -1477,8 +1469,8 @@ void SigninScreenHandler::HandleFocusPod(const AccountId& account_id, lock_screen_utils::SetUserInputMethod(account_id.GetUserEmail(), ime_state_.get()); lock_screen_utils::SetKeyboardSettings(account_id); - if (delegate_ && load_wallpaper) - delegate_->LoadWallpaper(account_id); + if (LoginDisplayHost::default_host() && load_wallpaper) + LoginDisplayHost::default_host()->LoadWallpaper(account_id); bool use_24hour_clock = false; if (user_manager::known_user::GetBooleanPref( @@ -1650,12 +1642,6 @@ bool SigninScreenHandler::IsGuestSigninAllowed() const { return allow_guest; } -void SigninScreenHandler::OnShowAddUser() { - is_account_picker_showing_first_time_ = false; - lock_screen_utils::EnforcePolicyInputMethods(std::string()); - gaia_screen_handler_->ShowGaiaAsync(); -} - net::Error SigninScreenHandler::FrameError() const { return gaia_screen_handler_->frame_error(); } diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h index d63b0652fe9..b2a51bc7ad8 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h @@ -143,18 +143,10 @@ class SigninScreenHandlerDelegate { // Signs out if the screen is currently locked. virtual void Signout() = 0; - // --------------- Account creation methods. - // Confirms sign up by provided credentials in |user_context|. - // Used for new user login via GAIA extension. - virtual void CompleteLogin(const UserContext& user_context) = 0; - // --------------- Shared with login display methods. // Notify the delegate when the sign-in UI is finished loading. virtual void OnSigninScreenReady() = 0; - // Notify the delegate when the GAIA UI is finished loading. - virtual void OnGaiaScreenReady() = 0; - // Shows Enterprise Enrollment screen. virtual void ShowEnterpriseEnrollmentScreen() = 0; @@ -173,26 +165,10 @@ class SigninScreenHandlerDelegate { // Show update required screen. virtual void ShowUpdateRequiredScreen() = 0; - // Sets the displayed email for the next login attempt. If it succeeds, - // user's displayed email value will be updated to |email|. - virtual void SetDisplayEmail(const std::string& email) = 0; - - // Sets the displayed name and given name for the next login attempt. If it - // succeeds, user's displayed name and give name values will be updated to - // |display_name| and |given_name|. - virtual void SetDisplayAndGivenName(const std::string& display_name, - const std::string& given_name) = 0; - // --------------- Rest of the methods. // Cancels user adding. virtual void CancelUserAdding() = 0; - // Load wallpaper for given |account_id|. - virtual void LoadWallpaper(const AccountId& account_id) = 0; - - // Loads the default sign-in wallpaper. - virtual void LoadSigninWallpaper() = 0; - // Attempts to remove given user. virtual void RemoveUser(const AccountId& account_id) = 0; @@ -224,9 +200,6 @@ class SigninScreenHandlerDelegate { // Runs an OAuth token validation check for user. virtual void CheckUserStatus(const AccountId& account_id) = 0; - // Returns true if user is allowed to log in by domain policy. - virtual bool IsUserWhitelisted(const AccountId& account_id) = 0; - protected: virtual ~SigninScreenHandlerDelegate() {} }; @@ -398,7 +371,6 @@ class SigninScreenHandler void HandleShutdownSystem(); void HandleRebootSystem(); void HandleRemoveUser(const AccountId& account_id); - void HandleShowAddUser(const base::ListValue* args); void HandleToggleEnrollmentScreen(); void HandleToggleEnrollmentAd(); void HandleToggleEnableDebuggingScreen(); @@ -468,9 +440,6 @@ class SigninScreenHandler bool ShouldLoadGaia() const; - // Shows signin. - void OnShowAddUser(); - net::Error FrameError() const; // input_method::ImeKeyboard::Observer implementation: diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc b/chromium/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc index f80653a91ac..54e29d5cec6 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc @@ -12,7 +12,6 @@ #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" #include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h" #include "chrome/browser/chromeos/login/users/multi_profile_user_controller_delegate.h" -#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #include "chrome/browser/ui/ash/test_wallpaper_controller.h" @@ -68,7 +67,6 @@ class SigninPrepareUserListTest : public ash::AshTestBase, fake_user_manager_->set_owner_id(AccountId::FromUserEmail(kOwner)); - chromeos::WallpaperManager::Initialize(); chromeos::DeviceSettingsService::Initialize(); chromeos::CrosSettings::Initialize(); wallpaper_controller_client_ = @@ -78,7 +76,6 @@ class SigninPrepareUserListTest : public ash::AshTestBase, } void TearDown() override { - chromeos::WallpaperManager::Shutdown(); controller_.reset(); profile_manager_.reset(); wallpaper_controller_client_.reset(); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc index f6838cd88dd..1936b235f4c 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc @@ -15,8 +15,8 @@ #include "chrome/browser/chromeos/login/supervised/supervised_user_creation_flow.h" #include "chrome/browser/chromeos/login/users/chrome_user_manager.h" #include "chrome/browser/chromeos/login/users/supervised_user_manager.h" -#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/ui/ash/wallpaper_controller_client.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" #include "chrome/grit/generated_resources.h" @@ -300,7 +300,7 @@ void SupervisedUserCreationScreenHandler::HandleManagerSelected( const AccountId& manager_id) { if (!delegate_) return; - WallpaperManager::Get()->ShowUserWallpaper(manager_id); + WallpaperControllerClient::Get()->ShowUserWallpaper(manager_id); } void SupervisedUserCreationScreenHandler::HandleImportUserSelected( diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc index 825e79595a6..4d1b2f4a302 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.cc @@ -35,31 +35,10 @@ void SyncConsentScreenHandler::DeclareLocalizedValues( builder->Add( "syncConsentScreenPersonalizeGoogleServicesDescription", IDS_LOGIN_SYNC_CONSENT_SCREEN_PERSONALIZE_GOOGLE_SERVICES_DESCRIPTION); - builder->Add("syncConsentScreenSettingsLink", - IDS_LOGIN_SYNC_CONSENT_SCREEN_SETTINGS_LINK); - builder->Add("syncConsentSettingsDialogTitle", - IDS_LOGIN_SYNC_CONSENT_SETTINGS_TITLE); - builder->Add("syncConsentSettingsDialogSubTitle", - IDS_LOGIN_SYNC_CONSENT_SETTINGS_SUBTITLE); - builder->Add("syncConsentSyncAllOptionTitle", - IDS_LOGIN_SYNC_CONSENT_SYNC_ALL_OPTION); - builder->Add("syncConsentSyncAllOptionOn", - IDS_LOGIN_SYNC_CONSENT_SYNC_ALL_OPTION_ON); - builder->Add("syncConsentSyncAllOptionOff", - IDS_LOGIN_SYNC_CONSENT_SYNC_ALL_OPTION_OFF); - builder->Add("syncConsentSettingsStatusSyncAllOn", - IDS_LOGIN_SYNC_CONSENT_STATUS_SYNC_ALL_ON); - builder->Add("syncConsentSettingsStatusSyncAllOff", - IDS_LOGIN_SYNC_CONSENT_STATUS_SYNC_ALL_OFF); - builder->Add("syncConsentSettingsSaveAndContinue", - IDS_LOGIN_SYNC_CONSENT_SAVE_AND_CONTINUE); -} - -void SyncConsentScreenHandler::RegisterMessages() { - BaseScreenHandler::RegisterMessages(); - - AddCallback("syncEverythingChanged", - &SyncConsentScreenHandler::HandleSyncEverythingChanged); + builder->Add("syncConsentReviewSyncOptionsText", + IDS_LOGIN_SYNC_CONSENT_SCREEN_REVIEW_SYNC_OPTIONS_LATER); + builder->Add("syncConsentAcceptAndContinue", + IDS_LOGIN_SYNC_CONSENT_SCREEN_ACCEPT_AND_CONTINUE); } void SyncConsentScreenHandler::Bind(SyncConsentScreen* screen) { @@ -75,14 +54,4 @@ void SyncConsentScreenHandler::Hide() {} void SyncConsentScreenHandler::Initialize() {} -void SyncConsentScreenHandler::HandleSyncEverythingChanged( - bool sync_everything) { - screen_->SetSyncAllValue(sync_everything); -} - -void SyncConsentScreenHandler::OnUserPrefKnown(bool sync_everything, - bool is_managed) { - CallJS("onUserSyncPrefsKnown", sync_everything, is_managed); -} - } // namespace chromeos diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.h b/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.h index 993601791f1..6e8242bf95e 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.h +++ b/chromium/chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.h @@ -24,21 +24,15 @@ class SyncConsentScreenHandler : public BaseScreenHandler, void DeclareLocalizedValues( ::login::LocalizedValuesBuilder* builder) override; - // WebUIMessageHandler: - void RegisterMessages() override; - // SyncConsentScreenView: void Bind(SyncConsentScreen* screen) override; void Show() override; void Hide() override; - void OnUserPrefKnown(bool sync_everything, bool is_managed) override; private: // BaseScreenHandler: void Initialize() override; - // WebUI message handlers: - void HandleSyncEverythingChanged(bool sync_everything); SyncConsentScreen* screen_ = nullptr; DISALLOW_COPY_AND_ASSIGN(SyncConsentScreenHandler); diff --git a/chromium/chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.cc b/chromium/chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.cc index e927f9b8cdc..a1afa85efa7 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.cc @@ -21,6 +21,7 @@ #include "chrome/common/pref_names.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" +#include "components/language/core/common/locale_util.h" #include "components/login/localized_values_builder.h" #include "components/prefs/pref_service.h" #include "components/user_manager/user.h" @@ -81,12 +82,13 @@ void TermsOfServiceScreenHandler::Show() { return; } - const std::string locale = + std::string locale = ProfileHelper::Get() ->GetProfileByUserUnsafe( user_manager::UserManager::Get()->GetActiveUser()) ->GetPrefs() ->GetString(prefs::kApplicationLocale); + language::ConvertToActualUILocale(&locale); if (locale.empty() || locale == g_browser_process->GetApplicationLocale()) { // If the user has not chosen a UI locale yet or the chosen locale matches diff --git a/chromium/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc b/chromium/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc index 3bca92cad9a..fc02a996fb7 100644 --- a/chromium/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc +++ b/chromium/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc @@ -32,6 +32,7 @@ struct { {"networkListItemConnecting", IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING}, {"networkListItemConnectingTo", IDS_NETWORK_LIST_CONNECTING_TO}, {"networkListItemInitializing", IDS_NETWORK_LIST_INITIALIZING}, + {"networkListItemScanning", IDS_SETTINGS_INTERNET_MOBILE_SEARCH}, {"networkListItemNotConnected", IDS_NETWORK_LIST_NOT_CONNECTED}, {"networkListItemNoNetwork", IDS_NETWORK_LIST_NO_NETWORK}, {"vpnNameTemplate", IDS_NETWORK_LIST_THIRD_PARTY_VPN_NAME_TEMPLATE}, @@ -294,6 +295,9 @@ void AddErrorLocalizedStrings(content::WebUIDataSource* html_source) { {"Error.PolicyControlled", IDS_NETWORK_ERROR_POLICY_CONTROLLED}, {"networkErrorNoUserCertificate", IDS_NETWORK_ERROR_NO_USER_CERT}, {"networkErrorUnknown", IDS_NETWORK_ERROR_UNKNOWN}, + // TODO(stevenjb): Move this id to settings_strings.grdp: + {"networkErrorNotHardwareBacked", + IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_REQUIRE_HARDWARE_BACKED}, }; for (const auto& entry : localized_strings) html_source->AddLocalizedString(entry.name, entry.id); diff --git a/chromium/chrome/browser/ui/webui/components_ui.cc b/chromium/chrome/browser/ui/webui/components_ui.cc index 1d24e81c5a6..f58e2500837 100644 --- a/chromium/chrome/browser/ui/webui/components_ui.cc +++ b/chromium/chrome/browser/ui/webui/components_ui.cc @@ -214,6 +214,8 @@ base::string16 ComponentsUI::ComponentEventToString(Events event) { return l10n_util::GetStringUTF16(IDS_COMPONENTS_EVT_STATUS_UPDATED); case Events::COMPONENT_NOT_UPDATED: return l10n_util::GetStringUTF16(IDS_COMPONENTS_EVT_STATUS_NOTUPDATED); + case Events::COMPONENT_UPDATE_ERROR: + return l10n_util::GetStringUTF16(IDS_COMPONENTS_EVT_STATUS_UPDATE_ERROR); case Events::COMPONENT_UPDATE_DOWNLOADING: return l10n_util::GetStringUTF16(IDS_COMPONENTS_EVT_STATUS_DOWNLOADING); } @@ -245,7 +247,7 @@ base::string16 ComponentsUI::ServiceStatusToString( case update_client::ComponentState::kUpToDate: return l10n_util::GetStringUTF16(IDS_COMPONENTS_SVC_STATUS_UPTODATE); case update_client::ComponentState::kUpdateError: - return l10n_util::GetStringUTF16(IDS_COMPONENTS_SVC_STATUS_NOUPDATE); + return l10n_util::GetStringUTF16(IDS_COMPONENTS_SVC_STATUS_UPDATE_ERROR); case update_client::ComponentState::kUninstalled: // Fall through. case update_client::ComponentState::kRun: case update_client::ComponentState::kLastStatus: diff --git a/chromium/chrome/browser/ui/webui/devtools_ui.cc b/chromium/chrome/browser/ui/webui/devtools_ui.cc index 37c210a2fdc..4bae28351f1 100644 --- a/chromium/chrome/browser/ui/webui/devtools_ui.cc +++ b/chromium/chrome/browser/ui/webui/devtools_ui.cc @@ -4,17 +4,21 @@ #include "chrome/browser/ui/webui/devtools_ui.h" +#include <list> +#include <utility> + #include "base/command_line.h" #include "base/macros.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "chrome/browser/devtools/url_constants.h" -#include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" +#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/devtools_frontend_host.h" +#include "content/public/browser/storage_partition.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" @@ -22,9 +26,8 @@ #include "net/base/filename_util.h" #include "net/base/load_flags.h" #include "net/traffic_annotation/network_traffic_annotation.h" -#include "net/url_request/url_fetcher.h" -#include "net/url_request/url_fetcher_delegate.h" -#include "net/url_request/url_request_context_getter.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/simple_url_loader.h" #include "third_party/WebKit/public/public_features.h" using content::BrowserThread; @@ -37,7 +40,11 @@ std::string PathWithoutParams(const std::string& path) { .path().substr(1); } -const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n"; +scoped_refptr<base::RefCountedMemory> CreateNotFoundResponse() { + const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n"; + return base::MakeRefCounted<base::RefCountedStaticMemory>( + kHttpNotFound, strlen(kHttpNotFound)); +} // DevToolsDataSource --------------------------------------------------------- @@ -73,12 +80,13 @@ std::string GetMimeTypeForPath(const std::string& path) { // 2. /remote/: remote DevTools frontend is served from App Engine. // 3. /custom/: custom DevTools frontend is served from the server as specified // by the --custom-devtools-frontend flag. -class DevToolsDataSource : public content::URLDataSource, - public net::URLFetcherDelegate { +class DevToolsDataSource : public content::URLDataSource { public: using GotDataCallback = content::URLDataSource::GotDataCallback; - explicit DevToolsDataSource(net::URLRequestContextGetter* request_context); + explicit DevToolsDataSource( + scoped_refptr<content::SharedURLLoaderFactory> url_loader_factory) + : url_loader_factory_(std::move(url_loader_factory)) {} // content::URLDataSource implementation. std::string GetSource() const override; @@ -89,14 +97,18 @@ class DevToolsDataSource : public content::URLDataSource, const GotDataCallback& callback) override; private: + struct PendingRequest; + + ~DevToolsDataSource() override = default; + // content::URLDataSource overrides. std::string GetMimeType(const std::string& path) const override; bool ShouldAddContentSecurityPolicy() const override; bool ShouldDenyXFrameOptions() const override; bool ShouldServeMimeTypeAsContentTypeHeader() const override; - // net::URLFetcherDelegate overrides. - void OnURLFetchComplete(const net::URLFetcher* source) override; + void OnLoadComplete(std::list<PendingRequest>::iterator request_iter, + std::unique_ptr<std::string> response_body); // Serves bundled DevTools frontend from ResourceBundle. void StartBundledDataRequest(const std::string& path, @@ -110,28 +122,33 @@ class DevToolsDataSource : public content::URLDataSource, void StartCustomDataRequest(const GURL& url, const GotDataCallback& callback); - ~DevToolsDataSource() override; + void StartNetworkRequest( + const GURL& url, + const net::NetworkTrafficAnnotationTag& traffic_annotation, + int load_flags, + const GotDataCallback& callback); - scoped_refptr<net::URLRequestContextGetter> request_context_; + struct PendingRequest { + PendingRequest() = default; + PendingRequest(PendingRequest&& other) = default; + PendingRequest& operator=(PendingRequest&& other) = default; - using PendingRequestsMap = std::map<const net::URLFetcher*, GotDataCallback>; - PendingRequestsMap pending_; + ~PendingRequest() { + if (callback) + callback.Run(CreateNotFoundResponse()); + } - DISALLOW_COPY_AND_ASSIGN(DevToolsDataSource); -}; + GotDataCallback callback; + std::unique_ptr<network::SimpleURLLoader> loader; -DevToolsDataSource::DevToolsDataSource( - net::URLRequestContextGetter* request_context) - : request_context_(request_context) { -} + DISALLOW_COPY_AND_ASSIGN(PendingRequest); + }; -DevToolsDataSource::~DevToolsDataSource() { - for (const auto& pair : pending_) { - delete pair.first; - pair.second.Run( - new base::RefCountedStaticMemory(kHttpNotFound, strlen(kHttpNotFound))); - } -} + scoped_refptr<content::SharedURLLoaderFactory> url_loader_factory_; + std::list<PendingRequest> pending_requests_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsDataSource); +}; std::string DevToolsDataSource::GetSource() const { return chrome::kChromeUIDevToolsHost; @@ -171,8 +188,7 @@ void DevToolsDataSource::StartDataRequest( StartRemoteDataRequest(url, callback); } else { DLOG(ERROR) << "Refusing to load invalid remote front-end URL"; - callback.Run(new base::RefCountedStaticMemory(kHttpNotFound, - strlen(kHttpNotFound))); + callback.Run(CreateNotFoundResponse()); } return; } @@ -261,20 +277,15 @@ void DevToolsDataSource::StartRemoteDataRequest( } } })"); - net::URLFetcher* fetcher = net::URLFetcher::Create(url, net::URLFetcher::GET, - this, traffic_annotation) - .release(); - pending_[fetcher] = callback; - fetcher->SetRequestContext(request_context_.get()); - fetcher->Start(); + + StartNetworkRequest(url, traffic_annotation, net::LOAD_NORMAL, callback); } void DevToolsDataSource::StartCustomDataRequest( const GURL& url, const content::URLDataSource::GotDataCallback& callback) { if (!url.is_valid()) { - callback.Run( - new base::RefCountedStaticMemory(kHttpNotFound, strlen(kHttpNotFound))); + callback.Run(CreateNotFoundResponse()); return; } net::NetworkTrafficAnnotationTag traffic_annotation = @@ -305,24 +316,38 @@ void DevToolsDataSource::StartCustomDataRequest( } } })"); - net::URLFetcher* fetcher = net::URLFetcher::Create(url, net::URLFetcher::GET, - this, traffic_annotation) - .release(); - pending_[fetcher] = callback; - fetcher->SetRequestContext(request_context_.get()); - fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); - fetcher->Start(); + + StartNetworkRequest(url, traffic_annotation, net::LOAD_DISABLE_CACHE, + callback); } -void DevToolsDataSource::OnURLFetchComplete(const net::URLFetcher* source) { - DCHECK(source); - PendingRequestsMap::iterator it = pending_.find(source); - DCHECK(it != pending_.end()); - std::string response; - source->GetResponseAsString(&response); - delete source; - it->second.Run(base::RefCountedString::TakeString(&response)); - pending_.erase(it); +void DevToolsDataSource::StartNetworkRequest( + const GURL& url, + const net::NetworkTrafficAnnotationTag& traffic_annotation, + int load_flags, + const GotDataCallback& callback) { + auto request = std::make_unique<network::ResourceRequest>(); + request->url = url; + request->load_flags = load_flags; + + auto request_iter = pending_requests_.emplace(pending_requests_.begin()); + request_iter->callback = callback; + request_iter->loader = + network::SimpleURLLoader::Create(std::move(request), traffic_annotation); + request_iter->loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( + url_loader_factory_.get(), + base::BindOnce(&DevToolsDataSource::OnLoadComplete, + base::Unretained(this), request_iter)); +} + +void DevToolsDataSource::OnLoadComplete( + std::list<PendingRequest>::iterator request_iter, + std::unique_ptr<std::string> response_body) { + std::move(request_iter->callback) + .Run(response_body + ? base::RefCountedString::TakeString(response_body.get()) + : CreateNotFoundResponse()); + pending_requests_.erase(request_iter); } } // namespace @@ -377,11 +402,11 @@ bool DevToolsUI::IsFrontendResourceURL(const GURL& url) { DevToolsUI::DevToolsUI(content::WebUI* web_ui) : WebUIController(web_ui), bindings_(web_ui->GetWebContents()) { web_ui->SetBindings(0); - Profile* profile = Profile::FromWebUI(web_ui); - content::URLDataSource::Add( - profile, - new DevToolsDataSource(profile->GetRequestContext())); + auto factory = content::BrowserContext::GetDefaultStoragePartition( + web_ui->GetWebContents()->GetBrowserContext()) + ->GetURLLoaderFactoryForBrowserProcess(); + content::URLDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(), + new DevToolsDataSource(std::move(factory))); } -DevToolsUI::~DevToolsUI() { -} +DevToolsUI::~DevToolsUI() = default; diff --git a/chromium/chrome/browser/ui/webui/discards/discards_ui.cc b/chromium/chrome/browser/ui/webui/discards/discards_ui.cc index d2377955f7c..2d53a02fc19 100644 --- a/chromium/chrome/browser/ui/webui/discards/discards_ui.cc +++ b/chromium/chrome/browser/ui/webui/discards/discards_ui.cc @@ -4,7 +4,9 @@ #include "chrome/browser/ui/webui/discards/discards_ui.h" -#include "base/memory/ptr_util.h" +#include <utility> +#include <vector> + #include "base/strings/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" @@ -23,15 +25,11 @@ namespace { -namespace { - resource_coordinator::DiscardReason GetDiscardReason(bool urgent) { return urgent ? resource_coordinator::DiscardReason::kUrgent : resource_coordinator::DiscardReason::kProactive; } -} // namespace - class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider { public: // This instance is deleted when the supplied pipe is destroyed. @@ -129,5 +127,6 @@ DiscardsUI::DiscardsUI(content::WebUI* web_ui) DiscardsUI::~DiscardsUI() {} void DiscardsUI::BindUIHandler(mojom::DiscardsDetailsProviderRequest request) { - ui_handler_.reset(new DiscardsDetailsProviderImpl(std::move(request))); + ui_handler_ = + std::make_unique<DiscardsDetailsProviderImpl>(std::move(request)); } diff --git a/chromium/chrome/browser/ui/webui/discards/discards_ui.h b/chromium/chrome/browser/ui/webui/discards/discards_ui.h index 442fd982277..212c6646059 100644 --- a/chromium/chrome/browser/ui/webui/discards/discards_ui.h +++ b/chromium/chrome/browser/ui/webui/discards/discards_ui.h @@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_UI_WEBUI_DISCARDS_DISCARDS_UI_H_ #define CHROME_BROWSER_UI_WEBUI_DISCARDS_DISCARDS_UI_H_ +#include <memory> + #include "base/macros.h" #include "chrome/browser/ui/webui/discards/discards.mojom.h" #include "chrome/browser/ui/webui/mojo_web_ui_controller.h" diff --git a/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc b/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc index 4330fe7e091..9988db2e568 100644 --- a/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc +++ b/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc @@ -5,11 +5,14 @@ #include "chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h" #include "base/bind.h" +#include "base/guid.h" #include "base/values.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/profiles/profile.h" -#include "components/download/public/download_service.h" +#include "components/download/public/background_service/download_params.h" +#include "components/download/public/background_service/download_service.h" #include "content/public/browser/web_ui.h" +#include "net/traffic_annotation/network_traffic_annotation.h" namespace download_internals { @@ -24,12 +27,19 @@ DownloadInternalsUIMessageHandler::~DownloadInternalsUIMessageHandler() { void DownloadInternalsUIMessageHandler::RegisterMessages() { web_ui()->RegisterMessageCallback( "getServiceStatus", - base::Bind(&DownloadInternalsUIMessageHandler::HandleGetServiceStatus, - weak_ptr_factory_.GetWeakPtr())); + base::BindRepeating( + &DownloadInternalsUIMessageHandler::HandleGetServiceStatus, + weak_ptr_factory_.GetWeakPtr())); web_ui()->RegisterMessageCallback( "getServiceDownloads", - base::Bind(&DownloadInternalsUIMessageHandler::HandleGetServiceDownloads, - weak_ptr_factory_.GetWeakPtr())); + base::BindRepeating( + &DownloadInternalsUIMessageHandler::HandleGetServiceDownloads, + weak_ptr_factory_.GetWeakPtr())); + web_ui()->RegisterMessageCallback( + "startDownload", + base::BindRepeating( + &DownloadInternalsUIMessageHandler::HandleStartDownload, + weak_ptr_factory_.GetWeakPtr())); Profile* profile = Profile::FromWebUI(web_ui()); download_service_ = DownloadServiceFactory::GetForBrowserContext(profile); @@ -96,4 +106,45 @@ void DownloadInternalsUIMessageHandler::HandleGetServiceDownloads( *callback_id, download_service_->GetLogger()->GetServiceDownloads()); } +void DownloadInternalsUIMessageHandler::HandleStartDownload( + const base::ListValue* args) { + CHECK_GT(args->GetList().size(), 1u) << "Missing argument download URL."; + GURL url = GURL(args->GetList()[1].GetString()); + if (!url.is_valid()) { + LOG(WARNING) << "Can't parse download URL, try to enter a valid URL."; + return; + } + + download::DownloadParams params; + params.guid = base::GenerateGUID(); + params.client = download::DownloadClient::DEBUGGING; + params.request_params.method = "GET"; + params.request_params.url = url; + + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation("download_internals_webui_source", R"( + semantics { + sender: "Download Internals Page" + description: + "Starts a download with background download service in WebUI." + trigger: + "User clicks on the download button in " + "chrome://download-internals." + data: "None" + destination: WEBSITE + } + policy { + cookies_allowed: YES + cookies_store: "user" + setting: "This feature cannot be disabled by settings." + policy_exception_justification: "Not implemented." + })"); + + params.traffic_annotation = + net::MutableNetworkTrafficAnnotationTag(traffic_annotation); + + DCHECK(download_service_); + download_service_->StartDownload(params); +} + } // namespace download_internals diff --git a/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h b/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h index cd8b9641cba..ac0a3c645b6 100644 --- a/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h +++ b/chromium/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h @@ -7,7 +7,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "components/download/public/logger.h" +#include "components/download/public/background_service/logger.h" #include "content/public/browser/web_ui_message_handler.h" namespace download { @@ -39,6 +39,9 @@ class DownloadInternalsUIMessageHandler : public content::WebUIMessageHandler, void HandleGetServiceStatus(const base::ListValue* args); void HandleGetServiceDownloads(const base::ListValue* args); + // Starts a background download. + void HandleStartDownload(const base::ListValue* args); + download::DownloadService* download_service_; base::WeakPtrFactory<DownloadInternalsUIMessageHandler> weak_ptr_factory_; diff --git a/chromium/chrome/browser/ui/webui/engagement/site_engagement_ui.cc b/chromium/chrome/browser/ui/webui/engagement/site_engagement_ui.cc index aa84e9ea54c..0b5991d6885 100644 --- a/chromium/chrome/browser/ui/webui/engagement/site_engagement_ui.cc +++ b/chromium/chrome/browser/ui/webui/engagement/site_engagement_ui.cc @@ -84,7 +84,7 @@ SiteEngagementUI::SiteEngagementUI(content::WebUI* web_ui) source->AddResourcePath( "chrome/browser/engagement/site_engagement_details.mojom.js", IDR_SITE_ENGAGEMENT_MOJO_JS); - source->AddResourcePath("url/mojo/url.mojom.js", IDR_URL_MOJO_JS); + source->AddResourcePath("url/mojom/url.mojom.js", IDR_URL_MOJO_JS); source->SetDefaultResource(IDR_SITE_ENGAGEMENT_HTML); source->UseGzip(); content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source.release()); diff --git a/chromium/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_browsertest.js b/chromium/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_browsertest.js index 993adb1a55a..85ec336b42c 100644 --- a/chromium/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_browsertest.js +++ b/chromium/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_browsertest.js @@ -3,6 +3,7 @@ // found in the LICENSE file. GEN('#include "base/command_line.h"'); +GEN('#include "chrome/common/chrome_features.h"'); /** * TestFixture for kiosk app settings WebUI testing. @@ -24,6 +25,9 @@ KioskAppSettingsWebUITest.prototype = { switchName: 'enable-consumer-kiosk', }], + /** @override */ + featureList: ['', 'features::kMaterialDesignExtensions'], + /** * Mock settings data. * @private diff --git a/chromium/chrome/browser/ui/webui/extensions/extension_settings_browsertest.js b/chromium/chrome/browser/ui/webui/extensions/extension_settings_browsertest.js index d0c34c854d1..7d4bef5721f 100644 --- a/chromium/chrome/browser/ui/webui/extensions/extension_settings_browsertest.js +++ b/chromium/chrome/browser/ui/webui/extensions/extension_settings_browsertest.js @@ -6,6 +6,7 @@ GEN('#include "chrome/browser/ui/webui/extensions/' + 'extension_settings_browsertest.h"'); +GEN('#include "chrome/common/chrome_features.h"'); // The id of the extension from |InstallGoodExtension|. var GOOD_EXTENSION_ID = 'ldnnhddmnhbkjipkidpdiheffobcpfmf'; @@ -50,6 +51,9 @@ ExtensionSettingsWebUITest.prototype = { typedefCppFixture: 'ExtensionSettingsUIBrowserTest', /** @override */ + featureList: ['', 'features::kMaterialDesignExtensions'], + + /** @override */ setUp: function() { testing.Test.prototype.setUp.call(this); testing.Test.disableAnimationsAndTransitions(); diff --git a/chromium/chrome/browser/ui/webui/extensions/extension_settings_handler.cc b/chromium/chrome/browser/ui/webui/extensions/extension_settings_handler.cc index b3c34254a83..8e9efd24aa9 100644 --- a/chromium/chrome/browser/ui/webui/extensions/extension_settings_handler.cc +++ b/chromium/chrome/browser/ui/webui/extensions/extension_settings_handler.cc @@ -13,7 +13,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/apps/app_info_dialog.h" #include "chrome/browser/ui/browser.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/chromium_strings.h" diff --git a/chromium/chrome/browser/ui/webui/extensions/extensions_ui.cc b/chromium/chrome/browser/ui/webui/extensions/extensions_ui.cc index a55a100e8a3..207eb0edb4a 100644 --- a/chromium/chrome/browser/ui/webui/extensions/extensions_ui.cc +++ b/chromium/chrome/browser/ui/webui/extensions/extensions_ui.cc @@ -203,6 +203,10 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) { IDS_MD_EXTENSIONS_DEPENDENT_ENTRY); source->AddLocalizedString("itemDetails", IDS_MD_EXTENSIONS_ITEM_DETAILS); source->AddLocalizedString("itemErrors", IDS_MD_EXTENSIONS_ITEM_ERRORS); + source->AddLocalizedString("accessibilityErrorLine", + IDS_MD_EXTENSIONS_ACCESSIBILITY_ERROR_LINE); + source->AddLocalizedString("accessibilityErrorMultiLine", + IDS_MD_EXTENSIONS_ACCESSIBILITY_ERROR_MULTI_LINE); source->AddLocalizedString("appIcon", IDS_MD_EXTENSIONS_APP_ICON); source->AddLocalizedString("extensionIcon", IDS_MD_EXTENSIONS_EXTENSION_ICON); source->AddLocalizedString("extensionA11yAssociation", @@ -253,7 +257,7 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) { l10n_util::GetStringFUTF16( IDS_EXTENSIONS_ADDED_WITHOUT_KNOWLEDGE, l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE))); - source->AddLocalizedString("learnMore", IDS_MD_EXTENSIONS_LEARN_MORE); + source->AddLocalizedString("learnMore", IDS_LEARN_MORE); source->AddLocalizedString( "loadErrorCouldNotLoadManifest", IDS_MD_EXTENSIONS_LOAD_ERROR_COULD_NOT_LOAD_MANIFEST); @@ -319,6 +323,8 @@ content::WebUIDataSource* CreateMdExtensionsSource(bool in_dev_mode) { IDS_MD_EXTENSIONS_TOOLBAR_UPDATE_NOW_TOOLTIP); source->AddLocalizedString("toolbarUpdateDone", IDS_MD_EXTENSIONS_TOOLBAR_UPDATE_DONE); + source->AddLocalizedString("toolbarUpdatingToast", + IDS_MD_EXTENSIONS_TOOLBAR_UPDATING_TOAST); source->AddLocalizedString( "updateRequiredByPolicy", IDS_MD_EXTENSIONS_DISABLED_UPDATE_REQUIRED_BY_POLICY); diff --git a/chromium/chrome/browser/ui/webui/extensions/install_extension_handler.cc b/chromium/chrome/browser/ui/webui/extensions/install_extension_handler.cc index 5395f3270da..953306e2712 100644 --- a/chromium/chrome/browser/ui/webui/extensions/install_extension_handler.cc +++ b/chromium/chrome/browser/ui/webui/extensions/install_extension_handler.cc @@ -9,11 +9,11 @@ #include "base/bind.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/chrome_zipfile_installer.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/unpacked_installer.h" -#include "chrome/browser/extensions/zipfile_installer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/web_contents.h" @@ -99,7 +99,9 @@ void InstallExtensionHandler::HandleInstallMessage( web_ui()->GetWebContents()->GetBrowserContext()); if (file_display_name_.MatchesExtension(FILE_PATH_LITERAL(".zip"))) { - ZipFileInstaller::Create(ExtensionSystem::Get(profile)->extension_service()) + ZipFileInstaller::Create( + MakeRegisterInExtensionServiceCallback( + ExtensionSystem::Get(profile)->extension_service())) ->LoadFromZipFile(file_to_install_); } else { std::unique_ptr<ExtensionInstallPrompt> prompt( diff --git a/chromium/chrome/browser/ui/webui/flags_ui.cc b/chromium/chrome/browser/ui/webui/flags_ui.cc index 63701c68fb3..cf719ba468f 100644 --- a/chromium/chrome/browser/ui/webui/flags_ui.cc +++ b/chromium/chrome/browser/ui/webui/flags_ui.cc @@ -46,10 +46,8 @@ #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/owner_flags_storage.h" -#include "chromeos/cryptohome/cryptohome_parameters.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "chromeos/dbus/session_manager_client.h" #include "components/pref_registry/pref_registry_syncable.h" +#include "components/signin/core/account_id/account_id.h" #include "components/user_manager/user_manager.h" #endif @@ -223,19 +221,20 @@ void FlagsDOMHandler::HandleRestartBrowser(const base::ListValue* args) { // Apply additional switches from policy that should not be dropped when // applying flags.. - chromeos::UserSessionManager::MaybeAppendPolicySwitches(&user_flags); + chromeos::UserSessionManager::MaybeAppendPolicySwitches( + Profile::FromWebUI(web_ui())->GetPrefs(), &user_flags); base::CommandLine::StringVector flags; // argv[0] is the program name |base::CommandLine::NO_PROGRAM|. flags.assign(user_flags.argv().begin() + 1, user_flags.argv().end()); VLOG(1) << "Restarting to apply per-session flags..."; - chromeos::DBusThreadManager::Get() - ->GetSessionManagerClient() - ->SetFlagsForUser( - cryptohome::Identification(user_manager::UserManager::Get() - ->GetActiveUser() - ->GetAccountId()), - flags); + AccountId account_id = + user_manager::UserManager::Get()->GetActiveUser()->GetAccountId(); + chromeos::UserSessionManager::GetInstance()->SetSwitchesForUser( + account_id, + chromeos::UserSessionManager::CommandLineSwitchesType:: + kPolicyAndFlagsAndKioskControl, + flags); #endif chrome::AttemptRestart(); } diff --git a/chromium/chrome/browser/ui/webui/foreign_session_handler.cc b/chromium/chrome/browser/ui/webui/foreign_session_handler.cc index 425ffff9c0a..cbae4e87be5 100644 --- a/chromium/chrome/browser/ui/webui/foreign_session_handler.cc +++ b/chromium/chrome/browser/ui/webui/foreign_session_handler.cc @@ -45,6 +45,31 @@ namespace { // Maximum number of sessions we're going to display on the NTP const size_t kMaxSessionsToShow = 10; +// Converts the DeviceType enum value to a string. This is used +// in the NTP handler for foreign sessions for matching session +// types to an icon style. +std::string DeviceTypeToString(sync_pb::SyncEnums::DeviceType device_type) { + switch (device_type) { + case sync_pb::SyncEnums::TYPE_UNSET: + break; + case sync_pb::SyncEnums::TYPE_WIN: + return "win"; + case sync_pb::SyncEnums::TYPE_MAC: + return "macosx"; + case sync_pb::SyncEnums::TYPE_LINUX: + return "linux"; + case sync_pb::SyncEnums::TYPE_CROS: + return "chromeos"; + case sync_pb::SyncEnums::TYPE_OTHER: + return "other"; + case sync_pb::SyncEnums::TYPE_PHONE: + return "phone"; + case sync_pb::SyncEnums::TYPE_TABLET: + return "tablet"; + } + return std::string(); +} + // Helper method to create JSON compatible objects from Session objects. std::unique_ptr<base::DictionaryValue> SessionTabToValue( const ::sessions::SessionTab& tab) { @@ -284,7 +309,8 @@ void ForeignSessionHandler::HandleGetForeignSessions( // remove any keys here. session_data->SetString("tag", session_tag); session_data->SetString("name", session->session_name); - session_data->SetString("deviceType", session->DeviceTypeAsString()); + session_data->SetString("deviceType", + DeviceTypeToString(session->device_type)); session_data->SetString("modifiedTime", FormatSessionTime(session->modified_time)); session_data->SetDouble("timestamp", session->modified_time.ToJsTime()); diff --git a/chromium/chrome/browser/ui/webui/help/version_updater_chromeos.cc b/chromium/chrome/browser/ui/webui/help/version_updater_chromeos.cc index 9a61c4f79d7..815a49eb16d 100644 --- a/chromium/chrome/browser/ui/webui/help/version_updater_chromeos.cc +++ b/chromium/chrome/browser/ui/webui/help/version_updater_chromeos.cc @@ -304,7 +304,7 @@ void VersionUpdaterCros::UpdateStatusChanged( break; case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING: progress = static_cast<int>(round(status.download_progress * 100)); - // Fall through. + FALLTHROUGH; case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE: my_status = UPDATING; break; diff --git a/chromium/chrome/browser/ui/webui/help/version_updater_mac.mm b/chromium/chrome/browser/ui/webui/help/version_updater_mac.mm index c7f0393ab93..243d5624542 100644 --- a/chromium/chrome/browser/ui/webui/help/version_updater_mac.mm +++ b/chromium/chrome/browser/ui/webui/help/version_updater_mac.mm @@ -191,7 +191,7 @@ void VersionUpdaterMac::UpdateStatus(NSDictionary* dictionary) { case kAutoupdateRegisterFailed: enable_promote_button = false; - // Fall through. + FALLTHROUGH; case kAutoupdateCheckFailed: case kAutoupdateInstallFailed: case kAutoupdatePromoteFailed: diff --git a/chromium/chrome/browser/ui/webui/inspect_ui.cc b/chromium/chrome/browser/ui/webui/inspect_ui.cc index 597ffcfa35e..ab19c4d204f 100644 --- a/chromium/chrome/browser/ui/webui/inspect_ui.cc +++ b/chromium/chrome/browser/ui/webui/inspect_ui.cc @@ -40,6 +40,7 @@ namespace { const char kInitUICommand[] = "init-ui"; const char kInspectCommand[] = "inspect"; +const char kInspectFallbackCommand[] = "inspect-fallback"; const char kInspectAdditionalCommand[] = "inspect-additional"; const char kActivateCommand[] = "activate"; const char kCloseCommand[] = "close"; @@ -90,6 +91,7 @@ class InspectMessageHandler : public WebUIMessageHandler { void HandleInitUICommand(const base::ListValue* args); void HandleInspectCommand(const base::ListValue* args); + void HandleInspectFallbackCommand(const base::ListValue* args); void HandleInspectAdditionalCommand(const base::ListValue* args); void HandleActivateCommand(const base::ListValue* args); void HandleCloseCommand(const base::ListValue* args); @@ -115,6 +117,10 @@ void InspectMessageHandler::RegisterMessages() { base::Bind(&InspectMessageHandler::HandleInspectCommand, base::Unretained(this))); web_ui()->RegisterMessageCallback( + kInspectFallbackCommand, + base::BindRepeating(&InspectMessageHandler::HandleInspectFallbackCommand, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( kInspectAdditionalCommand, base::Bind(&InspectMessageHandler::HandleInspectAdditionalCommand, base::Unretained(this))); @@ -177,6 +183,14 @@ void InspectMessageHandler::HandleInspectCommand(const base::ListValue* args) { inspect_ui_->Inspect(source, id); } +void InspectMessageHandler::HandleInspectFallbackCommand( + const base::ListValue* args) { + std::string source; + std::string id; + if (ParseStringArgs(args, &source, &id)) + inspect_ui_->InspectFallback(source, id); +} + void InspectMessageHandler::HandleInspectAdditionalCommand( const base::ListValue* args) { std::string url; @@ -358,6 +372,16 @@ void InspectUI::Inspect(const std::string& source_id, } } +void InspectUI::InspectFallback(const std::string& source_id, + const std::string& target_id) { + scoped_refptr<DevToolsAgentHost> target = FindTarget(source_id, target_id); + if (target) { + Profile* profile = Profile::FromBrowserContext( + web_ui()->GetWebContents()->GetBrowserContext()); + DevToolsWindow::OpenDevToolsWindowWithBundledFrontend(target, profile); + } +} + void InspectUI::Activate(const std::string& source_id, const std::string& target_id) { scoped_refptr<DevToolsAgentHost> target = FindTarget(source_id, target_id); diff --git a/chromium/chrome/browser/ui/webui/inspect_ui.h b/chromium/chrome/browser/ui/webui/inspect_ui.h index a0ccc386dbf..4838d34b1d0 100644 --- a/chromium/chrome/browser/ui/webui/inspect_ui.h +++ b/chromium/chrome/browser/ui/webui/inspect_ui.h @@ -38,6 +38,8 @@ class InspectUI : public content::WebUIController, void InitUI(); void Inspect(const std::string& source_id, const std::string& target_id); + void InspectFallback(const std::string& source_id, + const std::string& target_id); void Activate(const std::string& source_id, const std::string& target_id); void Close(const std::string& source_id, const std::string& target_id); void Reload(const std::string& source_id, const std::string& target_id); diff --git a/chromium/chrome/browser/ui/webui/interstitials/interstitial_ui.cc b/chromium/chrome/browser/ui/webui/interstitials/interstitial_ui.cc index 3ce482ef532..572f66c8fae 100644 --- a/chromium/chrome/browser/ui/webui/interstitials/interstitial_ui.cc +++ b/chromium/chrome/browser/ui/webui/interstitials/interstitial_ui.cc @@ -20,7 +20,7 @@ #include "chrome/browser/ssl/mitm_software_blocking_page.h" #include "chrome/browser/ssl/ssl_blocking_page.h" #include "chrome/browser/supervised_user/supervised_user_interstitial.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "chrome/common/url_constants.h" #include "components/grit/components_resources.h" #include "components/safe_browsing/db/database_manager.h" @@ -421,7 +421,7 @@ std::string InterstitialHTMLSource::GetSource() const { std::string InterstitialHTMLSource::GetContentSecurityPolicyScriptSrc() const { // 'unsafe-inline' is added to script-src. - return "script-src chrome://resources 'self' 'unsafe-eval' 'unsafe-inline';"; + return "script-src chrome://resources 'self' 'unsafe-inline';"; } std::string InterstitialHTMLSource::GetContentSecurityPolicyStyleSrc() const { diff --git a/chromium/chrome/browser/ui/webui/interventions_internals/BUILD.gn b/chromium/chrome/browser/ui/webui/interventions_internals/BUILD.gn index a2da942ee62..424c3a9cb0b 100644 --- a/chromium/chrome/browser/ui/webui/interventions_internals/BUILD.gn +++ b/chromium/chrome/browser/ui/webui/interventions_internals/BUILD.gn @@ -10,6 +10,6 @@ mojom("mojo_bindings") { ] public_deps = [ - "//url/mojo:url_mojom_gurl", + "//url/mojom:url_mojom_gurl", ] } diff --git a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals.mojom b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals.mojom index 0e401283453..0b39e904745 100644 --- a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals.mojom +++ b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals.mojom @@ -4,7 +4,7 @@ module mojom; -import "url/mojo/url.mojom"; +import "url/mojom/url.mojom"; struct PreviewsStatus { // The human readable description of the status that will be displayed on diff --git a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc index e31861f342c..0a4eb3c584d 100644 --- a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc +++ b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc @@ -23,18 +23,19 @@ namespace { // HTML DOM ID used in the JavaScript code. The IDs are generated here so that // the DOM would have sensible name instead of autogenerated IDs. -const char kAmpRedirectionPreviewsHtmlId[] = "amp-preview-status"; +const char kPreviewsAllowedHtmlId[] = "previews-allowed-status"; const char kClientLoFiPreviewsHtmlId[] = "client-lofi-preview-status"; const char kNoScriptPreviewsHtmlId[] = "noscript-preview-status"; const char kOfflinePreviewsHtmlId[] = "offline-preview-status"; // Descriptions for previews. -const char kAmpRedirectionDescription[] = "AMP Previews"; +const char kPreviewsAllowedDescription[] = "Previews Allowed"; const char kClientLoFiDescription[] = "Client LoFi Previews"; const char kNoScriptDescription[] = "NoScript Previews"; const char kOfflineDesciption[] = "Offline Previews"; // Flag feature name. +const char kPreviewsAllowedFeatureName[] = "Previews"; const char kNoScriptFeatureName[] = "NoScriptPreviews"; #if defined(OS_ANDROID) const char kOfflinePageFeatureName[] = "OfflinePreviews"; @@ -42,6 +43,7 @@ const char kOfflinePageFeatureName[] = "OfflinePreviews"; // HTML DOM ID used in the JavaScript code. The IDs are generated here so that // the DOM would have sensible name instead of autogenerated IDs. +const char kPreviewsAllowedFlagHtmlId[] = "previews-flag"; const char kEctFlagHtmlId[] = "ect-flag"; const char kNoScriptFlagHtmlId[] = "noscript-flag"; const char kOfflinePageFlagHtmlId[] = "offline-page-flag"; @@ -49,6 +51,7 @@ const char kIgnorePreviewsBlacklistFlagHtmlId[] = "ignore-previews-blacklist"; // Links to flags in chrome://flags. // TODO(thanhdle): Refactor into vector of structs. crbug.com/787010. +const char kPreviewsAllowedFlagLink[] = "chrome://flags/#allow-previews"; const char kEctFlagLink[] = "chrome://flags/#force-effective-connection-type"; const char kNoScriptFlagLink[] = "chrome://flags/#enable-noscript-previews"; const char kOfflinePageFlagLink[] = "chrome://flags/#enable-offline-previews"; @@ -186,11 +189,11 @@ void InterventionsInternalsPageHandler::GetPreviewsEnabled( GetPreviewsEnabledCallback callback) { std::vector<mojom::PreviewsStatusPtr> statuses; - auto amp_status = mojom::PreviewsStatus::New(); - amp_status->description = kAmpRedirectionDescription; - amp_status->enabled = previews::params::IsAMPRedirectionPreviewEnabled(); - amp_status->htmlId = kAmpRedirectionPreviewsHtmlId; - statuses.push_back(std::move(amp_status)); + auto previews_allowed_status = mojom::PreviewsStatus::New(); + previews_allowed_status->description = kPreviewsAllowedDescription; + previews_allowed_status->enabled = previews::params::ArePreviewsAllowed(); + previews_allowed_status->htmlId = kPreviewsAllowedHtmlId; + statuses.push_back(std::move(previews_allowed_status)); auto client_lofi_status = mojom::PreviewsStatus::New(); client_lofi_status->description = kClientLoFiDescription; @@ -217,6 +220,15 @@ void InterventionsInternalsPageHandler::GetPreviewsFlagsDetails( GetPreviewsFlagsDetailsCallback callback) { std::vector<mojom::PreviewsFlagPtr> flags; + auto previews_allowed_status = mojom::PreviewsFlag::New(); + previews_allowed_status->description = + flag_descriptions::kPreviewsAllowedName; + previews_allowed_status->link = kPreviewsAllowedFlagLink; + previews_allowed_status->value = + GetFeatureFlagStatus(kPreviewsAllowedFeatureName); + previews_allowed_status->htmlId = kPreviewsAllowedFlagHtmlId; + flags.push_back(std::move(previews_allowed_status)); + auto ect_status = mojom::PreviewsFlag::New(); ect_status->description = flag_descriptions::kForceEffectiveConnectionTypeName; diff --git a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc index 41e8772b096..d9572b773e3 100644 --- a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc @@ -44,13 +44,13 @@ namespace { // The HTML DOM ID used in Javascript. -constexpr char kAMPRedirectionPreviewsHtmlId[] = "amp-preview-status"; +constexpr char kPreviewsAllowedHtmlId[] = "previews-allowed-status"; constexpr char kClientLoFiPreviewsHtmlId[] = "client-lofi-preview-status"; constexpr char kNoScriptPreviewsHtmlId[] = "noscript-preview-status"; constexpr char kOfflinePreviewsHtmlId[] = "offline-preview-status"; // Descriptions for previews. -constexpr char kAmpRedirectionDescription[] = "AMP Previews"; +constexpr char kPreviewsAllowedDescription[] = "Previews Allowed"; constexpr char kClientLoFiDescription[] = "Client LoFi Previews"; constexpr char kNoScriptDescription[] = "NoScript Previews"; constexpr char kOfflineDesciption[] = "Offline Previews"; @@ -318,30 +318,28 @@ TEST_F(InterventionsInternalsPageHandlerTest, GetPreviewsEnabledCount) { EXPECT_EQ(expected, passed_in_modes.size()); } -TEST_F(InterventionsInternalsPageHandlerTest, AMPRedirectionDisabled) { - // Init with kAMPRedirection disabled. - scoped_feature_list_->InitWithFeatures({}, - {previews::features::kAMPRedirection}); +TEST_F(InterventionsInternalsPageHandlerTest, PreviewsAllowedDisabled) { + // Init with kPreviews disabled. + scoped_feature_list_->InitWithFeatures({}, {previews::features::kPreviews}); page_handler_->GetPreviewsEnabled( base::BindOnce(&MockGetPreviewsEnabledCallback)); - auto amp_redirection = passed_in_modes.find(kAMPRedirectionPreviewsHtmlId); - ASSERT_NE(passed_in_modes.end(), amp_redirection); - EXPECT_EQ(kAmpRedirectionDescription, amp_redirection->second->description); - EXPECT_FALSE(amp_redirection->second->enabled); + auto previews_allowed = passed_in_modes.find(kPreviewsAllowedHtmlId); + ASSERT_NE(passed_in_modes.end(), previews_allowed); + EXPECT_EQ(kPreviewsAllowedDescription, previews_allowed->second->description); + EXPECT_FALSE(previews_allowed->second->enabled); } -TEST_F(InterventionsInternalsPageHandlerTest, AMPRedirectionEnabled) { - // Init with kAMPRedirection enabled. - scoped_feature_list_->InitWithFeatures({previews::features::kAMPRedirection}, - {}); +TEST_F(InterventionsInternalsPageHandlerTest, PreviewsAllowedEnabled) { + // Init with kPreviews enabled. + scoped_feature_list_->InitWithFeatures({previews::features::kPreviews}, {}); page_handler_->GetPreviewsEnabled( base::BindOnce(&MockGetPreviewsEnabledCallback)); - auto amp_redirection = passed_in_modes.find(kAMPRedirectionPreviewsHtmlId); - ASSERT_NE(passed_in_modes.end(), amp_redirection); - EXPECT_EQ(kAmpRedirectionDescription, amp_redirection->second->description); - EXPECT_TRUE(amp_redirection->second->enabled); + auto previews_allowed = passed_in_modes.find(kPreviewsAllowedHtmlId); + ASSERT_NE(passed_in_modes.end(), previews_allowed); + EXPECT_EQ(kPreviewsAllowedDescription, previews_allowed->second->description); + EXPECT_TRUE(previews_allowed->second->enabled); } TEST_F(InterventionsInternalsPageHandlerTest, ClientLoFiDisabled) { @@ -425,7 +423,7 @@ TEST_F(InterventionsInternalsPageHandlerTest, GetFlagsCount) { page_handler_->GetPreviewsFlagsDetails( base::BindOnce(&MockGetPreviewsFlagsCallback)); - constexpr size_t expected = 4; + constexpr size_t expected = 5; EXPECT_EQ(expected, passed_in_flags.size()); } diff --git a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_ui.cc b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_ui.cc index 0e1930eda5a..e0b8fc7be54 100644 --- a/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/interventions_internals/interventions_internals_ui.cc @@ -28,7 +28,7 @@ content::WebUIDataSource* GetSource() { "chrome/browser/ui/webui/interventions_internals/" "interventions_internals.mojom.js", IDR_INTERVENTIONS_INTERNALS_MOJO_INDEX_JS); - source->AddResourcePath("url/mojo/url.mojom.js", IDR_URL_MOJO_JS); + source->AddResourcePath("url/mojom/url.mojom.js", IDR_URL_MOJO_JS); source->SetDefaultResource(IDR_INTERVENTIONS_INTERNALS_INDEX_HTML); source->UseGzip(std::vector<std::string>()); return source; diff --git a/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc b/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc index 44c818a989c..2c1cb6e3567 100644 --- a/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc +++ b/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc @@ -36,10 +36,8 @@ content::WebUIDataSource* CreateLocalDiscoveryHTMLSource() { IDS_LOCAL_DISCOVERY_SERVICE_REGISTER); source->AddLocalizedString("manageDevice", IDS_LOCAL_DISCOVERY_MANAGE_DEVICE); - source->AddLocalizedString("registerPrinterConfirmMessage", - IDS_LOCAL_DISCOVERY_REGISTER_PRINTER_CONFIRMATION); - source->AddLocalizedString("registerDeviceConfirmMessage", - IDS_LOCAL_DISCOVERY_REGISTER_DEVICE_CONFIRMATION); + source->AddLocalizedString("registerPrinterInformationMessage", + IDS_CLOUD_PRINT_REGISTER_PRINTER_INFORMATION); source->AddLocalizedString("registerUser", IDS_LOCAL_DISCOVERY_REGISTER_USER); source->AddLocalizedString("confirmRegistration", @@ -77,6 +75,7 @@ content::WebUIDataSource* CreateLocalDiscoveryHTMLSource() { source->AddLocalizedString("printersOnNetworkMultiple", IDS_LOCAL_DISCOVERY_PRINTERS_ON_NETWORK_MULTIPLE); source->AddLocalizedString("cancel", IDS_CANCEL); + source->AddLocalizedString("confirm", IDS_CONFIRM); source->AddLocalizedString("ok", IDS_OK); source->AddLocalizedString("loading", IDS_LOCAL_DISCOVERY_LOADING); source->AddLocalizedString("addPrinters", IDS_LOCAL_DISCOVERY_ADD_PRINTERS); diff --git a/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc b/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc index fc499de4454..3d74af7b946 100644 --- a/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc +++ b/chromium/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc @@ -18,6 +18,8 @@ #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "chrome/browser/local_discovery/test_service_discovery_client.h" +#include "chrome/browser/media/router/providers/cast/dual_media_sink_service.h" +#include "chrome/browser/media/router/test/noop_dual_media_sink_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" @@ -355,6 +357,16 @@ class LocalDiscoveryUITest : public WebUIBrowserTest { ~LocalDiscoveryUITest() override { } + void SetUp() override { + // We need to stub out DualMediaSinkService here, because the profile setup + // instantiates DualMediaSinkService, which in turn sets + // |g_service_discovery_client| with a real instance. This causes + // a DCHECK during TestServiceDiscoveryClient construction. + media_router::DualMediaSinkService::SetInstanceForTest( + new media_router::NoopDualMediaSinkService()); + WebUIBrowserTest::SetUp(); + } + void SetUpOnMainThread() override { WebUIBrowserTest::SetUpOnMainThread(); diff --git a/chromium/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc b/chromium/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc index e3ab4ba1125..63fcf51691f 100644 --- a/chromium/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc +++ b/chromium/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc @@ -54,6 +54,8 @@ content::WebUIDataSource* CreateMdBookmarksUIHTMLSource(Profile* profile) { AddLocalizedString(source, "editDialogUrlInput", IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER); AddLocalizedString(source, "emptyList", IDS_MD_BOOKMARK_MANAGER_EMPTY_LIST); + AddLocalizedString(source, "emptyUnmodifiableList", + IDS_MD_BOOKMARK_MANAGER_EMPTY_UNMODIFIABLE_LIST); AddLocalizedString(source, "folderLabel", IDS_MD_BOOKMARK_MANAGER_FOLDER_LABEL); AddLocalizedString(source, "itemsSelected", diff --git a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc index 2400a23bdfa..63dbceb45fa 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc +++ b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.cc @@ -21,8 +21,9 @@ #include "chrome/browser/extensions/api/downloads/downloads_api.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" +#include "components/download/public/common/download_item.h" #include "content/public/browser/browser_context.h" -#include "content/public/browser/download_item.h" +#include "content/public/browser/download_item_utils.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_ui.h" #include "extensions/browser/extension_system.h" @@ -31,7 +32,7 @@ #include "ui/base/l10n/time_format.h" using content::BrowserContext; -using content::DownloadItem; +using download::DownloadItem; using content::DownloadManager; using DownloadVector = DownloadManager::DownloadVector; @@ -42,24 +43,24 @@ namespace { // CreateDownloadItemValue(). Only return strings for DANGEROUS_FILE, // DANGEROUS_URL, DANGEROUS_CONTENT, and UNCOMMON_CONTENT because the // |danger_type| value is only defined if the value of |state| is |DANGEROUS|. -const char* GetDangerTypeString(content::DownloadDangerType danger_type) { +const char* GetDangerTypeString(download::DownloadDangerType danger_type) { switch (danger_type) { - case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: + case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: return "DANGEROUS_FILE"; - case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: + case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: return "DANGEROUS_URL"; - case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: + case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: return "DANGEROUS_CONTENT"; - case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: + case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: return "UNCOMMON_CONTENT"; - case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: + case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: return "DANGEROUS_HOST"; - case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: + case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: return "POTENTIALLY_UNWANTED"; - case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: - case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: - case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: - case content::DOWNLOAD_DANGER_TYPE_MAX: + case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: + case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: + case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: + case download::DOWNLOAD_DANGER_TYPE_MAX: break; } // Don't return a danger type string if it is NOT_DANGEROUS, @@ -185,7 +186,7 @@ DownloadsListTracker::DownloadsListTracker( std::unique_ptr<base::DictionaryValue> DownloadsListTracker::CreateDownloadItemValue( - content::DownloadItem* download_item) const { + download::DownloadItem* download_item) const { // TODO(asanka): Move towards using download_model here for getting status and // progress. The difference currently only matters to Drive downloads and // those don't show up on the downloads page, but should. @@ -225,9 +226,12 @@ DownloadsListTracker::CreateDownloadItemValue( // language. This won't work if the extension was uninstalled, so the name // might be the wrong language. bool include_disabled = true; - const extensions::Extension* extension = extensions::ExtensionSystem::Get( - Profile::FromBrowserContext(download_item->GetBrowserContext()))-> - extension_service()->GetExtensionById(by_ext->id(), include_disabled); + const extensions::Extension* extension = + extensions::ExtensionSystem::Get( + Profile::FromBrowserContext( + content::DownloadItemUtils::GetBrowserContext(download_item))) + ->extension_service() + ->GetExtensionById(by_ext->id(), include_disabled); if (extension) by_ext_name = extension->name(); } @@ -256,7 +260,7 @@ DownloadsListTracker::CreateDownloadItemValue( const char* state = nullptr; switch (download_item->GetState()) { - case content::DownloadItem::IN_PROGRESS: { + case download::DownloadItem::IN_PROGRESS: { if (download_item->IsDangerous()) { state = "DANGEROUS"; danger_type = GetDangerTypeString(download_item->GetDangerType()); @@ -270,7 +274,7 @@ DownloadsListTracker::CreateDownloadItemValue( break; } - case content::DownloadItem::INTERRUPTED: + case download::DownloadItem::INTERRUPTED: state = "INTERRUPTED"; progress_status_text = download_model.GetTabProgressStatusText(); @@ -282,23 +286,24 @@ DownloadsListTracker::CreateDownloadItemValue( // GetStatusText() as a temporary measure until the layout is fixed to // accommodate the longer string. http://crbug.com/609255 last_reason_text = download_model.GetStatusText(); - if (content::DOWNLOAD_INTERRUPT_REASON_CRASH == - download_item->GetLastReason() && !download_item->CanResume()) { + if (download::DOWNLOAD_INTERRUPT_REASON_CRASH == + download_item->GetLastReason() && + !download_item->CanResume()) { retry = true; } break; - case content::DownloadItem::CANCELLED: + case download::DownloadItem::CANCELLED: state = "CANCELLED"; retry = true; break; - case content::DownloadItem::COMPLETE: + case download::DownloadItem::COMPLETE: DCHECK(!download_item->IsDangerous()); state = "COMPLETE"; break; - case content::DownloadItem::MAX_DOWNLOAD_STATE: + case download::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); } @@ -343,8 +348,9 @@ bool DownloadsListTracker::ShouldShow(const DownloadItem& item) const { DownloadQuery::MatchesQuery(search_terms_, item); } -bool DownloadsListTracker::StartTimeComparator::operator() ( - const content::DownloadItem* a, const content::DownloadItem* b) const { +bool DownloadsListTracker::StartTimeComparator::operator()( + const download::DownloadItem* a, + const download::DownloadItem* b) const { return a->GetStartTime() > b->GetStartTime(); } diff --git a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h index b92245acd3a..438bbabc529 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h +++ b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker.h @@ -16,7 +16,7 @@ #include "base/time/time.h" #include "base/values.h" #include "components/download/content/public/all_download_item_notifier.h" -#include "content/public/browser/download_item.h" +#include "components/download/public/common/download_item.h" namespace base { class DictionaryValue; @@ -58,35 +58,35 @@ class DownloadsListTracker // AllDownloadItemNotifier::Observer: void OnDownloadCreated(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; void OnDownloadUpdated(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; void OnDownloadRemoved(content::DownloadManager* manager, - content::DownloadItem* download_item) override; + download::DownloadItem* download_item) override; protected: // Testing constructor. DownloadsListTracker(content::DownloadManager* download_manager, content::WebUI* web_ui, - base::Callback<bool(const content::DownloadItem&)>); + base::Callback<bool(const download::DownloadItem&)>); // Creates a dictionary value that's sent to the page as JSON. virtual std::unique_ptr<base::DictionaryValue> CreateDownloadItemValue( - content::DownloadItem* item) const; + download::DownloadItem* item) const; // Exposed for testing. - bool IsIncognito(const content::DownloadItem& item) const; + bool IsIncognito(const download::DownloadItem& item) const; - const content::DownloadItem* GetItemForTesting(size_t index) const; + const download::DownloadItem* GetItemForTesting(size_t index) const; void SetChunkSizeForTesting(size_t chunk_size); private: struct StartTimeComparator { - bool operator() (const content::DownloadItem* a, - const content::DownloadItem* b) const; + bool operator()(const download::DownloadItem* a, + const download::DownloadItem* b) const; }; - using SortedSet = std::set<content::DownloadItem*, StartTimeComparator>; + using SortedSet = std::set<download::DownloadItem*, StartTimeComparator>; // Called by both constructors to initialize common state. void Init(); @@ -95,7 +95,7 @@ class DownloadsListTracker void RebuildSortedItems(); // Whether |item| should show on the current page. - bool ShouldShow(const content::DownloadItem& item) const; + bool ShouldShow(const download::DownloadItem& item) const; // Returns the index of |item| in |sorted_items_|. size_t GetIndex(const SortedSet::iterator& item) const; @@ -118,7 +118,7 @@ class DownloadsListTracker // Callback used to determine if an item should show on the page. Set to // |ShouldShow()| in default constructor, passed in while testing. - base::Callback<bool(const content::DownloadItem&)> should_show_; + base::Callback<bool(const download::DownloadItem&)> should_show_; // When this is true, all changes to downloads that affect the page are sent // via JavaScript. diff --git a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker_unittest.cc b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker_unittest.cc index a2bad3f2d52..8300e101409 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker_unittest.cc +++ b/chromium/chrome/browser/ui/webui/md_downloads/downloads_list_tracker_unittest.cc @@ -22,7 +22,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -using content::DownloadItem; +using download::DownloadItem; using content::MockDownloadItem; using DownloadVector = std::vector<DownloadItem*>; using testing::_; @@ -82,7 +82,7 @@ class TestDownloadsListTracker : public DownloadsListTracker { protected: std::unique_ptr<base::DictionaryValue> CreateDownloadItemValue( - content::DownloadItem* item) const override { + download::DownloadItem* item) const override { std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); CHECK_LE(item->GetId(), static_cast<uint64_t>(INT_MAX)); dict->SetInteger("id", item->GetId()); diff --git a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc index 7cb3c72c564..4138366f5a8 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc +++ b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc @@ -32,11 +32,11 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" +#include "components/download/public/common/download_danger_type.h" +#include "components/download/public/common/download_item.h" #include "components/prefs/pref_service.h" #include "components/safe_browsing/common/safe_browsing_prefs.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/download_danger_type.h" -#include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_contents.h" @@ -163,14 +163,14 @@ void MdDownloadsDOMHandler::HandleGetDownloads(const base::ListValue* args) { void MdDownloadsDOMHandler::HandleOpenFile(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (file) file->OpenDownload(); } void MdDownloadsDOMHandler::HandleDrag(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (!file) return; @@ -179,7 +179,7 @@ void MdDownloadsDOMHandler::HandleDrag(const base::ListValue* args) { if (!web_contents) return; - if (file->GetState() != content::DownloadItem::COMPLETE) + if (file->GetState() != download::DownloadItem::COMPLETE) return; gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath( @@ -195,18 +195,17 @@ void MdDownloadsDOMHandler::HandleDrag(const base::ListValue* args) { void MdDownloadsDOMHandler::HandleSaveDangerous(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); SaveDownload(file); } -void MdDownloadsDOMHandler::SaveDownload( - content::DownloadItem* download) { +void MdDownloadsDOMHandler::SaveDownload(download::DownloadItem* download) { if (!download) return; // If danger type is NOT DANGEROUS_FILE, chrome shows users a download danger // prompt. if (download->GetDangerType() != - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE) { + download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE) { ShowDangerPrompt(download); } else { // If danger type is DANGEROUS_FILE, chrome proceeds to keep this download @@ -233,21 +232,21 @@ void MdDownloadsDOMHandler::HandleDiscardDangerous( void MdDownloadsDOMHandler::HandleShow(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (file) file->ShowDownloadInShell(); } void MdDownloadsDOMHandler::HandlePause(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (file) file->Pause(); } void MdDownloadsDOMHandler::HandleResume(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (file) file->Resume(); } @@ -275,7 +274,7 @@ void MdDownloadsDOMHandler::HandleUndo(const base::ListValue* args) { } for (auto id : last_removed_ids) { - content::DownloadItem* download = GetDownloadById(id); + download::DownloadItem* download = GetDownloadById(id); if (!download) continue; @@ -294,7 +293,7 @@ void MdDownloadsDOMHandler::HandleUndo(const base::ListValue* args) { void MdDownloadsDOMHandler::HandleCancel(const base::ListValue* args) { CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL); - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (file) file->Cancel(true); } @@ -332,7 +331,7 @@ void MdDownloadsDOMHandler::RemoveDownloads(const DownloadVector& to_remove) { DownloadItemModel item_model(download); if (!item_model.ShouldShowInShelf() || - download->GetState() == content::DownloadItem::IN_PROGRESS) { + download->GetState() == download::DownloadItem::IN_PROGRESS) { continue; } @@ -375,7 +374,7 @@ void MdDownloadsDOMHandler::FinalizeRemovals() { removals_.pop_back(); for (const auto id : remove) { - content::DownloadItem* download = GetDownloadById(id); + download::DownloadItem* download = GetDownloadById(id); if (download) download->Remove(); } @@ -383,7 +382,7 @@ void MdDownloadsDOMHandler::FinalizeRemovals() { } void MdDownloadsDOMHandler::ShowDangerPrompt( - content::DownloadItem* dangerous_item) { + download::DownloadItem* dangerous_item) { DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create( dangerous_item, GetWebUIWebContents(), @@ -398,7 +397,7 @@ void MdDownloadsDOMHandler::DangerPromptDone( int download_id, DownloadDangerPrompt::Action action) { if (action != DownloadDangerPrompt::ACCEPT) return; - content::DownloadItem* item = NULL; + download::DownloadItem* item = NULL; if (GetMainNotifierManager()) item = GetMainNotifierManager()->GetDownload(download_id); if (!item && GetOriginalNotifierManager()) @@ -416,7 +415,7 @@ bool MdDownloadsDOMHandler::IsDeletingHistoryAllowed() { GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory); } -content::DownloadItem* MdDownloadsDOMHandler::GetDownloadByValue( +download::DownloadItem* MdDownloadsDOMHandler::GetDownloadByValue( const base::ListValue* args) { std::string download_id; if (!args->GetString(0, &download_id)) { @@ -433,8 +432,8 @@ content::DownloadItem* MdDownloadsDOMHandler::GetDownloadByValue( return GetDownloadById(static_cast<uint32_t>(id)); } -content::DownloadItem* MdDownloadsDOMHandler::GetDownloadById(uint32_t id) { - content::DownloadItem* item = NULL; +download::DownloadItem* MdDownloadsDOMHandler::GetDownloadById(uint32_t id) { + download::DownloadItem* item = NULL; if (GetMainNotifierManager()) item = GetMainNotifierManager()->GetDownload(id); if (!item && GetOriginalNotifierManager()) @@ -454,7 +453,7 @@ void MdDownloadsDOMHandler::CheckForRemovedFiles() { } void MdDownloadsDOMHandler::RemoveDownloadInArgs(const base::ListValue* args) { - content::DownloadItem* file = GetDownloadByValue(args); + download::DownloadItem* file = GetDownloadByValue(args); if (!file) return; diff --git a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h index 162aced0f12..fd401f52edb 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h +++ b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h @@ -22,12 +22,15 @@ class ListValue; } namespace content { -class DownloadItem; class DownloadManager; class WebContents; class WebUI; } +namespace download { +class DownloadItem; +} + class Profile; // The handler for Javascript messages related to the "downloads" view, @@ -98,14 +101,14 @@ class MdDownloadsDOMHandler : public content::WebContentsObserver, // Actually remove downloads with an ID in |removals_|. This cannot be undone. void FinalizeRemovals(); - using DownloadVector = std::vector<content::DownloadItem*>; + using DownloadVector = std::vector<download::DownloadItem*>; // Remove all downloads in |to_remove|. Safe downloads can be revived, // dangerous ones are immediately removed. Protected for testing. void RemoveDownloads(const DownloadVector& to_remove); // Helper function to handle save download event. - void SaveDownload(content::DownloadItem* download); + void SaveDownload(download::DownloadItem* download); private: using IdSet = std::set<uint32_t>; @@ -124,7 +127,7 @@ class MdDownloadsDOMHandler : public content::WebContentsObserver, // user accepts the dangerous download. The native prompt will observe // |dangerous| until either the dialog is dismissed or |dangerous| is no // longer an in-progress dangerous download. - virtual void ShowDangerPrompt(content::DownloadItem* dangerous); + virtual void ShowDangerPrompt(download::DownloadItem* dangerous); // Conveys danger acceptance from the DownloadDangerPrompt to the // DownloadItem. @@ -136,10 +139,10 @@ class MdDownloadsDOMHandler : public content::WebContentsObserver, bool IsDeletingHistoryAllowed(); // Returns the download that is referred to in a given value. - content::DownloadItem* GetDownloadByValue(const base::ListValue* args); + download::DownloadItem* GetDownloadByValue(const base::ListValue* args); // Returns the download with |id| or NULL if it doesn't exist. - content::DownloadItem* GetDownloadById(uint32_t id); + download::DownloadItem* GetDownloadById(uint32_t id); // Removes the download specified by an ID from JavaScript in |args|. void RemoveDownloadInArgs(const base::ListValue* args); diff --git a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc index 947dfe8a049..5b018373bd6 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc @@ -32,7 +32,7 @@ class TestMdDownloadsDOMHandler : public MdDownloadsDOMHandler { int danger_prompt_count() { return danger_prompt_count_; } private: - void ShowDangerPrompt(content::DownloadItem* dangerous) override { + void ShowDangerPrompt(download::DownloadItem* dangerous) override { danger_prompt_count_++; } @@ -89,14 +89,14 @@ TEST_F(MdDownloadsDOMHandlerTest, HandleGetDownloads) { } TEST_F(MdDownloadsDOMHandlerTest, ClearAll) { - std::vector<content::DownloadItem*> downloads; + std::vector<download::DownloadItem*> downloads; // Safe, in-progress items should be passed over. testing::StrictMock<content::MockDownloadItem> in_progress; EXPECT_CALL(in_progress, IsDangerous()).WillOnce(testing::Return(false)); EXPECT_CALL(in_progress, IsTransient()).WillOnce(testing::Return(false)); - EXPECT_CALL(in_progress, GetState()).WillOnce( - testing::Return(content::DownloadItem::IN_PROGRESS)); + EXPECT_CALL(in_progress, GetState()) + .WillOnce(testing::Return(download::DownloadItem::IN_PROGRESS)); downloads.push_back(&in_progress); // Dangerous items should be removed (regardless of state). @@ -109,8 +109,8 @@ TEST_F(MdDownloadsDOMHandlerTest, ClearAll) { testing::StrictMock<content::MockDownloadItem> completed; EXPECT_CALL(completed, IsDangerous()).WillOnce(testing::Return(false)); EXPECT_CALL(completed, IsTransient()).WillRepeatedly(testing::Return(false)); - EXPECT_CALL(completed, GetState()).WillOnce( - testing::Return(content::DownloadItem::COMPLETE)); + EXPECT_CALL(completed, GetState()) + .WillOnce(testing::Return(download::DownloadItem::COMPLETE)); EXPECT_CALL(completed, GetId()).WillOnce(testing::Return(1)); EXPECT_CALL(completed, UpdateObservers()); downloads.push_back(&completed); @@ -135,7 +135,7 @@ TEST_F(MdDownloadsDOMHandlerTest, HandleSaveDownload) { testing::StrictMock<content::MockDownloadItem> dangerous_file_type; EXPECT_CALL(dangerous_file_type, GetDangerType()) .WillRepeatedly( - testing::Return(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE)); + testing::Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE)); EXPECT_CALL(dangerous_file_type, GetId()) .WillOnce(testing::Return(uint32_t())); TestMdDownloadsDOMHandler handler(manager(), web_ui()); @@ -148,7 +148,7 @@ TEST_F(MdDownloadsDOMHandlerTest, HandleSaveDownload) { testing::StrictMock<content::MockDownloadItem> malicious_download; EXPECT_CALL(malicious_download, GetDangerType()) .WillRepeatedly( - testing::Return(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL)); + testing::Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL)); handler.SaveDownload(&malicious_download); EXPECT_EQ(1, handler.danger_prompt_count()); } diff --git a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_ui.cc b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_ui.cc index 00665110a4a..e3938ed4ed6 100644 --- a/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_ui.cc +++ b/chromium/chrome/browser/ui/webui/md_downloads/md_downloads_ui.cc @@ -16,8 +16,8 @@ #include "chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h" #include "chrome/browser/ui/webui/metrics_handler.h" #include "chrome/browser/ui/webui/theme_source.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_switches.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" diff --git a/chromium/chrome/browser/ui/webui/media/media_engagement_ui.cc b/chromium/chrome/browser/ui/webui/media/media_engagement_ui.cc index 75a02a671c8..9bf372c9b25 100644 --- a/chromium/chrome/browser/ui/webui/media/media_engagement_ui.cc +++ b/chromium/chrome/browser/ui/webui/media/media_engagement_ui.cc @@ -73,7 +73,7 @@ MediaEngagementUI::MediaEngagementUI(content::WebUI* web_ui) source->AddResourcePath( "chrome/browser/media/media_engagement_score_details.mojom.js", IDR_MEDIA_ENGAGEMENT_MOJO_JS); - source->AddResourcePath("url/mojo/url.mojom.js", IDR_URL_MOJO_JS); + source->AddResourcePath("url/mojom/url.mojom.js", IDR_URL_MOJO_JS); source->SetDefaultResource(IDR_MEDIA_ENGAGEMENT_HTML); source->UseGzip(); content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source.release()); diff --git a/chromium/chrome/browser/ui/webui/media/webrtc_logs_ui.cc b/chromium/chrome/browser/ui/webui/media/webrtc_logs_ui.cc index a461fb4d442..782ea7d2fe4 100644 --- a/chromium/chrome/browser/ui/webui/media/webrtc_logs_ui.cc +++ b/chromium/chrome/browser/ui/webui/media/webrtc_logs_ui.cc @@ -19,7 +19,6 @@ #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/media/webrtc/webrtc_log_list.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" @@ -27,6 +26,7 @@ #include "components/prefs/pref_service.h" #include "components/upload_list/upload_list.h" #include "components/version_info/version_info.h" +#include "components/webrtc_logging/browser/log_list.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" @@ -110,11 +110,12 @@ class WebRtcLogsDOMHandler : public WebUIMessageHandler { }; WebRtcLogsDOMHandler::WebRtcLogsDOMHandler(Profile* profile) - : log_dir_(WebRtcLogList::GetWebRtcLogDirectoryForBrowserContextPath( - profile->GetPath())), + : log_dir_( + webrtc_logging::LogList::GetWebRtcLogDirectoryForBrowserContextPath( + profile->GetPath())), list_available_(false), js_request_pending_(false) { - upload_list_ = WebRtcLogList::CreateWebRtcLogList(profile); + upload_list_ = webrtc_logging::LogList::CreateWebRtcLogList(profile); } WebRtcLogsDOMHandler::~WebRtcLogsDOMHandler() { diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_file_dialog.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_file_dialog.cc index 7cd940c4877..2506cbba5b2 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_file_dialog.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_file_dialog.cc @@ -252,6 +252,7 @@ IssueInfo MediaRouterFileDialog::CreateIssue( // Create issue shouldn't be called with FILE_OK, but to ensure things // compile, fall through sets |issue_title| to the generic error. NOTREACHED(); + FALLTHROUGH; case MediaRouterFileDialog::UNKNOWN_FAILURE: issue_title = l10n_util::GetStringUTF8( IDS_MEDIA_ROUTER_ISSUE_FILE_CAST_GENERIC_ERROR); diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc index f709b8725f6..fc3afe5587f 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc @@ -5,6 +5,7 @@ #include "chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.h" #include "chrome/grit/generated_resources.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/web_ui_data_source.h" namespace { @@ -15,7 +16,7 @@ const char kLocalizedStringsFile[] = "strings.js"; void AddMediaRouterStrings(content::WebUIDataSource* html_source) { html_source->AddLocalizedString("mediaRouterTitle", IDS_MEDIA_ROUTER_TITLE); - html_source->AddLocalizedString("learnMoreText", IDS_MEDIA_ROUTER_LEARN_MORE); + html_source->AddLocalizedString("learnMoreText", IDS_LEARN_MORE); html_source->AddLocalizedString("backButtonTitle", IDS_MEDIA_ROUTER_BACK_BUTTON_TITLE); html_source->AddLocalizedString("closeButtonTitle", diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_ui.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_ui.cc index 63b144ef051..4e10bf5fd64 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_ui.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_ui.cc @@ -55,12 +55,17 @@ #include "extensions/browser/extension_registry.h" #include "extensions/common/constants.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" -#include "third_party/WebKit/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/WebKit/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/icu/source/i18n/unicode/coll.h" #include "ui/base/l10n/l10n_util.h" #include "ui/web_dialogs/web_dialog_delegate.h" #include "url/origin.h" +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) +#include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h" +#include "ui/display/display.h" +#endif + namespace media_router { namespace { @@ -111,7 +116,7 @@ base::TimeDelta GetRouteRequestTimeout(MediaCastMode cast_mode) { // used by the Media Router to find such a matching route if it exists. MediaSource GetSourceForRouteObserver(const std::vector<MediaSource>& sources) { auto source_it = - std::find_if(sources.begin(), sources.end(), CanConnectToMediaSource); + std::find_if(sources.begin(), sources.end(), IsCastPresentationUrl); return source_it != sources.end() ? *source_it : MediaSource(""); } @@ -338,11 +343,6 @@ MediaRouterUI::MediaRouterUI(content::WebUI* web_ui) std::unique_ptr<content::WebUIDataSource> html_source( content::WebUIDataSource::Create(chrome::kChromeUIMediaRouterHost)); - content::WebContents* wc = web_ui->GetWebContents(); - DCHECK(wc); - content::BrowserContext* context = wc->GetBrowserContext(); - router_ = MediaRouterFactory::GetApiForBrowserContext(context); - AddLocalizedStrings(html_source.get()); AddMediaRouterUIResources(html_source.get()); // Ownership of |html_source| is transferred to the BrowserContext. @@ -397,9 +397,10 @@ void MediaRouterUI::InitWithDefaultMediaSource( OnDefaultPresentationChanged(delegate->GetDefaultPresentationRequest()); } else { // Register for MediaRoute updates without a media source. - routes_observer_.reset(new UIMediaRoutesObserver( + routes_observer_ = std::make_unique<UIMediaRoutesObserver>( router_, MediaSource::Id(), - base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this)))); + base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, + base::Unretained(this))); } } @@ -423,11 +424,13 @@ void MediaRouterUI::InitWithStartPresentationContext( void MediaRouterUI::InitCommon(content::WebContents* initiator) { DCHECK(initiator); - DCHECK(router_); TRACE_EVENT_NESTABLE_ASYNC_INSTANT1("media_router", "UI", initiator, "MediaRouterUI::InitCommon", this); + router_ = GetMediaRouter(); + DCHECK(router_); + // Presentation requests from content must show the origin requesting // presentation: crbug.com/704964 if (start_presentation_context_) @@ -446,7 +449,7 @@ void MediaRouterUI::InitCommon(content::WebContents* initiator) { collator_.reset(); } - query_result_manager_.reset(new QueryResultManager(router_)); + query_result_manager_ = std::make_unique<QueryResultManager>(router_); query_result_manager_->AddObserver(this); // Use a placeholder URL as origin for mirroring. @@ -476,6 +479,11 @@ void MediaRouterUI::InitCommon(content::WebContents* initiator) { // Get the current list of media routes, so that the WebUI will have routes // information at initialization. OnRoutesUpdated(router_->GetCurrentRoutes(), std::vector<MediaRoute::Id>()); +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + display_observer_ = WebContentsDisplayObserver::Create( + initiator_, + base::BindRepeating(&MediaRouterUI::UpdateSinks, base::Unretained(this))); +#endif } void MediaRouterUI::InitForTest( @@ -484,7 +492,6 @@ void MediaRouterUI::InitForTest( MediaRouterWebUIMessageHandler* handler, std::unique_ptr<StartPresentationContext> context, std::unique_ptr<MediaRouterFileDialog> file_dialog) { - router_ = router; handler_ = handler; start_presentation_context_ = std::move(context); InitForTest(std::move(file_dialog)); @@ -517,9 +524,10 @@ void MediaRouterUI::OnDefaultPresentationChanged( // observer API for this case. const MediaSource source_for_route_observer = GetSourceForRouteObserver(sources); - routes_observer_.reset(new UIMediaRoutesObserver( + routes_observer_ = std::make_unique<UIMediaRoutesObserver>( router_, source_for_route_observer.id(), - base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this)))); + base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, + base::Unretained(this))); UpdateCastModes(); } @@ -534,9 +542,10 @@ void MediaRouterUI::OnDefaultPresentationRemoved() { forced_cast_mode_ = base::nullopt; // Register for MediaRoute updates without a media source. - routes_observer_.reset(new UIMediaRoutesObserver( + routes_observer_ = std::make_unique<UIMediaRoutesObserver>( router_, MediaSource::Id(), - base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this)))); + base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, + base::Unretained(this))); UpdateCastModes(); } @@ -580,8 +589,16 @@ void MediaRouterUI::UIInitialized() { TRACE_EVENT_NESTABLE_ASYNC_END0("media_router", "UI", initiator_); ui_initialized_ = true; + // Workaround for MediaRouterElementsBrowserTest, in which MediaRouterUI is + // created without calling one of the |Init*()| methods. + // TODO(imcheng): We should be able to instantiate |issue_observer_| during + // InitCommon by storing an initial Issue in this class. + if (!router_) + router_ = GetMediaRouter(); + // Register for Issue updates. - issues_observer_.reset(new UIIssuesObserver(GetIssueManager(), this)); + issues_observer_ = + std::make_unique<UIIssuesObserver>(GetIssueManager(), this); issues_observer_->Init(); } @@ -792,9 +809,10 @@ void MediaRouterUI::SearchSinksAndCreateRoute( // The CreateRoute() part of the function is accomplished in the callback // OnSearchSinkResponseReceived(). - router_->SearchSinks(sink_id, source_id, search_criteria, domain, - base::Bind(&MediaRouterUI::OnSearchSinkResponseReceived, - weak_factory_.GetWeakPtr(), cast_mode)); + router_->SearchSinks( + sink_id, source_id, search_criteria, domain, + base::BindRepeating(&MediaRouterUI::OnSearchSinkResponseReceived, + weak_factory_.GetWeakPtr(), cast_mode)); } bool MediaRouterUI::UserSelectedTabMirroringForCurrentOrigin() const { @@ -842,7 +860,7 @@ void MediaRouterUI::OnResultsUpdated( }); if (ui_initialized_) - handler_->UpdateSinks(sinks_); + UpdateSinks(); } void MediaRouterUI::SetIssue(const Issue& issue) { @@ -994,6 +1012,30 @@ GURL MediaRouterUI::GetFrameURL() const { : GURL(); } +std::vector<MediaSinkWithCastModes> MediaRouterUI::GetEnabledSinks() const { +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + if (!display_observer_) + return sinks_; + + // Filter out the wired display sink for the display that the dialog is on. + // This is not the best place to do this because MRUI should not perform a + // provider-specific behavior, but we currently do not have a way to + // communicate dialog-specific information to/from the + // WiredDisplayMediaRouteProvider. + std::vector<MediaSinkWithCastModes> enabled_sinks; + const std::string display_sink_id = + WiredDisplayMediaRouteProvider::GetSinkIdForDisplay( + display_observer_->GetCurrentDisplay()); + for (const MediaSinkWithCastModes& sink : sinks_) { + if (sink.sink.id() != display_sink_id) + enabled_sinks.push_back(sink); + } + return enabled_sinks; +#else + return sinks_; +#endif +} + std::string MediaRouterUI::GetPresentationRequestSourceName() const { GURL gurl = GetFrameURL(); return gurl.SchemeIs(extensions::kExtensionScheme) @@ -1082,4 +1124,12 @@ IssueManager* MediaRouterUI::GetIssueManager() { return router_->GetIssueManager(); } +void MediaRouterUI::UpdateSinks() { + handler_->UpdateSinks(GetEnabledSinks()); +} + +MediaRouter* MediaRouterUI::GetMediaRouter() { + return MediaRouterFactory::GetApiForBrowserContext( + web_ui()->GetWebContents()->GetBrowserContext()); +} } // namespace media_router diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_ui.h b/chromium/chrome/browser/ui/webui/media_router/media_router_ui.h index 018dafa34d8..e8abac52f0a 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_ui.h +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_ui.h @@ -8,12 +8,15 @@ #include <memory> #include <set> #include <string> +#include <unordered_map> +#include <utility> #include <vector> #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/timer/timer.h" +#include "build/build_config.h" #include "chrome/browser/media/router/media_router_dialog_controller.h" #include "chrome/browser/media/router/mojo/media_route_controller.h" #include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h" @@ -26,8 +29,13 @@ #include "chrome/common/media_router/media_source.h" #include "content/public/browser/web_ui_data_source.h" #include "third_party/icu/source/common/unicode/uversion.h" +#include "ui/base/ui_features.h" #include "url/gurl.h" +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) +#include "chrome/browser/ui/webui/media_router/web_contents_display_observer.h" +#endif + namespace content { struct PresentationRequest; class WebContents; @@ -145,13 +153,15 @@ class MediaRouterUI // mode is MediaCastMode::DESKTOP_MIRROR. virtual void RecordCastModeSelection(MediaCastMode cast_mode); + // Returns a subset of |sinks_| that should be listed in the dialog. + std::vector<MediaSinkWithCastModes> GetEnabledSinks() const; + // Returns the hostname of the PresentationRequest's parent frame URL. std::string GetPresentationRequestSourceName() const; std::string GetTruncatedPresentationRequestSourceName() const; bool HasPendingRouteRequest() const { return current_route_request_id_ != -1; } - const std::vector<MediaSinkWithCastModes>& sinks() const { return sinks_; } const std::vector<MediaRoute>& routes() const { return routes_; } const std::vector<MediaRoute::Id>& joinable_route_ids() const { return joinable_route_ids_; @@ -193,6 +203,13 @@ class MediaRouterUI void InitForTest(std::unique_ptr<MediaRouterFileDialog> file_dialog); +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + void set_display_observer_for_test( + std::unique_ptr<WebContentsDisplayObserver> display_observer) { + display_observer_ = std::move(display_observer); + } +#endif + private: friend class MediaRouterUITest; @@ -216,6 +233,8 @@ class MediaRouterUI FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SendMediaCommands); FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SendMediaStatusUpdate); FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SendInitialMediaStatusUpdate); + FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, + UpdateSinksWhenDialogMovesToAnotherDisplay); class UIIssuesObserver; class WebContentsFullscreenOnLoadedObserver; @@ -223,8 +242,8 @@ class MediaRouterUI class UIMediaRoutesObserver : public MediaRoutesObserver { public: using RoutesUpdatedCallback = - base::Callback<void(const std::vector<MediaRoute>&, - const std::vector<MediaRoute::Id>&)>; + base::RepeatingCallback<void(const std::vector<MediaRoute>&, + const std::vector<MediaRoute::Id>&)>; UIMediaRoutesObserver(MediaRouter* router, const MediaSource::Id& source_id, const RoutesUpdatedCallback& callback); @@ -381,6 +400,12 @@ class MediaRouterUI // Returns the IssueManager associated with |router_|. IssueManager* GetIssueManager(); + // Sends the current list of enabled sinks to |handler_|. + void UpdateSinks(); + + // Overridden by tests. + virtual MediaRouter* GetMediaRouter(); + // Owned by the |web_ui| passed in the ctor, and guaranteed to be deleted // only after it has deleted |this|. MediaRouterWebUIMessageHandler* handler_ = nullptr; @@ -449,6 +474,11 @@ class MediaRouterUI // If set, a cast mode that is required to be shown first. base::Optional<MediaCastMode> forced_cast_mode_; +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + // Keeps track of which display the dialog WebContents is on. + std::unique_ptr<WebContentsDisplayObserver> display_observer_; +#endif + // NOTE: Weak pointers must be invalidated before all other member variables. // Therefore |weak_factory_| must be placed at the end. base::WeakPtrFactory<MediaRouterUI> weak_factory_; diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_ui_service_factory_unittest.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_ui_service_factory_unittest.cc index c3d0cbdb82e..3ee9f6d215e 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_ui_service_factory_unittest.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_ui_service_factory_unittest.cc @@ -4,6 +4,8 @@ #include <memory> +#include "chrome/browser/media/router/media_router_factory.h" +#include "chrome/browser/media/router/test/mock_media_router.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model_factory.h" @@ -26,6 +28,8 @@ class MediaRouterUIServiceFactoryUnitTest : public testing::Test { // requires ToolbarActionsModel. builder.AddTestingFactory(ToolbarActionsModelFactory::GetInstance(), BuildFakeToolBarActionsModel); + builder.AddTestingFactory(MediaRouterFactory::GetInstance(), + MockMediaRouter::Create); profile_ = builder.Build(); } diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc index 2aa46df6beb..6a6e17efd68 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_ui_unittest.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_feature_list.h" +#include "build/build_config.h" #include "chrome/browser/media/router/media_router_factory.h" #include "chrome/browser/media/router/test/media_router_mojo_test.h" #include "chrome/browser/media/router/test/mock_media_router.h" @@ -31,6 +32,12 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/l10n/l10n_util.h" +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) +#include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h" +#include "chrome/browser/ui/webui/media_router/web_contents_display_observer.h" +#include "ui/display/display.h" +#endif + using content::WebContents; using testing::_; using testing::AnyNumber; @@ -53,6 +60,8 @@ class MockMediaRouterWebUIMessageHandler : MediaRouterWebUIMessageHandler(media_router_ui) {} ~MockMediaRouterWebUIMessageHandler() override {} + MOCK_METHOD1(UpdateSinks, + void(const std::vector<MediaSinkWithCastModes>& sinks)); MOCK_METHOD1(UpdateIssue, void(const Issue& issue)); MOCK_METHOD1(UpdateMediaRouteStatus, void(const MediaStatus& status)); MOCK_METHOD3(UpdateCastModes, @@ -71,6 +80,24 @@ class MockMediaRouterFileDialog : public MediaRouterFileDialog { MOCK_METHOD1(OpenFileDialog, void(Browser* browser)); }; +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) +class TestWebContentsDisplayObserver : public WebContentsDisplayObserver { + public: + explicit TestWebContentsDisplayObserver(const display::Display& display) + : display_(display) {} + ~TestWebContentsDisplayObserver() override {} + + const display::Display& GetCurrentDisplay() const override { + return display_; + } + + void set_display(const display::Display& display) { display_ = display; } + + private: + display::Display display_; +}; +#endif // !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + class PresentationRequestCallbacks { public: PresentationRequestCallbacks() {} @@ -90,6 +117,19 @@ class PresentationRequestCallbacks { content::PresentationError expected_error_; }; +class TestMediaRouterUI : public MediaRouterUI { + public: + TestMediaRouterUI(content::WebUI* web_ui, MediaRouter* router) + : MediaRouterUI(web_ui), router_(router) {} + ~TestMediaRouterUI() override = default; + + MediaRouter* GetMediaRouter() override { return router_; } + + private: + MediaRouter* router_; + DISALLOW_COPY_AND_ASSIGN(TestMediaRouterUI); +}; + class MediaRouterUITest : public ChromeRenderViewHostTestHarness { public: MediaRouterUITest() @@ -135,7 +175,8 @@ class MediaRouterUITest : public ChromeRenderViewHostTestHarness { web_ui_contents_.reset( WebContents::Create(WebContents::CreateParams(profile))); web_ui_.set_web_contents(web_ui_contents_.get()); - media_router_ui_ = std::make_unique<MediaRouterUI>(&web_ui_); + media_router_ui_ = + std::make_unique<TestMediaRouterUI>(&web_ui_, &mock_router_); message_handler_ = std::make_unique<MockMediaRouterWebUIMessageHandler>( media_router_ui_.get()); @@ -186,7 +227,7 @@ class MediaRouterUITest : public ChromeRenderViewHostTestHarness { content::TestWebUI web_ui_; std::unique_ptr<WebContents> web_ui_contents_; std::unique_ptr<StartPresentationContext> start_presentation_context_; - std::unique_ptr<MediaRouterUI> media_router_ui_; + std::unique_ptr<TestMediaRouterUI> media_router_ui_; std::unique_ptr<MockMediaRouterWebUIMessageHandler> message_handler_; MockMediaRouterFileDialog* mock_file_dialog_ = nullptr; std::vector<MediaSinksObserver*> media_sinks_observers_; @@ -760,7 +801,8 @@ TEST_F(MediaRouterUITest, SetsForcedCastModeWithPresentationURLs) { web_ui_contents_.reset( WebContents::Create(WebContents::CreateParams(profile()))); web_ui_.set_web_contents(web_ui_contents_.get()); - media_router_ui_ = std::make_unique<MediaRouterUI>(&web_ui_); + media_router_ui_ = + std::make_unique<TestMediaRouterUI>(&web_ui_, &mock_router_); message_handler_ = std::make_unique<MockMediaRouterWebUIMessageHandler>( media_router_ui_.get()); message_handler_->SetWebUIForTest(&web_ui_); @@ -770,28 +812,83 @@ TEST_F(MediaRouterUITest, SetsForcedCastModeWithPresentationURLs) { return true; })); EXPECT_CALL(mock_router_, RegisterMediaRoutesObserver(_)).Times(AnyNumber()); - // For some reason we push two sets of cast modes to the dialog, even when - // initializing the dialog with a presentation request. The WebUI can handle - // the forced mode that is not in the initial cast mode set, but is this a - // bug? - CastModeSet expected_modes({MediaCastMode::TAB_MIRROR, - MediaCastMode::DESKTOP_MIRROR, - MediaCastMode::LOCAL_FILE}); - EXPECT_CALL(*message_handler_, - UpdateCastModes( - expected_modes, "", - base::Optional<MediaCastMode>(MediaCastMode::PRESENTATION))); - expected_modes.insert(MediaCastMode::PRESENTATION); - EXPECT_CALL(*message_handler_, - UpdateCastModes( - expected_modes, "google.com", - base::Optional<MediaCastMode>(MediaCastMode::PRESENTATION))); - media_router_ui_->UIInitialized(); + + CastModeSet expected_modes( + {MediaCastMode::TAB_MIRROR, MediaCastMode::DESKTOP_MIRROR, + MediaCastMode::LOCAL_FILE, MediaCastMode::PRESENTATION}); media_router_ui_->InitForTest( &mock_router_, web_contents(), message_handler_.get(), std::move(start_presentation_context_), nullptr); + EXPECT_EQ(expected_modes, media_router_ui_->cast_modes()); + EXPECT_EQ(base::Optional<MediaCastMode>(MediaCastMode::PRESENTATION), + media_router_ui_->forced_cast_mode()); + EXPECT_EQ("google.com", media_router_ui_->GetPresentationRequestSourceName()); + // |media_router_ui_| takes ownership of |request_callbacks|. media_router_ui_.reset(); } +#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) +// A wired display sink should not be on the sinks list when the dialog is on +// that display, to prevent showing a fullscreen presentation window over the +// controlling window. +TEST_F(MediaRouterUITest, UpdateSinksWhenDialogMovesToAnotherDisplay) { + const display::Display display1(1000001); + const display::Display display2(1000002); + const std::string display_sink_id1 = + WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(display1); + const std::string display_sink_id2 = + WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(display2); + + CreateMediaRouterUI(profile()); + + auto display_observer_unique = + std::make_unique<TestWebContentsDisplayObserver>(display1); + TestWebContentsDisplayObserver* display_observer = + display_observer_unique.get(); + media_router_ui_->set_display_observer_for_test( + std::move(display_observer_unique)); + + std::vector<MediaSinkWithCastModes> sinks; + MediaSinkWithCastModes display_sink1( + MediaSink(display_sink_id1, "sink", SinkIconType::GENERIC)); + sinks.push_back(display_sink1); + MediaSinkWithCastModes display_sink2( + MediaSink(display_sink_id2, "sink", SinkIconType::GENERIC)); + sinks.push_back(display_sink2); + MediaSinkWithCastModes sink3(MediaSink("id3", "sink", SinkIconType::GENERIC)); + sinks.push_back(sink3); + media_router_ui_->OnResultsUpdated(sinks); + + // Initially |display_sink1| should not be on the sinks list because we are on + // |display1|. + EXPECT_CALL(*message_handler_, UpdateSinks(_)) + .WillOnce(Invoke([&display_sink_id1]( + const std::vector<MediaSinkWithCastModes>& sinks) { + EXPECT_EQ(2u, sinks.size()); + EXPECT_TRUE(std::find_if(sinks.begin(), sinks.end(), + [&display_sink_id1]( + const MediaSinkWithCastModes& sink) { + return sink.sink.id() == display_sink_id1; + }) == sinks.end()); + })); + media_router_ui_->UpdateSinks(); + + // Change the display to |display2|. Now |display_sink2| should be removed + // from the list of sinks. + EXPECT_CALL(*message_handler_, UpdateSinks(_)) + .WillOnce(Invoke([&display_sink_id2]( + const std::vector<MediaSinkWithCastModes>& sinks) { + EXPECT_EQ(2u, sinks.size()); + EXPECT_TRUE(std::find_if(sinks.begin(), sinks.end(), + [&display_sink_id2]( + const MediaSinkWithCastModes& sink) { + return sink.sink.id() == display_sink_id2; + }) == sinks.end()); + })); + display_observer->set_display(display2); + media_router_ui_->UpdateSinks(); +} +#endif // !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER) + } // namespace media_router diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_web_ui_test.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_web_ui_test.cc index 11ec23b4515..0b0a38ed222 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_web_ui_test.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_web_ui_test.cc @@ -4,6 +4,8 @@ #include "chrome/browser/ui/webui/media_router/media_router_web_ui_test.h" +#include "chrome/browser/media/router/media_router_factory.h" +#include "chrome/browser/media/router/test/mock_media_router.h" #include "chrome/browser/ui/toolbar/mock_media_router_action_controller.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model_factory.h" @@ -45,14 +47,18 @@ MediaRouterWebUITest::MediaRouterWebUITest(bool require_mock_ui_service) MediaRouterWebUITest::~MediaRouterWebUITest() {} TestingProfile::TestingFactories MediaRouterWebUITest::GetTestingFactories() { + TestingProfile::TestingFactories factories = { + {media_router::MediaRouterFactory::GetInstance(), + &media_router::MockMediaRouter::Create}}; if (require_mock_ui_service_) { - return { - {media_router::MediaRouterUIServiceFactory::GetInstance(), - BuildMockMediaRouterUIService}, - {ToolbarActionsModelFactory::GetInstance(), BuildToolbarActionsModel}}; + factories.emplace_back( + media_router::MediaRouterUIServiceFactory::GetInstance(), + BuildMockMediaRouterUIService); + factories.emplace_back(ToolbarActionsModelFactory::GetInstance(), + BuildToolbarActionsModel); } - return BrowserWithTestWindowTest::GetTestingFactories(); + return factories; } BrowserWindow* MediaRouterWebUITest::CreateBrowserWindow() { diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc b/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc index e528bf82973..96be58cdb9a 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc @@ -4,7 +4,9 @@ #include "chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h" +#include <algorithm> #include <memory> +#include <set> #include <string> #include <utility> @@ -506,7 +508,8 @@ void MediaRouterWebUIMessageHandler::OnRequestInitialData( base::StringPrintf(kHelpPageUrlPrefix, 3249268)); std::unique_ptr<base::DictionaryValue> sinks_and_identity( - SinksAndIdentityToValue(media_router_ui_->sinks(), GetAccountInfo())); + SinksAndIdentityToValue(media_router_ui_->GetEnabledSinks(), + GetAccountInfo())); initial_data.Set("sinksAndIdentity", std::move(sinks_and_identity)); std::unique_ptr<base::ListValue> routes(RoutesToValue( diff --git a/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h b/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h index 43e0f44b13f..8ebaa248630 100644 --- a/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h +++ b/chromium/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h @@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_MEDIA_ROUTER_WEBUI_MESSAGE_HANDLER_H_ #define CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_MEDIA_ROUTER_WEBUI_MESSAGE_HANDLER_H_ +#include <memory> +#include <string> #include <unordered_map> #include <vector> @@ -41,7 +43,7 @@ class MediaRouterWebUIMessageHandler : public content::WebUIMessageHandler { ~MediaRouterWebUIMessageHandler() override; // Methods to update the status displayed by the dialog. - void UpdateSinks(const std::vector<MediaSinkWithCastModes>& sinks); + virtual void UpdateSinks(const std::vector<MediaSinkWithCastModes>& sinks); void UpdateRoutes(const std::vector<MediaRoute>& routes, const std::vector<MediaRoute::Id>& joinable_route_ids, const std::unordered_map<MediaRoute::Id, MediaCastMode>& diff --git a/chromium/chrome/browser/ui/webui/media_router/web_contents_display_observer.h b/chromium/chrome/browser/ui/webui/media_router/web_contents_display_observer.h new file mode 100644 index 00000000000..40d2d9411cb --- /dev/null +++ b/chromium/chrome/browser/ui/webui/media_router/web_contents_display_observer.h @@ -0,0 +1,38 @@ +// 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_UI_WEBUI_MEDIA_ROUTER_WEB_CONTENTS_DISPLAY_OBSERVER_H_ +#define CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_WEB_CONTENTS_DISPLAY_OBSERVER_H_ + +#include <memory> + +#include "base/callback.h" + +namespace content { +class WebContents; +} + +namespace display { +class Display; +} + +namespace media_router { + +// Keeps track of the display that a WebContents is on. +class WebContentsDisplayObserver { + public: + // |web_contents|: WebContents to observe. + // |callback|: Gets called whenever |web_contents| moves to another display. + static std::unique_ptr<WebContentsDisplayObserver> Create( + content::WebContents* web_contents, + base::RepeatingClosure callback); + + virtual ~WebContentsDisplayObserver() = default; + + virtual const display::Display& GetCurrentDisplay() const = 0; +}; + +} // namespace media_router + +#endif // CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_WEB_CONTENTS_DISPLAY_OBSERVER_H_ diff --git a/chromium/chrome/browser/ui/webui/memory_internals_ui.cc b/chromium/chrome/browser/ui/webui/memory_internals_ui.cc index ebf83424203..c38eda39caa 100644 --- a/chromium/chrome/browser/ui/webui/memory_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/memory_internals_ui.cc @@ -10,7 +10,7 @@ #include <utility> #include <vector> -#include "base/allocator/features.h" +#include "base/allocator/buildflags.h" #include "base/bind.h" #include "base/memory/weak_ptr.h" #include "base/path_service.h" @@ -61,11 +61,6 @@ std::string GetMessageString() { case ProfilingProcessHost::Mode::kGpu: return std::string("Memory logging is enabled for just the gpu process."); - case ProfilingProcessHost::Mode::kManual: - return std::string( - "Memory logging must be manually enabled for each process via " - "chrome://memory-internals."); - case ProfilingProcessHost::Mode::kMinimal: return std::string( "Memory logging is enabled for the browser and GPU processes."); @@ -76,14 +71,11 @@ std::string GetMessageString() { "processes. This UI is disabled."); case ProfilingProcessHost::Mode::kNone: + case ProfilingProcessHost::Mode::kManual: default: - return base::StringPrintf( - "Memory logging is not enabled. Start with --%s=%s" - " to log all processes, or --%s=%s to log only the browser and GPU " - "processes. " - "Other options available in chrome://flags", - switches::kMemlog, switches::kMemlogModeAll, switches::kMemlog, - switches::kMemlogModeMinimal); + return std::string( + "Memory logging must be manually enabled for each process via " + "chrome://memory-internals."); } #elif defined(ADDRESS_SANITIZER) || defined(SYZYASAN) return "Memory logging is not available in this build because a memory " diff --git a/chromium/chrome/browser/ui/webui/nacl_ui.cc b/chromium/chrome/browser/ui/webui/nacl_ui.cc index 80e72f55980..d687697c457 100644 --- a/chromium/chrome/browser/ui/webui/nacl_ui.cc +++ b/chromium/chrome/browser/ui/webui/nacl_ui.cc @@ -24,7 +24,6 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/task_scheduler/post_task.h" -#include "base/threading/sequenced_worker_pool.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/plugins/plugin_prefs.h" diff --git a/chromium/chrome/browser/ui/webui/net_internals/net_internals_ui.cc b/chromium/chrome/browser/ui/webui/net_internals/net_internals_ui.cc index 158173bac8e..302df0f9a2c 100644 --- a/chromium/chrome/browser/ui/webui/net_internals/net_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/net_internals/net_internals_ui.cc @@ -72,7 +72,7 @@ #include "net/log/net_log_capture_mode.h" #include "net/log/net_log_entry.h" #include "net/log/net_log_util.h" -#include "net/proxy/proxy_service.h" +#include "net/proxy_resolution/proxy_service.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" @@ -84,6 +84,7 @@ #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/debug_daemon_client.h" #include "chromeos/network/onc/onc_certificate_importer_impl.h" +#include "chromeos/network/onc/onc_parsed_certificates.h" #include "chromeos/network/onc/onc_utils.h" #endif @@ -683,7 +684,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetNetInfo( void NetInternalsMessageHandler::IOThreadImpl::OnReloadProxySettings( const base::ListValue* list) { DCHECK(!list); - GetMainContext()->proxy_service()->ForceReloadProxyConfig(); + GetMainContext()->proxy_resolution_service()->ForceReloadProxyConfig(); // Cause the renderer to be notified of the new values. SendNetInfo(net::NET_INFO_PROXY_SETTINGS); @@ -692,7 +693,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnReloadProxySettings( void NetInternalsMessageHandler::IOThreadImpl::OnClearBadProxies( const base::ListValue* list) { DCHECK(!list); - GetMainContext()->proxy_service()->ClearBadProxiesCache(); + GetMainContext()->proxy_resolution_service()->ClearBadProxiesCache(); // Cause the renderer to be notified of the new values. SendNetInfo(net::NET_INFO_BAD_PROXIES); @@ -1020,11 +1021,10 @@ void NetInternalsMessageHandler::ImportONCFileToNSSDB( chromeos::onc::CertificateImporterImpl cert_importer( BrowserThread::GetTaskRunnerForThread(BrowserThread::IO), nssdb); cert_importer.ImportCertificates( - certificates, + std::make_unique<chromeos::onc::OncParsedCertificates>(certificates), onc_source, base::Bind(&NetInternalsMessageHandler::OnCertificatesImported, - AsWeakPtr(), - error)); + AsWeakPtr(), error)); } void NetInternalsMessageHandler::OnCertificatesImported( @@ -1133,9 +1133,9 @@ void NetInternalsMessageHandler::IOThreadImpl::OnSetCaptureMode( // can be called from ANY THREAD. void NetInternalsMessageHandler::IOThreadImpl::OnAddEntry( const net::NetLogEntry& entry) { - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::BindOnce(&IOThreadImpl::AddEntryToQueue, this, - base::Passed(entry.ToValue()))); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&IOThreadImpl::AddEntryToQueue, this, entry.ToValue())); } // Note that this can be called from ANY THREAD. @@ -1153,7 +1153,7 @@ void NetInternalsMessageHandler::IOThreadImpl::SendJavascriptCommand( BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::BindOnce(&IOThreadImpl::SendJavascriptCommand, - this, command, base::Passed(&arg))); + this, command, std::move(arg))); } void NetInternalsMessageHandler::IOThreadImpl::AddEntryToQueue( diff --git a/chromium/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chromium/chrome/browser/ui/webui/ntp/app_launcher_handler.cc index 708f400dd42..604df9e0b0c 100644 --- a/chromium/chrome/browser/ui/webui/ntp/app_launcher_handler.cc +++ b/chromium/chrome/browser/ui/webui/ntp/app_launcher_handler.cc @@ -33,6 +33,7 @@ #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/extensions/app_launch_params.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/extensions/extension_enable_flow.h" @@ -40,11 +41,11 @@ #include "chrome/browser/ui/webui/extensions/extension_basic_info.h" #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" #include "chrome/browser/ui/webui/ntp/new_tab_ui.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_metrics.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/common/web_application_info.h" @@ -298,7 +299,7 @@ void AppLauncherHandler::Observe(int type, crx_installer->profile())) { return; } - // Fall through. + FALLTHROUGH; } case extensions::NOTIFICATION_EXTENSION_LOAD_ERROR: { attempted_bookmark_app_install_ = false; @@ -608,6 +609,13 @@ void AppLauncherHandler::HandleShowAppInfo(const base::ListValue* args) { if (!extension) return; + if (extension->is_hosted_app() && extension->from_bookmark()) { + chrome::ShowSiteSettings( + chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()), + extensions::AppLaunchInfo::GetFullLaunchURL(extension)); + return; + } + UMA_HISTOGRAM_ENUMERATION("Apps.AppInfoDialog.Launches", AppInfoLaunchSource::FROM_APPS_PAGE, AppInfoLaunchSource::NUM_LAUNCH_SOURCES); diff --git a/chromium/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc b/chromium/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc index 0ce8d87bb43..36cdfcfe8e4 100644 --- a/chromium/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc +++ b/chromium/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc @@ -24,7 +24,7 @@ #include "chrome/browser/ui/bookmarks/bookmark_bar_constants.h" #include "chrome/browser/ui/webui/app_launcher_login_handler.h" #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" @@ -292,7 +292,7 @@ void NTPResourceCache::CreateNewTabGuestHTML() { int guest_tab_ids = IDR_GUEST_TAB_HTML; int guest_tab_description_ids = IDS_NEW_TAB_GUEST_SESSION_DESCRIPTION; int guest_tab_heading_ids = IDS_NEW_TAB_GUEST_SESSION_HEADING; - int guest_tab_link_ids = IDS_NEW_TAB_GUEST_SESSION_LEARN_MORE_LINK; + int guest_tab_link_ids = IDS_LEARN_MORE; #if defined(OS_CHROMEOS) guest_tab_ids = IDR_GUEST_SESSION_TAB_HTML; @@ -351,6 +351,16 @@ void NTPResourceCache::CreateNewTabGuestHTML() { new_tab_guest_html_ = base::RefCountedString::TakeString(&full_html); } +// TODO(alancutter): Consider moving this utility function up somewhere where it +// can be shared with md_bookmarks_ui.cc. +// Ampersands are used by menus to determine which characters to use as shortcut +// keys. This functionality is not implemented for NTP. +static base::string16 GetLocalizedString(int message_id) { + base::string16 result = l10n_util::GetStringUTF16(message_id); + result.erase(std::remove(result.begin(), result.end(), '&'), result.end()); + return result; +} + void NTPResourceCache::CreateNewTabHTML() { // TODO(estade): these strings should be defined in their relevant handlers // (in GetLocalizedValues) and should have more legible names. @@ -361,57 +371,63 @@ void NTPResourceCache::CreateNewTabHTML() { load_time_data.SetString( "bookmarkbarattached", prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar) ? "true" : "false"); - load_time_data.SetString("title", - l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE)); + load_time_data.SetString("title", GetLocalizedString(IDS_NEW_TAB_TITLE)); load_time_data.SetString("webStoreTitle", - l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE)); - load_time_data.SetString("webStoreTitleShort", - l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE_SHORT)); + GetLocalizedString(IDS_EXTENSION_WEB_STORE_TITLE)); + load_time_data.SetString( + "webStoreTitleShort", + GetLocalizedString(IDS_EXTENSION_WEB_STORE_TITLE_SHORT)); load_time_data.SetString("attributionintro", - l10n_util::GetStringUTF16(IDS_NEW_TAB_ATTRIBUTION_INTRO)); + GetLocalizedString(IDS_NEW_TAB_ATTRIBUTION_INTRO)); load_time_data.SetString("appuninstall", - l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL)); + GetLocalizedString(IDS_EXTENSIONS_UNINSTALL)); load_time_data.SetString("appoptions", - l10n_util::GetStringUTF16(IDS_NEW_TAB_APP_OPTIONS)); + GetLocalizedString(IDS_NEW_TAB_APP_OPTIONS)); load_time_data.SetString("appdetails", - l10n_util::GetStringUTF16(IDS_NEW_TAB_APP_DETAILS)); + GetLocalizedString(IDS_NEW_TAB_APP_DETAILS)); load_time_data.SetString("appinfodialog", - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_SHOW_INFO)); + GetLocalizedString(IDS_APP_CONTEXT_MENU_SHOW_INFO)); load_time_data.SetString("appcreateshortcut", - l10n_util::GetStringUTF16(IDS_NEW_TAB_APP_CREATE_SHORTCUT)); + GetLocalizedString(IDS_NEW_TAB_APP_CREATE_SHORTCUT)); load_time_data.SetString("appDefaultPageName", - l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME)); - load_time_data.SetString("applaunchtypepinned", - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED)); - load_time_data.SetString("applaunchtyperegular", - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR)); - load_time_data.SetString("applaunchtypewindow", - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); - load_time_data.SetString("applaunchtypefullscreen", - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN)); - load_time_data.SetString("syncpromotext", - l10n_util::GetStringUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL)); + GetLocalizedString(IDS_APP_DEFAULT_PAGE_NAME)); + load_time_data.SetString( + "applaunchtypepinned", + GetLocalizedString(IDS_APP_CONTEXT_MENU_OPEN_PINNED)); + load_time_data.SetString( + "applaunchtyperegular", + GetLocalizedString(IDS_APP_CONTEXT_MENU_OPEN_REGULAR)); + load_time_data.SetString( + "applaunchtypewindow", + GetLocalizedString(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); + load_time_data.SetString( + "applaunchtypefullscreen", + GetLocalizedString(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN)); + load_time_data.SetString( + "syncpromotext", GetLocalizedString(IDS_SYNC_START_SYNC_BUTTON_LABEL)); load_time_data.SetString("syncLinkText", - l10n_util::GetStringUTF16(IDS_SYNC_ADVANCED_OPTIONS)); + GetLocalizedString(IDS_SYNC_ADVANCED_OPTIONS)); load_time_data.SetBoolean("shouldShowSyncLogin", AppLauncherLoginHandler::ShouldShow(profile_)); - load_time_data.SetString("learnMore", - l10n_util::GetStringUTF16(IDS_LEARN_MORE)); + load_time_data.SetString("learnMore", GetLocalizedString(IDS_LEARN_MORE)); const std::string& app_locale = g_browser_process->GetApplicationLocale(); load_time_data.SetString( "webStoreLink", google_util::AppendGoogleLocaleParam( extension_urls::GetWebstoreLaunchURL(), app_locale) .spec()); - load_time_data.SetString("appInstallHintText", - l10n_util::GetStringUTF16(IDS_NEW_TAB_APP_INSTALL_HINT_LABEL)); - load_time_data.SetString("learn_more", - l10n_util::GetStringUTF16(IDS_LEARN_MORE)); - load_time_data.SetString("tile_grid_screenreader_accessible_description", - l10n_util::GetStringUTF16(IDS_NEW_TAB_TILE_GRID_ACCESSIBLE_DESCRIPTION)); - load_time_data.SetString("page_switcher_change_title", - l10n_util::GetStringUTF16(IDS_NEW_TAB_PAGE_SWITCHER_CHANGE_TITLE)); - load_time_data.SetString("page_switcher_same_title", - l10n_util::GetStringUTF16(IDS_NEW_TAB_PAGE_SWITCHER_SAME_TITLE)); + load_time_data.SetString( + "appInstallHintText", + GetLocalizedString(IDS_NEW_TAB_APP_INSTALL_HINT_LABEL)); + load_time_data.SetString("learn_more", GetLocalizedString(IDS_LEARN_MORE)); + load_time_data.SetString( + "tile_grid_screenreader_accessible_description", + GetLocalizedString(IDS_NEW_TAB_TILE_GRID_ACCESSIBLE_DESCRIPTION)); + load_time_data.SetString( + "page_switcher_change_title", + GetLocalizedString(IDS_NEW_TAB_PAGE_SWITCHER_CHANGE_TITLE)); + load_time_data.SetString( + "page_switcher_same_title", + GetLocalizedString(IDS_NEW_TAB_PAGE_SWITCHER_SAME_TITLE)); // On Mac OS X 10.7+, horizontal scrolling can be treated as a back or // forward gesture. Pass through a flag that indicates whether or not that // feature is enabled. diff --git a/chromium/chrome/browser/ui/webui/ntp_tiles_internals_ui.cc b/chromium/chrome/browser/ui/webui/ntp_tiles_internals_ui.cc index 47f2794447b..5513e2c6e93 100644 --- a/chromium/chrome/browser/ui/webui/ntp_tiles_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/ntp_tiles_internals_ui.cc @@ -18,7 +18,6 @@ #include "components/grit/components_resources.h" #include "components/history/core/browser/top_sites.h" #include "components/image_fetcher/core/image_fetcher_impl.h" -#include "components/ntp_tiles/field_trial.h" #include "components/ntp_tiles/icon_cacher.h" #include "components/ntp_tiles/most_visited_sites.h" #include "components/ntp_tiles/webui/ntp_tiles_internals_message_handler.h" diff --git a/chromium/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chromium/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc index e8c51f8d221..8053b7ab8bd 100644 --- a/chromium/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc +++ b/chromium/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc @@ -157,7 +157,6 @@ void OfflineInternalsUIMessageHandler::HandleStoredPagesCallback( std::string callback_id, const offline_pages::MultipleOfflinePageItemResult& pages) { base::ListValue results; - for (const auto& page : pages) { auto offline_page = std::make_unique<base::DictionaryValue>(); offline_page->SetString("onlineUrl", page.url.spec()); @@ -172,6 +171,13 @@ void OfflineInternalsUIMessageHandler::HandleStoredPagesCallback( offline_page->SetString("requestOrigin", page.request_origin); results.Append(std::move(offline_page)); } + // Sort by creation order. + std::sort(results.GetList().begin(), results.GetList().end(), + [](auto& a, auto& b) { + return a.FindKey({"creationTime"})->GetDouble() < + b.FindKey({"creationTime"})->GetDouble(); + }); + ResolveJavascriptCallback(base::Value(callback_id), results); } @@ -317,13 +323,26 @@ void OfflineInternalsUIMessageHandler::HandleGeneratePageBundle( for (auto& page_url : page_urls) { // Creates a dummy prefetch URL with a bogus ID, and using the URL as the // page title. - prefetch_urls.push_back(offline_pages::PrefetchURL( - "dummy id", GURL(page_url), base::UTF8ToUTF16(page_url))); + GURL url(page_url); + if (url.is_valid()) { + prefetch_urls.push_back(offline_pages::PrefetchURL( + "offline-internals", url, base::UTF8ToUTF16(page_url))); + } } prefetch_service_->GetPrefetchDispatcher()->AddCandidatePrefetchURLs( offline_pages::kSuggestedArticlesNamespace, prefetch_urls); - std::string message("Added candidate URLs.\n"); + // Note: Static casts are needed here so that both Windows and Android can + // compile these printf formats. + std::string message = + base::StringPrintf("Added %zu candidate URLs.", prefetch_urls.size()); + if (prefetch_urls.size() < page_urls.size()) { + size_t invalid_urls_count = page_urls.size() - prefetch_urls.size(); + message.append( + base::StringPrintf(" Ignored %zu invalid URLs.", invalid_urls_count)); + } + message.append("\n"); + // Construct a JSON array containing all the URLs. To guard against malicious // URLs that might contain special characters, we create a ListValue and then // serialize it into JSON, instead of doing direct string manipulation. diff --git a/chromium/chrome/browser/ui/webui/policy_ui.cc b/chromium/chrome/browser/ui/webui/policy_ui.cc index f6a5f67d606..0c2f31ea748 100644 --- a/chromium/chrome/browser/ui/webui/policy_ui.cc +++ b/chromium/chrome/browser/ui/webui/policy_ui.cc @@ -24,7 +24,6 @@ content::WebUIDataSource* CreatePolicyUIHtmlSource() { IDS_POLICY_FILTER_PLACEHOLDER); source->AddLocalizedString("reloadPolicies", IDS_POLICY_RELOAD_POLICIES); source->AddLocalizedString("exportPoliciesJSON", IDS_EXPORT_POLICIES_JSON); - source->AddLocalizedString("chromeForWork", IDS_POLICY_CHROME_FOR_WORK); source->AddLocalizedString("status", IDS_POLICY_STATUS); source->AddLocalizedString("statusDevice", IDS_POLICY_STATUS_DEVICE); source->AddLocalizedString("statusUser", IDS_POLICY_STATUS_USER); diff --git a/chromium/chrome/browser/ui/webui/predictors/predictors_handler.cc b/chromium/chrome/browser/ui/webui/predictors/predictors_handler.cc index 832038b8a75..7a338593fa9 100644 --- a/chromium/chrome/browser/ui/webui/predictors/predictors_handler.cc +++ b/chromium/chrome/browser/ui/webui/predictors/predictors_handler.cc @@ -23,27 +23,6 @@ using predictors::AutocompleteActionPredictor; using predictors::ResourcePrefetchPredictor; using predictors::ResourcePrefetchPredictorTables; -namespace { - -using predictors::ResourceData; - -std::string ConvertResourceType(ResourceData::ResourceType type) { - switch (type) { - case ResourceData::RESOURCE_TYPE_IMAGE: - return "Image"; - case ResourceData::RESOURCE_TYPE_STYLESHEET: - return "Stylesheet"; - case ResourceData::RESOURCE_TYPE_SCRIPT: - return "Script"; - case ResourceData::RESOURCE_TYPE_FONT_RESOURCE: - return "Font"; - default: - return "Unknown"; - } -} - -} // namespace - PredictorsHandler::PredictorsHandler(Profile* profile) { autocomplete_action_predictor_ = predictors::AutocompleteActionPredictorFactory::GetForProfile(profile); @@ -103,22 +82,10 @@ void PredictorsHandler::RequestResourcePrefetchPredictorDb( ResourcePrefetchPredictor::INITIALIZED; if (initialized) { - // URL table cache. - auto db = std::make_unique<base::ListValue>(); - AddPrefetchDataMapToListValue( - *resource_prefetch_predictor->url_resource_data_->data_cache_, - db.get()); - dict.Set("url_db", std::move(db)); - - // Host table cache. - db = std::make_unique<base::ListValue>(); - AddPrefetchDataMapToListValue( - *resource_prefetch_predictor->host_resource_data_->data_cache_, - db.get()); - dict.Set("host_db", std::move(db)); + // TODO(alexilin): Add redirects table. // Origin table cache. - db = std::make_unique<base::ListValue>(); + auto db = std::make_unique<base::ListValue>(); AddOriginDataMapToListValue( *resource_prefetch_predictor->origin_data_->data_cache_, db.get()); dict.Set("origin_db", std::move(db)); @@ -129,38 +96,6 @@ void PredictorsHandler::RequestResourcePrefetchPredictorDb( dict); } -void PredictorsHandler::AddPrefetchDataMapToListValue( - const std::map<std::string, predictors::PrefetchData>& data_map, - base::ListValue* db) const { - for (const auto& p : data_map) { - auto main = std::make_unique<base::DictionaryValue>(); - main->SetString("main_frame_url", p.first); - auto resources = std::make_unique<base::ListValue>(); - for (const predictors::ResourceData& r : p.second.resources()) { - auto resource = std::make_unique<base::DictionaryValue>(); - resource->SetString("resource_url", r.resource_url()); - resource->SetString("resource_type", - ConvertResourceType(r.resource_type())); - resource->SetInteger("number_of_hits", r.number_of_hits()); - resource->SetInteger("number_of_misses", r.number_of_misses()); - resource->SetInteger("consecutive_misses", r.consecutive_misses()); - resource->SetDouble("position", r.average_position()); - resource->SetDouble( - "score", ResourcePrefetchPredictorTables::ComputeResourceScore(r)); - resource->SetBoolean("before_first_contentful_paint", - r.before_first_contentful_paint()); - auto* resource_prefetch_predictor = - loading_predictor_->resource_prefetch_predictor(); - resource->SetBoolean( - "is_prefetchable", - resource_prefetch_predictor->IsResourcePrefetchable(r)); - resources->Append(std::move(resource)); - } - main->Set("resources", std::move(resources)); - db->Append(std::move(main)); - } -} - void PredictorsHandler::AddOriginDataMapToListValue( const std::map<std::string, predictors::OriginData>& data_map, base::ListValue* db) const { diff --git a/chromium/chrome/browser/ui/webui/predictors/predictors_handler.h b/chromium/chrome/browser/ui/webui/predictors/predictors_handler.h index e9a4e086b00..61d5d9ab730 100644 --- a/chromium/chrome/browser/ui/webui/predictors/predictors_handler.h +++ b/chromium/chrome/browser/ui/webui/predictors/predictors_handler.h @@ -40,9 +40,6 @@ class PredictorsHandler : public content::WebUIMessageHandler { void RequestResourcePrefetchPredictorDb(const base::ListValue* args); // Helpers for RequestResourcePrefetchPredictorDb. - void AddPrefetchDataMapToListValue( - const std::map<std::string, predictors::PrefetchData>& data_map, - base::ListValue* db) const; void AddOriginDataMapToListValue( const std::map<std::string, predictors::OriginData>& data_map, base::ListValue* db) const; diff --git a/chromium/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc b/chromium/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc index fb98de43578..2f453449b5b 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc @@ -801,10 +801,12 @@ TEST_F(ExtensionPrinterHandlerTest, Print_Pwg) { EXPECT_FALSE(pwg_raster_converter_->bitmap_settings().reverse_page_order); EXPECT_TRUE(pwg_raster_converter_->bitmap_settings().use_color); - EXPECT_EQ(printing::kDefaultPdfDpi, + EXPECT_EQ(gfx::Size(printing::kDefaultPdfDpi, printing::kDefaultPdfDpi), pwg_raster_converter_->conversion_settings().dpi); EXPECT_TRUE(pwg_raster_converter_->conversion_settings().autorotate); - EXPECT_EQ("0,0 208x416", // vertically_oriented_size * dpi / points_per_inch + // size = vertically_oriented_size * vertical_dpi / points_per_inch x + // horizontally_oriented_size * horizontal_dpi / points_per_inch + EXPECT_EQ("0,0 208x416", pwg_raster_converter_->conversion_settings().area.ToString()); const PrinterProviderPrintJob* print_job = fake_api->GetNextPendingPrintJob(); @@ -855,10 +857,12 @@ TEST_F(ExtensionPrinterHandlerTest, Print_Pwg_NonDefaultSettings) { EXPECT_TRUE(pwg_raster_converter_->bitmap_settings().reverse_page_order); EXPECT_TRUE(pwg_raster_converter_->bitmap_settings().use_color); - EXPECT_EQ(200, // max(vertical_dpi, horizontal_dpi) + EXPECT_EQ(gfx::Size(200, 100), pwg_raster_converter_->conversion_settings().dpi); EXPECT_TRUE(pwg_raster_converter_->conversion_settings().autorotate); - EXPECT_EQ("0,0 138x277", // vertically_oriented_size * dpi / points_per_inch + // size = vertically_oriented_size * vertical_dpi / points_per_inch x + // horizontally_oriented_size * horizontal_dpi / points_per_inch + EXPECT_EQ("0,0 138x138", pwg_raster_converter_->conversion_settings().area.ToString()); const PrinterProviderPrintJob* print_job = fake_api->GetNextPendingPrintJob(); diff --git a/chromium/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc b/chromium/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc index 7615db3985a..b17d6ba0c5a 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc @@ -5,6 +5,7 @@ #include "chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.h" #include <memory> +#include <utility> #include <vector> #include "base/bind_helpers.h" @@ -78,8 +79,6 @@ LocalPrinterHandlerChromeos::LocalPrinterHandlerChromeos( printers_manager_(CupsPrintersManager::Create(profile)), printer_configurer_(chromeos::PrinterConfigurer::Create(profile)), weak_factory_(this) { - printers_manager_->Start(); - // Construct the CupsPrintJobManager to listen for printing events. chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(profile); } diff --git a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc index 4da653cb56b..52fbb52e895 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc @@ -134,9 +134,13 @@ base::FilePath GetUniquePath(const base::FilePath& path) { return unique_path; } -void CreateDirectoryIfNeeded(const base::FilePath& path) { - if (!base::DirectoryExists(path)) - base::CreateDirectory(path); +base::FilePath SelectSaveDirectory(const base::FilePath& path, + const base::FilePath& default_path) { + if (base::DirectoryExists(path)) + return path; + if (!base::DirectoryExists(default_path)) + base::CreateDirectory(default_path); + return default_path; } } // namespace @@ -330,19 +334,21 @@ void PdfPrinterHandler::SelectFile(const base::FilePath& default_filename, return; } - // If the directory is empty there is no reason to create it. + // If the directory is empty there is no reason to create it or use the + // default location. if (path.empty()) { - OnDirectoryCreated(default_filename); + OnDirectorySelected(default_filename, path); return; } - // Create the directory to save in if it does not exist. - base::PostTaskWithTraitsAndReply( + // Get default download directory. This will be used as a fallback if the + // save directory does not exist. + base::FilePath default_path = download_prefs->DownloadPath(); + base::PostTaskWithTraitsAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, - base::Bind(&CreateDirectoryIfNeeded, path), - base::Bind(&PdfPrinterHandler::OnDirectoryCreated, - weak_ptr_factory_.GetWeakPtr(), - path.Append(default_filename))); + base::BindOnce(&SelectSaveDirectory, path, default_path), + base::BindOnce(&PdfPrinterHandler::OnDirectorySelected, + weak_ptr_factory_.GetWeakPtr(), default_filename)); } void PdfPrinterHandler::PostPrintToPdfTask() { @@ -358,7 +364,10 @@ void PdfPrinterHandler::OnGotUniqueFileName(const base::FilePath& path) { FileSelected(path, 0, nullptr); } -void PdfPrinterHandler::OnDirectoryCreated(const base::FilePath& path) { +void PdfPrinterHandler::OnDirectorySelected(const base::FilePath& filename, + const base::FilePath& directory) { + base::FilePath path = directory.Append(filename); + // Prompts the user to select the file. ui::SelectFileDialog::FileTypeInfo file_type_info; file_type_info.extensions.resize(1); diff --git a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.h b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.h index 5fcc74cb63d..641fb89cc83 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.h +++ b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler.h @@ -90,7 +90,11 @@ class PdfPrinterHandler : public PrinterHandler, private: void PostPrintToPdfTask(); void OnGotUniqueFileName(const base::FilePath& path); - void OnDirectoryCreated(const base::FilePath& path); + + // Prompts the user to save the file. The dialog will default to saving + // the file with name |filename| in |directory|. + void OnDirectorySelected(const base::FilePath& filename, + const base::FilePath& directory); Profile* const profile_; printing::StickySettings* const sticky_settings_; diff --git a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler_win_unittest.cc b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler_win_unittest.cc index 9246f32c1a3..f46f4498638 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler_win_unittest.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/pdf_printer_handler_win_unittest.cc @@ -26,8 +26,6 @@ class FakePdfPrinterHandler; bool GetOpenFileNameImpl(OPENFILENAME* ofn); bool GetSaveFileNameImpl(FakePdfPrinterHandler* handler, OPENFILENAME* ofn); -void EmptyPrintCallback(const base::Value& error) {} - class FakePdfPrinterHandler : public PdfPrinterHandler { public: FakePdfPrinterHandler(Profile* profile, @@ -51,8 +49,7 @@ class FakePdfPrinterHandler : public PdfPrinterHandler { } void StartPrintToPdf(const base::string16& job_title) { - StartPrint("", "", job_title, "", gfx::Size(), nullptr, - base::Bind(&EmptyPrintCallback)); + StartPrint("", "", job_title, "", gfx::Size(), nullptr, base::DoNothing()); run_loop_.Run(); } diff --git a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.cc index 2913dbfdc78..9bce25e7094 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.cc @@ -46,17 +46,17 @@ #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" #include "chrome/browser/ui/webui/print_preview/printer_handler.h" #include "chrome/browser/ui/webui/print_preview/sticky_settings.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_switches.h" -#include "chrome/common/cloud_print/cloud_print_cdd_conversion.h" #include "chrome/common/cloud_print/cloud_print_constants.h" #include "chrome/common/crash_keys.h" -#include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "chrome/common/webui_url_constants.h" #include "components/cloud_devices/common/cloud_device_description.h" #include "components/cloud_devices/common/cloud_devices_urls.h" #include "components/cloud_devices/common/printer_description.h" #include "components/prefs/pref_service.h" +#include "components/printing/common/cloud_print_cdd_conversion.h" #include "components/printing/common/print_messages.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" #include "components/signin/core/browser/profile_management_switches.h" @@ -891,7 +891,7 @@ void PrintPreviewHandler::GetNumberFormatAndMeasurementSystem( // Getting the number formatting based on the locale and writing to // dictionary. base::string16 number_format = base::FormatDouble(123456.78, 2); - settings->SetString(kDecimalDelimeter, number_format.substr(6, 1)); + settings->SetString(kDecimalDelimeter, number_format.substr(7, 1)); settings->SetString(kThousandsDelimeter, number_format.substr(3, 1)); settings->SetInteger(kUnitType, system); } diff --git a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.h b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.h index 894f5e9adca..8efb84708d7 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.h +++ b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler.h @@ -15,7 +15,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/timer/timer.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" #include "content/public/browser/web_ui_message_handler.h" #include "printing/backend/print_backend.h" diff --git a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc index 382461c39d9..bc72b510408 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc @@ -6,6 +6,7 @@ #include "base/base64.h" #include "base/containers/flat_set.h" +#include "base/i18n/rtl.h" #include "base/json/json_writer.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string16.h" @@ -339,6 +340,10 @@ class PrintPreviewHandlerTest : public testing::Test { } void Initialize() { + // Set locale since the delimeters we check in VerifyInitialSettings() + // depend on it. + base::i18n::SetICUDefaultLocale("en"); + // Sending this message will enable javascript, so it must always be called // before any other messages are sent. base::Value args(base::Value::Type::LIST); @@ -376,7 +381,8 @@ class PrintPreviewHandlerTest : public testing::Test { // print_preview.NativeInitialSettings type in // chrome/browser/resources/print_preview/native_layer.js. Checks that // |default_printer_name| is the printer name returned and that - // |initiator_title| is the initiator title returned. Assumes + // |initiator_title| is the initiator title returned and validates that + // delimeters are correct for "en" locale (set in Initialize()). Assumes // "test-callback-id-0" was used as the callback id. void ValidateInitialSettings(const content::TestWebUI::CallData& data, const std::string& default_printer_name, @@ -387,10 +393,16 @@ class PrintPreviewHandlerTest : public testing::Test { base::Value::Type::BOOLEAN)); ASSERT_TRUE(settings->FindKeyOfType("isInAppKioskMode", base::Value::Type::BOOLEAN)); - ASSERT_TRUE(settings->FindKeyOfType("thousandsDelimeter", - base::Value::Type::STRING)); - ASSERT_TRUE( - settings->FindKeyOfType("decimalDelimeter", base::Value::Type::STRING)); + + const base::Value* thousandsDelimeter = settings->FindKeyOfType( + "thousandsDelimeter", base::Value::Type::STRING); + ASSERT_TRUE(thousandsDelimeter); + EXPECT_EQ(",", thousandsDelimeter->GetString()); + const base::Value* decimalDelimeter = + settings->FindKeyOfType("decimalDelimeter", base::Value::Type::STRING); + ASSERT_TRUE(decimalDelimeter); + EXPECT_EQ(".", decimalDelimeter->GetString()); + ASSERT_TRUE( settings->FindKeyOfType("unitType", base::Value::Type::INTEGER)); ASSERT_TRUE(settings->FindKeyOfType("previewModifiable", diff --git a/chromium/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chromium/chrome/browser/ui/webui/print_preview/print_preview_ui.cc index 003106131a5..8c0162c47a4 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/print_preview_ui.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/print_preview_ui.cc @@ -241,6 +241,8 @@ void AddPrintPreviewStrings(content::WebUIDataSource* source) { source->AddString( "noDestsPromoLearnMoreUrl", chrome::kCloudPrintNoDestinationsLearnMoreURL); + source->AddString("gcpCertificateErrorLearnMoreURL", + chrome::kCloudPrintCertificateErrorLearnMoreURL); source->AddLocalizedString("pageRangeLimitInstruction", IDS_PRINT_PREVIEW_PAGE_RANGE_LIMIT_INSTRUCTION); source->AddLocalizedString( @@ -313,6 +315,8 @@ void AddPrintPreviewStrings(content::WebUIDataSource* source) { source->AddLocalizedString("offlineForWeek", IDS_PRINT_PREVIEW_OFFLINE_FOR_WEEK); source->AddLocalizedString("offline", IDS_PRINT_PREVIEW_OFFLINE); + source->AddLocalizedString("noLongerSupportedFragment", + IDS_PRINT_PREVIEW_NO_LONGER_SUPPORTED_FRAGMENT); source->AddLocalizedString("noLongerSupported", IDS_PRINT_PREVIEW_NO_LONGER_SUPPORTED); source->AddLocalizedString("couldNotPrint", @@ -347,6 +351,8 @@ void AddPrintPreviewStrings(content::WebUIDataSource* source) { "groupPrinterSharingInviteText", IDS_PRINT_PREVIEW_GROUP_INVITE_TEXT); source->AddLocalizedString( "printerSharingInviteText", IDS_PRINT_PREVIEW_INVITE_TEXT); + source->AddLocalizedString("registerPrinterInformationMessage", + IDS_CLOUD_PRINT_REGISTER_PRINTER_INFORMATION); source->AddLocalizedString("moreOptionsLabel", IDS_MORE_OPTIONS_LABEL); source->AddLocalizedString("lessOptionsLabel", IDS_LESS_OPTIONS_LABEL); #if defined(OS_CHROMEOS) @@ -379,8 +385,6 @@ void AddPrintPreviewImages(content::WebUIDataSource* source) { IDR_PRINT_PREVIEW_IMAGES_2X_PRINTER_SHARED); source->AddResourcePath("images/business.svg", IDR_PRINT_PREVIEW_IMAGES_ENTERPRISE_PRINTER); - source->AddResourcePath("images/third_party.png", - IDR_PRINT_PREVIEW_IMAGES_THIRD_PARTY); source->AddResourcePath("images/google_doc.png", IDR_PRINT_PREVIEW_IMAGES_GOOGLE_DOC); source->AddResourcePath("images/pdf.png", IDR_PRINT_PREVIEW_IMAGES_PDF); diff --git a/chromium/chrome/browser/ui/webui/print_preview/printer_capabilities.cc b/chromium/chrome/browser/ui/webui/print_preview/printer_capabilities.cc index ffef1099d74..918401ab811 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/printer_capabilities.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/printer_capabilities.cc @@ -21,8 +21,8 @@ #include "chrome/browser/printing/print_view_manager.h" #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" #include "chrome/browser/ui/webui/print_preview/printer_handler.h" -#include "chrome/common/cloud_print/cloud_print_cdd_conversion.h" -#include "chrome/common/crash_keys.h" +#include "components/crash/core/common/crash_keys.h" +#include "components/printing/common/cloud_print_cdd_conversion.h" #include "content/public/browser/render_frame_host.h" #include "printing/backend/print_backend.h" #include "printing/backend/print_backend_consts.h" @@ -32,7 +32,7 @@ #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/grit/generated_resources.h" +#include "components/strings/grit/components_strings.h" #include "ui/base/l10n/l10n_util.h" #endif diff --git a/chromium/chrome/browser/ui/webui/print_preview/printer_handler.cc b/chromium/chrome/browser/ui/webui/print_preview/printer_handler.cc index bf297f51a01..af0d09391cb 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/printer_handler.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/printer_handler.cc @@ -7,7 +7,7 @@ #include "build/buildflag.h" #include "chrome/browser/ui/webui/print_preview/extension_printer_handler.h" #include "chrome/browser/ui/webui/print_preview/pdf_printer_handler.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #if BUILDFLAG(ENABLE_SERVICE_DISCOVERY) #include "chrome/browser/ui/webui/print_preview/privet_printer_handler.h" diff --git a/chromium/chrome/browser/ui/webui/print_preview/printer_handler.h b/chromium/chrome/browser/ui/webui/print_preview/printer_handler.h index 6231bafd927..29fc3194f90 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/printer_handler.h +++ b/chromium/chrome/browser/ui/webui/print_preview/printer_handler.h @@ -11,7 +11,7 @@ #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/strings/string16.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" namespace base { class DictionaryValue; diff --git a/chromium/chrome/browser/ui/webui/print_preview/privet_printer_handler.cc b/chromium/chrome/browser/ui/webui/print_preview/privet_printer_handler.cc index eedc4c4573b..6df2047be61 100644 --- a/chromium/chrome/browser/ui/webui/print_preview/privet_printer_handler.cc +++ b/chromium/chrome/browser/ui/webui/print_preview/privet_printer_handler.cc @@ -100,15 +100,19 @@ void PrivetPrinterHandler::LocalPrinterChanged( bool has_local_printing, const cloud_print::DeviceDescription& description) { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (has_local_printing || - command_line->HasSwitch(switches::kEnablePrintPreviewRegisterPromos)) { - auto printer_info = std::make_unique<base::DictionaryValue>(); - FillPrinterDescription(name, description, has_local_printing, - printer_info.get()); - base::ListValue printers; - printers.Set(0, std::move(printer_info)); - added_printers_callback_.Run(printers); + if (!added_printers_callback_ || + (!has_local_printing && + !command_line->HasSwitch(switches::kEnablePrintPreviewRegisterPromos))) { + // If Print Preview is not expecting this printer (callback reset or no + // registration promos and not a local printer), return early. + return; } + auto printer_info = std::make_unique<base::DictionaryValue>(); + FillPrinterDescription(name, description, has_local_printing, + printer_info.get()); + base::ListValue printers; + printers.Set(0, std::move(printer_info)); + added_printers_callback_.Run(printers); } void PrivetPrinterHandler::LocalPrinterRemoved(const std::string& name) {} diff --git a/chromium/chrome/browser/ui/webui/profile_helper.cc b/chromium/chrome/browser/ui/webui/profile_helper.cc index 7bf6fd82b9e..e0e10904671 100644 --- a/chromium/chrome/browser/ui/webui/profile_helper.cc +++ b/chromium/chrome/browser/ui/webui/profile_helper.cc @@ -83,10 +83,7 @@ void OpenNewWindowForProfile(Profile* profile) { } void DeleteProfileAtPath(base::FilePath file_path, - content::WebUI* web_ui, ProfileMetrics::ProfileDelete deletion_source) { - DCHECK(web_ui); - if (!profiles::IsMultipleProfilesEnabled()) return; g_browser_process->profile_manager()->MaybeScheduleProfileForDeletion( diff --git a/chromium/chrome/browser/ui/webui/profile_helper.h b/chromium/chrome/browser/ui/webui/profile_helper.h index 1b0f930765d..3c82a62112e 100644 --- a/chromium/chrome/browser/ui/webui/profile_helper.h +++ b/chromium/chrome/browser/ui/webui/profile_helper.h @@ -9,17 +9,12 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_metrics.h" -namespace content { -class WebUI; -} - namespace webui { void OpenNewWindowForProfile(Profile* profile); // Deletes the profile at the given |file_path|. void DeleteProfileAtPath(base::FilePath file_path, - content::WebUI* web_ui, ProfileMetrics::ProfileDelete deletion_source); } // namespace webui diff --git a/chromium/chrome/browser/ui/webui/profile_helper_browsertest.cc b/chromium/chrome/browser/ui/webui/profile_helper_browsertest.cc index 05f532d44bd..37f4e959945 100644 --- a/chromium/chrome/browser/ui/webui/profile_helper_browsertest.cc +++ b/chromium/chrome/browser/ui/webui/profile_helper_browsertest.cc @@ -128,7 +128,7 @@ IN_PROC_BROWSER_TEST_F(ProfileHelperTest, DeleteSoleProfile) { content::NotificationService::AllSources()); content::WindowedNotificationObserver close_observer( chrome::NOTIFICATION_BROWSER_CLOSED, content::Source<Browser>(browser())); - webui::DeleteProfileAtPath(original_browser->profile()->GetPath(), &web_ui, + webui::DeleteProfileAtPath(original_browser->profile()->GetPath(), ProfileMetrics::DELETE_PROFILE_SETTINGS); open_observer.Wait(); close_observer.Wait(); @@ -158,7 +158,7 @@ IN_PROC_BROWSER_TEST_F(ProfileHelperTest, DeleteActiveProfile) { content::NotificationService::AllSources()); content::WindowedNotificationObserver close_observer( chrome::NOTIFICATION_BROWSER_CLOSED, content::Source<Browser>(browser())); - webui::DeleteProfileAtPath(original_browser->profile()->GetPath(), &web_ui, + webui::DeleteProfileAtPath(original_browser->profile()->GetPath(), ProfileMetrics::DELETE_PROFILE_SETTINGS); open_observer.Wait(); close_observer.Wait(); @@ -184,7 +184,7 @@ IN_PROC_BROWSER_TEST_F(ProfileHelperTest, DeleteInactiveProfile) { content::BrowsingDataRemoverCompletionInhibitor inhibitor( content::BrowserContext::GetBrowsingDataRemover(additional_profile)); - webui::DeleteProfileAtPath(additional_profile->GetPath(), &web_ui, + webui::DeleteProfileAtPath(additional_profile->GetPath(), ProfileMetrics::DELETE_PROFILE_SETTINGS); inhibitor.BlockUntilNearCompletion(); inhibitor.ContinueToCompletion(); diff --git a/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h b/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h index 0c1fc6e5d71..26f5bfdffcd 100644 --- a/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h +++ b/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h @@ -18,7 +18,7 @@ #include "base/sequenced_task_runner_helpers.h" #include "content/public/browser/browser_thread.h" #include "storage/browser/quota/quota_manager.h" -#include "third_party/WebKit/common/quota/quota_types.mojom.h" +#include "third_party/WebKit/public/mojom/quota/quota_types.mojom.h" namespace quota_internals { diff --git a/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_types.h b/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_types.h index c9b5c7a34cd..b6a1295afbb 100644 --- a/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_types.h +++ b/chromium/chrome/browser/ui/webui/quota_internals/quota_internals_types.h @@ -12,7 +12,7 @@ #include <string> #include "base/time/time.h" -#include "third_party/WebKit/common/quota/quota_types.mojom.h" +#include "third_party/WebKit/public/mojom/quota/quota_types.mojom.h" #include "url/gurl.h" namespace base { diff --git a/chromium/chrome/browser/ui/webui/sandbox_internals_ui.cc b/chromium/chrome/browser/ui/webui/sandbox_internals_ui.cc index b73d1a50a04..66b53a7a944 100644 --- a/chromium/chrome/browser/ui/webui/sandbox_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/sandbox_internals_ui.cc @@ -6,8 +6,8 @@ #include <string> +#include "build/build_config.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" #include "content/public/browser/render_frame_host.h" @@ -15,6 +15,11 @@ #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" +#if defined(OS_ANDROID) +#include "chrome/common/sandbox_status_extension_android.mojom.h" +#include "third_party/WebKit/public/common/associated_interfaces/associated_interface_provider.h" +#endif + #if defined(OS_LINUX) #include "content/public/browser/zygote_host_linux.h" #include "services/service_manager/sandbox/sandbox.h" @@ -75,8 +80,10 @@ SandboxInternalsUI::SandboxInternalsUI(content::WebUI* web_ui) void SandboxInternalsUI::RenderFrameCreated( content::RenderFrameHost* render_frame_host) { #if defined(OS_ANDROID) - render_frame_host->Send(new ChromeViewMsg_AddSandboxStatusExtension( - render_frame_host->GetRoutingID())); + chrome::mojom::SandboxStatusExtensionAssociatedPtr sandbox_status; + render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface( + &sandbox_status); + sandbox_status->AddSandboxStatusExtension(); #endif } diff --git a/chromium/chrome/browser/ui/webui/settings/about_handler.cc b/chromium/chrome/browser/ui/webui/settings/about_handler.cc index 11da4972a25..0e22bfc93e3 100644 --- a/chromium/chrome/browser/ui/webui/settings/about_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/about_handler.cc @@ -602,7 +602,8 @@ void AboutHandler::HandleRefreshTPMFirmwareUpdateStatus( const base::ListValue* args) { chromeos::tpm_firmware_update::ShouldOfferUpdateViaPowerwash( base::Bind(&AboutHandler::RefreshTPMFirmwareUpdateStatus, - weak_factory_.GetWeakPtr())); + weak_factory_.GetWeakPtr()), + base::TimeDelta()); } void AboutHandler::RefreshTPMFirmwareUpdateStatus(bool update_available) { diff --git a/chromium/chrome/browser/ui/webui/settings/appearance_handler.cc b/chromium/chrome/browser/ui/webui/settings/appearance_handler.cc index 128c7f1efd1..3e9bb1a4d96 100644 --- a/chromium/chrome/browser/ui/webui/settings/appearance_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/appearance_handler.cc @@ -13,17 +13,13 @@ #include "content/public/browser/web_ui.h" #if defined(OS_CHROMEOS) -#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h" #include "chrome/browser/ui/ash/wallpaper_controller_client.h" -#include "chromeos/login/login_state.h" -#include "components/user_manager/user_manager.h" #endif namespace settings { AppearanceHandler::AppearanceHandler(content::WebUI* webui) - : profile_(Profile::FromWebUI(webui)) { -} + : profile_(Profile::FromWebUI(webui)), weak_ptr_factory_(this) {} AppearanceHandler::~AppearanceHandler() {} @@ -75,49 +71,28 @@ void AppearanceHandler::HandleUseSystemTheme(const base::ListValue* args) { #if defined(OS_CHROMEOS) void AppearanceHandler::IsWallpaperSettingVisible(const base::ListValue* args) { CHECK_EQ(args->GetSize(), 1U); - const base::Value* callback_id; - CHECK(args->Get(0, &callback_id)); - AllowJavascript(); - - bool is_wallpaper_visible = false; - const chromeos::LoginState* login_state = chromeos::LoginState::Get(); - const chromeos::LoginState::LoggedInUserType user_type = - login_state->GetLoggedInUserType(); - const user_manager::User* user = - user_manager::UserManager::Get()->GetActiveUser(); - - // Only login, whitelist types and active users are allowed to change - // their wallpaper. Then show the wallpaper setting row for them. - if (login_state->IsUserLoggedIn() && user && - (user_type == chromeos::LoginState::LOGGED_IN_USER_REGULAR || - user_type == chromeos::LoginState::LOGGED_IN_USER_OWNER || - user_type == chromeos::LoginState::LOGGED_IN_USER_PUBLIC_ACCOUNT || - user_type == chromeos::LoginState::LOGGED_IN_USER_SUPERVISED)) { - is_wallpaper_visible = true; - } - - ResolveJavascriptCallback(*callback_id, base::Value(is_wallpaper_visible)); + WallpaperControllerClient::Get()->ShouldShowWallpaperSetting( + base::Bind(&AppearanceHandler::ResolveCallback, + weak_ptr_factory_.GetWeakPtr(), args->GetList()[0].Clone())); } void AppearanceHandler::IsWallpaperPolicyControlled( const base::ListValue* args) { CHECK_EQ(args->GetSize(), 1U); - const base::Value* callback_id; - CHECK(args->Get(0, &callback_id)); - AllowJavascript(); - - ResolveJavascriptCallback( - *callback_id, - base::Value(chromeos::WallpaperManager::Get()->IsPolicyControlled( - user_manager::UserManager::Get()->GetActiveUser()->GetAccountId()))); + WallpaperControllerClient::Get()->IsActiveUserWallpaperControlledByPolicy( + base::Bind(&AppearanceHandler::ResolveCallback, + weak_ptr_factory_.GetWeakPtr(), args->GetList()[0].Clone())); } void AppearanceHandler::HandleOpenWallpaperManager( - const base::ListValue* /*args*/) { - if (!chromeos::WallpaperManager::Get()->IsPolicyControlled( - user_manager::UserManager::Get()->GetActiveUser()->GetAccountId())) { - WallpaperControllerClient::Get()->OpenWallpaperPicker(); - } + const base::ListValue* args) { + WallpaperControllerClient::Get()->OpenWallpaperPickerIfAllowed(); +} + +void AppearanceHandler::ResolveCallback(const base::Value& callback_id, + bool result) { + AllowJavascript(); + ResolveJavascriptCallback(callback_id, base::Value(result)); } #endif diff --git a/chromium/chrome/browser/ui/webui/settings/appearance_handler.h b/chromium/chrome/browser/ui/webui/settings/appearance_handler.h index d31af5aa186..3acb6d567f4 100644 --- a/chromium/chrome/browser/ui/webui/settings/appearance_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/appearance_handler.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_UI_WEBUI_SETTINGS_APPEARANCE_HANDLER_H_ #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" namespace base { @@ -41,7 +42,7 @@ class AppearanceHandler : public SettingsPageUIHandler { #endif #if defined(OS_CHROMEOS) - // Whether should show the wallpaper setting row. + // Whether the wallpaper setting should be shown. void IsWallpaperSettingVisible(const base::ListValue* args); // Whether the wallpaper is policy controlled. @@ -49,10 +50,15 @@ class AppearanceHandler : public SettingsPageUIHandler { // Open the wallpaper manager app. void HandleOpenWallpaperManager(const base::ListValue* args); + + // Helper function to resolve the Javascript callback. + void ResolveCallback(const base::Value& callback_id, bool result); #endif Profile* profile_; // Weak pointer. + base::WeakPtrFactory<AppearanceHandler> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(AppearanceHandler); }; diff --git a/chromium/chrome/browser/ui/webui/settings/browser_lifetime_handler.cc b/chromium/chrome/browser/ui/webui/settings/browser_lifetime_handler.cc index d8fce5d42bb..551123b58ae 100644 --- a/chromium/chrome/browser/ui/webui/settings/browser_lifetime_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/browser_lifetime_handler.cc @@ -75,7 +75,7 @@ void BrowserLifetimeHandler::HandleFactoryReset( true); prefs->CommitPendingWrite(); chrome::AttemptRelaunch(); - })); + }), base::TimeDelta()); return; } diff --git a/chromium/chrome/browser/ui/webui/settings/change_password_handler.cc b/chromium/chrome/browser/ui/webui/settings/change_password_handler.cc index 1590e25bf22..a077bd75bd2 100644 --- a/chromium/chrome/browser/ui/webui/settings/change_password_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/change_password_handler.cc @@ -60,7 +60,8 @@ void ChangePasswordHandler::HandleChangePassword(const base::ListValue* args) { void ChangePasswordHandler::UpdateChangePasswordCardVisibility() { FireWebUIListener( "change-password-visibility", - base::Value(safe_browsing::ChromePasswordProtectionService:: + base::Value(service_->IsWarningEnabled() && + safe_browsing::ChromePasswordProtectionService:: ShouldShowChangePasswordSettingUI(profile_))); } diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc index 6d0cf18456f..a535d490d14 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc @@ -8,12 +8,14 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/containers/flat_map.h" #include "base/files/file_util.h" #include "base/json/json_string_value_serializer.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/path_service.h" #include "base/strings/string_util.h" +#include "base/task_scheduler/post_task.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/values.h" #include "chrome/browser/browser_process.h" @@ -32,7 +34,11 @@ #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/debug_daemon_client.h" #include "chromeos/printing/ppd_cache.h" +#include "chromeos/printing/ppd_line_reader.h" #include "chromeos/printing/ppd_provider.h" +#include "chromeos/printing/printer_configuration.h" +#include "chromeos/printing/printing_constants.h" +#include "chromeos/printing/uri_components.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_ui.h" @@ -40,33 +46,17 @@ #include "net/base/filename_util.h" #include "net/url_request/url_request_context_getter.h" #include "printing/backend/print_backend.h" -#include "url/third_party/mozilla/url_parse.h" namespace chromeos { namespace settings { namespace { -constexpr char kIppScheme[] = "ipp"; -constexpr char kIppsScheme[] = "ipps"; - -constexpr int kIppPort = 631; -// IPPS commonly uses the HTTPS port despite the spec saying it should use the -// IPP port. -constexpr int kIppsPort = 443; - // These values are written to logs. New enum values can be added, but existing // enums must never be renumbered or deleted and reused. enum PpdSourceForHistogram { kUser = 0, kScs = 1, kPpdSourceMax }; -// A parsed representation of a printer uri. -struct PrinterUri { - bool encrypted = false; - std::string scheme; - std::string host; - int port = url::SpecialPort::PORT_INVALID; - std::string path; -}; +constexpr int kPpdMaxLineLength = 255; void RecordPpdSource(const PpdSourceForHistogram& source) { UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PpdSource", source, kPpdSourceMax); @@ -82,43 +72,6 @@ void RecordIppQuerySuccess(bool success) { UMA_HISTOGRAM_BOOLEAN("Printing.CUPS.IppAttributesSuccess", success); } -// Parses |printer_uri| into its components and written into |uri|. Returns -// true if the uri was parsed successfully, returns false otherwise. No changes -// are made to |uri| if this function returns false. -bool ParseUri(const std::string& printer_uri, PrinterUri* uri) { - DCHECK(uri); - const char* uri_ptr = printer_uri.c_str(); - url::Parsed parsed; - url::ParseStandardURL(uri_ptr, printer_uri.length(), &parsed); - if (!parsed.scheme.is_valid() || !parsed.host.is_valid() || - !parsed.path.is_valid()) { - return false; - } - base::StringPiece scheme(&uri_ptr[parsed.scheme.begin], parsed.scheme.len); - base::StringPiece host(&uri_ptr[parsed.host.begin], parsed.host.len); - base::StringPiece path(&uri_ptr[parsed.path.begin], parsed.path.len); - - bool encrypted = scheme != kIppScheme; - int port = ParsePort(uri_ptr, parsed.port); - // Port not specified. - if (port == url::SpecialPort::PORT_UNSPECIFIED || - port == url::SpecialPort::PORT_INVALID) { - if (scheme == kIppScheme) { - port = kIppPort; - } else if (scheme == kIppsScheme) { - port = kIppsPort; - } - } - - uri->encrypted = encrypted; - uri->scheme = scheme.as_string(); - uri->host = host.as_string(); - uri->port = port; - uri->path = path.as_string(); - - return true; -} - // Returns true if |printer_uri| is an IPP uri. bool IsIppUri(base::StringPiece printer_uri) { base::StringPiece::size_type separator_location = @@ -136,15 +89,17 @@ bool IsIppUri(base::StringPiece printer_uri) { // error to attempt this with a non-IPP printer. void QueryAutoconf(const std::string& printer_uri, const PrinterInfoCallback& callback) { - PrinterUri uri; + auto optional = ParseUri(printer_uri); // Behavior for querying a non-IPP uri is undefined and disallowed. - if (!IsIppUri(printer_uri) || !ParseUri(printer_uri, &uri)) { + if (!IsIppUri(printer_uri) || !optional.has_value()) { LOG(WARNING) << "Printer uri is invalid: " << printer_uri; callback.Run(false, "", "", "", false); return; } - QueryIppPrinter(uri.host, uri.port, uri.path, uri.encrypted, callback); + UriComponents uri = optional.value(); + QueryIppPrinter(uri.host(), uri.port(), uri.path(), uri.encrypted(), + callback); } // Create an empty CupsPrinterInfo dictionary value. It should be consistent @@ -193,8 +148,8 @@ std::unique_ptr<base::DictionaryValue> GetPrinterInfo(const Printer& printer) { printer_info->SetString("printerModel", printer.model()); printer_info->SetString("printerMakeAndModel", printer.make_and_model()); - PrinterUri uri; - if (!ParseUri(printer.uri(), &uri)) { + auto optional = printer.GetUriComponents(); + if (!optional.has_value()) { // Uri is invalid so we set default values. LOG(WARNING) << "Could not parse uri. Defaulting values"; printer_info->SetString("printerAddress", ""); @@ -204,7 +159,9 @@ std::unique_ptr<base::DictionaryValue> GetPrinterInfo(const Printer& printer) { return printer_info; } - if (base::ToLowerASCII(uri.scheme) == "usb") { + UriComponents uri = optional.value(); + + if (base::ToLowerASCII(uri.scheme()) == "usb") { // USB has URI path (and, maybe, query) components that aren't really // associated with a queue -- the mapping between printing semantics and URI // semantics breaks down a bit here. From the user's point of view, the @@ -213,12 +170,12 @@ std::unique_ptr<base::DictionaryValue> GetPrinterInfo(const Printer& printer) { printer.uri().substr(strlen("usb://"))); } else { printer_info->SetString("printerAddress", - PrinterAddress(uri.host, uri.port)); - if (!uri.path.empty()) { - printer_info->SetString("printerQueue", uri.path.substr(1)); + PrinterAddress(uri.host(), uri.port())); + if (!uri.path().empty()) { + printer_info->SetString("printerQueue", uri.path().substr(1)); } } - printer_info->SetString("printerProtocol", base::ToLowerASCII(uri.scheme)); + printer_info->SetString("printerProtocol", base::ToLowerASCII(uri.scheme())); return printer_info; } @@ -284,6 +241,16 @@ std::unique_ptr<chromeos::Printer> DictToPrinter( return printer; } +std::string ReadFileToStringWithMaxSize(const base::FilePath& path, + int max_size) { + std::string contents; + // This call can fail, but it doesn't matter for our purposes. If it fails, + // we simply return an empty string for the contents, and it will be rejected + // as an invalid PPD. + base::ReadFileToStringWithMaxSize(path, &contents, max_size); + return contents; +} + } // namespace CupsPrintersHandler::CupsPrintersHandler(content::WebUI* webui) @@ -348,7 +315,6 @@ void CupsPrintersHandler::RegisterMessages() { void CupsPrintersHandler::OnJavascriptAllowed() { printers_manager_->AddObserver(this); - printers_manager_->Start(); } void CupsPrintersHandler::OnJavascriptDisallowed() { @@ -412,9 +378,8 @@ void CupsPrintersHandler::HandleRemoveCupsPrinter(const base::ListValue* args) { printers_manager_->RemoveConfiguredPrinter(printer_id); DebugDaemonClient* client = DBusThreadManager::Get()->GetDebugDaemonClient(); - client->CupsRemovePrinter(printer_name, - base::Bind(&OnRemovedPrinter, protocol), - base::Bind(&base::DoNothing)); + client->CupsRemovePrinter( + printer_name, base::Bind(&OnRemovedPrinter, protocol), base::DoNothing()); } void CupsPrintersHandler::HandleGetPrinterInfo(const base::ListValue* args) { @@ -532,9 +497,17 @@ void CupsPrintersHandler::HandleAddCupsPrinter(const base::ListValue* args) { CHECK(args->GetDictionary(0, &printer_dict)); std::unique_ptr<Printer> printer = DictToPrinter(*printer_dict); - PrinterUri uri; - if (!printer || !ParseUri(printer->uri(), &uri)) { - LOG(ERROR) << "Failed to parse printer"; + if (!printer) { + LOG(ERROR) << "Failed to parse printer URI"; + OnAddPrinterError(PrinterSetupResult::kFatalError); + return; + } + + auto optional = printer->GetUriComponents(); + if (!optional.has_value()) { + // If the returned optional does not contain a value then it means that the + // printer's uri was not able to be parsed successfully. + LOG(ERROR) << "Failed to parse printer URI"; OnAddPrinterError(PrinterSetupResult::kFatalError); return; } @@ -761,8 +734,26 @@ void CupsPrintersHandler::FileSelected(const base::FilePath& path, int index, void* params) { DCHECK(!webui_callback_id_.empty()); + + // Load the beggining contents of the file located at |path| and callback into + // VerifyPpdContents() in order to determine whether the file appears to be a + // PPD file. The task's priority is USER_BLOCKING because the this task + // updates the UI as a result of a direct user action. + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING}, + base::BindOnce(&ReadFileToStringWithMaxSize, path, kPpdMaxLineLength), + base::BindOnce(&CupsPrintersHandler::VerifyPpdContents, + weak_factory_.GetWeakPtr(), path)); +} + +void CupsPrintersHandler::VerifyPpdContents(const base::FilePath& path, + const std::string& contents) { + std::string result = ""; + if (PpdLineReader::ContainsMagicNumber(contents, kPpdMaxLineLength)) + result = path.value(); + ResolveJavascriptCallback(base::Value(webui_callback_id_), - base::Value(path.value())); + base::Value(result)); webui_callback_id_.clear(); } @@ -837,11 +828,17 @@ void CupsPrintersHandler::HandleAddDiscoveredPrinter( CHECK(args->GetString(0, &printer_id)); std::unique_ptr<Printer> printer = printers_manager_->GetPrinter(printer_id); - PrinterUri uri; - if (printer == nullptr || !ParseUri(printer->uri(), &uri)) { + if (printer == nullptr) { // Printer disappeared, so we don't have information about it anymore and - // can't really do much. Or the printer uri was not parsed successfully. - // Fail the add. + // can't really do much. Fail the add. + FireWebUIListener("on-add-cups-printer", base::Value(false), + base::Value(printer_id)); + return; + } + + auto optional = printer->GetUriComponents(); + if (!optional.has_value()) { + // The printer uri was not parsed successfully. Fail the add. FireWebUIListener("on-add-cups-printer", base::Value(false), base::Value(printer_id)); return; diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h b/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h index f3541827eed..0f9452af13e 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h @@ -145,6 +145,13 @@ class CupsPrintersHandler : public ::settings::SettingsPageUIHandler, int index, void* params) override; + // Used by FileSelected() in order to verify whether the beginning contents of + // the selected file contain the magic number present in all PPD files. |path| + // is used for display in the UI as this function calls back into javascript + // with |path| as the result. + void VerifyPpdContents(const base::FilePath& path, + const std::string& contents); + Profile* profile_; // Discovery support. discovery_active_ tracks whether or not the UI diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc b/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc index f4176c34c2d..4ff2b1c8713 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc @@ -29,7 +29,9 @@ #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/grit/generated_resources.h" -#include "chromeos/cryptohome/homedir_methods.h" +#include "chromeos/cryptohome/cryptohome_util.h" +#include "chromeos/dbus/cryptohome_client.h" +#include "chromeos/dbus/dbus_thread_manager.h" #include "components/arc/arc_util.h" #include "components/browsing_data/content/conditional_cache_counting_helper.h" #include "components/drive/chromeos/file_system_interface.h" @@ -300,10 +302,10 @@ void StorageHandler::UpdateOtherUsersSize() { if (user->is_active()) continue; other_users_.push_back(user); - cryptohome::HomedirMethods::GetInstance()->GetAccountDiskUsage( + DBusThreadManager::Get()->GetCryptohomeClient()->GetAccountDiskUsage( cryptohome::Identification(user->GetAccountId()), - base::Bind(&StorageHandler::OnGetOtherUserSize, - weak_ptr_factory_.GetWeakPtr())); + base::BindOnce(&StorageHandler::OnGetOtherUserSize, + weak_ptr_factory_.GetWeakPtr())); } // We should show "0 B" if there is no other user. if (other_users_.empty()) { @@ -313,8 +315,9 @@ void StorageHandler::UpdateOtherUsersSize() { } } -void StorageHandler::OnGetOtherUserSize(bool success, int64_t size) { - user_sizes_.push_back(success ? size : -1); +void StorageHandler::OnGetOtherUserSize( + base::Optional<cryptohome::BaseReply> reply) { + user_sizes_.push_back(cryptohome::AccountDiskUsageReplyToUsageSize(reply)); if (user_sizes_.size() == other_users_.size()) { base::string16 size_string; // If all the requests succeed, shows the total bytes in the UI. diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h b/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h index 9da1d5d437b..48f3d5b5875 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h @@ -12,8 +12,10 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" #include "chrome/browser/browsing_data/site_data_size_collector.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" +#include "chromeos/dbus/cryptohome/rpc.pb.h" #include "components/arc/storage_manager/arc_storage_manager.h" #include "components/user_manager/user.h" @@ -76,7 +78,7 @@ class StorageHandler : public ::settings::SettingsPageUIHandler { void UpdateOtherUsersSize(); // Callback to save the fetched user sizes and update the UI. - void OnGetOtherUserSize(bool success, int64_t size); + void OnGetOtherUserSize(base::Optional<cryptohome::BaseReply> reply); // Requests updating the space size used by Android apps and cache. void UpdateAndroidSize(); diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc b/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc index 45d050cc78c..a97d004c949 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc @@ -19,7 +19,7 @@ #include "components/prefs/pref_service.h" #include "components/session_manager/core/session_manager.h" #include "content/public/common/service_manager_connection.h" -#include "services/device/public/interfaces/constants.mojom.h" +#include "services/device/public/mojom/constants.mojom.h" #include "services/service_manager/public/cpp/connector.h" #include "ui/base/l10n/l10n_util.h" diff --git a/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.h b/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.h index 5c296d69fe7..f63ea22d163 100644 --- a/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.h @@ -10,7 +10,7 @@ #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" #include "components/session_manager/core/session_manager_observer.h" #include "mojo/public/cpp/bindings/binding.h" -#include "services/device/public/interfaces/fingerprint.mojom.h" +#include "services/device/public/mojom/fingerprint.mojom.h" class Profile; diff --git a/chromium/chrome/browser/ui/webui/settings/downloads_handler_unittest.cc b/chromium/chrome/browser/ui/webui/settings/downloads_handler_unittest.cc index c20ca4db582..7e7628fb021 100644 --- a/chromium/chrome/browser/ui/webui/settings/downloads_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/settings/downloads_handler_unittest.cc @@ -6,6 +6,8 @@ #include "base/memory/ptr_util.h" #include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_core_service_factory.h" +#include "chrome/browser/download/download_core_service_impl.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_profile.h" @@ -22,15 +24,17 @@ class DownloadsHandlerTest : public testing::Test { public: DownloadsHandlerTest() : download_manager_(new content::MockDownloadManager()), - chrome_download_manager_delegate_(&profile_), handler_(&profile_) { content::BrowserContext::SetDownloadManagerForTesting( &profile_, base::WrapUnique(download_manager_)); - EXPECT_EQ(download_manager_, - content::BrowserContext::GetDownloadManager(&profile_)); - - EXPECT_CALL(*download_manager_, GetDelegate()) - .WillRepeatedly(testing::Return(&chrome_download_manager_delegate_)); + std::unique_ptr<ChromeDownloadManagerDelegate> delegate = + std::make_unique<ChromeDownloadManagerDelegate>(&profile_); + chrome_download_manager_delegate_ = delegate.get(); + service_ = DownloadCoreServiceFactory::GetForBrowserContext(&profile_); + service_->SetDownloadManagerDelegateForTesting(std::move(delegate)); + + EXPECT_CALL(*download_manager_, GetBrowserContext()) + .WillRepeatedly(testing::Return(&profile_)); EXPECT_CALL(*download_manager_, Shutdown()); handler_.set_web_ui(&test_web_ui_); @@ -49,7 +53,7 @@ class DownloadsHandlerTest : public testing::Test { } void TearDown() override { - chrome_download_manager_delegate_.Shutdown(); + service_->SetDownloadManagerDelegateForTesting(nullptr); testing::Test::TearDown(); } @@ -74,8 +78,9 @@ class DownloadsHandlerTest : public testing::Test { content::TestWebUI test_web_ui_; TestingProfile profile_; + DownloadCoreService* service_; content::MockDownloadManager* download_manager_; // Owned by |profile_|. - ChromeDownloadManagerDelegate chrome_download_manager_delegate_; + ChromeDownloadManagerDelegate* chrome_download_manager_delegate_; DownloadsHandler handler_; }; diff --git a/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc b/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc new file mode 100644 index 00000000000..af1646f42db --- /dev/null +++ b/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc @@ -0,0 +1,168 @@ +// 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/ui/webui/settings/incompatible_applications_handler_win.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "base/win/registry.h" +#include "chrome/browser/conflicts/problematic_programs_updater_win.h" +#include "chrome/browser/conflicts/registry_key_watcher_win.h" +#include "chrome/browser/conflicts/uninstall_application_win.h" +#include "chrome/grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace settings { + +IncompatibleApplicationsHandler::IncompatibleApplicationsHandler() = default; + +IncompatibleApplicationsHandler::~IncompatibleApplicationsHandler() = default; + +void IncompatibleApplicationsHandler::RegisterMessages() { + web_ui()->RegisterMessageCallback( + "requestIncompatibleApplicationsList", + base::BindRepeating(&IncompatibleApplicationsHandler:: + HandleRequestIncompatibleApplicationsList, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "startProgramUninstallation", + base::BindRepeating( + &IncompatibleApplicationsHandler::HandleStartProgramUninstallation, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getSubtitlePluralString", + base::BindRepeating( + &IncompatibleApplicationsHandler::HandleGetSubtitlePluralString, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getSubtitleNoAdminRightsPluralString", + base::BindRepeating(&IncompatibleApplicationsHandler:: + HandleGetSubtitleNoAdminRightsPluralString, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getListTitlePluralString", + base::BindRepeating( + &IncompatibleApplicationsHandler::HandleGetListTitlePluralString, + base::Unretained(this))); +} + +void IncompatibleApplicationsHandler::OnJavascriptAllowed() {} + +void IncompatibleApplicationsHandler::OnJavascriptDisallowed() { + registry_key_watchers_.clear(); +} + +void IncompatibleApplicationsHandler::HandleRequestIncompatibleApplicationsList( + const base::ListValue* args) { + CHECK_EQ(1u, args->GetList().size()); + + AllowJavascript(); + + // Reset the registry watchers, to correctly handle repeated calls to + // requestIncompatibleApplicationsList(). + registry_key_watchers_.clear(); + + std::vector<ProblematicProgramsUpdater::ProblematicProgram> + problematic_programs = ProblematicProgramsUpdater::GetCachedPrograms(); + + base::Value application_list(base::Value::Type::LIST); + application_list.GetList().reserve(problematic_programs.size()); + + for (const auto& program : problematic_programs) { + // Set up a registry watcher for each problem application. + // Since this instance owns the watcher, it is safe to use + // base::Unretained() because the callback won't be invoked when the watcher + // gets deleted. + auto registry_key_watcher = RegistryKeyWatcher::Create( + program.info.registry_root, program.info.registry_key_path.c_str(), + program.info.registry_wow64_access, + base::BindOnce(&IncompatibleApplicationsHandler::OnApplicationRemoved, + base::Unretained(this), program.info)); + + // Only keep the watcher if it was successfully initialized. A failure here + // is unlikely, but the worst that can happen is that the |program| will not + // get removed from the list automatically in the Incompatible Applications + // subpage. + if (registry_key_watcher) { + registry_key_watchers_.insert( + {program.info, std::move(registry_key_watcher)}); + } + + // Also add the application to the list that is passed to the javascript. + base::Value dict(base::Value::Type::DICTIONARY); + dict.SetKey("name", base::Value(program.info.name)); + dict.SetKey("type", base::Value(program.blacklist_action->message_type())); + dict.SetKey("url", base::Value(program.blacklist_action->message_url())); + application_list.GetList().push_back(std::move(dict)); + } + + UMA_HISTOGRAM_COUNTS_100("IncompatibleApplicationsPage.NumApplications", + problematic_programs.size()); + + const base::Value& callback_id = args->GetList().front(); + ResolveJavascriptCallback(callback_id, application_list); +} + +void IncompatibleApplicationsHandler::HandleStartProgramUninstallation( + const base::ListValue* args) { + CHECK_EQ(1u, args->GetList().size()); + base::RecordAction(base::UserMetricsAction( + "IncompatibleApplicationsPage.UninstallationStarted")); + + // Open the Apps & Settings page with the program name highlighted. + uninstall_application::LaunchUninstallFlow( + base::UTF8ToUTF16(args->GetList()[0].GetString())); +} + +void IncompatibleApplicationsHandler::HandleGetSubtitlePluralString( + const base::ListValue* args) { + GetPluralString(IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_SUBPAGE_SUBTITLE, + args); +} + +void IncompatibleApplicationsHandler:: + HandleGetSubtitleNoAdminRightsPluralString(const base::ListValue* args) { + GetPluralString( + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_SUBPAGE_SUBTITLE_NO_ADMIN_RIGHTS, + args); +} + +void IncompatibleApplicationsHandler::HandleGetListTitlePluralString( + const base::ListValue* args) { + GetPluralString(IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_LIST_TITLE, args); +} + +void IncompatibleApplicationsHandler::GetPluralString( + int id, + const base::ListValue* args) { + CHECK_EQ(2U, args->GetList().size()); + + const base::Value& callback_id = args->GetList()[0]; + int num_applications = args->GetList()[1].GetInt(); + DCHECK_GT(0, num_applications); + + ResolveJavascriptCallback( + callback_id, + base::Value(l10n_util::GetPluralStringFUTF16(id, num_applications))); +} + +void IncompatibleApplicationsHandler::OnApplicationRemoved( + const InstalledPrograms::ProgramInfo& program) { + base::RecordAction(base::UserMetricsAction( + "IncompatibleApplicationsPage.ApplicationRemoved")); + + registry_key_watchers_.erase(program); + FireWebUIListener("incompatible-application-removed", + base::Value(program.name)); +} + +} // namespace settings diff --git a/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.h b/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.h new file mode 100644 index 00000000000..b7518f69a45 --- /dev/null +++ b/chromium/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.h @@ -0,0 +1,58 @@ +// 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_UI_WEBUI_SETTINGS_INCOMPATIBLE_APPLICATIONS_HANDLER_WIN_H_ +#define CHROME_BROWSER_UI_WEBUI_SETTINGS_INCOMPATIBLE_APPLICATIONS_HANDLER_WIN_H_ + +#include <map> +#include <memory> + +#include "base/macros.h" +#include "chrome/browser/conflicts/installed_programs_win.h" +#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" + +class RegistryKeyWatcher; + +namespace base { +class ListValue; +} + +namespace settings { + +// Incompatible Applications settings page UI handler. +class IncompatibleApplicationsHandler : public SettingsPageUIHandler { + public: + IncompatibleApplicationsHandler(); + ~IncompatibleApplicationsHandler() override; + + // SettingsPageUIHandler: + void RegisterMessages() override; + void OnJavascriptAllowed() override; + void OnJavascriptDisallowed() override; + + private: + // Sends the list of incompatible applications to the caller via a promise. + void HandleRequestIncompatibleApplicationsList(const base::ListValue* args); + + // Initiates the uninstallation of the program passed using |args|. + void HandleStartProgramUninstallation(const base::ListValue* args); + + void HandleGetSubtitlePluralString(const base::ListValue* args); + void HandleGetSubtitleNoAdminRightsPluralString(const base::ListValue* args); + void HandleGetListTitlePluralString(const base::ListValue* args); + void GetPluralString(int id, const base::ListValue* args); + + // Callback for the registry key watchers. + void OnApplicationRemoved(const InstalledPrograms::ProgramInfo& program); + + // Container for the watchers. + std::map<InstalledPrograms::ProgramInfo, std::unique_ptr<RegistryKeyWatcher>> + registry_key_watchers_; + + DISALLOW_COPY_AND_ASSIGN(IncompatibleApplicationsHandler); +}; + +} // namespace settings + +#endif // CHROME_BROWSER_UI_WEBUI_SETTINGS_INCOMPATIBLE_APPLICATIONS_HANDLER_WIN_H_ diff --git a/chromium/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chromium/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc index 906dbf0f698..1c76ec475ac 100644 --- a/chromium/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc +++ b/chromium/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc @@ -11,6 +11,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" +#include "build/buildflag.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/plugins/plugin_utils.h" @@ -28,12 +29,15 @@ #include "components/google/core/browser/google_util.h" #include "components/password_manager/core/browser/password_manager_constants.h" #include "components/safe_browsing/common/safe_browsing_prefs.h" +#include "components/signin/core/browser/signin_features.h" #include "components/strings/grit/components_strings.h" #include "components/subresource_filter/core/browser/subresource_filter_features.h" #include "content/public/browser/web_ui_data_source.h" +#include "services/device/public/cpp/device_features.h" #include "ui/base/l10n/l10n_util.h" #if defined(OS_CHROMEOS) +#include "ash/public/cpp/ash_features.h" #include "ash/public/cpp/ash_switches.h" #include "ash/public/interfaces/voice_interaction_controller.mojom.h" #include "ash/strings/grit/ash_strings.h" @@ -43,6 +47,7 @@ #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" +#include "chrome/browser/signin/account_consistency_mode_manager.h" #include "chrome/browser/ui/webui/chromeos/bluetooth_dialog_localized_strings_provider.h" #include "chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.h" #include "chromeos/chromeos_switches.h" @@ -54,6 +59,7 @@ #include "ui/display/manager/chromeos/touch_device_manager.h" #else #include "chrome/browser/ui/webui/settings/system_handler.h" +#include "components/signin/core/browser/profile_management_switches.h" #endif #if defined(OS_WIN) @@ -176,6 +182,19 @@ void AddA11yStrings(content::WebUIDataSource* html_source) { {"chromeVoxLabel", IDS_SETTINGS_CHROMEVOX_LABEL}, {"chromeVoxOptionsLabel", IDS_SETTINGS_CHROMEVOX_OPTIONS_LABEL}, {"screenMagnifierLabel", IDS_SETTINGS_SCREEN_MAGNIFIER_LABEL}, + {"screenMagnifierZoomLabel", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_LABEL}, + {"dockedMagnifierLabel", IDS_SETTINGS_DOCKED_MAGNIFIER_LABEL}, + {"dockedMagnifierZoomLabel", IDS_SETTINGS_DOCKED_MAGNIFIER_ZOOM_LABEL}, + {"screenMagnifierZoom2x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_2_X}, + {"screenMagnifierZoom4x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_4_X}, + {"screenMagnifierZoom6x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_6_X}, + {"screenMagnifierZoom8x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_8_X}, + {"screenMagnifierZoom10x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_10_X}, + {"screenMagnifierZoom12x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_12_X}, + {"screenMagnifierZoom14x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_14_X}, + {"screenMagnifierZoom16x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_16_X}, + {"screenMagnifierZoom18x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_18_X}, + {"screenMagnifierZoom20x", IDS_SETTINGS_SCREEN_MAGNIFIER_ZOOM_20_X}, {"tapDraggingLabel", IDS_SETTINGS_TAP_DRAGGING_LABEL}, {"clickOnStopLabel", IDS_SETTINGS_CLICK_ON_STOP_LABEL}, {"delayBeforeClickLabel", IDS_SETTINGS_DELAY_BEFORE_CLICK_LABEL}, @@ -231,13 +250,17 @@ void AddA11yStrings(content::WebUIDataSource* html_source) { arraysize(localized_strings)); #if defined(OS_CHROMEOS) - html_source->AddString("a11yLearnMoreUrl", - chrome::kChromeAccessibilityHelpURL); + html_source->AddString( + "a11yLearnMoreUrl", + GetHelpUrlWithBoard(chrome::kChromeAccessibilityHelpURL)); html_source->AddBoolean( "showExperimentalA11yFeatures", base::CommandLine::ForCurrentProcess()->HasSwitch( chromeos::switches::kEnableExperimentalAccessibilityFeatures)); + + html_source->AddBoolean("dockedMagnifierFeatureEnabled", + ash::features::IsDockedMagnifierEnabled()); #endif } @@ -448,22 +471,24 @@ void AddChangePasswordStrings(content::WebUIDataSource* html_source) { #endif } -void AddClearBrowsingDataStrings(content::WebUIDataSource* html_source) { +void AddClearBrowsingDataStrings(content::WebUIDataSource* html_source, + Profile* profile) { int clear_cookies_summary_msg_id = IDS_SETTINGS_CLEAR_COOKIES_AND_SITE_DATA_SUMMARY_BASIC; #if defined(OS_CHROMEOS) - // Mirror account reconciliation behavior is turned on for child accounts on - // Chrome OS. - if (user_manager::UserManager::Get()->GetPrimaryUser()->GetType() == - user_manager::USER_TYPE_CHILD) { + if (AccountConsistencyModeManager::IsMirrorEnabledForProfile(profile)) { + clear_cookies_summary_msg_id = + IDS_SETTINGS_CLEAR_COOKIES_AND_SITE_DATA_SUMMARY_BASIC_WITH_EXCEPTION; + } +#else // !defined(OS_CHROMEOS) + if (signin::IsDiceEnabledForProfile(profile->GetPrefs())) { clear_cookies_summary_msg_id = - IDS_SETTINGS_CLEAR_COOKIES_AND_SITE_DATA_MIRROR_SUMMARY_BASIC; + IDS_SETTINGS_CLEAR_COOKIES_AND_SITE_DATA_SUMMARY_BASIC_WITH_EXCEPTION; } #endif LocalizedString localized_strings[] = { - {"clearFollowingItemsFrom", IDS_SETTINGS_CLEAR_FOLLOWING_ITEMS_FROM}, {"clearTimeRange", IDS_SETTINGS_CLEAR_PERIOD_TITLE}, {"clearBrowsingHistory", IDS_SETTINGS_CLEAR_BROWSING_HISTORY}, {"clearBrowsingHistorySummary", @@ -478,19 +503,11 @@ void AddClearBrowsingDataStrings(content::WebUIDataSource* html_source) { {"clearFormData", IDS_SETTINGS_CLEAR_FORM_DATA}, {"clearHostedAppData", IDS_SETTINGS_CLEAR_HOSTED_APP_DATA}, {"clearMediaLicenses", IDS_SETTINGS_CLEAR_MEDIA_LICENSES}, - {"clearDataHour", IDS_SETTINGS_CLEAR_DATA_HOUR}, - {"clearDataDay", IDS_SETTINGS_CLEAR_DATA_DAY}, - {"clearDataWeek", IDS_SETTINGS_CLEAR_DATA_WEEK}, - {"clearData4Weeks", IDS_SETTINGS_CLEAR_DATA_4WEEKS}, - {"clearDataEverything", IDS_SETTINGS_CLEAR_DATA_EVERYTHING}, {"clearPeriodHour", IDS_SETTINGS_CLEAR_PERIOD_HOUR}, {"clearPeriod24Hours", IDS_SETTINGS_CLEAR_PERIOD_24_HOURS}, {"clearPeriod7Days", IDS_SETTINGS_CLEAR_PERIOD_7_DAYS}, {"clearPeriod4Weeks", IDS_SETTINGS_CLEAR_PERIOD_FOUR_WEEKS}, {"clearPeriodEverything", IDS_SETTINGS_CLEAR_PERIOD_EVERYTHING}, - {"warnAboutNonClearedData", IDS_SETTINGS_CLEAR_DATA_SOME_STUFF_REMAINS}, - {"clearsSyncedData", IDS_SETTINGS_CLEAR_DATA_CLEARS_SYNCED_DATA}, - {"clearBrowsingDataLearnMoreUrl", IDS_SETTINGS_CLEAR_DATA_LEARN_MORE_URL}, {"historyDeletionDialogTitle", IDS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_TITLE}, {"historyDeletionDialogOK", IDS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_OK}, @@ -513,12 +530,6 @@ void AddClearBrowsingDataStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_CLEAR_BROWSING_HISTORY_SUMMARY_SYNCED, base::ASCIIToUTF16(chrome::kMyActivityUrlInClearBrowsingData))); html_source->AddString( - "otherFormsOfBrowsingHistory", - l10n_util::GetStringFUTF16( - IDS_CLEAR_BROWSING_DATA_HISTORY_FOOTER, - l10n_util::GetStringUTF16( - IDS_SETTINGS_CLEAR_DATA_WEB_HISTORY_URL_IN_FOOTER))); - html_source->AddString( "historyDeletionDialogBody", l10n_util::GetStringFUTF16( IDS_CLEAR_BROWSING_DATA_HISTORY_NOTICE, @@ -552,7 +563,7 @@ void AddDeviceStrings(content::WebUIDataSource* html_source) { {"scrollLabel", IDS_SETTINGS_SCROLL_LABEL}, {"traditionalScrollLabel", IDS_SETTINGS_TRADITIONAL_SCROLL_LABEL}, {"naturalScrollLabel", IDS_SETTINGS_NATURAL_SCROLL_LABEL}, - {"naturalScrollLearnMore", IDS_SETTINGS_NATURAL_SCROLL_LEARN_MORE}, + {"naturalScrollLearnMore", IDS_LEARN_MORE}, }; AddLocalizedStringsBulk(html_source, device_strings, arraysize(device_strings)); @@ -785,8 +796,6 @@ void AddChromeCleanupStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_RESET_CLEANUP_DETAILS_FILES_AND_PROGRAMS}, {"chromeCleanupDetailsRegistryEntries", IDS_SETTINGS_RESET_CLEANUP_DETAILS_REGISTRY_ENTRIES}, - {"chromeCleanupDoneButtonLabel", - IDS_SETTINGS_RESET_CLEANUP_DONE_BUTTON_LABEL}, {"chromeCleanupExplanationCleanupError", IDS_SETTINGS_RESET_CLEANUP_EXPLANATION_CLEANUP_ERROR}, {"chromeCleanupExplanationFindAndRemove", @@ -858,6 +867,33 @@ void AddChromeCleanupStrings(content::WebUIDataSource* html_source) { html_source->AddString("chromeCleanupDetailsExplanation", cleanup_details_explanation); } + +void AddIncompatibleApplicationsStrings(content::WebUIDataSource* html_source) { + LocalizedString localized_strings[] = { + {"incompatibleApplicationsResetCardTitle", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_RESET_CARD_TITLE}, + {"incompatibleApplicationsSubpageSubtitle", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_SUBPAGE_SUBTITLE}, + {"incompatibleApplicationsSubpageSubtitleNoAdminRights", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_SUBPAGE_SUBTITLE_NO_ADMIN_RIGHTS}, + {"incompatibleApplicationsListTitle", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_LIST_TITLE}, + {"incompatibleApplicationsRemoveButton", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_REMOVE_BUTTON}, + {"incompatibleApplicationsUpdateButton", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_UPDATE_BUTTON}, + {"incompatibleApplicationsDone", + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_DONE}, + }; + AddLocalizedStringsBulk(html_source, localized_strings, + arraysize(localized_strings)); + // TODO(pmonette): Add the help URL when available. + base::string16 learn_how_text = l10n_util::GetStringFUTF16( + IDS_SETTINGS_INCOMPATIBLE_APPLICATIONS_SUBPAGE_LEARN_HOW, + base::ASCIIToUTF16("chrome://placeholder")); + html_source->AddString("incompatibleApplicationsSubpageLearnHow", + learn_how_text); +} #endif // defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) void AddResetStrings(content::WebUIDataSource* html_source) { @@ -892,8 +928,6 @@ void AddResetStrings(content::WebUIDataSource* html_source) { #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) {"resetCleanupComputerTrigger", IDS_SETTINGS_RESET_CLEAN_UP_COMPUTER_TRIGGER}, - {"resetCleanupComputerTriggerDescription", - IDS_SETTINGS_RESET_CLEAN_UP_COMPUTER_TRIGGER_DESCRIPTION}, #endif }; AddLocalizedStringsBulk(html_source, localized_strings, @@ -1078,8 +1112,7 @@ void AddInternetStrings(content::WebUIDataSource* html_source) { {"networkConnectNotAllowed", IDS_SETTINGS_INTERNET_CONNECT_NOT_ALLOWED}, {"networkIPAddress", IDS_SETTINGS_INTERNET_NETWORK_IP_ADDRESS}, {"networkIPConfigAuto", IDS_SETTINGS_INTERNET_NETWORK_IP_CONFIG_AUTO}, - {"networkNameserversLearnMore", - IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_LEARN_MORE}, + {"networkNameserversLearnMore", IDS_LEARN_MORE}, {"networkPrefer", IDS_SETTINGS_INTERNET_NETWORK_PREFER}, {"networkPrimaryUserControlled", IDS_SETTINGS_INTERNET_NETWORK_PRIMARY_USER_CONTROLLED}, @@ -1112,6 +1145,8 @@ void AddInternetStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_INTERNET_GMSCORE_NOTIFICATIONS_SECOND_STEP}, {"gmscoreNotificationsThirdStep", IDS_SETTINGS_INTERNET_GMSCORE_NOTIFICATIONS_THIRD_STEP}, + {"gmscoreNotificationsFourthStep", + IDS_SETTINGS_INTERNET_GMSCORE_NOTIFICATIONS_FOURTH_STEP}, {"tetherConnectionDialogTitle", IDS_SETTINGS_INTERNET_TETHER_CONNECTION_DIALOG_TITLE}, {"tetherConnectionAvailableDeviceTitle", @@ -1276,6 +1311,7 @@ void AddPasswordsAndFormsStrings(content::WebUIDataSource* html_source) { {"autofill", IDS_SETTINGS_AUTOFILL}, {"googlePayments", IDS_SETTINGS_GOOGLE_PAYMENTS}, {"googlePaymentsCached", IDS_SETTINGS_GOOGLE_PAYMENTS_CACHED}, + {"autofillFormsLabel", IDS_SETTINGS_AUTOFILL_TOGGLE_LABEL}, {"addresses", IDS_SETTINGS_AUTOFILL_ADDRESSES_HEADING}, {"addAddressTitle", IDS_SETTINGS_AUTOFILL_ADDRESSES_ADD_TITLE}, {"editAddressTitle", IDS_SETTINGS_AUTOFILL_ADDRESSES_EDIT_TITLE}, @@ -1297,6 +1333,8 @@ void AddPasswordsAndFormsStrings(content::WebUIDataSource* html_source) { {"addCreditCardTitle", IDS_SETTINGS_ADD_CREDIT_CARD_TITLE}, {"autofillDetail", IDS_SETTINGS_AUTOFILL_DETAIL}, {"passwords", IDS_SETTINGS_PASSWORDS}, + {"passwordsSavePasswordsLabel", + IDS_SETTINGS_PASSWORDS_SAVE_PASSWORDS_TOGGLE_LABEL}, {"passwordsAutosigninLabel", IDS_SETTINGS_PASSWORDS_AUTOSIGNIN_CHECKBOX_LABEL}, {"passwordsAutosigninDescription", @@ -1305,7 +1343,6 @@ void AddPasswordsAndFormsStrings(content::WebUIDataSource* html_source) { {"savedPasswordsHeading", IDS_SETTINGS_PASSWORDS_SAVED_HEADING}, {"passwordExceptionsHeading", IDS_SETTINGS_PASSWORDS_EXCEPTIONS_HEADING}, {"deletePasswordException", IDS_SETTINGS_PASSWORDS_DELETE_EXCEPTION}, - {"passwordsDone", IDS_SETTINGS_PASSWORD_DONE}, {"removePassword", IDS_SETTINGS_PASSWORD_REMOVE}, {"searchPasswords", IDS_SETTINGS_PASSWORD_SEARCH}, {"showPassword", IDS_SETTINGS_PASSWORD_SHOW}, @@ -1321,12 +1358,22 @@ void AddPasswordsAndFormsStrings(content::WebUIDataSource* html_source) { {"noPasswordsFound", IDS_SETTINGS_PASSWORDS_NONE}, {"noExceptionsFound", IDS_SETTINGS_PASSWORDS_EXCEPTIONS_NONE}, {"import", IDS_PASSWORD_MANAGER_IMPORT_BUTTON}, - {"export", IDS_PASSWORD_MANAGER_EXPORT_BUTTON}, + {"exportMenuItem", IDS_SETTINGS_PASSWORDS_EXPORT_MENU_ITEM}, {"undoRemovePassword", IDS_SETTINGS_PASSWORD_UNDO}, {"passwordDeleted", IDS_SETTINGS_PASSWORD_DELETED_PASSWORD}, {"exportPasswordsTitle", IDS_SETTINGS_PASSWORDS_EXPORT_TITLE}, {"exportPasswordsDescription", IDS_SETTINGS_PASSWORDS_EXPORT_DESCRIPTION}, - {"exportPasswords", IDS_SETTINGS_PASSWORDS_EXPORT}}; + {"exportPasswords", IDS_SETTINGS_PASSWORDS_EXPORT}, + {"exportingPasswordsTitle", IDS_SETTINGS_PASSWORDS_EXPORTING_TITLE}, + {"exportPasswordsTryAgain", IDS_SETTINGS_PASSWORDS_EXPORT_TRY_AGAIN}, + {"exportPasswordsFailTitle", + IDS_SETTINGS_PASSWORDS_EXPORTING_FAILURE_TITLE}, + {"exportPasswordsFailTips", + IDS_SETTINGS_PASSWORDS_EXPORTING_FAILURE_TIPS}, + {"exportPasswordsFailTipsEnoughSpace", + IDS_SETTINGS_PASSWORDS_EXPORTING_FAILURE_TIP_ENOUGH_SPACE}, + {"exportPasswordsFailTipsAnotherFolder", + IDS_SETTINGS_PASSWORDS_EXPORTING_FAILURE_TIP_ANOTHER_FOLDER}}; html_source->AddString( "managePasswordsLabel", @@ -1345,11 +1392,10 @@ void AddPasswordsAndFormsStrings(content::WebUIDataSource* html_source) { arraysize(localized_strings)); } -void AddPeopleStrings(content::WebUIDataSource* html_source) { +void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) { LocalizedString localized_strings[] = { {"peoplePageTitle", IDS_SETTINGS_PEOPLE}, {"manageOtherPeople", IDS_SETTINGS_PEOPLE_MANAGE_OTHER_PEOPLE}, - {"manageSupervisedUsers", IDS_SETTINGS_PEOPLE_MANAGE_SUPERVISED_USERS}, #if defined(OS_CHROMEOS) {"configureFingerprintTitle", IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_TITLE}, {"configureFingerprintInstructionLocateScannerStep", @@ -1372,8 +1418,6 @@ void AddPeopleStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_FINGER_TOO_FAST}, {"configureFingerprintImmobile", IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_FINGER_IMMOBILE}, - {"configureFingerprintDoneButton", - IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_DONE_BUTTON}, {"configureFingerprintAddAnotherButton", IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_ADD_ANOTHER_BUTTON}, {"configurePinChoosePinTitle", @@ -1448,8 +1492,19 @@ void AddPeopleStrings(content::WebUIDataSource* html_source) { #else // !defined(OS_CHROMEOS) {"domainManagedProfile", IDS_SETTINGS_PEOPLE_DOMAIN_MANAGED_PROFILE}, {"editPerson", IDS_SETTINGS_EDIT_PERSON}, + {"profileNameAndPicture", IDS_SETTINGS_PROFILE_NAME_AND_PICTURE}, {"showShortcutLabel", IDS_SETTINGS_PROFILE_SHORTCUT_TOGGLE_LABEL}, #endif // defined(OS_CHROMEOS) +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + {"peopleSignIn", IDS_SETTINGS_PEOPLE_SIGN_IN}, + {"peopleSignInPrompt", IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT}, + {"peopleSignInPromptSecondary", + IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY}, + {"useAnotherAccount", IDS_SETTINGS_PEOPLE_SYNC_ANOTHER_ACCOUNT}, + {"syncAsName", IDS_SETTINGS_PEOPLE_SYNC_AS_NAME}, + {"syncedToName", IDS_SETTINGS_PEOPLE_SYNCED_AS_NAME}, + {"turnOffSync", IDS_SETTINGS_PEOPLE_SYNC_TURN_OFF}, +#endif {"syncOverview", IDS_SETTINGS_SYNC_OVERVIEW}, {"syncDisabledByAdministrator", IDS_SETTINGS_SYNC_DISABLED_BY_ADMINISTRATOR}, @@ -1563,6 +1618,20 @@ void AddPeopleStrings(content::WebUIDataSource* html_source) { IDS_SETTINGS_SYNC_DISCONNECT_MANAGED_PROFILE_EXPLANATION, base::ASCIIToUTF16("$1"), base::ASCIIToUTF16(sync_dashboard_url))); + + // The syncDisconnect text differs depending on Dice-enabledness. + if (signin::IsDiceEnabledForProfile(profile->GetPrefs())) { + LocalizedString sync_disconnect_strings[] = { + {"syncDisconnect", IDS_SETTINGS_TURN_OFF_SYNC_DIALOG_CONFIRM}, + {"syncDisconnectTitle", IDS_SETTINGS_TURN_OFF_SYNC_DIALOG_TITLE}, + {"syncDisconnectDeleteProfile", + IDS_SETTINGS_TURN_OFF_SYNC_DIALOG_CHECKBOX}, + {"syncDisconnectConfirm", + IDS_SETTINGS_TURN_OFF_SYNC_DIALOG_MANAGED_CONFIRM}, + }; + AddLocalizedStringsBulk(html_source, sync_disconnect_strings, + arraysize(sync_disconnect_strings)); + } #endif html_source->AddString("syncErrorHelpUrl", chrome::kSyncErrorsHelpURL); @@ -1595,6 +1664,7 @@ void AddPrintingStrings(content::WebUIDataSource* html_source) { {"editPrinter", IDS_SETTINGS_PRINTING_CUPS_PRINTERS_EDIT}, {"removePrinter", IDS_SETTINGS_PRINTING_CUPS_PRINTERS_REMOVE}, {"searchLabel", IDS_SETTINGS_PRINTING_CUPS_SEARCH_LABEL}, + {"noSearchResults", IDS_SEARCH_NO_RESULTS}, {"printerDetailsTitle", IDS_SETTINGS_PRINTING_CUPS_PRINTER_DETAILS_TITLE}, {"printerName", IDS_SETTINGS_PRINTING_CUPS_PRINTER_DETAILS_NAME}, {"printerModel", IDS_SETTINGS_PRINTING_CUPS_PRINTER_DETAILS_MODEL}, @@ -1626,13 +1696,12 @@ void AddPrintingStrings(content::WebUIDataSource* html_source) { {"printerProtocolUsb", IDS_SETTINGS_PRINTING_CUPS_PRINTER_PROTOCOL_USB}, {"printerConfiguringMessage", IDS_SETTINGS_PRINTING_CUPS_PRINTER_CONFIGURING_MESSAGE}, - {"searchingPrinter", IDS_SETTINGS_PRINTING_CUPS_PRINTER_SEARCHING_PRINTER}, - {"printerNotFound", IDS_SETTINGS_PRINTING_CUPS_PRINTER_NOT_FOUND_PRINTER}, - {"printerFound", IDS_SETTINGS_PRINTING_CUPS_PRINTER_FOUND_PRINTER}, {"printerManufacturer", IDS_SETTINGS_PRINTING_CUPS_PRINTER_MANUFACTURER}, {"selectDriver", IDS_SETTINGS_PRINTING_CUPS_PRINTER_SELECT_DRIVER}, {"selectDriverButtonText", IDS_SETTINGS_PRINTING_CUPS_PRINTER_BUTTON_SELECT_DRIVER}, + {"selectDriverErrorMessage", + IDS_SETTINGS_PRINTING_CUPS_PRINTER_INVALID_DRIVER}, {"printerAddedSuccessfulMessage", IDS_SETTINGS_PRINTING_CUPS_PRINTER_ADDED_PRINTER_DONE_MESSAGE}, {"noPrinterNearbyMessage", @@ -1711,9 +1780,6 @@ void AddPrivacyStrings(content::WebUIDataSource* html_source, AddLocalizedStringsBulk(html_source, localized_strings, arraysize(localized_strings)); - html_source->AddBoolean("tabsInCbd", - base::FeatureList::IsEnabled(features::kTabsInCbd)); - html_source->AddBoolean( "importantSitesInCbd", base::FeatureList::IsEnabled(features::kImportantSitesInCbd)); @@ -1946,6 +2012,9 @@ void AddSiteSettingsStrings(content::WebUIDataSource* html_source, {"siteSettingsSoundAllowRecommended", IDS_SETTINGS_SITE_SETTINGS_SOUND_ALLOW_RECOMMENDED}, {"siteSettingsSoundBlock", IDS_SETTINGS_SITE_SETTINGS_SOUND_BLOCK}, + {"siteSettingsSensors", IDS_SETTINGS_SITE_SETTINGS_SENSORS}, + {"siteSettingsSensorsAllow", IDS_SETTINGS_SITE_SETTINGS_SENSORS_ALLOW}, + {"siteSettingsSensorsBlock", IDS_SETTINGS_SITE_SETTINGS_SENSORS_BLOCK}, {"siteSettingsFlash", IDS_SETTINGS_SITE_SETTINGS_FLASH}, {"siteSettingsPdfDocuments", IDS_SETTINGS_SITE_SETTINGS_PDF_DOCUMENTS}, {"siteSettingsPdfDownloadPdfs", @@ -2101,7 +2170,7 @@ void AddSiteSettingsStrings(content::WebUIDataSource* html_source, {"handlerSetDefault", IDS_SETTINGS_SITE_SETTINGS_HANDLER_SET_DEFAULT}, {"handlerRemove", IDS_SETTINGS_SITE_SETTINGS_REMOVE}, {"adobeFlashStorage", IDS_SETTINGS_SITE_SETTINGS_ADOBE_FLASH_SETTINGS}, - {"learnMore", IDS_SETTINGS_SITE_SETTINGS_LEARN_MORE}, + {"learnMore", IDS_LEARN_MORE}, {"incognitoSite", IDS_SETTINGS_SITE_SETTINGS_INCOGNITO}, {"incognitoSiteOnly", IDS_SETTINGS_SITE_SETTINGS_INCOGNITO_ONLY}, {"embeddedIncognitoSite", IDS_SETTINGS_SITE_SETTINGS_INCOGNITO_EMBEDDED}, @@ -2130,6 +2199,10 @@ void AddSiteSettingsStrings(content::WebUIDataSource* html_source, "enableClipboardContentSetting", base::FeatureList::IsEnabled(features::kClipboardContentSetting)); + html_source->AddBoolean( + "enableSensorsContentSetting", + base::FeatureList::IsEnabled(features::kGenericSensorExtraClasses)); + if (PluginUtils::ShouldPreferHtmlOverPlugins( HostContentSettingsMapFactory::GetForProfile(profile))) { LocalizedString flash_strings[] = { @@ -2157,7 +2230,6 @@ void AddUsersStrings(content::WebUIDataSource* html_source) { {"usersModifiedByOwnerLabel", IDS_SETTINGS_USERS_MODIFIED_BY_OWNER_LABEL}, {"guestBrowsingLabel", IDS_SETTINGS_USERS_GUEST_BROWSING_LABEL}, {"settingsManagedLabel", IDS_SETTINGS_USERS_MANAGED_LABEL}, - {"supervisedUsersLabel", IDS_SETTINGS_USERS_SUPERVISED_USERS_LABEL}, {"showOnSigninLabel", IDS_SETTINGS_USERS_SHOW_ON_SIGNIN_LABEL}, {"restrictSigninLabel", IDS_SETTINGS_USERS_RESTRICT_SIGNIN_LABEL}, {"deviceOwnerLabel", IDS_SETTINGS_USERS_DEVICE_OWNER_LABEL}, @@ -2245,16 +2317,17 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source, #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) AddChromeCleanupStrings(html_source); + AddIncompatibleApplicationsStrings(html_source); #endif // defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) AddChangePasswordStrings(html_source); - AddClearBrowsingDataStrings(html_source); + AddClearBrowsingDataStrings(html_source, profile); AddCommonStrings(html_source, profile); AddDownloadsStrings(html_source); AddLanguagesStrings(html_source); AddOnStartupStrings(html_source); AddPasswordsAndFormsStrings(html_source); - AddPeopleStrings(html_source); + AddPeopleStrings(html_source, profile); AddPrintingStrings(html_source); AddPrivacyStrings(html_source, profile); AddResetStrings(html_source); diff --git a/chromium/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chromium/chrome/browser/ui/webui/settings/md_settings_ui.cc index 30779acd938..c3d1a1d2812 100644 --- a/chromium/chrome/browser/ui/webui/settings/md_settings_ui.cc +++ b/chromium/chrome/browser/ui/webui/settings/md_settings_ui.cc @@ -54,6 +54,9 @@ #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h" #include "chrome/browser/ui/webui/settings/chrome_cleanup_handler.h" #if defined(GOOGLE_CHROME_BUILD) +#include "chrome/browser/conflicts/problematic_programs_updater_win.h" +#include "chrome/browser/conflicts/token_util_win.h" +#include "chrome/browser/ui/webui/settings/incompatible_applications_handler_win.h" #include "chrome/grit/chrome_unscaled_resources.h" #endif #endif // defined(OS_WIN) @@ -88,6 +91,7 @@ #include "chrome/browser/ui/webui/settings/settings_default_browser_handler.h" #include "chrome/browser/ui/webui/settings/settings_manage_profile_handler.h" #include "chrome/browser/ui/webui/settings/system_handler.h" +#include "components/signin/core/browser/profile_management_switches.h" #endif // defined(OS_CHROMEOS) #if defined(USE_NSS_CERTS) @@ -222,9 +226,21 @@ MdSettingsUI::MdSettingsUI(content::WebUI* web_ui) // should never change while Chrome is open. html_source->AddBoolean("userInitiatedCleanupsEnabled", userInitiatedCleanupsEnabled); - #endif // defined(OS_WIN) +#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) + bool has_incompatible_applications = + ProblematicProgramsUpdater::IsIncompatibleApplicationsWarningEnabled() && + ProblematicProgramsUpdater::HasCachedPrograms(); + html_source->AddBoolean("showIncompatibleApplications", + has_incompatible_applications); + html_source->AddBoolean("hasAdminRights", HasAdminRights()); + + if (has_incompatible_applications) + AddSettingsPageUIHandler( + std::make_unique<IncompatibleApplicationsHandler>()); +#endif // OS_WIN && defined(GOOGLE_CHROME_BUILD) + bool password_protection_available = false; #if defined(SAFE_BROWSING_DB_LOCAL) safe_browsing::ChromePasswordProtectionService* password_protection = @@ -281,7 +297,10 @@ MdSettingsUI::MdSettingsUI(content::WebUI* web_ui) AddSettingsPageUIHandler(std::make_unique<chromeos::settings::PowerHandler>( profile->GetPrefs())); } -#endif +#else // !defined(OS_CHROMEOS) + html_source->AddBoolean("diceEnabled", + signin::IsDiceEnabledForProfile(profile->GetPrefs())); +#endif // defined(OS_CHROMEOS) html_source->AddBoolean("showExportPasswords", base::FeatureList::IsEnabled( diff --git a/chromium/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc b/chromium/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc index 60d70778592..def21ea78e0 100644 --- a/chromium/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc @@ -63,7 +63,8 @@ std::unique_ptr<base::DictionaryValue> MetricsReportingHandler::CreateMetricsReportingDict() { std::unique_ptr<base::DictionaryValue> dict( std::make_unique<base::DictionaryValue>()); - dict->SetBoolean("enabled", + dict->SetBoolean( + "enabled", ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled()); dict->SetBoolean("managed", IsMetricsReportingPolicyManaged()); return dict; diff --git a/chromium/chrome/browser/ui/webui/settings/people_handler.cc b/chromium/chrome/browser/ui/webui/settings/people_handler.cc index 170db11ae9c..8c8cddf5897 100644 --- a/chromium/chrome/browser/ui/webui/settings/people_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/people_handler.cc @@ -31,8 +31,6 @@ #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/singleton_tabs.h" -#include "chrome/browser/ui/user_manager.h" -#include "chrome/browser/ui/webui/profile_helper.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/common/chrome_switches.h" @@ -62,8 +60,17 @@ #if defined(OS_CHROMEOS) #include "components/signin/core/browser/signin_manager_base.h" #else +#include "chrome/browser/ui/user_manager.h" +#include "chrome/browser/ui/webui/profile_helper.h" #include "components/signin/core/browser/signin_manager.h" #endif +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +#include "chrome/browser/profiles/profile_avatar_icon_util.h" +#include "chrome/browser/signin/account_tracker_service_factory.h" +#include "components/signin/core/browser/account_tracker_service.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/image/image.h" +#endif using browser_sync::ProfileSyncService; using content::WebContents; @@ -186,7 +193,14 @@ PeopleHandler::PeopleHandler(Profile* profile) : profile_(profile), configuring_sync_(false), signin_observer_(this), - sync_service_observer_(this) {} +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + sync_service_observer_(this), + account_tracker_observer_(this) { +} +#else + sync_service_observer_(this) { +} +#endif PeopleHandler::~PeopleHandler() { // Early exit if running unit tests (no actual WebUI is attached). @@ -231,6 +245,16 @@ void PeopleHandler::RegisterMessages() { "SyncSetupStartSignIn", base::Bind(&PeopleHandler::HandleStartSignin, base::Unretained(this))); #endif +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + web_ui()->RegisterMessageCallback( + "SyncSetupGetStoredAccounts", + base::BindRepeating(&PeopleHandler::HandleGetStoredAccounts, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "SyncSetupStartSyncingWithEmail", + base::BindRepeating(&PeopleHandler::HandleStartSyncingWithEmail, + base::Unretained(this))); +#endif } void PeopleHandler::OnJavascriptAllowed() { @@ -249,12 +273,22 @@ void PeopleHandler::OnJavascriptAllowed() { ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_)); if (sync_service) sync_service_observer_.Add(sync_service); + +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + AccountTrackerService* account_tracker( + AccountTrackerServiceFactory::GetForProfile(profile_)); + if (account_tracker) + account_tracker_observer_.Add(account_tracker); +#endif } void PeopleHandler::OnJavascriptDisallowed() { profile_pref_registrar_.RemoveAll(); signin_observer_.RemoveAll(); sync_service_observer_.RemoveAll(); +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + account_tracker_observer_.RemoveAll(); +#endif } #if !defined(OS_CHROMEOS) @@ -407,6 +441,68 @@ void PeopleHandler::HandleSetDatatypes(const base::ListValue* args) { ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE); } +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +void PeopleHandler::HandleGetStoredAccounts(const base::ListValue* args) { + CHECK_EQ(1U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + + ResolveJavascriptCallback(*callback_id, *GetStoredAccountsList()); +} + +void PeopleHandler::OnAccountUpdated(const AccountInfo& info) { + FireWebUIListener("stored-accounts-updated", *GetStoredAccountsList()); +} + +void PeopleHandler::OnAccountRemoved(const AccountInfo& info) { + FireWebUIListener("stored-accounts-updated", *GetStoredAccountsList()); +} + +std::unique_ptr<base::ListValue> PeopleHandler::GetStoredAccountsList() { + std::vector<AccountInfo> accounts = + signin_ui_util::GetAccountsForDicePromos(profile_); + + AccountTrackerService* account_tracker = + AccountTrackerServiceFactory::GetForProfile(profile_); + std::unique_ptr<base::ListValue> accounts_list(new base::ListValue); + accounts_list->Reserve(accounts.size()); + + for (auto const& account : accounts) { + accounts_list->GetList().push_back( + base::Value(base::Value::Type::DICTIONARY)); + base::Value& acc = accounts_list->GetList().back(); + acc.SetKey("email", base::Value(account.email)); + acc.SetKey("fullName", base::Value(account.full_name)); + acc.SetKey("givenName", base::Value(account.given_name)); + const gfx::Image& account_image = + account_tracker->GetAccountImage(account.account_id); + if (!account_image.IsEmpty()) { + acc.SetKey( + "avatarImage", + base::Value(webui::GetBitmapDataUrl(account_image.AsBitmap()))); + } + } + + return accounts_list; +} + +void PeopleHandler::HandleStartSyncingWithEmail(const base::ListValue* args) { + const base::Value* email; + CHECK(args->Get(0, &email)); + + Browser* browser = + chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()); + + AccountTrackerService* account_tracker = + AccountTrackerServiceFactory::GetForProfile(profile_); + AccountInfo account = + account_tracker->FindAccountInfoByEmail(email->GetString()); + + signin_ui_util::EnableSync( + browser, account, signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS); +} +#endif + void PeopleHandler::HandleSetEncryption(const base::ListValue* args) { DCHECK(!sync_startup_tracker_); @@ -545,7 +641,6 @@ void PeopleHandler::HandleStopSyncing(const base::ListValue* args) { if (delete_profile) { webui::DeleteProfileAtPath(profile_->GetPath(), - web_ui(), ProfileMetrics::DELETE_PROFILE_SETTINGS); } } @@ -562,8 +657,10 @@ void PeopleHandler::HandleGetSyncStatus(const base::ListValue* args) { } void PeopleHandler::HandleManageOtherPeople(const base::ListValue* /* args */) { +#if !defined(OS_CHROMEOS) UserManager::Show(base::FilePath(), profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION); +#endif // !defined(OS_CHROMEOS) } void PeopleHandler::CloseSyncSetup() { @@ -762,8 +859,6 @@ PeopleHandler::GetSyncStatusDictionary() { ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_); sync_status->SetBoolean("signinAllowed", signin->IsSigninAllowed()); sync_status->SetBoolean("syncSystemEnabled", (service != nullptr)); - sync_status->SetBoolean("setupCompleted", - service && service->IsFirstSetupComplete()); sync_status->SetBoolean( "setupInProgress", service && !service->IsManaged() && service->IsFirstSetupInProgress()); @@ -786,7 +881,6 @@ PeopleHandler::GetSyncStatusDictionary() { signin_ui_util::GetAuthenticatedUsername(signin)); sync_status->SetBoolean("hasUnrecoverableError", service && service->HasUnrecoverableError()); - return sync_status; } diff --git a/chromium/chrome/browser/ui/webui/settings/people_handler.h b/chromium/chrome/browser/ui/webui/settings/people_handler.h index be8ab4ea3a4..0ea976fa175 100644 --- a/chromium/chrome/browser/ui/webui/settings/people_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/people_handler.h @@ -14,13 +14,19 @@ #include "base/strings/utf_string_conversions.h" #include "base/timer/timer.h" #include "build/build_config.h" +#include "build/buildflag.h" #include "chrome/browser/sync/sync_startup_tracker.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "components/prefs/pref_change_registrar.h" +#include "components/signin/core/browser/signin_features.h" #include "components/signin/core/browser/signin_manager_base.h" #include "components/sync/driver/sync_service_observer.h" +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +#include "components/signin/core/browser/account_tracker_service.h" +#endif + class LoginUIService; class SigninManagerBase; @@ -45,6 +51,9 @@ namespace settings { class PeopleHandler : public SettingsPageUIHandler, public SigninManagerBase::Observer, public SyncStartupTracker::Observer, +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + public AccountTrackerService::Observer, +#endif public LoginUIService::LoginUI, public syncer::SyncServiceObserver { public: @@ -122,6 +131,12 @@ class PeopleHandler : public SettingsPageUIHandler, // syncer::SyncServiceObserver implementation. void OnStateChanged(syncer::SyncService* sync) override; +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + // AccountTrackerService::Observer implementation. + void OnAccountUpdated(const AccountInfo& info) override; + void OnAccountRemoved(const AccountInfo& info) override; +#endif + // Returns a newly created dictionary with a number of properties that // correspond to the status of sync. std::unique_ptr<base::DictionaryValue> GetSyncStatusDictionary(); @@ -155,6 +170,12 @@ class PeopleHandler : public SettingsPageUIHandler, signin_metrics::AccessPoint access_point); #endif +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + void HandleGetStoredAccounts(const base::ListValue* args); + void HandleStartSyncingWithEmail(const base::ListValue* args); + std::unique_ptr<base::ListValue> GetStoredAccountsList(); +#endif + // Displays spinner-only UI indicating that something is going on in the // background. // TODO(kochi): better to show some message that the user can understand what @@ -211,6 +232,11 @@ class PeopleHandler : public SettingsPageUIHandler, ScopedObserver<browser_sync::ProfileSyncService, PeopleHandler> sync_service_observer_; +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + ScopedObserver<AccountTrackerService, PeopleHandler> + account_tracker_observer_; +#endif + DISALLOW_COPY_AND_ASSIGN(PeopleHandler); }; diff --git a/chromium/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chromium/chrome/browser/ui/webui/settings/people_handler_unittest.cc index ec5442d0d1b..13312118858 100644 --- a/chromium/chrome/browser/ui/webui/settings/people_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/settings/people_handler_unittest.cc @@ -47,6 +47,7 @@ #include "ui/base/layout.h" using ::testing::_; +using ::testing::Invoke; using ::testing::Mock; using ::testing::Return; using ::testing::ReturnRef; @@ -197,8 +198,7 @@ class PeopleHandlerTest : public ChromeRenderViewHostTestHarness { error_ = GoogleServiceAuthError::AuthErrorNone(); // Sign in the user. - mock_signin_ = static_cast<SigninManagerBase*>( - SigninManagerFactory::GetForProfile(profile())); + mock_signin_ = SigninManagerFactory::GetForProfile(profile()); std::string username = GetTestUser(); if (!username.empty()) mock_signin_->SetAuthenticatedAccountInfo(username, username); @@ -213,6 +213,10 @@ class PeopleHandlerTest : public ChromeRenderViewHostTestHarness { Return(base::Time())); ON_CALL(*mock_pss_, GetRegisteredDataTypes()) .WillByDefault(Return(syncer::ModelTypeSet())); + ON_CALL(*mock_pss_, GetSetupInProgressHandle()) + .WillByDefault( + Invoke(mock_pss_, + &ProfileSyncServiceMock::GetSetupInProgressHandleConcrete)); mock_pss_->Initialize(); @@ -332,7 +336,7 @@ TEST_F(PeopleHandlerFirstSigninTest, DisplayBasicLogin) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); // Ensure that the user is not signed in before calling |HandleStartSignin()|. - SigninManager* manager = static_cast<SigninManager*>(mock_signin_); + SigninManager* manager = SigninManager::FromSigninManagerBase(mock_signin_); manager->SignOut(signin_metrics::SIGNOUT_TEST, signin_metrics::SignoutDelete::IGNORE_METRIC); base::ListValue list_args; diff --git a/chromium/chrome/browser/ui/webui/settings/profile_info_handler.cc b/chromium/chrome/browser/ui/webui/settings/profile_info_handler.cc index 6261f7f9c28..299d6fe9a8d 100644 --- a/chromium/chrome/browser/ui/webui/settings/profile_info_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/profile_info_handler.cc @@ -9,7 +9,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_attributes_entry.h" -#include "chrome/browser/ui/user_manager.h" +#include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/pref_names.h" #include "ui/base/webui/web_ui_util.h" @@ -32,9 +32,6 @@ namespace settings { // static const char ProfileInfoHandler::kProfileInfoChangedEventName[] = "profile-info-changed"; -const char - ProfileInfoHandler::kProfileManagesSupervisedUsersChangedEventName[] = - "profile-manages-supervised-users-changed"; const char ProfileInfoHandler::kProfileStatsCountReadyEventName[] = "profile-stats-count-ready"; @@ -63,23 +60,12 @@ void ProfileInfoHandler::RegisterMessages() { base::Bind(&ProfileInfoHandler::HandleGetProfileStats, base::Unretained(this))); #endif - web_ui()->RegisterMessageCallback( - "getProfileManagesSupervisedUsers", - base::Bind(&ProfileInfoHandler::HandleGetProfileManagesSupervisedUsers, - base::Unretained(this))); } void ProfileInfoHandler::OnJavascriptAllowed() { profile_observer_.Add( &g_browser_process->profile_manager()->GetProfileAttributesStorage()); - PrefService* prefs = profile_->GetPrefs(); - profile_pref_registrar_.Init(prefs); - profile_pref_registrar_.Add( - prefs::kSupervisedUsers, - base::Bind(&ProfileInfoHandler::PushProfileManagesSupervisedUsersStatus, - base::Unretained(this))); - #if defined(OS_CHROMEOS) user_manager_observer_.Add(user_manager::UserManager::Get()); #endif @@ -91,8 +77,6 @@ void ProfileInfoHandler::OnJavascriptDisallowed() { profile_observer_.Remove( &g_browser_process->profile_manager()->GetProfileAttributesStorage()); - profile_pref_registrar_.RemoveAll(); - #if defined(OS_CHROMEOS) user_manager_observer_.Remove(user_manager::UserManager::Get()); #endif @@ -147,29 +131,10 @@ void ProfileInfoHandler::PushProfileStatsCount( } #endif -void ProfileInfoHandler::HandleGetProfileManagesSupervisedUsers( - const base::ListValue* args) { - AllowJavascript(); - - CHECK_EQ(1U, args->GetSize()); - const base::Value* callback_id; - CHECK(args->Get(0, &callback_id)); - - ResolveJavascriptCallback(*callback_id, - base::Value(IsProfileManagingSupervisedUsers())); -} - void ProfileInfoHandler::PushProfileInfo() { FireWebUIListener(kProfileInfoChangedEventName, *GetAccountNameAndIcon()); } -void ProfileInfoHandler::PushProfileManagesSupervisedUsersStatus() { - CallJavascriptFunction( - "cr.webUIListenerCallback", - base::Value(kProfileManagesSupervisedUsersChangedEventName), - base::Value(IsProfileManagingSupervisedUsers())); -} - std::unique_ptr<base::DictionaryValue> ProfileInfoHandler::GetAccountNameAndIcon() const { std::string name; @@ -208,8 +173,4 @@ ProfileInfoHandler::GetAccountNameAndIcon() const { return response; } -bool ProfileInfoHandler::IsProfileManagingSupervisedUsers() const { - return !profile_->GetPrefs()->GetDictionary(prefs::kSupervisedUsers)->empty(); -} - } // namespace settings diff --git a/chromium/chrome/browser/ui/webui/settings/profile_info_handler.h b/chromium/chrome/browser/ui/webui/settings/profile_info_handler.h index 5d8386beace..0d7d986709c 100644 --- a/chromium/chrome/browser/ui/webui/settings/profile_info_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/profile_info_handler.h @@ -32,7 +32,6 @@ class ProfileInfoHandler : public SettingsPageUIHandler, public ProfileAttributesStorage::Observer { public: static const char kProfileInfoChangedEventName[]; - static const char kProfileManagesSupervisedUsersChangedEventName[]; static const char kProfileStatsCountReadyEventName[]; explicit ProfileInfoHandler(Profile* profile); @@ -56,14 +55,9 @@ class ProfileInfoHandler : public SettingsPageUIHandler, private: FRIEND_TEST_ALL_PREFIXES(ProfileInfoHandlerTest, GetProfileInfo); FRIEND_TEST_ALL_PREFIXES(ProfileInfoHandlerTest, PushProfileInfo); - FRIEND_TEST_ALL_PREFIXES(ProfileInfoHandlerTest, - GetProfileManagesSupervisedUsers); - FRIEND_TEST_ALL_PREFIXES(ProfileInfoHandlerTest, - PushProfileManagesSupervisedUsers); // Callbacks from the page. void HandleGetProfileInfo(const base::ListValue* args); - void HandleGetProfileManagesSupervisedUsers(const base::ListValue* args); void PushProfileInfo(); #if !defined(OS_CHROMEOS) @@ -74,12 +68,6 @@ class ProfileInfoHandler : public SettingsPageUIHandler, void PushProfileStatsCount(profiles::ProfileCategoryStats stats); #endif - // Pushes whether the current profile manages supervised users to JavaScript. - void PushProfileManagesSupervisedUsersStatus(); - - // Returns true if this profile manages supervised users. - bool IsProfileManagingSupervisedUsers() const; - std::unique_ptr<base::DictionaryValue> GetAccountNameAndIcon() const; // Weak pointer. @@ -93,9 +81,6 @@ class ProfileInfoHandler : public SettingsPageUIHandler, ScopedObserver<ProfileAttributesStorage, ProfileInfoHandler> profile_observer_; - // Used to listen for changes in the list of managed supervised users. - PrefChangeRegistrar profile_pref_registrar_; - // Used to cancel callbacks when JavaScript becomes disallowed. base::WeakPtrFactory<ProfileInfoHandler> callback_weak_ptr_factory_; diff --git a/chromium/chrome/browser/ui/webui/settings/profile_info_handler_unittest.cc b/chromium/chrome/browser/ui/webui/settings/profile_info_handler_unittest.cc index 734d9b70fe2..a5229444221 100644 --- a/chromium/chrome/browser/ui/webui/settings/profile_info_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/settings/profile_info_handler_unittest.cc @@ -151,53 +151,4 @@ TEST_F(ProfileInfoHandlerTest, PushProfileInfo) { VerifyProfileInfo(data.arg2()); } -TEST_F(ProfileInfoHandlerTest, GetProfileManagesSupervisedUsers) { - base::ListValue list_args; - list_args.AppendString("get-profile-manages-supervised-users-callback-id"); - handler()->HandleGetProfileManagesSupervisedUsers(&list_args); - - EXPECT_EQ(1U, web_ui()->call_data().size()); - - const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); - EXPECT_EQ("cr.webUIResponse", data.function_name()); - - std::string callback_id; - ASSERT_TRUE(data.arg1()->GetAsString(&callback_id)); - EXPECT_EQ("get-profile-manages-supervised-users-callback-id", callback_id); - - bool success = false; - ASSERT_TRUE(data.arg2()->GetAsBoolean(&success)); - EXPECT_TRUE(success); - - bool has_supervised_users = false; - ASSERT_TRUE(data.arg3()->GetAsBoolean(&has_supervised_users)); - EXPECT_FALSE(has_supervised_users); -} - -TEST_F(ProfileInfoHandlerTest, PushProfileManagesSupervisedUsers) { - handler()->AllowJavascript(); - - // The handler is notified of the change after |update| is destroyed. - std::unique_ptr<DictionaryPrefUpdate> update( - new DictionaryPrefUpdate(profile()->GetPrefs(), prefs::kSupervisedUsers)); - base::DictionaryValue* dict = update->Get(); - dict->SetWithoutPathExpansion("supervised-user-id", - std::make_unique<base::DictionaryValue>()); - update.reset(); - - EXPECT_EQ(1U, web_ui()->call_data().size()); - - const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); - EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); - - std::string event_id; - ASSERT_TRUE(data.arg1()->GetAsString(&event_id)); - EXPECT_EQ(ProfileInfoHandler::kProfileManagesSupervisedUsersChangedEventName, - event_id); - - bool has_supervised_users = false; - ASSERT_TRUE(data.arg2()->GetAsBoolean(&has_supervised_users)); - EXPECT_TRUE(has_supervised_users); -} - } // namespace settings diff --git a/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc b/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc index 062d170c0c7..3b1453e6170 100644 --- a/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc @@ -47,9 +47,6 @@ void ProtocolHandlersHandler::RegisterMessages() { base::Bind( &ProtocolHandlersHandler::HandleObserveProtocolHandlersEnabledState, base::Unretained(this))); - web_ui()->RegisterMessageCallback("clearDefault", - base::Bind(&ProtocolHandlersHandler::HandleClearDefault, - base::Unretained(this))); web_ui()->RegisterMessageCallback("removeHandler", base::Bind(&ProtocolHandlersHandler::HandleRemoveHandler, base::Unretained(this))); @@ -59,9 +56,6 @@ void ProtocolHandlersHandler::RegisterMessages() { web_ui()->RegisterMessageCallback("setDefault", base::Bind(&ProtocolHandlersHandler::HandleSetDefault, base::Unretained(this))); - web_ui()->RegisterMessageCallback("removeIgnoredHandler", - base::Bind(&ProtocolHandlersHandler::HandleRemoveIgnoredHandler, - base::Unretained(this))); } ProtocolHandlerRegistry* ProtocolHandlersHandler::GetProtocolHandlerRegistry() { @@ -70,6 +64,7 @@ ProtocolHandlerRegistry* ProtocolHandlersHandler::GetProtocolHandlerRegistry() { } static void GetHandlersAsListValue( + const ProtocolHandlerRegistry& registry, const ProtocolHandlerRegistry::ProtocolHandlerList& handlers, base::ListValue* handler_list) { ProtocolHandlerRegistry::ProtocolHandlerList::const_iterator handler; @@ -79,6 +74,7 @@ static void GetHandlersAsListValue( handler_value->SetString("protocol", handler->protocol()); handler_value->SetString("spec", handler->url().spec()); handler_value->SetString("host", handler->url().host()); + handler_value->SetBoolean("is_default", registry.IsDefault(*handler)); handler_list->Append(std::move(handler_value)); } } @@ -88,16 +84,9 @@ void ProtocolHandlersHandler::GetHandlersForProtocol( base::DictionaryValue* handlers_value) { ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry(); handlers_value->SetString("protocol", protocol); - handlers_value->SetInteger("default_handler", - registry->GetHandlerIndex(protocol)); - handlers_value->SetBoolean( - "is_default_handler_set_by_user", - registry->IsRegisteredByUser(registry->GetHandlerFor(protocol))); - handlers_value->SetBoolean("has_policy_recommendations", - registry->HasPolicyRegisteredHandler(protocol)); auto handlers_list = std::make_unique<base::ListValue>(); - GetHandlersAsListValue(registry->GetHandlersFor(protocol), + GetHandlersAsListValue(*registry, registry->GetHandlersFor(protocol), handlers_list.get()); handlers_value->Set("handlers", std::move(handlers_list)); } @@ -106,7 +95,7 @@ void ProtocolHandlersHandler::GetIgnoredHandlers(base::ListValue* handlers) { ProtocolHandlerRegistry* registry = GetProtocolHandlerRegistry(); ProtocolHandlerRegistry::ProtocolHandlerList ignored_handlers = registry->GetIgnoredHandlers(); - return GetHandlersAsListValue(ignored_handlers, handlers); + return GetHandlersAsListValue(*registry, ignored_handlers, handlers); } void ProtocolHandlersHandler::UpdateHandlerList() { @@ -148,13 +137,8 @@ void ProtocolHandlersHandler::SendHandlersEnabledValue() { } void ProtocolHandlersHandler::HandleRemoveHandler(const base::ListValue* args) { - const base::ListValue* list; - if (!args->GetList(0, &list)) { - NOTREACHED(); - return; - } - - ProtocolHandler handler(ParseHandlerFromArgs(list)); + ProtocolHandler handler(ParseHandlerFromArgs(args)); + CHECK(!handler.IsEmpty()); GetProtocolHandlerRegistry()->RemoveHandler(handler); // No need to call UpdateHandlerList() - we should receive a notification @@ -162,18 +146,6 @@ void ProtocolHandlersHandler::HandleRemoveHandler(const base::ListValue* args) { // then. } -void ProtocolHandlersHandler::HandleRemoveIgnoredHandler( - const base::ListValue* args) { - const base::ListValue* list; - if (!args->GetList(0, &list)) { - NOTREACHED(); - return; - } - - ProtocolHandler handler(ParseHandlerFromArgs(list)); - GetProtocolHandlerRegistry()->RemoveIgnoredHandler(handler); -} - void ProtocolHandlersHandler::HandleSetHandlersEnabled( const base::ListValue* args) { bool enabled = true; @@ -184,18 +156,8 @@ void ProtocolHandlersHandler::HandleSetHandlersEnabled( GetProtocolHandlerRegistry()->Disable(); } -void ProtocolHandlersHandler::HandleClearDefault(const base::ListValue* args) { - const base::Value* value; - CHECK(args->Get(0, &value)); - std::string protocol_to_clear; - CHECK(value->GetAsString(&protocol_to_clear)); - GetProtocolHandlerRegistry()->ClearDefault(protocol_to_clear); -} - void ProtocolHandlersHandler::HandleSetDefault(const base::ListValue* args) { - const base::ListValue* list; - CHECK(args->GetList(0, &list)); - const ProtocolHandler& handler(ParseHandlerFromArgs(list)); + const ProtocolHandler& handler(ParseHandlerFromArgs(args)); CHECK(!handler.IsEmpty()); GetProtocolHandlerRegistry()->OnAcceptRegisterProtocolHandler(handler); } diff --git a/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.h b/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.h index 269f27f6061..cf5094ad895 100644 --- a/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/protocol_handlers_handler.h @@ -63,12 +63,8 @@ class ProtocolHandlersHandler : public SettingsPageUIHandler, // Called when the user sets a new default handler for a protocol. void HandleSetDefault(const base::ListValue* args); - // Called when the user clears the default handler for a protocol. - // |args| is the string name of the protocol to clear. - void HandleClearDefault(const base::ListValue* args); - // Parses a ProtocolHandler out of the arguments passed back from the view. - // |args| is a list of [protocol, url, title]. + // |args| is a list of [protocol, url]. ProtocolHandler ParseHandlerFromArgs(const base::ListValue* args) const; // Returns a JSON object describing the set of protocol handlers for the @@ -83,13 +79,9 @@ class ProtocolHandlersHandler : public SettingsPageUIHandler, void UpdateHandlerList(); // Remove a handler. - // |args| is a list of [protocol, url, title]. + // |args| is a list of [protocol, url]. void HandleRemoveHandler(const base::ListValue* args); - // Remove an ignored handler. - // |args| is a list of [protocol, url, title]. - void HandleRemoveIgnoredHandler(const base::ListValue* args); - ProtocolHandlerRegistry* GetProtocolHandlerRegistry(); content::NotificationRegistrar notification_registrar_; diff --git a/chromium/chrome/browser/ui/webui/settings/safe_browsing_handler.cc b/chromium/chrome/browser/ui/webui/settings/safe_browsing_handler.cc index e42d983ce75..f017287acdf 100644 --- a/chromium/chrome/browser/ui/webui/settings/safe_browsing_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/safe_browsing_handler.cc @@ -47,8 +47,17 @@ void SafeBrowsingHandler::HandleGetSafeBrowsingExtendedReporting( AllowJavascript(); const base::Value* callback_id; CHECK(args->Get(0, &callback_id)); - base::Value is_enabled(safe_browsing::IsExtendedReportingEnabled(*prefs_)); - ResolveJavascriptCallback(*callback_id, is_enabled); + + base::DictionaryValue dict; + dict.SetBoolean("enabled", + safe_browsing::IsExtendedReportingEnabled(*prefs_)); + // TODO(crbug.com/813107): SBEROIA policy is being deprecated, revisit this + // after it is removed. + dict.SetBoolean("managed", + !safe_browsing::IsExtendedReportingOptInAllowed(*prefs_) || + safe_browsing::IsExtendedReportingPolicyManaged(*prefs_)); + + ResolveJavascriptCallback(*callback_id, dict); } void SafeBrowsingHandler::HandleSetSafeBrowsingExtendedReportingEnabled( diff --git a/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc b/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc index 49cd2fb9f4e..b4c04a907d9 100644 --- a/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc @@ -27,7 +27,7 @@ #include "chrome/common/pref_names.h" #include "components/browsing_data/core/history_notice_utils.h" #include "components/browsing_data/core/pref_names.h" -#include "components/feature_engagement/features.h" +#include "components/feature_engagement/buildflags.h" #include "components/prefs/pref_member.h" #include "components/prefs/pref_service.h" #include "components/signin/core/browser/signin_manager.h" @@ -50,26 +50,23 @@ namespace { const int kMaxTimesHistoryNoticeShown = 1; // TODO(msramek): Get the list of deletion preferences from the JS side. -const char* kCounterPrefs[] = { +const char* kCounterPrefsAdvanced[] = { browsing_data::prefs::kDeleteBrowsingHistory, browsing_data::prefs::kDeleteCache, + browsing_data::prefs::kDeleteCookies, browsing_data::prefs::kDeleteDownloadHistory, browsing_data::prefs::kDeleteFormData, browsing_data::prefs::kDeleteHostedAppsData, browsing_data::prefs::kDeleteMediaLicenses, browsing_data::prefs::kDeletePasswords, + browsing_data::prefs::kDeleteSiteSettings, }; -// Additional counters for the tabbed ui. +// Additional counters for the basic tab of CBD. const char* kCounterPrefsBasic[] = { browsing_data::prefs::kDeleteCacheBasic, }; -const char* kCounterPrefsAdvanced[] = { - browsing_data::prefs::kDeleteCookies, - browsing_data::prefs::kDeleteSiteSettings, -}; - const char kRegisterableDomainField[] = "registerableDomain"; const char kReasonBitField[] = "reasonBitfield"; const char kExampleOriginField[] = "exampleOrigin"; @@ -87,13 +84,8 @@ ClearBrowsingDataHandler::ClearBrowsingDataHandler(content::WebUI* webui) : profile_(Profile::FromWebUI(webui)), sync_service_(ProfileSyncServiceFactory::GetForProfile(profile_)), sync_service_observer_(this), - show_history_footer_(false), show_history_deletion_dialog_(false), - weak_ptr_factory_(this) { - if (base::FeatureList::IsEnabled(features::kTabsInCbd)) { - browsing_data::MigratePreferencesToBasic(profile_->GetPrefs()); - } -} + weak_ptr_factory_(this) {} ClearBrowsingDataHandler::~ClearBrowsingDataHandler() { } @@ -120,32 +112,25 @@ void ClearBrowsingDataHandler::OnJavascriptAllowed() { sync_service_observer_.Add(sync_service_); DCHECK(counters_.empty()); - for (const std::string& pref : kCounterPrefs) { + for (const std::string& pref : kCounterPrefsBasic) { AddCounter(BrowsingDataCounterFactory::GetForProfileAndPref(profile_, pref), - browsing_data::ClearBrowsingDataTab::ADVANCED); + browsing_data::ClearBrowsingDataTab::BASIC); } - if (base::FeatureList::IsEnabled(features::kTabsInCbd)) { - for (const std::string& pref : kCounterPrefsBasic) { - AddCounter( - BrowsingDataCounterFactory::GetForProfileAndPref(profile_, pref), - browsing_data::ClearBrowsingDataTab::BASIC); - } - for (const std::string& pref : kCounterPrefsAdvanced) { - AddCounter( - BrowsingDataCounterFactory::GetForProfileAndPref(profile_, pref), - browsing_data::ClearBrowsingDataTab::ADVANCED); - } - PrefService* prefs = profile_->GetPrefs(); - period_ = std::make_unique<IntegerPrefMember>(); - period_->Init(browsing_data::prefs::kDeleteTimePeriod, prefs, - base::Bind(&ClearBrowsingDataHandler::HandleTimePeriodChanged, - base::Unretained(this))); - periodBasic_ = std::make_unique<IntegerPrefMember>(); - periodBasic_->Init( - browsing_data::prefs::kDeleteTimePeriodBasic, prefs, - base::Bind(&ClearBrowsingDataHandler::HandleTimePeriodChanged, - base::Unretained(this))); + for (const std::string& pref : kCounterPrefsAdvanced) { + AddCounter(BrowsingDataCounterFactory::GetForProfileAndPref(profile_, pref), + browsing_data::ClearBrowsingDataTab::ADVANCED); } + PrefService* prefs = profile_->GetPrefs(); + period_ = std::make_unique<IntegerPrefMember>(); + period_->Init( + browsing_data::prefs::kDeleteTimePeriod, prefs, + base::BindRepeating(&ClearBrowsingDataHandler::HandleTimePeriodChanged, + base::Unretained(this))); + periodBasic_ = std::make_unique<IntegerPrefMember>(); + periodBasic_->Init( + browsing_data::prefs::kDeleteTimePeriodBasic, prefs, + base::BindRepeating(&ClearBrowsingDataHandler::HandleTimePeriodChanged, + base::Unretained(this))); } void ClearBrowsingDataHandler::OnJavascriptDisallowed() { @@ -239,8 +224,10 @@ void ClearBrowsingDataHandler::HandleClearBrowsingData( case BrowsingDataType::BOOKMARKS: // Only implemented on Android. NOTREACHED(); + break; case BrowsingDataType::NUM_TYPES: NOTREACHED(); + break; } } @@ -455,25 +442,15 @@ void ClearBrowsingDataHandler::OnStateChanged(syncer::SyncService* sync) { void ClearBrowsingDataHandler::UpdateSyncState() { auto* signin_manager = SigninManagerFactory::GetForProfile(profile_); - // TODO(dullweber): Remove "show_history_footer" attribute when the new UI - // is launched as it doesn't have this footer anymore. Instead the - // myactivity.google.com link is shown when a user is signed in or syncing. CallJavascriptFunction( "cr.webUIListenerCallback", base::Value("update-sync-state"), base::Value(signin_manager && signin_manager->IsAuthenticated()), base::Value(sync_service_ && sync_service_->IsSyncActive() && sync_service_->GetActiveDataTypes().Has( - syncer::HISTORY_DELETE_DIRECTIVES)), - base::Value(show_history_footer_)); + syncer::HISTORY_DELETE_DIRECTIVES))); } void ClearBrowsingDataHandler::RefreshHistoryNotice() { - browsing_data::ShouldShowNoticeAboutOtherFormsOfBrowsingHistory( - sync_service_, - WebHistoryServiceFactory::GetForProfile(profile_), - base::Bind(&ClearBrowsingDataHandler::UpdateHistoryNotice, - weak_ptr_factory_.GetWeakPtr())); - // If the dialog with history notice has been shown less than // |kMaxTimesHistoryNoticeShown| times, we might have to show it when the // user deletes history. Find out if the conditions are met. @@ -490,15 +467,6 @@ void ClearBrowsingDataHandler::RefreshHistoryNotice() { } } -void ClearBrowsingDataHandler::UpdateHistoryNotice(bool show) { - show_history_footer_ = show; - UpdateSyncState(); - - UMA_HISTOGRAM_BOOLEAN( - "History.ClearBrowsingData.HistoryNoticeShownInFooterWhenUpdated", - show_history_footer_); -} - void ClearBrowsingDataHandler::UpdateHistoryDeletionDialog(bool show) { // This is used by OnClearingTaskFinished (when the deletion finishes). show_history_deletion_dialog_ = show; diff --git a/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h b/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h index 1cbd4ea698a..f135b0172af 100644 --- a/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h @@ -80,10 +80,6 @@ class ClearBrowsingDataHandler : public SettingsPageUIHandler, // in user's account. void RefreshHistoryNotice(); - // Called as an asynchronous response to |RefreshHistoryNotice()|. Shows or - // hides the footer about other forms of history stored in user's account. - void UpdateHistoryNotice(bool show); - // Called as an asynchronous response to |RefreshHistoryNotice()|. Enables or // disables the dialog about other forms of history stored in user's account // that is shown when the history deletion is finished. @@ -111,11 +107,6 @@ class ClearBrowsingDataHandler : public SettingsPageUIHandler, ScopedObserver<browser_sync::ProfileSyncService, syncer::SyncServiceObserver> sync_service_observer_; - // Whether the sentence about other forms of history stored in user's account - // should be displayed in the footer. This value is retrieved asynchronously, - // so we cache it here. - bool show_history_footer_; - // Whether we should show a dialog informing the user about other forms of // history stored in their account after the history deletion is finished. bool show_history_deletion_dialog_; diff --git a/chromium/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chromium/chrome/browser/ui/webui/settings/site_settings_handler.cc index 0a4b4442f20..c413d8569ab 100644 --- a/chromium/chrome/browser/ui/webui/settings/site_settings_handler.cc +++ b/chromium/chrome/browser/ui/webui/settings/site_settings_handler.cc @@ -9,6 +9,7 @@ #include <string> #include <utility> +#include "base/barrier_closure.h" #include "base/bind.h" #include "base/i18n/number_formatting.h" #include "base/macros.h" @@ -45,7 +46,7 @@ #include "extensions/common/permissions/api_permission.h" #include "extensions/common/permissions/permissions_data.h" #include "storage/browser/quota/quota_manager.h" -#include "third_party/WebKit/common/quota/quota_types.mojom.h" +#include "third_party/WebKit/public/mojom/quota/quota_types.mojom.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/text/bytes_formatting.h" @@ -92,7 +93,7 @@ void AddExceptionsGrantedByHostedApps(content::BrowserContext* context, !(*extension)->permissions_data()->HasAPIPermission(permission)) continue; - extensions::URLPatternSet web_extent = (*extension)->web_extent(); + const extensions::URLPatternSet& web_extent = (*extension)->web_extent(); // Add patterns from web extent. for (extensions::URLPatternSet::const_iterator pattern = web_extent.begin(); pattern != web_extent.end(); ++pattern) { @@ -159,6 +160,9 @@ void SiteSettingsHandler::RegisterMessages() { base::Bind(&SiteSettingsHandler::HandleSetOriginPermissions, base::Unretained(this))); web_ui()->RegisterMessageCallback( + "clearFlashPref", base::Bind(&SiteSettingsHandler::HandleClearFlashPref, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( "resetCategoryPermissionForPattern", base::Bind(&SiteSettingsHandler::HandleResetCategoryPermissionForPattern, base::Unretained(this))); @@ -248,14 +252,18 @@ void SiteSettingsHandler::OnGetUsageInfo( } } -void SiteSettingsHandler::OnUsageInfoCleared( - blink::mojom::QuotaStatusCode code) { +void SiteSettingsHandler::OnStorageCleared(base::OnceClosure callback, + blink::mojom::QuotaStatusCode code) { if (code == blink::mojom::QuotaStatusCode::kOk) { - CallJavascriptFunction("settings.WebsiteUsagePrivateApi.onUsageCleared", - base::Value(clearing_origin_)); + std::move(callback).Run(); } } +void SiteSettingsHandler::OnUsageCleared() { + CallJavascriptFunction("settings.WebsiteUsagePrivateApi.onUsageCleared", + base::Value(clearing_origin_)); +} + #if defined(OS_CHROMEOS) void SiteSettingsHandler::OnPrefEnableDrmChanged() { CallJavascriptFunction("cr.webUIListenerCallback", @@ -355,19 +363,25 @@ void SiteSettingsHandler::HandleClearUsage( if (url.is_valid()) { clearing_origin_ = origin; + // Call OnUsageCleared when StorageInfoFetcher::ClearStorage and + // BrowsingDataLocalStorageHelper::DeleteOrigin are done. + base::RepeatingClosure barrier = base::BarrierClosure( + 2, base::BindOnce(&SiteSettingsHandler::OnUsageCleared, + base::Unretained(this))); + // Start by clearing the storage data asynchronously. scoped_refptr<StorageInfoFetcher> storage_info_fetcher = new StorageInfoFetcher(profile_); storage_info_fetcher->ClearStorage( url.host(), static_cast<blink::mojom::StorageType>(static_cast<int>(storage_type)), - base::Bind(&SiteSettingsHandler::OnUsageInfoCleared, - base::Unretained(this))); + base::BindRepeating(&SiteSettingsHandler::OnStorageCleared, + base::Unretained(this), barrier)); // Also clear the *local* storage data. scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper = new BrowsingDataLocalStorageHelper(profile_); - local_storage_helper->DeleteOrigin(url); + local_storage_helper->DeleteOrigin(url, barrier); } } @@ -621,6 +635,19 @@ void SiteSettingsHandler::HandleSetOriginPermissions( } } +void SiteSettingsHandler::HandleClearFlashPref(const base::ListValue* args) { + CHECK_EQ(1U, args->GetSize()); + std::string origin_string; + CHECK(args->GetString(0, &origin_string)); + + HostContentSettingsMap* map = + HostContentSettingsMapFactory::GetForProfile(profile_); + const GURL origin(origin_string); + map->SetWebsiteSettingDefaultScope(origin, origin, + CONTENT_SETTINGS_TYPE_PLUGINS_DATA, + std::string(), nullptr); +} + void SiteSettingsHandler::HandleResetCategoryPermissionForPattern( const base::ListValue* args) { CHECK_EQ(4U, args->GetSize()); diff --git a/chromium/chrome/browser/ui/webui/settings/site_settings_handler.h b/chromium/chrome/browser/ui/webui/settings/site_settings_handler.h index 63fa495c22e..01be8c4849d 100644 --- a/chromium/chrome/browser/ui/webui/settings/site_settings_handler.h +++ b/chromium/chrome/browser/ui/webui/settings/site_settings_handler.h @@ -16,7 +16,8 @@ #include "content/public/browser/host_zoom_map.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -#include "third_party/WebKit/common/quota/quota_types.mojom.h" +#include "ppapi/features/features.h" +#include "third_party/WebKit/public/mojom/quota/quota_types.mojom.h" class HostContentSettingsMap; class Profile; @@ -46,7 +47,9 @@ class SiteSettingsHandler : public SettingsPageUIHandler, // Usage info. void OnGetUsageInfo(const storage::UsageInfoEntries& entries); - void OnUsageInfoCleared(blink::mojom::QuotaStatusCode code); + void OnStorageCleared(base::OnceClosure callback, + blink::mojom::QuotaStatusCode code); + void OnUsageCleared(); #if defined(OS_CHROMEOS) // Alert the Javascript that the |kEnableDRM| pref has changed. @@ -70,6 +73,10 @@ class SiteSettingsHandler : public SettingsPageUIHandler, private: friend class SiteSettingsHandlerTest; friend class SiteSettingsHandlerInfobarTest; +#if BUILDFLAG(ENABLE_PLUGINS) + FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, + ChangingFlashSettingForSiteIsRemembered); +#endif FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, DefaultSettingSource); FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, ExceptionHelpers); FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, ExtensionDisplayName); @@ -110,6 +117,10 @@ class SiteSettingsHandler : public SettingsPageUIHandler, void HandleGetOriginPermissions(const base::ListValue* args); void HandleSetOriginPermissions(const base::ListValue* args); + // Clears the Flash data setting used to remember if the user has changed the + // Flash permission for an origin. + void HandleClearFlashPref(const base::ListValue* args); + // Handles setting and resetting an origin permission. void HandleResetCategoryPermissionForPattern(const base::ListValue* args); void HandleSetCategoryPermissionForPattern(const base::ListValue* args); diff --git a/chromium/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chromium/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc index 434947cf2a6..7e40e5e5081 100644 --- a/chromium/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc @@ -31,6 +31,7 @@ #include "content/public/test/test_web_ui.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/extension_builder.h" +#include "ppapi/features/features.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_CHROMEOS) @@ -38,6 +39,10 @@ #include "components/user_manager/scoped_user_manager.h" #endif +#if BUILDFLAG(ENABLE_PLUGINS) +#include "chrome/browser/plugins/chrome_plugin_service_filter.h" +#endif + namespace { constexpr char kCallbackId[] = "test-callback-id"; @@ -45,7 +50,41 @@ constexpr char kSetting[] = "setting"; constexpr char kSource[] = "source"; constexpr char kExtensionName[] = "Test Extension"; -} +#if BUILDFLAG(ENABLE_PLUGINS) +// Waits until a change is observed in content settings. +class FlashContentSettingsChangeWaiter : public content_settings::Observer { + public: + explicit FlashContentSettingsChangeWaiter(Profile* profile) + : profile_(profile) { + HostContentSettingsMapFactory::GetForProfile(profile)->AddObserver(this); + } + ~FlashContentSettingsChangeWaiter() override { + HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver( + this); + } + + // content_settings::Observer: + void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern, + const ContentSettingsPattern& secondary_pattern, + ContentSettingsType content_type, + std::string resource_identifier) override { + if (content_type == CONTENT_SETTINGS_TYPE_PLUGINS) + Proceed(); + } + + void Wait() { run_loop_.Run(); } + + private: + void Proceed() { run_loop_.Quit(); } + + Profile* profile_; + base::RunLoop run_loop_; + + DISALLOW_COPY_AND_ASSIGN(FlashContentSettingsChangeWaiter); +}; +#endif + +} // namespace namespace settings { @@ -90,6 +129,8 @@ class SiteSettingsHandlerTest : public testing::Test { CONTENT_SETTINGS_TYPE_NOTIFICATIONS)), kCookies(site_settings::ContentSettingsTypeToGroupName( CONTENT_SETTINGS_TYPE_COOKIES)), + kFlash(site_settings::ContentSettingsTypeToGroupName( + CONTENT_SETTINGS_TYPE_PLUGINS)), handler_(&profile_) { #if defined(OS_CHROMEOS) user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>( @@ -277,6 +318,7 @@ class SiteSettingsHandlerTest : public testing::Test { // Content setting group name for the relevant ContentSettingsType. const std::string kNotifications; const std::string kCookies; + const std::string kFlash; private: content::TestBrowserThreadBundle thread_bundle_; @@ -476,6 +518,56 @@ TEST_F(SiteSettingsHandlerTest, GetAndSetOriginPermissions) { site_settings::SiteSettingSource::kDefault, 4U); } +#if BUILDFLAG(ENABLE_PLUGINS) +TEST_F(SiteSettingsHandlerTest, ChangingFlashSettingForSiteIsRemembered) { + ChromePluginServiceFilter::GetInstance()->RegisterResourceContext( + profile(), profile()->GetResourceContext()); + FlashContentSettingsChangeWaiter waiter(profile()); + + const std::string origin_with_port("https://www.example.com:443"); + // The display name won't show the port if it's default for that scheme. + const std::string origin("https://www.example.com"); + base::ListValue get_args; + get_args.AppendString(kCallbackId); + get_args.AppendString(origin_with_port); + const GURL url(origin_with_port); + + HostContentSettingsMap* map = + HostContentSettingsMapFactory::GetForProfile(profile()); + // Make sure the site being tested doesn't already have this marker set. + EXPECT_EQ(nullptr, + map->GetWebsiteSetting(url, url, CONTENT_SETTINGS_TYPE_PLUGINS_DATA, + std::string(), nullptr)); + + // Change the Flash setting. + base::ListValue set_args; + set_args.AppendString(origin_with_port); + { + auto category_list = std::make_unique<base::ListValue>(); + category_list->AppendString(kFlash); + set_args.Append(std::move(category_list)); + } + set_args.AppendString( + content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); + handler()->HandleSetOriginPermissions(&set_args); + EXPECT_EQ(1U, web_ui()->call_data().size()); + waiter.Wait(); + + // Check that this site has now been marked for displaying Flash always, then + // clear it and check this works. + EXPECT_NE(nullptr, + map->GetWebsiteSetting(url, url, CONTENT_SETTINGS_TYPE_PLUGINS_DATA, + std::string(), nullptr)); + base::ListValue clear_args; + clear_args.AppendString(origin_with_port); + handler()->HandleSetOriginPermissions(&set_args); + handler()->HandleClearFlashPref(&clear_args); + EXPECT_EQ(nullptr, + map->GetWebsiteSetting(url, url, CONTENT_SETTINGS_TYPE_PLUGINS_DATA, + std::string(), nullptr)); +} +#endif + TEST_F(SiteSettingsHandlerTest, GetAndSetForInvalidURLs) { const std::string origin("arbitrary string"); EXPECT_FALSE(GURL(origin).is_valid()); diff --git a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc index a1962a59807..707c0a05630 100644 --- a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc +++ b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc @@ -5,37 +5,28 @@ #include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h" #include "base/bind.h" +#include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics_action.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/policy/cloud/user_policy_signin_service.h" #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h" #include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/profiles/profile_avatar_icon_util.h" #include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/profiles/profile_window.h" #include "chrome/browser/signin/account_tracker_service_factory.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/signin_util.h" #include "chrome/browser/sync/profile_sync_service_factory.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_finder.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_tabstrip.h" -#include "chrome/browser/ui/browser_window.h" -#include "chrome/browser/ui/chrome_pages.h" -#include "chrome/browser/ui/startup/startup_types.h" -#include "chrome/browser/ui/tab_dialogs.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/browser/ui/webui/signin/login_ui_service.h" -#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" +#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.h" #include "chrome/browser/ui/webui/signin/signin_utils_desktop.h" -#include "chrome/common/url_constants.h" #include "components/browser_sync/profile_sync_service.h" +#include "components/policy/core/browser/browser_policy_connector.h" #include "components/prefs/pref_service.h" #include "components/signin/core/browser/account_info.h" #include "components/signin/core/browser/account_tracker_service.h" @@ -48,117 +39,32 @@ namespace { -// UMA histogram for tracking what users do when presented with the signin -// screen. -// Hence, -// (a) existing enumerated constants should never be deleted or reordered, and -// (b) new constants should only be appended at the end of the enumeration. -// -// Keep this in sync with SigninChoice in histograms.xml. -enum SigninChoice { - SIGNIN_CHOICE_CANCEL = 0, - SIGNIN_CHOICE_CONTINUE = 1, - SIGNIN_CHOICE_NEW_PROFILE = 2, - // SIGNIN_CHOICE_SIZE should always be last - this is a count of the number - // of items in this enum. - SIGNIN_CHOICE_SIZE, -}; - -void SetUserChoiceHistogram(SigninChoice choice) { - UMA_HISTOGRAM_ENUMERATION("Enterprise.UserSigninChoice", choice, - SIGNIN_CHOICE_SIZE); -} - AccountInfo GetAccountInfo(Profile* profile, const std::string& account_id) { return AccountTrackerServiceFactory::GetForProfile(profile)->GetAccountInfo( account_id); } -// If the |browser| argument is non-null, returns the pointer directly. -// Otherwise creates a new browser for the given profile on the given desktop, -// adds an empty tab and makes sure the browser is visible. -Browser* EnsureBrowser(Browser* browser, Profile* profile) { - if (!browser) { - // The user just created a new profile or has closed the browser that - // we used previously. Grab the most recently active browser or else - // create a new one. - browser = chrome::FindLastActiveWithProfile(profile); - if (!browser) { - browser = new Browser(Browser::CreateParams(profile, true)); - chrome::AddTabAt(browser, GURL(), -1, true); - } - browser->window()->Show(); - } - return browser; -} - -void StartNewSigninInNewProfile(Profile* new_profile, - const std::string& username) { - profiles::FindOrCreateNewWindowForProfile( - new_profile, chrome::startup::IS_PROCESS_STARTUP, - chrome::startup::IS_FIRST_RUN, false); - Browser* browser = chrome::FindTabbedBrowser(new_profile, false); - browser->signin_view_controller()->ShowDiceSigninTab( - profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, browser, - signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE, username); -} - } // namespace -DiceTurnSyncOnHelper::SigninDialogDelegate::SigninDialogDelegate( - base::WeakPtr<DiceTurnSyncOnHelper> sync_starter) - : sync_starter_(sync_starter) {} - -DiceTurnSyncOnHelper::SigninDialogDelegate::~SigninDialogDelegate() {} - -void DiceTurnSyncOnHelper::SigninDialogDelegate::OnCancelSignin() { - SetUserChoiceHistogram(SIGNIN_CHOICE_CANCEL); - base::RecordAction( - base::UserMetricsAction("Signin_EnterpriseAccountPrompt_Cancel")); - - if (sync_starter_) - sync_starter_->AbortAndDelete(); -} - -void DiceTurnSyncOnHelper::SigninDialogDelegate::OnContinueSignin() { - SetUserChoiceHistogram(SIGNIN_CHOICE_CONTINUE); - base::RecordAction( - base::UserMetricsAction("Signin_EnterpriseAccountPrompt_ImportData")); - - if (sync_starter_) - sync_starter_->LoadPolicyWithCachedCredentials(); -} - -void DiceTurnSyncOnHelper::SigninDialogDelegate::OnSigninWithNewProfile() { - SetUserChoiceHistogram(SIGNIN_CHOICE_NEW_PROFILE); - base::RecordAction( - base::UserMetricsAction("Signin_EnterpriseAccountPrompt_DontImportData")); - - if (sync_starter_) - sync_starter_->CreateNewSignedInProfile(); -} - DiceTurnSyncOnHelper::DiceTurnSyncOnHelper( Profile* profile, - Browser* browser, signin_metrics::AccessPoint signin_access_point, signin_metrics::Reason signin_reason, const std::string& account_id, - SigninAbortedMode signin_aborted_mode) - : profile_(profile), - browser_(browser), + SigninAbortedMode signin_aborted_mode, + std::unique_ptr<Delegate> delegate) + : delegate_(std::move(delegate)), + profile_(profile), signin_manager_(SigninManagerFactory::GetForProfile(profile)), token_service_(ProfileOAuth2TokenServiceFactory::GetForProfile(profile)), signin_access_point_(signin_access_point), signin_reason_(signin_reason), signin_aborted_mode_(signin_aborted_mode), account_info_(GetAccountInfo(profile, account_id)), - scoped_browser_list_observer_(this), - scoped_login_ui_service_observer_(this), weak_pointer_factory_(this) { + DCHECK(delegate_); DCHECK(signin::IsDicePrepareMigrationEnabled()); DCHECK(profile_); - DCHECK(browser_); DCHECK(!account_info_.gaia.empty()); DCHECK(!account_info_.email.empty()); // Should not start syncing if the profile is already authenticated @@ -168,7 +74,10 @@ DiceTurnSyncOnHelper::DiceTurnSyncOnHelper( DCHECK(!signin_util::IsForceSigninEnabled()); if (HasCanOfferSigninError()) { - AbortAndDelete(); + // Do not self-destruct synchronously in the constructor. + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&DiceTurnSyncOnHelper::AbortAndDelete, + base::Unretained(this))); return; } @@ -181,22 +90,33 @@ DiceTurnSyncOnHelper::DiceTurnSyncOnHelper( // last authenticated account of the current profile, then Chrome will show a // confirmation dialog before starting sync. // TODO(skym): Warn for high risk upgrade scenario (https://crbug.com/572754). - content::WebContents* web_contents = - browser_->tab_strip_model()->GetActiveWebContents(); std::string last_email = profile_->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); - SigninEmailConfirmationDialog::AskForConfirmation( - web_contents, profile_, last_email, account_info_.email, - base::Bind(&DiceTurnSyncOnHelper::ConfirmEmailAction, - weak_pointer_factory_.GetWeakPtr())); + delegate_->ShowMergeSyncDataConfirmation( + last_email, account_info_.email, + base::BindOnce(&DiceTurnSyncOnHelper::OnMergeAccountConfirmation, + weak_pointer_factory_.GetWeakPtr())); } +DiceTurnSyncOnHelper::DiceTurnSyncOnHelper( + Profile* profile, + Browser* browser, + signin_metrics::AccessPoint signin_access_point, + signin_metrics::Reason signin_reason, + const std::string& account_id, + SigninAbortedMode signin_aborted_mode) + : DiceTurnSyncOnHelper( + profile, + signin_access_point, + signin_reason, + account_id, + signin_aborted_mode, + std::make_unique<DiceTurnSyncOnHelperDelegateImpl>(browser)) {} + DiceTurnSyncOnHelper::~DiceTurnSyncOnHelper() { - DCHECK(!scoped_login_ui_service_observer_.IsObservingSources()); } bool DiceTurnSyncOnHelper::HasCanOfferSigninError() { - DCHECK(browser_); std::string error_msg; bool can_offer = CanOfferSignin(profile_, CAN_OFFER_SIGNIN_FOR_ALL_ACCOUNTS, @@ -205,36 +125,62 @@ bool DiceTurnSyncOnHelper::HasCanOfferSigninError() { return false; // Display the error message - LoginUIServiceFactory::GetForProfile(profile_)->DisplayLoginResult( - browser_, base::UTF8ToUTF16(error_msg), - base::UTF8ToUTF16(account_info_.email)); + delegate_->ShowLoginError(account_info_.email, error_msg); return true; } -void DiceTurnSyncOnHelper::ConfirmEmailAction( - SigninEmailConfirmationDialog::Action action) { - switch (action) { - case SigninEmailConfirmationDialog::CREATE_NEW_USER: +void DiceTurnSyncOnHelper::OnMergeAccountConfirmation(SigninChoice choice) { + switch (choice) { + case SIGNIN_CHOICE_NEW_PROFILE: base::RecordAction( base::UserMetricsAction("Signin_ImportDataPrompt_DontImport")); TurnSyncOnWithProfileMode(ProfileMode::NEW_PROFILE); break; - case SigninEmailConfirmationDialog::START_SYNC: + case SIGNIN_CHOICE_CONTINUE: base::RecordAction( base::UserMetricsAction("Signin_ImportDataPrompt_ImportData")); TurnSyncOnWithProfileMode(ProfileMode::CURRENT_PROFILE); break; - case SigninEmailConfirmationDialog::CLOSE: + case SIGNIN_CHOICE_CANCEL: base::RecordAction( base::UserMetricsAction("Signin_ImportDataPrompt_Cancel")); AbortAndDelete(); break; + case SIGNIN_CHOICE_SIZE: + NOTREACHED(); + AbortAndDelete(); + break; } } -void DiceTurnSyncOnHelper::TurnSyncOnWithProfileMode(ProfileMode profile_mode) { - scoped_browser_list_observer_.Add(BrowserList::GetInstance()); +void DiceTurnSyncOnHelper::OnEnterpriseAccountConfirmation( + SigninChoice choice) { + UMA_HISTOGRAM_ENUMERATION("Enterprise.UserSigninChoice", choice, + DiceTurnSyncOnHelper::SIGNIN_CHOICE_SIZE); + switch (choice) { + case SIGNIN_CHOICE_CANCEL: + base::RecordAction( + base::UserMetricsAction("Signin_EnterpriseAccountPrompt_Cancel")); + AbortAndDelete(); + break; + case SIGNIN_CHOICE_CONTINUE: + base::RecordAction( + base::UserMetricsAction("Signin_EnterpriseAccountPrompt_ImportData")); + LoadPolicyWithCachedCredentials(); + break; + case SIGNIN_CHOICE_NEW_PROFILE: + base::RecordAction(base::UserMetricsAction( + "Signin_EnterpriseAccountPrompt_DontImportData")); + CreateNewSignedInProfile(); + break; + case SIGNIN_CHOICE_SIZE: + NOTREACHED(); + AbortAndDelete(); + break; + } +} +void DiceTurnSyncOnHelper::TurnSyncOnWithProfileMode(ProfileMode profile_mode) { // Make sure the syncing is requested, otherwise the SigninManager // will not be able to complete successfully. syncer::SyncPrefs sync_prefs(profile_->GetPrefs()); @@ -282,20 +228,10 @@ void DiceTurnSyncOnHelper::OnRegisteredForPolicy(const std::string& dm_token, client_id_ = client_id; // Allow user to create a new profile before continuing with sign-in. - browser_ = EnsureBrowser(browser_, profile_); - content::WebContents* web_contents = - browser_->tab_strip_model()->GetActiveWebContents(); - if (!web_contents) { - AbortAndDelete(); - return; - } - - base::RecordAction( - base::UserMetricsAction("Signin_Show_EnterpriseAccountPrompt")); - TabDialogs::FromWebContents(web_contents) - ->ShowProfileSigninConfirmation(browser_, profile_, account_info_.email, - std::make_unique<SigninDialogDelegate>( - weak_pointer_factory_.GetWeakPtr())); + delegate_->ShowEnterpriseAccountConfirmation( + account_info_.email, + base::BindOnce(&DiceTurnSyncOnHelper::OnEnterpriseAccountConfirmation, + weak_pointer_factory_.GetWeakPtr())); } void DiceTurnSyncOnHelper::LoadPolicyWithCachedCredentials() { @@ -349,7 +285,7 @@ void DiceTurnSyncOnHelper::CompleteInitForNewProfile( break; case Profile::CREATE_STATUS_INITIALIZED: // The user needs to sign in to the new profile in order to enable sync. - StartNewSigninInNewProfile(new_profile, account_info_.email); + delegate_->ShowSigninPageInNewProfile(new_profile, account_info_.email); AbortAndDelete(); break; case Profile::CREATE_STATUS_REMOTE_FAIL: @@ -383,9 +319,14 @@ void DiceTurnSyncOnHelper::SigninAndShowSyncConfirmationUI() { // progress. // TODO(https://crbug.com/811211): Remove this handle. sync_blocker_ = sync_service->GetSetupInProgressHandle(); - if (SyncStartupTracker::GetSyncServiceState(profile_) == - SyncStartupTracker::SYNC_STARTUP_PENDING) { - // Wait until sync is initialized so that the confirmation UI can be + bool is_enterprise_user = + !policy::BrowserPolicyConnector::IsNonEnterpriseUser( + account_info_.email); + if (is_enterprise_user && + SyncStartupTracker::GetSyncServiceState(profile_) == + SyncStartupTracker::SYNC_STARTUP_PENDING) { + // For enterprise users it is important to wait until sync is initialized + // so that the confirmation UI can be // aware of startup errors. This is needed to make sure that the sync // confirmation dialog is shown only after the sync service had a chance // to check whether sync was disabled by admin. @@ -411,19 +352,16 @@ void DiceTurnSyncOnHelper::SyncStartupFailed() { } void DiceTurnSyncOnHelper::ShowSyncConfirmationUI() { - scoped_login_ui_service_observer_.Add( - LoginUIServiceFactory::GetForProfile(profile_)); - browser_ = EnsureBrowser(browser_, profile_); - browser_->signin_view_controller()->ShowModalSyncConfirmationDialog(browser_); + delegate_->ShowSyncConfirmation( + base::BindOnce(&DiceTurnSyncOnHelper::FinishSyncSetupAndDelete, + weak_pointer_factory_.GetWeakPtr())); } -void DiceTurnSyncOnHelper::OnSyncConfirmationUIClosed( +void DiceTurnSyncOnHelper::FinishSyncSetupAndDelete( LoginUIService::SyncConfirmationUIClosedResult result) { - scoped_login_ui_service_observer_.RemoveAll(); switch (result) { case LoginUIService::CONFIGURE_SYNC_FIRST: - browser_ = EnsureBrowser(browser_, profile_); - chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage); + delegate_->ShowSyncSettings(); break; case LoginUIService::SYNC_WITH_DEFAULT_SETTINGS: { browser_sync::ProfileSyncService* sync_service = GetProfileSyncService(); @@ -441,11 +379,6 @@ void DiceTurnSyncOnHelper::OnSyncConfirmationUIClosed( delete this; } -void DiceTurnSyncOnHelper::OnBrowserRemoved(Browser* browser) { - if (browser == browser_) - browser_ = nullptr; -} - void DiceTurnSyncOnHelper::AbortAndDelete() { if (signin_aborted_mode_ == SigninAbortedMode::REMOVE_ACCOUNT) { // Revoke the token, and the AccountReconcilor and/or the Gaia server will diff --git a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h index 85ef34e80f9..6641331e0fc 100644 --- a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h +++ b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h @@ -5,21 +5,19 @@ #ifndef CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_TURN_SYNC_ON_HELPER_H_ #define CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_TURN_SYNC_ON_HELPER_H_ +#include <memory> #include <string> +#include "base/callback_forward.h" +#include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "base/scoped_observer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/sync_startup_tracker.h" -#include "chrome/browser/ui/browser_list_observer.h" -#include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" -#include "chrome/browser/ui/webui/signin/signin_email_confirmation_dialog.h" #include "components/signin/core/browser/account_info.h" #include "components/signin/core/browser/signin_metrics.h" class Browser; -class BrowserList; class ProfileOAuth2TokenService; class SigninManager; @@ -33,9 +31,7 @@ class SyncSetupInProgressHandle; // Handles details of signing the user in with SigninManager and turning on // sync for an account that is already present in the token service. -class DiceTurnSyncOnHelper : public BrowserListObserver, - public LoginUIService::Observer, - public SyncStartupTracker::Observer { +class DiceTurnSyncOnHelper : public SyncStartupTracker::Observer { public: // Behavior when the signin is aborted (by an error or cancelled by the user). enum class SigninAbortedMode { @@ -45,9 +41,67 @@ class DiceTurnSyncOnHelper : public BrowserListObserver, KEEP_ACCOUNT }; + // User choice when signing in. + // Used for UMA histograms, Hence, constants should never be deleted or + // reordered, and new constants should only be appended at the end. + // Keep this in sync with SigninChoice in histograms.xml. + enum SigninChoice { + SIGNIN_CHOICE_CANCEL = 0, // Signin is cancelled. + SIGNIN_CHOICE_CONTINUE = 1, // Signin continues in the current profile. + SIGNIN_CHOICE_NEW_PROFILE = 2, // Signin continues in a new profile. + // SIGNIN_CHOICE_SIZE should always be last. + SIGNIN_CHOICE_SIZE, + }; + + using SigninChoiceCallback = base::OnceCallback<void(SigninChoice)>; + + // Delegate implementing the UI prompts. + class Delegate { + public: + virtual ~Delegate() {} + + // Shows a login error to the user. + virtual void ShowLoginError(const std::string& email, + const std::string& error_message) = 0; + + // Shows a confirmation dialog when the user was previously signed in with a + // different account in the same profile. |callback| must be called. + virtual void ShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + SigninChoiceCallback callback) = 0; + + // Shows a confirmation dialog when the user is signing in a managed + // account. |callback| must be called. + virtual void ShowEnterpriseAccountConfirmation( + const std::string& email, + SigninChoiceCallback callback) = 0; + + // Shows a sync confirmation screen offering to open the Sync settings. + // |callback| must be called. + virtual void ShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) = 0; + + // Opens the Sync settings page. + virtual void ShowSyncSettings() = 0; + + // Opens the signin page in a new profile. + virtual void ShowSigninPageInNewProfile(Profile* new_profile, + const std::string& username) = 0; + }; + // Create a helper that turns sync on for an account that is already present // in the token service. DiceTurnSyncOnHelper(Profile* profile, + signin_metrics::AccessPoint signin_access_point, + signin_metrics::Reason signin_reason, + const std::string& account_id, + SigninAbortedMode signin_aborted_mode, + std::unique_ptr<Delegate> delegate); + + // Convenience constructor using the default delegate. + DiceTurnSyncOnHelper(Profile* profile, Browser* browser, signin_metrics::AccessPoint signin_access_point, signin_metrics::Reason signin_reason, @@ -70,21 +124,6 @@ class DiceTurnSyncOnHelper : public BrowserListObserver, NEW_PROFILE }; - // User input handler for the signin confirmation dialog. - class SigninDialogDelegate : public ui::ProfileSigninConfirmationDelegate { - public: - explicit SigninDialogDelegate( - base::WeakPtr<DiceTurnSyncOnHelper> sync_starter); - ~SigninDialogDelegate() override; - void OnCancelSignin() override; - void OnContinueSignin() override; - void OnSigninWithNewProfile() override; - - private: - base::WeakPtr<DiceTurnSyncOnHelper> sync_starter_; - }; - friend class SigninDialogDelegate; - // DiceTurnSyncOnHelper deletes itself. ~DiceTurnSyncOnHelper() override; @@ -92,8 +131,11 @@ class DiceTurnSyncOnHelper : public BrowserListObserver, // and false otherwise. bool HasCanOfferSigninError(); - // Callback used with ConfirmEmailDialogDelegate. - void ConfirmEmailAction(SigninEmailConfirmationDialog::Action action); + // Used as callback for ShowMergeSyncDataConfirmation(). + void OnMergeAccountConfirmation(SigninChoice choice); + + // Used as callback for ShowEnterpriseAccountConfirmation(). + void OnEnterpriseAccountConfirmation(SigninChoice choice); // Turns sync on with the current profile or a new profile. void TurnSyncOnWithProfileMode(ProfileMode profile_mode); @@ -132,18 +174,16 @@ class DiceTurnSyncOnHelper : public BrowserListObserver, // confirmation dialog will be updated accordingly. void ShowSyncConfirmationUI(); - // LoginUIService::Observer override. Deletes this object. - void OnSyncConfirmationUIClosed( - LoginUIService::SyncConfirmationUIClosedResult result) override; - - // BrowserListObserver override. - void OnBrowserRemoved(Browser* browser) override; + // Handles the user input from the sync confirmation UI and deletes this + // object. + void FinishSyncSetupAndDelete( + LoginUIService::SyncConfirmationUIClosedResult result); // Aborts the flow and deletes this object. void AbortAndDelete(); + std::unique_ptr<Delegate> delegate_; Profile* profile_; - Browser* browser_; SigninManager* signin_manager_; ProfileOAuth2TokenService* token_service_; const signin_metrics::AccessPoint signin_access_point_; @@ -163,10 +203,6 @@ class DiceTurnSyncOnHelper : public BrowserListObserver, std::string dm_token_; std::string client_id_; - ScopedObserver<BrowserList, BrowserListObserver> - scoped_browser_list_observer_; - ScopedObserver<LoginUIService, LoginUIService::Observer> - scoped_login_ui_service_observer_; std::unique_ptr<SyncStartupTracker> sync_startup_tracker_; base::WeakPtrFactory<DiceTurnSyncOnHelper> weak_pointer_factory_; diff --git a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.cc b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.cc new file mode 100644 index 00000000000..c89424c5a6c --- /dev/null +++ b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.cc @@ -0,0 +1,183 @@ +// 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/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.h" + +#include "base/logging.h" +#include "base/metrics/user_metrics.h" +#include "base/metrics/user_metrics_action.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/profiles/profile_window.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/chrome_pages.h" +#include "chrome/browser/ui/startup/startup_types.h" +#include "chrome/browser/ui/tab_dialogs.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" +#include "chrome/browser/ui/webui/signin/signin_email_confirmation_dialog.h" +#include "chrome/common/url_constants.h" + +namespace { + +// If the |browser| argument is non-null, returns the pointer directly. +// Otherwise creates a new browser for the profile, adds an empty tab and makes +// sure the browser is visible. +Browser* EnsureBrowser(Browser* browser, Profile* profile) { + if (!browser) { + // The user just created a new profile or has closed the browser that + // we used previously. Grab the most recently active browser or else + // create a new one. + browser = chrome::FindLastActiveWithProfile(profile); + if (!browser) { + browser = new Browser(Browser::CreateParams(profile, true)); + chrome::AddTabAt(browser, GURL(), -1, true); + } + browser->window()->Show(); + } + return browser; +} + +// Converts SigninEmailConfirmationDialog::Action to +// DiceTurnSyncOnHelper::SigninChoice and invokes |callback| on it. +void OnEmailConfirmation(DiceTurnSyncOnHelper::SigninChoiceCallback callback, + SigninEmailConfirmationDialog::Action action) { + DCHECK(callback) << "This function should be called only once."; + switch (action) { + case SigninEmailConfirmationDialog::START_SYNC: + std::move(callback).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CONTINUE); + return; + case SigninEmailConfirmationDialog::CREATE_NEW_USER: + std::move(callback).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_NEW_PROFILE); + return; + case SigninEmailConfirmationDialog::CLOSE: + std::move(callback).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CANCEL); + return; + } + NOTREACHED(); +} + +} // namespace + +DiceTurnSyncOnHelperDelegateImpl::SigninDialogDelegate::SigninDialogDelegate( + DiceTurnSyncOnHelper::SigninChoiceCallback callback) + : callback_(std::move(callback)) { + DCHECK(callback_); +} + +DiceTurnSyncOnHelperDelegateImpl::SigninDialogDelegate:: + ~SigninDialogDelegate() = default; + +void DiceTurnSyncOnHelperDelegateImpl::SigninDialogDelegate::OnCancelSignin() { + DCHECK(callback_); + std::move(callback_).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CANCEL); +} + +void DiceTurnSyncOnHelperDelegateImpl::SigninDialogDelegate:: + OnContinueSignin() { + DCHECK(callback_); + std::move(callback_).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CONTINUE); +} + +void DiceTurnSyncOnHelperDelegateImpl::SigninDialogDelegate:: + OnSigninWithNewProfile() { + DCHECK(callback_); + std::move(callback_).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_NEW_PROFILE); +} + +DiceTurnSyncOnHelperDelegateImpl::DiceTurnSyncOnHelperDelegateImpl( + Browser* browser) + : browser_(browser), + profile_(browser_->profile()), + scoped_browser_list_observer_(this), + scoped_login_ui_service_observer_(this) { + DCHECK(browser); + DCHECK(profile_); + scoped_browser_list_observer_.Add(BrowserList::GetInstance()); +} + +DiceTurnSyncOnHelperDelegateImpl::~DiceTurnSyncOnHelperDelegateImpl() {} + +void DiceTurnSyncOnHelperDelegateImpl::ShowLoginError( + const std::string& email, + const std::string& error_message) { + LoginUIServiceFactory::GetForProfile(profile_)->DisplayLoginResult( + browser_, base::UTF8ToUTF16(error_message), base::UTF8ToUTF16(email)); +} + +void DiceTurnSyncOnHelperDelegateImpl::ShowEnterpriseAccountConfirmation( + const std::string& email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + DCHECK(callback); + browser_ = EnsureBrowser(browser_, profile_); + content::WebContents* web_contents = + browser_->tab_strip_model()->GetActiveWebContents(); + if (!web_contents) { + std::move(callback).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CANCEL); + return; + } + + base::RecordAction( + base::UserMetricsAction("Signin_Show_EnterpriseAccountPrompt")); + TabDialogs::FromWebContents(web_contents) + ->ShowProfileSigninConfirmation( + browser_, profile_, email, + std::make_unique<SigninDialogDelegate>(std::move(callback))); +} + +void DiceTurnSyncOnHelperDelegateImpl::ShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) { + DCHECK(callback); + sync_confirmation_callback_ = std::move(callback); + scoped_login_ui_service_observer_.Add( + LoginUIServiceFactory::GetForProfile(profile_)); + browser_ = EnsureBrowser(browser_, profile_); + browser_->signin_view_controller()->ShowModalSyncConfirmationDialog(browser_); +} + +void DiceTurnSyncOnHelperDelegateImpl::ShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + DCHECK(callback); + content::WebContents* web_contents = + browser_->tab_strip_model()->GetActiveWebContents(); + // TODO(droger): Replace Bind with BindOnce once the + // SigninEmailConfirmationDialog supports it. + SigninEmailConfirmationDialog::AskForConfirmation( + web_contents, profile_, previous_email, new_email, + base::Bind(&OnEmailConfirmation, base::Passed(std::move(callback)))); +} + +void DiceTurnSyncOnHelperDelegateImpl::ShowSyncSettings() { + browser_ = EnsureBrowser(browser_, profile_); + chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage); +} + +void DiceTurnSyncOnHelperDelegateImpl::ShowSigninPageInNewProfile( + Profile* new_profile, + const std::string& username) { + profiles::FindOrCreateNewWindowForProfile( + new_profile, chrome::startup::IS_PROCESS_STARTUP, + chrome::startup::IS_FIRST_RUN, false); + Browser* browser = chrome::FindTabbedBrowser(new_profile, false); + browser->signin_view_controller()->ShowDiceSigninTab( + profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, browser, + signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE, username); +} + +void DiceTurnSyncOnHelperDelegateImpl::OnSyncConfirmationUIClosed( + LoginUIService::SyncConfirmationUIClosedResult result) { + DCHECK(sync_confirmation_callback_); + std::move(sync_confirmation_callback_).Run(result); +} + +void DiceTurnSyncOnHelperDelegateImpl::OnBrowserRemoved(Browser* browser) { + if (browser == browser_) + browser_ = nullptr; +} diff --git a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.h b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.h new file mode 100644 index 00000000000..58f4fb8bf79 --- /dev/null +++ b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_delegate_impl.h @@ -0,0 +1,81 @@ +// 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_UI_WEBUI_SIGNIN_DICE_TURN_SYNC_ON_HELPER_DELEGATE_IMPL_H_ +#define CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_TURN_SYNC_ON_HELPER_DELEGATE_IMPL_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/scoped_observer.h" +#include "chrome/browser/ui/browser_list_observer.h" +#include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h" +#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h" +#include "chrome/browser/ui/webui/signin/login_ui_service.h" + +class Browser; +class BrowserList; +class Profile; + +// Default implementation for DiceTurnSyncOnHelper::Delegate. +class DiceTurnSyncOnHelperDelegateImpl : public DiceTurnSyncOnHelper::Delegate, + public BrowserListObserver, + public LoginUIService::Observer { + public: + explicit DiceTurnSyncOnHelperDelegateImpl(Browser* browser); + ~DiceTurnSyncOnHelperDelegateImpl() override; + + private: + // User input handler for the signin confirmation dialog. + class SigninDialogDelegate : public ui::ProfileSigninConfirmationDelegate { + public: + explicit SigninDialogDelegate( + DiceTurnSyncOnHelper::SigninChoiceCallback callback); + ~SigninDialogDelegate() override; + void OnCancelSignin() override; + void OnContinueSignin() override; + void OnSigninWithNewProfile() override; + + private: + DiceTurnSyncOnHelper::SigninChoiceCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(SigninDialogDelegate); + }; + + // DiceTurnSyncOnHelper::Delegate: + void ShowLoginError(const std::string& email, + const std::string& error_message) override; + void ShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) override; + void ShowEnterpriseAccountConfirmation( + const std::string& email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) override; + void ShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) override; + void ShowSyncSettings() override; + void ShowSigninPageInNewProfile(Profile* new_profile, + const std::string& username) override; + + // LoginUIService::Observer: + void OnSyncConfirmationUIClosed( + LoginUIService::SyncConfirmationUIClosedResult result) override; + + // BrowserListObserver: + void OnBrowserRemoved(Browser* browser) override; + + Browser* browser_; + Profile* profile_; + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + sync_confirmation_callback_; + ScopedObserver<BrowserList, BrowserListObserver> + scoped_browser_list_observer_; + ScopedObserver<LoginUIService, LoginUIService::Observer> + scoped_login_ui_service_observer_; + + DISALLOW_COPY_AND_ASSIGN(DiceTurnSyncOnHelperDelegateImpl); +}; + +#endif // CHROME_BROWSER_UI_WEBUI_SIGNIN_DICE_TURN_SYNC_ON_HELPER_DELEGATE_IMPL_H_ diff --git a/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_unittest.cc b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_unittest.cc new file mode 100644 index 00000000000..ec518a4c526 --- /dev/null +++ b/chromium/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper_unittest.cc @@ -0,0 +1,715 @@ +// 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/ui/webui/signin/dice_turn_sync_on_helper.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/policy/cloud/user_policy_signin_service.h" +#include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/signin/account_tracker_service_factory.h" +#include "chrome/browser/signin/chrome_signin_client_factory.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h" +#include "chrome/browser/signin/fake_signin_manager_builder.h" +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" +#include "chrome/browser/signin/signin_manager_factory.h" +#include "chrome/browser/signin/test_signin_client_builder.h" +#include "chrome/browser/sync/profile_sync_service_factory.h" +#include "chrome/browser/sync/profile_sync_test_util.h" +#include "chrome/test/base/scoped_testing_local_state.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "components/browser_sync/profile_sync_service_mock.h" +#include "components/prefs/pref_service.h" +#include "components/signin/core/browser/account_tracker_service.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/scoped_account_consistency.h" +#include "components/signin/core/browser/signin_metrics.h" +#include "components/signin/core/browser/signin_pref_names.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AtLeast; +using ::testing::Return; + +class DiceTurnSyncOnHelperTest; + +namespace { + +const char kEmail[] = "foo@gmail.com"; +const char kGaiaID[] = "foo_gaia_id"; +const char kPreviousEmail[] = "notme@bar.com"; +const char kEnterpriseEmail[] = "enterprise@managed.com"; +const char kEnterpriseGaiaID[] = "enterprise_gaia_id"; + +const signin_metrics::AccessPoint kAccessPoint = + signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER; +const signin_metrics::Reason kSigninReason = + signin_metrics::Reason::REASON_REAUTHENTICATION; + +// Dummy delegate forwarding all the calls the test fixture. +// Owned by the DiceTurnOnSyncHelper. +class TestDiceTurnSyncOnHelperDelegate : public DiceTurnSyncOnHelper::Delegate { + public: + explicit TestDiceTurnSyncOnHelperDelegate( + DiceTurnSyncOnHelperTest* test_fixture); + ~TestDiceTurnSyncOnHelperDelegate() override; + + private: + // DiceTurnSyncOnHelper::Delegate: + void ShowLoginError(const std::string& email, + const std::string& error_message) override; + void ShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) override; + void ShowEnterpriseAccountConfirmation( + const std::string& email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) override; + void ShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) override; + void ShowSyncSettings() override; + void ShowSigninPageInNewProfile(Profile* new_profile, + const std::string& username) override; + + DiceTurnSyncOnHelperTest* test_fixture_; +}; + +// Simple ProfileManager creating testing profiles. +class UnittestProfileManager : public ProfileManagerWithoutInit { + public: + explicit UnittestProfileManager(const base::FilePath& user_data_dir) + : ProfileManagerWithoutInit(user_data_dir) {} + + protected: + Profile* CreateProfileHelper(const base::FilePath& file_path) override { + if (!base::PathExists(file_path) && !base::CreateDirectory(file_path)) + return nullptr; + return new TestingProfile(file_path, nullptr); + } + + Profile* CreateProfileAsyncHelper(const base::FilePath& path, + Delegate* delegate) override { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(base::IgnoreResult(&base::CreateDirectory), path)); + return new TestingProfile(path, this); + } +}; + +// Fake user policy signin service immediately invoking the callbacks. +class FakeUserPolicySigninService : public policy::UserPolicySigninService { + public: + // Static method to use with BrowserContextKeyedServiceFactory. + static std::unique_ptr<KeyedService> Build(content::BrowserContext* context) { + Profile* profile = Profile::FromBrowserContext(context); + return std::make_unique<FakeUserPolicySigninService>( + profile, SigninManagerFactory::GetForProfile(profile), + ProfileOAuth2TokenServiceFactory::GetForProfile(profile)); + } + + FakeUserPolicySigninService(Profile* profile, + SigninManager* signin_manager, + ProfileOAuth2TokenService* oauth2_token_service) + : UserPolicySigninService(profile, + nullptr, + nullptr, + nullptr, + signin_manager, + nullptr, + oauth2_token_service) {} + + void set_dm_token(const std::string& dm_token) { dm_token_ = dm_token; } + void set_client_id(const std::string& client_id) { client_id_ = client_id; } + void set_account(const std::string& account_id, const std::string& email) { + account_id_ = account_id; + email_ = email; + } + + // policy::UserPolicySigninService: + void RegisterForPolicyWithAccountId( + const std::string& username, + const std::string& account_id, + const PolicyRegistrationCallback& callback) override { + EXPECT_EQ(email_, username); + EXPECT_EQ(account_id_, account_id); + callback.Run(dm_token_, client_id_); + } + + // policy::UserPolicySigninServiceBase: + void FetchPolicyForSignedInUser( + const std::string& username, + const std::string& dm_token, + const std::string& client_id, + scoped_refptr<net::URLRequestContextGetter> profile_request_context, + const PolicyFetchCallback& callback) override { + callback.Run(true); + } + + private: + std::string dm_token_; + std::string client_id_; + std::string account_id_; + std::string email_; +}; + +} // namespace + +class DiceTurnSyncOnHelperTest : public testing::Test { + public: + DiceTurnSyncOnHelperTest() + : local_state_(TestingBrowserProcess::GetGlobal()) { + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + TestingBrowserProcess::GetGlobal()->SetProfileManager( + new UnittestProfileManager(temp_dir_.GetPath())); + + TestingProfile::Builder profile_builder; + profile_builder.AddTestingFactory( + ProfileOAuth2TokenServiceFactory::GetInstance(), + BuildFakeProfileOAuth2TokenService); + profile_builder.AddTestingFactory(SigninManagerFactory::GetInstance(), + BuildFakeSigninManagerBase); + profile_builder.AddTestingFactory(ChromeSigninClientFactory::GetInstance(), + signin::BuildTestSigninClient); + profile_builder.AddTestingFactory(ProfileSyncServiceFactory::GetInstance(), + &BuildMockProfileSyncService); + profile_builder.AddTestingFactory( + policy::UserPolicySigninServiceFactory::GetInstance(), + &FakeUserPolicySigninService::Build); + profile_ = profile_builder.Build(); + account_tracker_service_ = + AccountTrackerServiceFactory::GetForProfile(profile()); + account_id_ = account_tracker_service_->SeedAccountInfo(kGaiaID, kEmail); + user_policy_signin_service_ = static_cast<FakeUserPolicySigninService*>( + policy::UserPolicySigninServiceFactory::GetForProfile(profile())); + user_policy_signin_service_->set_account(account_id_, kEmail); + token_service_ = ProfileOAuth2TokenServiceFactory::GetForProfile(profile()); + token_service_->UpdateCredentials(account_id_, "refresh_token"); + signin_manager_ = SigninManagerFactory::GetForProfile(profile()); + EXPECT_TRUE(token_service_->RefreshTokenIsAvailable(account_id_)); + } + + ~DiceTurnSyncOnHelperTest() override { + DCHECK(delegate_destroyed_); + // Destroy extra profiles. + TestingBrowserProcess::GetGlobal()->SetProfileManager(nullptr); + base::RunLoop().RunUntilIdle(); + } + + // Basic accessors. + Profile* profile() { return profile_.get(); } + ProfileOAuth2TokenService* token_service() { return token_service_; } + SigninManager* signin_manager() { return signin_manager_; } + const std::string& account_id() { return account_id_; } + FakeUserPolicySigninService* user_policy_signin_service() { + return user_policy_signin_service_; + } + + // Gets the ProfileSyncServiceMock. + browser_sync::ProfileSyncServiceMock* GetProfileSyncServiceMock() { + return static_cast<browser_sync::ProfileSyncServiceMock*>( + ProfileSyncServiceFactory::GetForProfile(profile())); + } + + DiceTurnSyncOnHelper* CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode mode) { + return new DiceTurnSyncOnHelper( + profile(), kAccessPoint, kSigninReason, account_id_, mode, + std::make_unique<TestDiceTurnSyncOnHelperDelegate>(this)); + } + + void UseEnterpriseAccount() { + account_id_ = account_tracker_service_->SeedAccountInfo(kEnterpriseGaiaID, + kEnterpriseEmail); + user_policy_signin_service_->set_account(account_id_, kEnterpriseEmail); + token_service_->UpdateCredentials(account_id_, "enterprise_refresh_token"); + } + + void SetExpectationsForSyncStartupCompleted() { + browser_sync::ProfileSyncServiceMock* sync_service_mock = + GetProfileSyncServiceMock(); + EXPECT_CALL(*sync_service_mock, GetSetupInProgressHandle()).Times(1); + EXPECT_CALL(*sync_service_mock, CanSyncStart()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*sync_service_mock, IsEngineInitialized()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(true)); + } + + void SetExpectationsForSyncStartupPending() { + browser_sync::ProfileSyncServiceMock* sync_service_mock = + GetProfileSyncServiceMock(); + EXPECT_CALL(*sync_service_mock, GetSetupInProgressHandle()).Times(1); + EXPECT_CALL(*sync_service_mock, CanSyncStart()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*sync_service_mock, IsEngineInitialized()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*sync_service_mock, waiting_for_auth()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(true)); + } + + void CheckDelegateCalls() { + EXPECT_EQ(expected_login_error_email_, login_error_email_); + EXPECT_EQ(expected_login_error_message_, login_error_message_); + EXPECT_EQ(expected_merge_data_previous_email_, merge_data_previous_email_); + EXPECT_EQ(expected_merge_data_new_email_, merge_data_new_email_); + EXPECT_EQ(expected_enterprise_confirmation_email_, + enterprise_confirmation_email_); + EXPECT_EQ(expected_new_profile_username_, new_profile_username_); + EXPECT_EQ(expected_sync_confirmation_shown_, sync_confirmation_shown_); + EXPECT_EQ(expected_sync_settings_shown_, sync_settings_shown_); + } + + // Functions called by the DiceTurnSyncOnHelper::Delegate: + void OnShowLoginError(const std::string& email, + const std::string& error_message) { + EXPECT_FALSE(sync_confirmation_shown_); + EXPECT_FALSE(email.empty()); + EXPECT_TRUE(login_error_email_.empty()) + << "Login error should be shown only once."; + login_error_email_ = email; + login_error_message_ = error_message; // May be empty. + } + + void OnShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + EXPECT_FALSE(sync_confirmation_shown_); + EXPECT_FALSE(previous_email.empty()); + EXPECT_FALSE(new_email.empty()); + EXPECT_TRUE(merge_data_previous_email_.empty()) + << "Merge data confirmation should be shown only once"; + EXPECT_TRUE(merge_data_new_email_.empty()) + << "Merge data confirmation should be shown only once"; + merge_data_previous_email_ = previous_email; + merge_data_new_email_ = new_email; + std::move(callback).Run(merge_data_choice_); + } + + void OnShowEnterpriseAccountConfirmation( + const std::string& email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + EXPECT_FALSE(sync_confirmation_shown_); + EXPECT_FALSE(email.empty()); + EXPECT_TRUE(enterprise_confirmation_email_.empty()) + << "Enterprise confirmation should be shown only once."; + enterprise_confirmation_email_ = email; + std::move(callback).Run(enterprise_choice_); + } + + void OnShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) { + EXPECT_FALSE(sync_confirmation_shown_) + << "Sync confirmation should be shown only once."; + sync_confirmation_shown_ = true; + std::move(callback).Run(sync_confirmation_result_); + } + + void OnShowSyncSettings() { + EXPECT_TRUE(sync_confirmation_shown_) + << "Must show sync confirmation first"; + EXPECT_FALSE(sync_settings_shown_); + sync_settings_shown_ = true; + } + + void OnShowSigninPageInNewProfile(Profile* new_profile, + const std::string& username) { + EXPECT_TRUE(new_profile); + EXPECT_NE(profile(), new_profile) + << "new_profile should not be the existing profile"; + EXPECT_FALSE(username.empty()); + EXPECT_TRUE(new_profile_username_.empty()) + << "Signin page should be shown only once"; + new_profile_username_ = username; + } + + void OnDelegateDestroyed() { delegate_destroyed_ = true; } + + protected: + // Delegate behavior. + DiceTurnSyncOnHelper::SigninChoice merge_data_choice_ = + DiceTurnSyncOnHelper::SIGNIN_CHOICE_CANCEL; + DiceTurnSyncOnHelper::SigninChoice enterprise_choice_ = + DiceTurnSyncOnHelper::SIGNIN_CHOICE_CANCEL; + LoginUIService::SyncConfirmationUIClosedResult sync_confirmation_result_ = + LoginUIService::SyncConfirmationUIClosedResult::ABORT_SIGNIN; + + // Expected delegate calls. + std::string expected_login_error_email_; + std::string expected_login_error_message_; + std::string expected_enterprise_confirmation_email_; + std::string expected_merge_data_previous_email_; + std::string expected_merge_data_new_email_; + std::string expected_new_profile_username_; + bool expected_sync_confirmation_shown_ = false; + bool expected_sync_settings_shown_ = false; + + private: + signin::ScopedAccountConsistencyDicePrepareMigration scoped_dice; + content::TestBrowserThreadBundle thread_bundle_; + base::ScopedTempDir temp_dir_; + ScopedTestingLocalState local_state_; + std::string account_id_; + std::unique_ptr<TestingProfile> profile_; + AccountTrackerService* account_tracker_service_ = nullptr; + ProfileOAuth2TokenService* token_service_ = nullptr; + SigninManager* signin_manager_ = nullptr; + FakeUserPolicySigninService* user_policy_signin_service_ = nullptr; + + // State of the delegate calls. + bool delegate_destroyed_ = false; + std::string login_error_email_; + std::string login_error_message_; + std::string enterprise_confirmation_email_; + std::string merge_data_previous_email_; + std::string merge_data_new_email_; + std::string new_profile_username_; + bool sync_confirmation_shown_ = false; + bool sync_settings_shown_ = false; +}; + +// TestDiceTurnSyncOnHelperDelegate implementation. + +TestDiceTurnSyncOnHelperDelegate::TestDiceTurnSyncOnHelperDelegate( + DiceTurnSyncOnHelperTest* test_fixture) + : test_fixture_(test_fixture) {} + +TestDiceTurnSyncOnHelperDelegate::~TestDiceTurnSyncOnHelperDelegate() { + test_fixture_->OnDelegateDestroyed(); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowLoginError( + const std::string& email, + const std::string& error_message) { + test_fixture_->OnShowLoginError(email, error_message); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowMergeSyncDataConfirmation( + const std::string& previous_email, + const std::string& new_email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + test_fixture_->OnShowMergeSyncDataConfirmation(previous_email, new_email, + std::move(callback)); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowEnterpriseAccountConfirmation( + const std::string& email, + DiceTurnSyncOnHelper::SigninChoiceCallback callback) { + test_fixture_->OnShowEnterpriseAccountConfirmation(email, + std::move(callback)); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowSyncConfirmation( + base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)> + callback) { + test_fixture_->OnShowSyncConfirmation(std::move(callback)); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowSyncSettings() { + test_fixture_->OnShowSyncSettings(); +} + +void TestDiceTurnSyncOnHelperDelegate::ShowSigninPageInNewProfile( + Profile* new_profile, + const std::string& username) { + test_fixture_->OnShowSigninPageInNewProfile(new_profile, username); +} + +// Tests that the login error is displayed and that the account is kept. +TEST_F(DiceTurnSyncOnHelperTest, CanOfferSigninErrorKeepAccount) { + // Set expectations. + expected_login_error_email_ = kEmail; + // Configure the test. + profile()->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT); + base::RunLoop().RunUntilIdle(); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Tests that the login error is displayed and that the account is removed. +TEST_F(DiceTurnSyncOnHelperTest, CanOfferSigninErrorRemoveAccount) { + // Set expectations. + expected_login_error_email_ = kEmail; + // Configure the test. + profile()->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + base::RunLoop().RunUntilIdle(); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Aborts the flow after the cross account dialog. +TEST_F(DiceTurnSyncOnHelperTest, CrossAccountAbort) { + // Set expectations. + expected_merge_data_previous_email_ = kPreviousEmail; + expected_merge_data_new_email_ = kEmail; + // Configure the test. + profile()->GetPrefs()->SetString(prefs::kGoogleServicesLastUsername, + kPreviousEmail); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Merge data after the cross account dialog. +TEST_F(DiceTurnSyncOnHelperTest, CrossAccountContinue) { + // Set expectations. + expected_merge_data_previous_email_ = kPreviousEmail; + expected_merge_data_new_email_ = kEmail; + expected_sync_confirmation_shown_ = true; + // Configure the test. + merge_data_choice_ = DiceTurnSyncOnHelper::SIGNIN_CHOICE_CONTINUE; + profile()->GetPrefs()->SetString(prefs::kGoogleServicesLastUsername, + kPreviousEmail); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Create a new profile after the cross account dialog and show the signin page. +TEST_F(DiceTurnSyncOnHelperTest, CrossAccountNewProfile) { + // Set expectations. + expected_merge_data_previous_email_ = kPreviousEmail; + expected_merge_data_new_email_ = kEmail; + expected_new_profile_username_ = kEmail; + // Configure the test. + merge_data_choice_ = DiceTurnSyncOnHelper::SIGNIN_CHOICE_NEW_PROFILE; + profile()->GetPrefs()->SetString(prefs::kGoogleServicesLastUsername, + kPreviousEmail); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + base::RunLoop().RunUntilIdle(); // Profile creation is asynchronous. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Abort after the enterprise confirmation prompt. +TEST_F(DiceTurnSyncOnHelperTest, EnterpriseConfirmationAbort) { + // Set expectations. + expected_enterprise_confirmation_email_ = kEmail; + // Configure the test. + user_policy_signin_service()->set_dm_token("foo"); + user_policy_signin_service()->set_client_id("bar"); + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Continue after the enterprise confirmation prompt. +TEST_F(DiceTurnSyncOnHelperTest, EnterpriseConfirmationContinue) { + // Set expectations. + expected_enterprise_confirmation_email_ = kEmail; + expected_sync_confirmation_shown_ = true; + // Configure the test. + user_policy_signin_service()->set_dm_token("foo"); + user_policy_signin_service()->set_client_id("bar"); + enterprise_choice_ = DiceTurnSyncOnHelper::SIGNIN_CHOICE_CONTINUE; + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Continue with a new profile after the enterprise confirmation prompt. +TEST_F(DiceTurnSyncOnHelperTest, EnterpriseConfirmationNewProfile) { + // Set expectations. + expected_enterprise_confirmation_email_ = kEmail; + expected_new_profile_username_ = kEmail; + // Configure the test. + user_policy_signin_service()->set_dm_token("foo"); + user_policy_signin_service()->set_client_id("bar"); + enterprise_choice_ = DiceTurnSyncOnHelper::SIGNIN_CHOICE_NEW_PROFILE; + // Signin flow. + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + base::RunLoop().RunUntilIdle(); // Profile creation is asynchronous. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Tests that the sync confirmation is shown and the user can abort. +TEST_F(DiceTurnSyncOnHelperTest, UndoSync) { + // Set expectations. + expected_sync_confirmation_shown_ = true; + SetExpectationsForSyncStartupCompleted(); + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(0); + + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Tests that the sync settings page is shown. +TEST_F(DiceTurnSyncOnHelperTest, ConfigureSync) { + // Set expectations. + expected_sync_confirmation_shown_ = true; + expected_sync_settings_shown_ = true; + SetExpectationsForSyncStartupCompleted(); + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(0); + + // Configure the test. + sync_confirmation_result_ = + LoginUIService::SyncConfirmationUIClosedResult::CONFIGURE_SYNC_FIRST; + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_TRUE(signin_manager()->IsAuthenticated()); + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + CheckDelegateCalls(); +} + +// Tests that the user is signed in and Sync configuration is complete. +TEST_F(DiceTurnSyncOnHelperTest, StartSync) { + // Set expectations. + expected_sync_confirmation_shown_ = true; + SetExpectationsForSyncStartupCompleted(); + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(1); + // Configure the test. + sync_confirmation_result_ = LoginUIService::SyncConfirmationUIClosedResult:: + SYNC_WITH_DEFAULT_SETTINGS; + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + // Check expectations. + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + EXPECT_EQ(account_id(), signin_manager()->GetAuthenticatedAccountId()); + CheckDelegateCalls(); +} + +// Tests that the user is signed in and Sync configuration is complete. +// Regression test for http://crbug.com/812546 +TEST_F(DiceTurnSyncOnHelperTest, ShowSyncDialogForEndConsumerAccount) { + // Set expectations. + expected_sync_confirmation_shown_ = true; + sync_confirmation_result_ = LoginUIService::SyncConfirmationUIClosedResult:: + SYNC_WITH_DEFAULT_SETTINGS; + SetExpectationsForSyncStartupCompleted(); + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(1); + + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + + // Check expectations. + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + EXPECT_EQ(account_id(), signin_manager()->GetAuthenticatedAccountId()); + CheckDelegateCalls(); +} + +// For enterprise user, tests that the user is signed in only after Sync engine +// starts. +// Regression test for http://crbug.com/812546 +TEST_F(DiceTurnSyncOnHelperTest, + ShowSyncDialogBlockedUntilSyncStartupCompletedForEnterpriseAccount) { + // Reset the account info to be an enterprise account. + UseEnterpriseAccount(); + + // Set expectations. + expected_sync_confirmation_shown_ = false; + SetExpectationsForSyncStartupPending(); + + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + DiceTurnSyncOnHelper* dice_sync_starter = CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + + // Check that the account was set in the sign-in manager, but the sync + // confirmation dialog was not yet shown. + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + EXPECT_EQ(account_id(), signin_manager()->GetAuthenticatedAccountId()); + CheckDelegateCalls(); + + // Simulate that sync startup has completed. + expected_sync_confirmation_shown_ = true; + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(1); + sync_confirmation_result_ = LoginUIService::SyncConfirmationUIClosedResult:: + SYNC_WITH_DEFAULT_SETTINGS; + dice_sync_starter->SyncStartupCompleted(); + CheckDelegateCalls(); +} + +// For enterprise user, tests that the user is signed in only after Sync engine +// fails to start. +// Regression test for http://crbug.com/812546 +TEST_F(DiceTurnSyncOnHelperTest, + ShowSyncDialogBlockedUntilSyncStartupFailedForEnterpriseAccount) { + // Reset the account info to be an enterprise account. + UseEnterpriseAccount(); + + // Set expectations. + expected_sync_confirmation_shown_ = false; + SetExpectationsForSyncStartupPending(); + + // Signin flow. + EXPECT_FALSE(signin_manager()->IsAuthenticated()); + DiceTurnSyncOnHelper* dice_sync_starter = CreateDiceTurnOnSyncHelper( + DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT); + + // Check that the primary account was added to the token service and in the + // sign-in manager. + EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id())); + EXPECT_EQ(account_id(), signin_manager()->GetAuthenticatedAccountId()); + CheckDelegateCalls(); + + // Simulate that sync startup has failed. + expected_sync_confirmation_shown_ = true; + EXPECT_CALL(*GetProfileSyncServiceMock(), SetFirstSetupComplete()).Times(1); + sync_confirmation_result_ = LoginUIService::SyncConfirmationUIClosedResult:: + SYNC_WITH_DEFAULT_SETTINGS; + dice_sync_starter->SyncStartupFailed(); + CheckDelegateCalls(); +} diff --git a/chromium/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc b/chromium/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc index b0bd02847f1..b98fde04295 100644 --- a/chromium/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc +++ b/chromium/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc @@ -239,8 +239,8 @@ void InlineSigninHelper::OnClientOAuthSuccessAndBrowserOpened( PasswordStoreFactory::GetForProfile(profile_, ServiceAccessType::EXPLICIT_ACCESS); if (password_store) { - password_store->SaveSyncPasswordHash(base::UTF8ToUTF16(password_)); - password_manager::metrics_util::LogSyncPasswordHashChange( + password_store->SaveSyncPasswordHash( + base::UTF8ToUTF16(password_), password_manager::metrics_util::SyncPasswordHashChange:: SAVED_ON_CHROME_SIGNIN); } diff --git a/chromium/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc b/chromium/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc index 7e4a5f2f307..4911e8662fe 100644 --- a/chromium/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc +++ b/chromium/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc @@ -354,8 +354,9 @@ IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, OneProcessLimit) { base::FilePath(base::FilePath::kCurrentDirectory), base::FilePath(FILE_PATH_LITERAL("title1.html"))); GURL test_url_2 = ui_test_utils::GetTestUrl( - base::FilePath(base::FilePath::kCurrentDirectory), - base::FilePath(FILE_PATH_LITERAL("data:text/html,Hello world!"))); + base::FilePath(base::FilePath::kCurrentDirectory) + .Append(FILE_PATH_LITERAL("frame_tree")), + base::FilePath(FILE_PATH_LITERAL("simple.htm"))); // Even when the process limit is set to one, the signin process should // still be given its own process and storage partition. diff --git a/chromium/chrome/browser/ui/webui/signin/login_ui_service.cc b/chromium/chrome/browser/ui/webui/signin/login_ui_service.cc index 1e21d92418c..4ef22822f58 100644 --- a/chromium/chrome/browser/ui/webui/signin/login_ui_service.cc +++ b/chromium/chrome/browser/ui/webui/signin/login_ui_service.cc @@ -11,12 +11,15 @@ #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" -#include "chrome/browser/ui/user_manager.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/common/url_constants.h" #include "components/signin/core/browser/profile_management_switches.h" #include "components/signin/core/browser/signin_header_helper.h" +#if !defined(OS_CHROMEOS) +#include "chrome/browser/ui/user_manager.h" +#endif // !defined(OS_CHROMEOS) + LoginUIService::LoginUIService(Profile* profile) #if !defined(OS_CHROMEOS) : profile_(profile) diff --git a/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.cc b/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.cc index 31cb8022e77..4ae11a15f61 100644 --- a/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.cc +++ b/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.cc @@ -17,8 +17,8 @@ #include "chrome/browser/ui/webui/signin/signin_utils.h" #include "chrome/browser/ui/webui/signin/user_manager_screen_handler.h" #include "chrome/browser/ui/webui/theme_source.h" +#include "chrome/common/buildflags.h" #include "chrome/common/chrome_features.h" -#include "chrome/common/features.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" #include "content/public/browser/web_ui.h" diff --git a/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.h b/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.h index 0aa98246525..63bf9ceed78 100644 --- a/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.h +++ b/chromium/chrome/browser/ui/webui/signin/md_user_manager_ui.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_UI_WEBUI_SIGNIN_MD_USER_MANAGER_UI_H_ #include "base/macros.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "content/public/browser/web_ui_controller.h" class SigninCreateProfileHandler; diff --git a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.cc b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.cc index 44d69c0bfd9..2ce9d8cbab1 100644 --- a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.cc +++ b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.cc @@ -33,7 +33,7 @@ #include "chrome/browser/ui/user_manager.h" #include "chrome/browser/ui/webui/profile_helper.h" #include "chrome/browser/ui/webui/signin/signin_utils.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/chromium_strings.h" @@ -447,7 +447,6 @@ void SigninCreateProfileHandler::ShowProfileCreationError( // The ProfileManager calls us back with a NULL profile in some cases. if (profile) { webui::DeleteProfileAtPath(profile->GetPath(), - web_ui(), ProfileMetrics::DELETE_PROFILE_SETTINGS); } profile_creation_type_ = NO_CREATION_IN_PROGRESS; @@ -717,7 +716,6 @@ void SigninCreateProfileHandler::CancelProfileRegistration( // RegisterAndInitSync() won't be called, so the cleanup must be done here. profile_path_being_created_.clear(); webui::DeleteProfileAtPath(new_profile->GetPath(), - web_ui(), ProfileMetrics::DELETE_PROFILE_SETTINGS); } diff --git a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.h b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.h index 87bf6e44a84..dd6b4f00db4 100644 --- a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.h +++ b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler.h @@ -13,7 +13,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/profiles/profile_window.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_ui_message_handler.h" diff --git a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler_unittest.cc b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler_unittest.cc index f9337a1e0ff..94c07cbe51c 100644 --- a/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/signin/signin_create_profile_handler_unittest.cc @@ -15,14 +15,13 @@ #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/signin_util.h" #include "chrome/browser/ui/webui/signin/signin_utils.h" -#include "chrome/common/features.h" +#include "chrome/common/buildflags.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/testing_profile_manager.h" #include "components/signin/core/browser/fake_auth_status_provider.h" -#include "components/sync/model/attachments/attachment_service_proxy_for_test.h" #include "components/sync/model/fake_sync_change_processor.h" #include "components/sync/model/sync_data.h" #include "components/sync/model/sync_error_factory_mock.h" @@ -73,12 +72,7 @@ syncer::SyncData CreateSyncData(const std::string& id, specifics.mutable_managed_user()->set_acknowledged(true); specifics.mutable_managed_user()->set_chrome_avatar(chrome_avatar); - return syncer::SyncData::CreateRemoteData( - 1, - specifics, - base::Time(), - syncer::AttachmentIdList(), - syncer::AttachmentServiceProxyForTest::Create()); + return syncer::SyncData::CreateRemoteData(1, specifics, base::Time()); } #endif diff --git a/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.cc b/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.cc deleted file mode 100644 index e6699179018..00000000000 --- a/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.cc +++ /dev/null @@ -1,85 +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. - -#include "chrome/browser/ui/webui/signin/signin_dice_internals_handler.h" - -#include "base/values.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/signin/account_tracker_service_factory.h" -#include "chrome/browser/signin/dice_tab_helper.h" -#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" -#include "chrome/browser/signin/signin_manager_factory.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/scoped_tabbed_browser_displayer.h" -#include "chrome/browser/ui/singleton_tabs.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h" -#include "google_apis/gaia/gaia_urls.h" - -SigninDiceInternalsHandler::SigninDiceInternalsHandler(Profile* profile) - : profile_(profile) { - DCHECK(profile_); - DCHECK(!profile_->IsOffTheRecord()); -} - -SigninDiceInternalsHandler::~SigninDiceInternalsHandler() {} - -void SigninDiceInternalsHandler::RegisterMessages() { - web_ui()->RegisterMessageCallback( - "enableSync", base::Bind(&SigninDiceInternalsHandler::HandleEnableSync, - base::Unretained(this))); - - web_ui()->RegisterMessageCallback( - "disableSync", base::Bind(&SigninDiceInternalsHandler::HandleDisableSync, - base::Unretained(this))); -} - -void SigninDiceInternalsHandler::HandleEnableSync(const base::ListValue* args) { - if (SigninManagerFactory::GetForProfile(profile_)->IsAuthenticated()) { - VLOG(1) << "[Dice] Cannot enable sync as profile is already authenticated"; - return; - } - - Browser* browser = chrome::FindLastActiveWithProfile(profile_); - DCHECK(browser); - - AccountTrackerService* tracker = - AccountTrackerServiceFactory::GetForProfile(profile_); - ProfileOAuth2TokenService* token_service = - ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); - std::vector<std::string> account_ids = token_service->GetAccounts(); - if (account_ids.empty()) { - browser->window()->ShowAvatarBubbleFromAvatarButton( - BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN, - signin::ManageAccountsParams(), - signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN, false); - return; - } - - std::string account_id = account_ids[0]; - std::string email = tracker->GetAccountInfo(account_id).email; - VLOG(1) << "[Dice] Start syncing with account " << email; - - // DiceTurnSyncOnHelper is suicidal (it will kill itself once it finishes - // enabling sync). - new DiceTurnSyncOnHelper( - profile_, browser, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN, - signin_metrics::Reason::REASON_UNKNOWN_REASON, account_id, - DiceTurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT); -} - -void SigninDiceInternalsHandler::HandleDisableSync( - const base::ListValue* args) { - SigninManager* signin_manager = SigninManagerFactory::GetForProfile(profile_); - if (!signin_manager->IsAuthenticated()) { - VLOG(2) << "[Dice] Cannot disable sync as profile is not authenticated"; - return; - } - - VLOG(2) << "[Dice] Sign out."; - signin_manager->SignOut(signin_metrics::USER_TUNED_OFF_SYNC_FROM_DICE_UI, - signin_metrics::SignoutDelete::IGNORE_METRIC); -} diff --git a/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.h b/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.h deleted file mode 100644 index 84513efaa6a..00000000000 --- a/chromium/chrome/browser/ui/webui/signin/signin_dice_internals_handler.h +++ /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. - -#ifndef CHROME_BROWSER_UI_WEBUI_SIGNIN_SIGNIN_DICE_INTERNALS_HANDLER_H_ -#define CHROME_BROWSER_UI_WEBUI_SIGNIN_SIGNIN_DICE_INTERNALS_HANDLER_H_ - -#include "base/macros.h" -#include "content/public/browser/web_ui_message_handler.h" - -namespace base { -class ListValue; -} -class Profile; - -class SigninDiceInternalsHandler : public content::WebUIMessageHandler { - public: - explicit SigninDiceInternalsHandler(Profile* profile); - ~SigninDiceInternalsHandler() override; - - // content::WebUIMessageHandler: - void RegisterMessages() override; - - private: - // Handler for enable sync event. - void HandleEnableSync(const base::ListValue* args); - - // Handler for disable sync event. - void HandleDisableSync(const base::ListValue* args); - - // Start a Gaia web-sign in and once done automatically start syncing with the - // account that was signed in. - void StartWebGaiaSigninAndStartSyncWhenDone(); - - Profile* profile_; - - DISALLOW_COPY_AND_ASSIGN(SigninDiceInternalsHandler); -}; - -#endif // CHROME_BROWSER_UI_WEBUI_SIGNIN_SIGNIN_DICE_INTERNALS_HANDLER_H_ diff --git a/chromium/chrome/browser/ui/webui/signin/signin_error_ui.cc b/chromium/chrome/browser/ui/webui/signin/signin_error_ui.cc index e127aefb512..7b1e9041935 100644 --- a/chromium/chrome/browser/ui/webui/signin/signin_error_ui.cc +++ b/chromium/chrome/browser/ui/webui/signin/signin_error_ui.cc @@ -11,6 +11,7 @@ #include "chrome/browser/profiles/profile_attributes_entry.h" #include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/signin/signin_ui_util.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/user_manager.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" @@ -19,6 +20,9 @@ #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" #include "chrome/grit/generated_resources.h" +#include "components/prefs/pref_service.h" +#include "components/signin/core/browser/signin_pref_names.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" #include "ui/base/l10n/l10n_util.h" @@ -96,8 +100,21 @@ void SigninErrorUI::Initialize(Browser* browser, bool is_system_profile) { if (is_profile_blocked) { source->AddLocalizedString("profileBlockedMessage", IDS_OLD_PROFILES_DISABLED_MESSAGE); - source->AddLocalizedString("profileBlockedAddPersonSuggestion", - IDS_OLD_PROFILES_DISABLED_ADD_PERSON_SUGGESTION); + std::string allowed_domain = signin_ui_util::GetAllowedDomain( + g_browser_process->local_state()->GetString( + prefs::kGoogleServicesUsernamePattern)); + if (allowed_domain.empty()) { + source->AddLocalizedString( + "profileBlockedAddPersonSuggestion", + IDS_OLD_PROFILES_DISABLED_ADD_PERSON_SUGGESTION); + } else { + source->AddString( + "profileBlockedAddPersonSuggestion", + l10n_util::GetStringFUTF16( + IDS_OLD_PROFILES_DISABLED_ADD_PERSON_SUGGESTION_WITH_DOMAIN, + base::ASCIIToUTF16(allowed_domain))); + } + source->AddLocalizedString("profileBlockedRemoveProfileSuggestion", IDS_OLD_PROFILES_DISABLED_REMOVED_OLD_PROFILE); } else if (!is_system_profile && @@ -133,8 +150,7 @@ void SigninErrorUI::Initialize(Browser* browser, bool is_system_profile) { source->AddString("signinErrorSwitchLabel", l10n_util::GetStringFUTF16( IDS_SIGNIN_ERROR_SWITCH_BUTTON_LABEL, existing_name)); - source->AddLocalizedString("signinErrorLearnMore", - IDS_SIGNIN_ERROR_LEARN_MORE_LINK); + source->AddLocalizedString("signinErrorLearnMore", IDS_LEARN_MORE); source->AddLocalizedString("signinErrorCloseLabel", IDS_SIGNIN_ERROR_CLOSE_BUTTON_LABEL); source->AddLocalizedString("signinErrorOkLabel", diff --git a/chromium/chrome/browser/ui/webui/signin/signin_supervised_user_import_handler_unittest.cc b/chromium/chrome/browser/ui/webui/signin/signin_supervised_user_import_handler_unittest.cc index 13bd890f5b2..cc615490d3c 100644 --- a/chromium/chrome/browser/ui/webui/signin/signin_supervised_user_import_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/signin/signin_supervised_user_import_handler_unittest.cc @@ -17,8 +17,6 @@ #include "chrome/test/base/testing_profile.h" #include "chrome/test/base/testing_profile_manager.h" #include "components/signin/core/browser/fake_auth_status_provider.h" -#include "components/sync/model/attachments/attachment_id.h" -#include "components/sync/model/attachments/attachment_service_proxy_for_test.h" #include "components/sync/model/fake_sync_change_processor.h" #include "components/sync/model/sync_error_factory_mock.h" #include "components/sync/protocol/sync.pb.h" @@ -48,12 +46,7 @@ syncer::SyncData CreateSyncData(const std::string& id, specifics.mutable_managed_user()->set_acknowledged(true); specifics.mutable_managed_user()->set_chrome_avatar(chrome_avatar); - return syncer::SyncData::CreateRemoteData( - 1, - specifics, - base::Time(), - syncer::AttachmentIdList(), - syncer::AttachmentServiceProxyForTest::Create()); + return syncer::SyncData::CreateRemoteData(1, specifics, base::Time()); } } // namespace diff --git a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc index 170211b6417..e2ed758f0ee 100644 --- a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc +++ b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc @@ -8,6 +8,8 @@ #include "base/bind.h" #include "base/metrics/user_metrics.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/consent_auditor/consent_auditor_factory.h" #include "chrome/browser/profiles/profile_avatar_icon_util.h" #include "chrome/browser/signin/account_tracker_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" @@ -16,6 +18,8 @@ #include "chrome/browser/ui/signin_view_controller_delegate.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/browser/ui/webui/signin/signin_utils.h" +#include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h" +#include "components/consent_auditor/consent_auditor.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/avatar_icon_util.h" #include "components/signin/core/browser/signin_manager.h" @@ -25,10 +29,13 @@ const int kProfileImageSize = 128; -SyncConfirmationHandler::SyncConfirmationHandler(Browser* browser) +SyncConfirmationHandler::SyncConfirmationHandler( + Browser* browser, + const std::unordered_map<std::string, int>& string_to_grd_id_map) : profile_(browser->profile()), browser_(browser), - did_user_explicitly_interact(false) { + did_user_explicitly_interact(false), + string_to_grd_id_map_(string_to_grd_id_map) { DCHECK(profile_); DCHECK(browser_); BrowserList::AddObserver(this); @@ -67,11 +74,13 @@ void SyncConfirmationHandler::RegisterMessages() { void SyncConfirmationHandler::HandleConfirm(const base::ListValue* args) { did_user_explicitly_interact = true; + RecordConsent(args); CloseModalSigninWindow(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS); } void SyncConfirmationHandler::HandleGoToSettings(const base::ListValue* args) { did_user_explicitly_interact = true; + RecordConsent(args); CloseModalSigninWindow(LoginUIService::CONFIGURE_SYNC_FIRST); } @@ -80,6 +89,35 @@ void SyncConfirmationHandler::HandleUndo(const base::ListValue* args) { CloseModalSigninWindow(LoginUIService::ABORT_SIGNIN); } +void SyncConfirmationHandler::RecordConsent(const base::ListValue* args) { + CHECK_EQ(2U, args->GetSize()); + const std::vector<base::Value>& consent_description = + args->GetList()[0].GetList(); + const std::string& consent_confirmation = args->GetList()[1].GetString(); + + std::vector<int> consent_text_ids; + + // The strings returned by the WebUI are not free-form, they must belong into + // a pre-determined set of strings (stored in |string_to_grd_id_map_|). As + // this has privacy and legal implications, CHECK the integrity of the strings + // received from the renderer process before recording the consent. + for (const base::Value& text : consent_description) { + auto iter = string_to_grd_id_map_.find(text.GetString()); + CHECK(iter != string_to_grd_id_map_.end()) << "Unexpected string:\n" + << text.GetString(); + consent_text_ids.push_back(iter->second); + } + + auto iter = string_to_grd_id_map_.find(consent_confirmation); + CHECK(iter != string_to_grd_id_map_.end()) << "Unexpected string:\n" + << consent_confirmation; + int consent_confirmation_id = iter->second; + + ConsentAuditorFactory::GetForProfile(profile_)->RecordGaiaConsent( + consent_auditor::Feature::CHROME_SYNC, consent_text_ids, + consent_confirmation_id, consent_auditor::ConsentStatus::GIVEN); +} + void SyncConfirmationHandler::SetUserImageURL(const std::string& picture_url) { std::string picture_url_to_load; GURL picture_gurl(picture_url); diff --git a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.h b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.h index 6fded10e455..8cab0b742a7 100644 --- a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.h +++ b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_HANDLER_H_ #include <string> +#include <unordered_map> #include "base/macros.h" #include "chrome/browser/ui/browser_list_observer.h" @@ -21,7 +22,12 @@ class SyncConfirmationHandler : public content::WebUIMessageHandler, public AccountTrackerService::Observer, public BrowserListObserver { public: - explicit SyncConfirmationHandler(Browser* browser); + // Creates a SyncConfirmationHandler for the |browser|. All strings in the + // corresponding Web UI should be represented in |string_to_grd_id_map| and + // mapped to their GRD IDs. + explicit SyncConfirmationHandler( + Browser* browser, + const std::unordered_map<std::string, int>& string_to_grd_id_map); ~SyncConfirmationHandler() override; // content::WebUIMessageHandler: @@ -55,6 +61,16 @@ class SyncConfirmationHandler : public content::WebUIMessageHandler, // a single integer value for the height the native view should resize to. virtual void HandleInitializedWithSize(const base::ListValue* args); + // Records the user's consent to sync. Called from |HandleConfirm| and + // |HandleGoToSettings|, and expects two parameters to be passed through + // these methods from the WebUI: + // 1. List of strings (names of the string resources constituting the consent + // description as per WebUIDataSource) + // 2. Strings (name of the string resource of the consent confirmation) + // This message is sent when the user interacts with the dialog in a positive + // manner, i.e. clicks on the confirmation button or the settings link. + virtual void RecordConsent(const base::ListValue* args); + // Sets the profile picture shown in the dialog to the image at |url|. virtual void SetUserImageURL(const std::string& url); @@ -73,6 +89,10 @@ class SyncConfirmationHandler : public content::WebUIMessageHandler, // Records whether the user clicked on Undo, Ok, or Settings. bool did_user_explicitly_interact; + // Mapping between strings displayed in the UI corresponding to this handler + // and their respective GRD IDs. + std::unordered_map<std::string, int> string_to_grd_id_map_; + DISALLOW_COPY_AND_ASSIGN(SyncConfirmationHandler); }; diff --git a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc index a5caccc0dd3..28843bfe753 100644 --- a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc +++ b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_handler_unittest.cc @@ -5,9 +5,12 @@ #include "chrome/browser/ui/webui/signin/sync_confirmation_handler.h" #include <memory> +#include <unordered_map> +#include <vector> #include "base/test/user_action_tester.h" #include "base/values.h" +#include "chrome/browser/consent_auditor/consent_auditor_factory.h" #include "chrome/browser/profiles/profile_avatar_icon_util.h" #include "chrome/browser/signin/account_fetcher_service_factory.h" #include "chrome/browser/signin/account_tracker_service_factory.h" @@ -15,6 +18,7 @@ #include "chrome/browser/signin/fake_signin_manager_builder.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/sync/profile_sync_service_factory.h" +#include "chrome/browser/sync/user_event_service_factory.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h" @@ -22,6 +26,7 @@ #include "chrome/test/base/dialog_test_browser_window.h" #include "chrome/test/base/testing_profile.h" #include "components/browser_sync/profile_sync_service.h" +#include "components/consent_auditor/consent_auditor.h" #include "components/signin/core/browser/account_fetcher_service.h" #include "components/signin/core/browser/avatar_icon_util.h" #include "components/signin/core/browser/fake_account_fetcher_service.h" @@ -35,10 +40,17 @@ const int kExpectedProfileImageSize = 128; // really matter in unit tests. const double kDefaultDialogHeight = 350.0; +const std::string kGaiaID = "gaia"; +const std::string kUsername = "foo@example.com"; +const std::string kPassword = "password"; + class TestingSyncConfirmationHandler : public SyncConfirmationHandler { public: - TestingSyncConfirmationHandler(Browser* browser, content::WebUI* web_ui) - : SyncConfirmationHandler(browser) { + TestingSyncConfirmationHandler( + Browser* browser, + content::WebUI* web_ui, + std::unordered_map<std::string, int> string_to_grd_id_map) + : SyncConfirmationHandler(browser, string_to_grd_id_map) { set_web_ui(web_ui); } @@ -46,6 +58,7 @@ class TestingSyncConfirmationHandler : public SyncConfirmationHandler { using SyncConfirmationHandler::HandleUndo; using SyncConfirmationHandler::HandleInitializedWithSize; using SyncConfirmationHandler::HandleGoToSettings; + using SyncConfirmationHandler::RecordConsent; using SyncConfirmationHandler::SetUserImageURL; private: @@ -88,8 +101,47 @@ class TestingOneClickSigninSyncStarter : public OneClickSigninSyncStarter { DISALLOW_COPY_AND_ASSIGN(TestingOneClickSigninSyncStarter); }; +// TODO(msramek): Extract this into "consent_auditor_test_utils" for reusability +// and to remove unnecessary dependencies from this test. +class FakeConsentAuditor : public consent_auditor::ConsentAuditor { + public: + static std::unique_ptr<KeyedService> Build(content::BrowserContext* context) { + return std::make_unique<FakeConsentAuditor>( + Profile::FromBrowserContext(context)); + } + + FakeConsentAuditor(Profile* profile) + : ConsentAuditor( + profile->GetPrefs(), + browser_sync::UserEventServiceFactory::GetForProfile(profile), + std::string(), + std::string()) {} + ~FakeConsentAuditor() override {} + + void RecordGaiaConsent(consent_auditor::Feature feature, + const std::vector<int>& description_grd_ids, + int confirmation_grd_id, + consent_auditor::ConsentStatus status) override { + recorded_ids_ = description_grd_ids; + recorded_ids_.push_back(confirmation_grd_id); + } + + const std::vector<int>& recorded_ids() { return recorded_ids_; } + + private: + std::vector<int> recorded_ids_; + + DISALLOW_COPY_AND_ASSIGN(FakeConsentAuditor); +}; + class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest { public: + static const std::string kConsentText1; + static const std::string kConsentText2; + static const std::string kConsentText3; + static const std::string kConsentText4; + static const std::string kConsentText5; + SyncConfirmationHandlerTest() : did_user_explicitly_interact(false), web_ui_(new content::TestWebUI) {} void SetUp() override { @@ -98,8 +150,8 @@ class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest { web_ui()->set_web_contents( browser()->tab_strip_model()->GetActiveWebContents()); - auto handler = - std::make_unique<TestingSyncConfirmationHandler>(browser(), web_ui()); + auto handler = std::make_unique<TestingSyncConfirmationHandler>( + browser(), web_ui(), GetStringToGrdIdMap()); handler_ = handler.get(); sync_confirmation_ui_.reset(new SyncConfirmationUI(web_ui())); web_ui()->AddMessageHandler(std::move(handler)); @@ -154,6 +206,11 @@ class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest { return &user_action_tester_; } + FakeConsentAuditor* consent_auditor() { + return static_cast<FakeConsentAuditor*>( + ConsentAuditorFactory::GetForProfile(profile())); + } + // BrowserWithTestWindowTest BrowserWindow* CreateBrowserWindow() override { return new DialogTestBrowserWindow; @@ -162,7 +219,19 @@ class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest { TestingProfile::TestingFactories GetTestingFactories() override { return {{AccountFetcherServiceFactory::GetInstance(), FakeAccountFetcherServiceBuilder::BuildForTests}, - {SigninManagerFactory::GetInstance(), BuildFakeSigninManagerBase}}; + {SigninManagerFactory::GetInstance(), BuildFakeSigninManagerBase}, + {ConsentAuditorFactory::GetInstance(), FakeConsentAuditor::Build}}; + } + + const std::unordered_map<std::string, int>& GetStringToGrdIdMap() { + if (string_to_grd_id_map_.empty()) { + string_to_grd_id_map_[kConsentText1] = 1; + string_to_grd_id_map_[kConsentText2] = 2; + string_to_grd_id_map_[kConsentText3] = 3; + string_to_grd_id_map_[kConsentText4] = 4; + string_to_grd_id_map_[kConsentText5] = 5; + } + return string_to_grd_id_map_; } protected: @@ -173,10 +242,17 @@ class SyncConfirmationHandlerTest : public BrowserWithTestWindowTest { std::unique_ptr<SyncConfirmationUI> sync_confirmation_ui_; TestingSyncConfirmationHandler* handler_; // Not owned. base::UserActionTester user_action_tester_; + std::unordered_map<std::string, int> string_to_grd_id_map_; DISALLOW_COPY_AND_ASSIGN(SyncConfirmationHandlerTest); }; +const std::string SyncConfirmationHandlerTest::kConsentText1 = "consentText1"; +const std::string SyncConfirmationHandlerTest::kConsentText2 = "consentText2"; +const std::string SyncConfirmationHandlerTest::kConsentText3 = "consentText3"; +const std::string SyncConfirmationHandlerTest::kConsentText4 = "consentText4"; +const std::string SyncConfirmationHandlerTest::kConsentText5 = "consentText5"; + TEST_F(SyncConfirmationHandlerTest, TestSetImageIfPrimaryAccountReady) { account_fetcher_service()->FakeUserInfoFetchSuccess( "gaia", @@ -310,10 +386,27 @@ TEST_F(SyncConfirmationHandlerTest, TestHandleUndo) { } TEST_F(SyncConfirmationHandlerTest, TestHandleConfirm) { + // The consent description consists of strings 1, 2, and 4. + base::ListValue consent_description; + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText1)); + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText2)); + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText4)); + + // The consent confirmation contains string 5. + base::Value consent_confirmation(SyncConfirmationHandlerTest::kConsentText5); + + // These are passed as parameters to HandleConfirm(). + base::ListValue args; + args.GetList().push_back(std::move(consent_description)); + args.GetList().push_back(std::move(consent_confirmation)); + EXPECT_FALSE(sync()->IsFirstSetupComplete()); EXPECT_TRUE(sync()->IsFirstSetupInProgress()); - handler()->HandleConfirm(nullptr); + handler()->HandleConfirm(&args); did_user_explicitly_interact = true; EXPECT_FALSE(sync()->IsFirstSetupInProgress()); @@ -325,13 +418,34 @@ TEST_F(SyncConfirmationHandlerTest, TestHandleConfirm) { "Signin_Signin_WithDefaultSyncSettings")); EXPECT_EQ(0, user_action_tester()->GetActionCount( "Signin_Signin_WithAdvancedSyncSettings")); + + // The corresponding string IDs get recorded. + std::vector<int> expected_ids = {1, 2, 4, 5}; + EXPECT_EQ(expected_ids, consent_auditor()->recorded_ids()); } TEST_F(SyncConfirmationHandlerTest, TestHandleConfirmWithAdvancedSyncSettings) { + // The consent description consists of strings 2, 3, and 5. + base::ListValue consent_description; + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText2)); + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText3)); + consent_description.GetList().push_back( + base::Value(SyncConfirmationHandlerTest::kConsentText5)); + + // The consent confirmation contains string 2. + base::Value consent_confirmation(SyncConfirmationHandlerTest::kConsentText2); + + // These are passed as parameters to HandleGoToSettings(). + base::ListValue args; + args.GetList().push_back(std::move(consent_description)); + args.GetList().push_back(std::move(consent_confirmation)); + EXPECT_FALSE(sync()->IsFirstSetupComplete()); EXPECT_TRUE(sync()->IsFirstSetupInProgress()); - handler()->HandleGoToSettings(nullptr); + handler()->HandleGoToSettings(&args); did_user_explicitly_interact = true; EXPECT_FALSE(sync()->IsFirstSetupInProgress()); @@ -343,4 +457,8 @@ TEST_F(SyncConfirmationHandlerTest, TestHandleConfirmWithAdvancedSyncSettings) { "Signin_Signin_WithDefaultSyncSettings")); EXPECT_EQ(1, user_action_tester()->GetActionCount( "Signin_Signin_WithAdvancedSyncSettings")); + + // The corresponding string IDs get recorded. + std::vector<int> expected_ids = {2, 3, 5, 2}; + EXPECT_EQ(expected_ids, consent_auditor()->recorded_ids()); } diff --git a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc index 446569de14a..119d9bb48e7 100644 --- a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc +++ b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc @@ -4,32 +4,37 @@ #include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/account_consistency_mode_manager.h" +#include "chrome/browser/signin/unified_consent_helper.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/ui/webui/signin/sync_confirmation_handler.h" #include "chrome/common/url_constants.h" #include "chrome/grit/browser_resources.h" #include "chrome/grit/generated_resources.h" -#include "components/signin/core/browser/profile_management_switches.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/base/webui/web_ui_util.h" SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui) : SigninWebDialogUI(web_ui) { Profile* profile = Profile::FromWebUI(web_ui); bool is_sync_allowed = profile->IsSyncAllowed(); - bool is_dice_enabled = signin::IsDiceEnabledForProfile(profile->GetPrefs()); + bool is_unified_consent_enabled = IsUnifiedConsentEnabled(profile); content::WebUIDataSource* source = content::WebUIDataSource::Create(chrome::kChromeUISyncConfirmationHost); source->SetJsonPath("strings.js"); - source->AddResourcePath("signin_shared_css.html", IDR_SIGNIN_SHARED_CSS_HTML); - int title_ids, confirm_button_ids, undo_button_ids; - if (is_dice_enabled && is_sync_allowed) { + int title_ids = -1; + int confirm_button_ids = -1; + int undo_button_ids = -1; + if (is_unified_consent_enabled && is_sync_allowed) { source->SetDefaultResource(IDR_DICE_SYNC_CONFIRMATION_HTML); source->AddResourcePath("sync_confirmation_browser_proxy.html", IDR_DICE_SYNC_CONFIRMATION_BROWSER_PROXY_HTML); @@ -42,21 +47,18 @@ SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui) source->AddResourcePath("sync_confirmation.js", IDR_DICE_SYNC_CONFIRMATION_JS); - source->AddLocalizedString("syncConfirmationChromeSyncBody", - IDS_SYNC_CONFIRMATION_DICE_CHROME_SYNC_MESSAGE); - source->AddLocalizedString( - "syncConfirmationPersonalizeServicesBody", - IDS_SYNC_CONFIRMATION_DICE_PERSONALIZE_SERVICES_BODY); - source->AddLocalizedString("syncConfirmationGoogleServicesBody", - IDS_SYNC_CONFIRMATION_DICE_GOOGLE_SERVICES_BODY); - source->AddLocalizedString( - "syncConfirmationSyncSettingsLinkBody", - IDS_SYNC_CONFIRMATION_DICE_SYNC_SETTINGS_LINK_BODY); - source->AddLocalizedString( - "syncConfirmationSyncSettingsDescription", - IDS_SYNC_CONFIRMATION_DICE_SYNC_SETTINGS_DESCRIPTION); - - title_ids = IDS_SYNC_CONFIRMATION_DICE_TITLE; + AddStringResource(source, "syncConfirmationChromeSyncBody", + IDS_SYNC_CONFIRMATION_DICE_CHROME_SYNC_MESSAGE); + AddStringResource(source, "syncConfirmationPersonalizeServicesBody", + IDS_SYNC_CONFIRMATION_DICE_PERSONALIZE_SERVICES_BODY); + AddStringResource(source, "syncConfirmationGoogleServicesBody", + IDS_SYNC_CONFIRMATION_DICE_GOOGLE_SERVICES_BODY); + AddStringResource(source, "syncConfirmationSyncSettingsLinkBody", + IDS_SYNC_CONFIRMATION_DICE_SYNC_SETTINGS_LINK_BODY); + AddStringResource(source, "syncConfirmationSyncSettingsDescription", + IDS_SYNC_CONFIRMATION_DICE_SYNC_SETTINGS_DESCRIPTION); + + title_ids = IDS_SYNC_CONFIRMATION_UNITY_TITLE; confirm_button_ids = IDS_SYNC_CONFIRMATION_DICE_CONFIRM_BUTTON_LABEL; undo_button_ids = IDS_SYNC_CONFIRMATION_DICE_UNDO_BUTTON_LABEL; } else { @@ -66,21 +68,22 @@ SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui) source->AddBoolean("isSyncAllowed", is_sync_allowed); - source->AddLocalizedString("syncConfirmationChromeSyncTitle", - IDS_SYNC_CONFIRMATION_CHROME_SYNC_TITLE); - source->AddLocalizedString("syncConfirmationChromeSyncBody", - IDS_SYNC_CONFIRMATION_CHROME_SYNC_MESSAGE); - source->AddLocalizedString( - "syncConfirmationPersonalizeServicesTitle", - IDS_SYNC_CONFIRMATION_PERSONALIZE_SERVICES_TITLE); - source->AddLocalizedString("syncConfirmationPersonalizeServicesBody", - IDS_SYNC_CONFIRMATION_PERSONALIZE_SERVICES_BODY); - source->AddLocalizedString("syncConfirmationSyncSettingsLinkBody", - IDS_SYNC_CONFIRMATION_SYNC_SETTINGS_LINK_BODY); - source->AddLocalizedString("syncDisabledConfirmationDetails", - IDS_SYNC_DISABLED_CONFIRMATION_DETAILS); - - title_ids = IDS_SYNC_CONFIRMATION_TITLE; + AddStringResource(source, "syncConfirmationChromeSyncTitle", + IDS_SYNC_CONFIRMATION_CHROME_SYNC_TITLE); + AddStringResource(source, "syncConfirmationChromeSyncBody", + IDS_SYNC_CONFIRMATION_CHROME_SYNC_MESSAGE); + AddStringResource(source, "syncConfirmationPersonalizeServicesTitle", + IDS_SYNC_CONFIRMATION_PERSONALIZE_SERVICES_TITLE); + AddStringResource(source, "syncConfirmationPersonalizeServicesBody", + IDS_SYNC_CONFIRMATION_PERSONALIZE_SERVICES_BODY); + AddStringResource(source, "syncConfirmationSyncSettingsLinkBody", + IDS_SYNC_CONFIRMATION_SYNC_SETTINGS_LINK_BODY); + AddStringResource(source, "syncDisabledConfirmationDetails", + IDS_SYNC_DISABLED_CONFIRMATION_DETAILS); + + title_ids = AccountConsistencyModeManager::IsDiceEnabledForProfile(profile) + ? IDS_SYNC_CONFIRMATION_DICE_TITLE + : IDS_SYNC_CONFIRMATION_TITLE; confirm_button_ids = IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL; undo_button_ids = IDS_SYNC_CONFIRMATION_UNDO_BUTTON_LABEL; if (!is_sync_allowed) { @@ -90,10 +93,13 @@ SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui) } } - source->AddLocalizedString("syncConfirmationTitle", title_ids); - source->AddLocalizedString("syncConfirmationConfirmLabel", - confirm_button_ids); - source->AddLocalizedString("syncConfirmationUndoLabel", undo_button_ids); + DCHECK_GE(title_ids, 0); + DCHECK_GE(confirm_button_ids, 0); + DCHECK_GE(undo_button_ids, 0); + + AddStringResource(source, "syncConfirmationTitle", title_ids); + AddStringResource(source, "syncConfirmationConfirmLabel", confirm_button_ids); + AddStringResource(source, "syncConfirmationUndoLabel", undo_button_ids); base::DictionaryValue strings; webui::SetLoadTimeDataDefaults( @@ -103,7 +109,27 @@ SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui) content::WebUIDataSource::Add(profile, source); } +SyncConfirmationUI::~SyncConfirmationUI() {} + void SyncConfirmationUI::InitializeMessageHandlerWithBrowser(Browser* browser) { - web_ui()->AddMessageHandler( - std::make_unique<SyncConfirmationHandler>(browser)); + web_ui()->AddMessageHandler(std::make_unique<SyncConfirmationHandler>( + browser, js_localized_string_to_ids_map_)); +} + +void SyncConfirmationUI::AddStringResource(content::WebUIDataSource* source, + const std::string& name, + int ids) { + source->AddLocalizedString(name, ids); + + // When the strings are passed to the HTML, the Unicode NBSP symbol (\u00A0) + // will be automatically replaced with " ". This change must be mirrored + // in the string-to-ids map. Note that "\u00A0" is actually two characters, + // so we must use base::ReplaceSubstrings* rather than base::ReplaceChars. + // TODO(msramek): Find a more elegant solution. + std::string sanitized_string = + base::UTF16ToUTF8(l10n_util::GetStringUTF16(ids)); + base::ReplaceSubstringsAfterOffset(&sanitized_string, 0, "\u00A0" /* NBSP */, + " "); + + js_localized_string_to_ids_map_[sanitized_string] = ids; } diff --git a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.h b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.h index 7d5b5431041..0763d791a89 100644 --- a/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.h +++ b/chromium/chrome/browser/ui/webui/signin/sync_confirmation_ui.h @@ -6,10 +6,16 @@ #define CHROME_BROWSER_UI_WEBUI_SIGNIN_SYNC_CONFIRMATION_UI_H_ #include <memory> +#include <string> +#include <unordered_map> #include "base/macros.h" #include "chrome/browser/ui/webui/signin/signin_web_dialog_ui.h" +namespace content { +class WebUIDataSource; +} + namespace ui { class WebUI; } @@ -21,11 +27,22 @@ class WebUI; class SyncConfirmationUI : public SigninWebDialogUI { public: explicit SyncConfirmationUI(content::WebUI* web_ui); - ~SyncConfirmationUI() override {} + ~SyncConfirmationUI() override; // SigninWebDialogUI: void InitializeMessageHandlerWithBrowser(Browser* browser) override; + private: + // Adds a string resource with the given GRD |ids| to the WebUI data |source| + // named as |name|. Also stores a reverse mapping from the localized version + // of the string to the |ids| in order to later pass it to + // SyncConfirmationHandler. + void AddStringResource(content::WebUIDataSource* source, + const std::string& name, + int ids); + + std::unordered_map<std::string, int> js_localized_string_to_ids_map_; + DISALLOW_COPY_AND_ASSIGN(SyncConfirmationUI); }; diff --git a/chromium/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc b/chromium/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc index dff00446d93..8642979e27a 100644 --- a/chromium/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc +++ b/chromium/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc @@ -51,7 +51,6 @@ #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "components/prefs/pref_service.h" -#include "components/proximity_auth/screenlock_bridge.h" #include "components/signin/core/account_id/account_id.h" #include "components/signin/core/browser/profile_management_switches.h" #include "components/strings/grit/components_strings.h" @@ -92,7 +91,6 @@ const char kJsApiUserManagerAuthLaunchUser[] = "authenticatedLaunchUser"; const char kJsApiUserManagerLaunchGuest[] = "launchGuest"; const char kJsApiUserManagerLaunchUser[] = "launchUser"; const char kJsApiUserManagerRemoveUser[] = "removeUser"; -const char kJsApiUserManagerAttemptUnlock[] = "attemptUnlock"; const char kJsApiUserManagerLogRemoveUserWarningShown[] = "logRemoveUserWarningShown"; const char kJsApiUserManagerRemoveUserWarningLoadStats[] = @@ -102,9 +100,6 @@ const char kJsApiUserManagerAreAllProfilesLocked[] = const size_t kAvatarIconSize = 180; const int kMaxOAuthRetries = 3; -void HandleAndDoNothing(const base::ListValue* args) { -} - std::string GetAvatarImage(const ProfileAttributesEntry* entry) { bool is_gaia_picture = entry->IsUsingGAIAPicture() && entry->GetGAIAPicture() != nullptr; @@ -123,17 +118,6 @@ std::string GetAvatarImage(const ProfileAttributesEntry* entry) { return webui::GetBitmapDataUrl(resized_image.AsBitmap()); } -extensions::ScreenlockPrivateEventRouter* GetScreenlockRouter( - const std::string& email) { - base::FilePath path = - profiles::GetPathOfProfileWithEmail(g_browser_process->profile_manager(), - email); - Profile* profile = g_browser_process->profile_manager() - ->GetProfileByPath(path); - return extensions::ScreenlockPrivateEventRouter::GetFactoryInstance()->Get( - profile); -} - bool IsGuestModeEnabled() { PrefService* service = g_browser_process->local_state(); DCHECK(service); @@ -537,7 +521,6 @@ void UserManagerScreenHandler::HandleRemoveUser(const base::ListValue* args) { // The callback is run if the only profile has been deleted, and a new // profile has been created to replace it. webui::DeleteProfileAtPath(profile_path, - web_ui(), ProfileMetrics::DELETE_PROFILE_USER_MANAGER); } @@ -598,14 +581,6 @@ void UserManagerScreenHandler::HandleLaunchUser(const base::ListValue* args) { ProfileMetrics::SWITCH_PROFILE_MANAGER); } -void UserManagerScreenHandler::HandleAttemptUnlock( - const base::ListValue* args) { - std::string email; - CHECK(args->GetString(0, &email)); - GetScreenlockRouter(email) - ->OnAuthAttempted(GetAuthType(AccountId::FromUserEmail(email)), ""); -} - void UserManagerScreenHandler::HandleHardlockUserPod( const base::ListValue* args) { std::string email; @@ -712,9 +687,6 @@ void UserManagerScreenHandler::RegisterMessages() { web_ui()->RegisterMessageCallback(kJsApiUserManagerRemoveUser, base::Bind(&UserManagerScreenHandler::HandleRemoveUser, base::Unretained(this))); - web_ui()->RegisterMessageCallback(kJsApiUserManagerAttemptUnlock, - base::Bind(&UserManagerScreenHandler::HandleAttemptUnlock, - base::Unretained(this))); web_ui()->RegisterMessageCallback(kJsApiUserManagerLogRemoveUserWarningShown, base::Bind(&HandleLogRemoveUserWarningShown)); web_ui()->RegisterMessageCallback(kJsApiUserManagerRemoveUserWarningLoadStats, @@ -725,21 +697,18 @@ void UserManagerScreenHandler::RegisterMessages() { base::Bind(&UserManagerScreenHandler::HandleAreAllProfilesLocked, base::Unretained(this))); - const content::WebUI::MessageCallback& kDoNothingCallback = - base::Bind(&HandleAndDoNothing); - // Unused callbacks from screen_account_picker.js - web_ui()->RegisterMessageCallback("accountPickerReady", kDoNothingCallback); - web_ui()->RegisterMessageCallback("loginUIStateChanged", kDoNothingCallback); - web_ui()->RegisterMessageCallback("hideCaptivePortal", kDoNothingCallback); - web_ui()->RegisterMessageCallback("getTabletModeState", kDoNothingCallback); + web_ui()->RegisterMessageCallback("accountPickerReady", base::DoNothing()); + web_ui()->RegisterMessageCallback("loginUIStateChanged", base::DoNothing()); + web_ui()->RegisterMessageCallback("hideCaptivePortal", base::DoNothing()); + web_ui()->RegisterMessageCallback("getTabletModeState", base::DoNothing()); // Unused callbacks from display_manager.js - web_ui()->RegisterMessageCallback("showAddUser", kDoNothingCallback); - web_ui()->RegisterMessageCallback("updateCurrentScreen", kDoNothingCallback); - web_ui()->RegisterMessageCallback("loginVisible", kDoNothingCallback); + web_ui()->RegisterMessageCallback("showAddUser", base::DoNothing()); + web_ui()->RegisterMessageCallback("updateCurrentScreen", base::DoNothing()); + web_ui()->RegisterMessageCallback("loginVisible", base::DoNothing()); // Unused callbacks from user_pod_row.js - web_ui()->RegisterMessageCallback("focusPod", kDoNothingCallback); - web_ui()->RegisterMessageCallback("noPodFocused", kDoNothingCallback); + web_ui()->RegisterMessageCallback("focusPod", base::DoNothing()); + web_ui()->RegisterMessageCallback("noPodFocused", base::DoNothing()); } void UserManagerScreenHandler::GetLocalizedValues( diff --git a/chromium/chrome/browser/ui/webui/signin_internals_ui.cc b/chromium/chrome/browser/ui/webui/signin_internals_ui.cc index e377c6f7767..b94b04d84b6 100644 --- a/chromium/chrome/browser/ui/webui/signin_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/signin_internals_ui.cc @@ -12,13 +12,10 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/about_signin_internals_factory.h" #include "chrome/browser/signin/gaia_cookie_manager_service_factory.h" -#include "chrome/browser/ui/webui/signin/signin_dice_internals_handler.h" #include "chrome/common/url_constants.h" #include "components/grit/components_resources.h" #include "components/signin/core/browser/about_signin_internals.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" -#include "components/signin/core/browser/profile_management_switches.h" -#include "components/signin/core/browser/signin_features.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" @@ -46,12 +43,6 @@ SignInInternalsUI::SignInInternalsUI(content::WebUI* web_ui) AboutSigninInternalsFactory::GetForProfile(profile); if (about_signin_internals) about_signin_internals->AddSigninObserver(this); -#if BUILDFLAG(ENABLE_DICE_SUPPORT) - if (signin::IsDiceEnabledForProfile(profile->GetPrefs())) { - web_ui->AddMessageHandler( - std::make_unique<SigninDiceInternalsHandler>(profile)); - } -#endif } } diff --git a/chromium/chrome/browser/ui/webui/site_settings_helper.cc b/chromium/chrome/browser/ui/webui/site_settings_helper.cc index 4f4d73d167d..cf47f689458 100644 --- a/chromium/chrome/browser/ui/webui/site_settings_helper.cc +++ b/chromium/chrome/browser/ui/webui/site_settings_helper.cc @@ -69,11 +69,13 @@ const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = { {CONTENT_SETTINGS_TYPE_PPAPI_BROKER, "ppapi-broker"}, {CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS, "multiple-automatic-downloads"}, {CONTENT_SETTINGS_TYPE_MIDI_SYSEX, "midi-sysex"}, - {CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER, "protectedContent"}, + {CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER, "protected-content"}, {CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC, "background-sync"}, {CONTENT_SETTINGS_TYPE_ADS, "ads"}, {CONTENT_SETTINGS_TYPE_SOUND, "sound"}, {CONTENT_SETTINGS_TYPE_CLIPBOARD_READ, "clipboard"}, + {CONTENT_SETTINGS_TYPE_SENSORS, "sensors"}, + {CONTENT_SETTINGS_TYPE_PAYMENT_HANDLER, "payment-handler"}, // Add new content settings here if a corresponding Javascript string // representation for it is not required. Note some exceptions, such as @@ -96,9 +98,9 @@ const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = { {CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, nullptr}, {CONTENT_SETTINGS_TYPE_MEDIA_ENGAGEMENT, nullptr}, {CONTENT_SETTINGS_TYPE_CLIENT_HINTS, nullptr}, - {CONTENT_SETTINGS_TYPE_SENSORS, nullptr}, {CONTENT_SETTINGS_TYPE_ACCESSIBILITY_EVENTS, nullptr}, {CONTENT_SETTINGS_TYPE_CLIPBOARD_WRITE, nullptr}, + {CONTENT_SETTINGS_TYPE_PLUGINS_DATA, nullptr}, }; static_assert(arraysize(kContentSettingsTypeGroupNames) == // ContentSettingsType starts at -1, so add 1 here. diff --git a/chromium/chrome/browser/ui/webui/snippets_internals_message_handler.cc b/chromium/chrome/browser/ui/webui/snippets_internals_message_handler.cc index 206412cb5dd..52527a8d360 100644 --- a/chromium/chrome/browser/ui/webui/snippets_internals_message_handler.cc +++ b/chromium/chrome/browser/ui/webui/snippets_internals_message_handler.cc @@ -21,9 +21,9 @@ #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/time/time.h" +#include "base/time/time_to_iso8601.h" #include "base/values.h" #include "chrome/browser/android/chrome_feature_list.h" #include "chrome/browser/android/ntp/android_content_suggestions_notifier.h" @@ -162,15 +162,6 @@ std::set<variations::VariationID> SnippetsExperiments() { return result; } -std::string TimeToJSONTimeString(const base::Time time) { - base::Time::Exploded exploded; - time.UTCExplode(&exploded); - return base::StringPrintf( - "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month, - exploded.day_of_month, exploded.hour, exploded.minute, exploded.second, - exploded.millisecond); -} - ntp_snippets::BreakingNewsListener* GetBreakingNewsListener( ntp_snippets::ContentSuggestionsService* service) { DCHECK(service); @@ -331,6 +322,7 @@ void SnippetsInternalsMessageHandler::HandleDownload( DCHECK_EQ(0u, args->GetSize()); SendString("remote-status", std::string()); + SendString("remote-authenticated", std::string()); if (!remote_suggestions_provider_) { return; @@ -611,6 +603,12 @@ void SnippetsInternalsMessageHandler::SendContentSuggestions() { ->GetLastStatusForDebugging(); if (!status.empty()) { SendString("remote-status", "Finished: " + status); + SendString( + "remote-authenticated", + remote_suggestions_provider_->suggestions_fetcher_for_debugging() + ->WasLastFetchAuthenticatedForDebugging() + ? "Authenticated" + : "Non-authenticated"); } } @@ -664,8 +662,8 @@ void SnippetsInternalsMessageHandler::PushDummySuggestion() { const base::Time now = base::Time::Now(); json = base::StringPrintf( json.c_str(), base::UTF16ToUTF8(base::TimeFormatTimeOfDay(now)).c_str(), - TimeToJSONTimeString(now).c_str(), - TimeToJSONTimeString(now + base::TimeDelta::FromMinutes(60)).c_str()); + base::TimeToISO8601(now).c_str(), + base::TimeToISO8601(now + base::TimeDelta::FromMinutes(60)).c_str()); gcm::IncomingMessage message; message.data["payload"] = json; diff --git a/chromium/chrome/browser/ui/webui/theme_source.cc b/chromium/chrome/browser/ui/webui/theme_source.cc index 1aa3453da8a..f3681500ad5 100644 --- a/chromium/chrome/browser/ui/webui/theme_source.cc +++ b/chromium/chrome/browser/ui/webui/theme_source.cc @@ -117,6 +117,7 @@ void ThemeSource::StartDataRequest( case version_info::Channel::BETA: case version_info::Channel::STABLE: NOTREACHED(); + FALLTHROUGH; #endif case version_info::Channel::UNKNOWN: resource_id = IDR_PRODUCT_LOGO_32; diff --git a/chromium/chrome/browser/ui/webui/usb_internals/BUILD.gn b/chromium/chrome/browser/ui/webui/usb_internals/BUILD.gn index 4d0059e3ae2..1ed96b376e7 100644 --- a/chromium/chrome/browser/ui/webui/usb_internals/BUILD.gn +++ b/chromium/chrome/browser/ui/webui/usb_internals/BUILD.gn @@ -10,7 +10,7 @@ mojom("mojo_bindings") { ] public_deps = [ - "//url/mojo:url_mojom_gurl", - "//url/mojo:url_mojom_origin", + "//url/mojom:url_mojom_gurl", + "//url/mojom:url_mojom_origin", ] } diff --git a/chromium/chrome/browser/ui/webui/usb_internals/usb_internals.mojom b/chromium/chrome/browser/ui/webui/usb_internals/usb_internals.mojom index 408c09dfdc6..37ec5de0c3a 100644 --- a/chromium/chrome/browser/ui/webui/usb_internals/usb_internals.mojom +++ b/chromium/chrome/browser/ui/webui/usb_internals/usb_internals.mojom @@ -4,8 +4,8 @@ module mojom; -import "url/mojo/origin.mojom"; -import "url/mojo/url.mojom"; +import "url/mojom/origin.mojom"; +import "url/mojom/url.mojom"; struct TestDeviceInfo { string guid; diff --git a/chromium/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc b/chromium/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc index 0de8441ea74..36785e19586 100644 --- a/chromium/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc +++ b/chromium/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc @@ -20,8 +20,8 @@ UsbInternalsUI::UsbInternalsUI(content::WebUI* web_ui) source->AddResourcePath( "chrome/browser/ui/webui/usb_internals/usb_internals.mojom.js", IDR_USB_INTERNALS_MOJO_JS); - source->AddResourcePath("url/mojo/origin.mojom.js", IDR_ORIGIN_MOJO_JS); - source->AddResourcePath("url/mojo/url.mojom.js", IDR_URL_MOJO_JS); + source->AddResourcePath("url/mojom/origin.mojom.js", IDR_ORIGIN_MOJO_JS); + source->AddResourcePath("url/mojom/url.mojom.js", IDR_URL_MOJO_JS); source->SetDefaultResource(IDR_USB_INTERNALS_HTML); source->UseGzip(); diff --git a/chromium/chrome/browser/ui/webui/version_handler.cc b/chromium/chrome/browser/ui/webui/version_handler.cc index ebfc7d09590..9bd498db4c0 100644 --- a/chromium/chrome/browser/ui/webui/version_handler.cc +++ b/chromium/chrome/browser/ui/webui/version_handler.cc @@ -23,6 +23,7 @@ #include "components/version_ui/version_ui_constants.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/plugin_service.h" +#include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "content/public/common/content_constants.h" #include "ppapi/features/features.h" @@ -53,9 +54,7 @@ void GetFilePaths(const base::FilePath& profile_path, } // namespace -VersionHandler::VersionHandler() - : weak_ptr_factory_(this) { -} +VersionHandler::VersionHandler() : weak_ptr_factory_(this) {} VersionHandler::~VersionHandler() { } @@ -68,6 +67,7 @@ void VersionHandler::RegisterMessages() { } void VersionHandler::HandleRequestVersionInfo(const base::ListValue* args) { + AllowJavascript(); #if BUILDFLAG(ENABLE_PLUGINS) // The Flash version information is needed in the response, so make sure // the plugins are loaded. @@ -90,8 +90,14 @@ void VersionHandler::HandleRequestVersionInfo(const base::ListValue* args) { base::Owned(exec_path_buffer), base::Owned(profile_path_buffer))); // Respond with the variations info immediately. - web_ui()->CallJavascriptFunctionUnsafe(version_ui::kReturnVariationInfo, - *version_ui::GetVariationsList()); + CallJavascriptFunction(version_ui::kReturnVariationInfo, + *version_ui::GetVariationsList()); + GURL current_url = web_ui()->GetWebContents()->GetVisibleURL(); + if (current_url.query().find(version_ui::kVariationsShowCmdQuery) != + std::string::npos) { + CallJavascriptFunction(version_ui::kReturnVariationCmd, + version_ui::GetVariationsCommandLineAsValue()); + } } void VersionHandler::OnGotFilePaths(base::string16* executable_path_data, @@ -100,8 +106,7 @@ void VersionHandler::OnGotFilePaths(base::string16* executable_path_data, base::Value exec_path(*executable_path_data); base::Value profile_path(*profile_path_data); - web_ui()->CallJavascriptFunctionUnsafe(version_ui::kReturnFilePaths, - exec_path, profile_path); + CallJavascriptFunction(version_ui::kReturnFilePaths, exec_path, profile_path); } #if BUILDFLAG(ENABLE_PLUGINS) @@ -128,6 +133,6 @@ void VersionHandler::OnGotPlugins( base::Value arg(flash_version_and_path); - web_ui()->CallJavascriptFunctionUnsafe(version_ui::kReturnFlashVersion, arg); + CallJavascriptFunction(version_ui::kReturnFlashVersion, arg); } #endif // BUILDFLAG(ENABLE_PLUGINS) diff --git a/chromium/chrome/browser/ui/webui/version_ui.cc b/chromium/chrome/browser/ui/webui/version_ui.cc index 456e94e4f58..4d83440102f 100644 --- a/chromium/chrome/browser/ui/webui/version_ui.cc +++ b/chromium/chrome/browser/ui/webui/version_ui.cc @@ -90,7 +90,8 @@ WebUIDataSource* CreateVersionUIDataSource() { html_source->AddString(version_ui::kProfilePath, std::string()); html_source->AddLocalizedString(version_ui::kVariationsName, IDS_VERSION_UI_VARIATIONS); - + html_source->AddLocalizedString(version_ui::kVariationsCmdName, + IDS_VERSION_UI_VARIATIONS_CMD); #if defined(OS_CHROMEOS) html_source->AddLocalizedString(version_ui::kARC, IDS_ARC_LABEL); html_source->AddLocalizedString(version_ui::kPlatform, IDS_PLATFORM_LABEL); diff --git a/chromium/chrome/browser/ui/webui/web_ui_test_handler.cc b/chromium/chrome/browser/ui/webui/web_ui_test_handler.cc index 9f070d576ed..8ae8cd38394 100644 --- a/chromium/chrome/browser/ui/webui/web_ui_test_handler.cc +++ b/chromium/chrome/browser/ui/webui/web_ui_test_handler.cc @@ -21,7 +21,7 @@ #include "content/public/browser/web_ui.h" #include "content/public/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/WebKit/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/WebKit/public/common/associated_interfaces/associated_interface_provider.h" using content::RenderViewHost; diff --git a/chromium/chrome/browser/ui/webui/webui_browsertest.cc b/chromium/chrome/browser/ui/webui/webui_browsertest.cc index 70289d0a452..95e722ad1f2 100644 --- a/chromium/chrome/browser/ui/webui/webui_browsertest.cc +++ b/chromium/chrome/browser/ui/webui/webui_browsertest.cc @@ -3,14 +3,18 @@ // found in the LICENSE file. #include "base/command_line.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.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/browser/ui/webui/chrome_web_ui_controller_factory.h" +#include "chrome/common/chrome_features.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 "components/strings/grit/components_strings.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -18,6 +22,7 @@ #include "content/public/common/content_switches.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_utils.h" +#include "ui/base/l10n/l10n_util.h" namespace { @@ -64,7 +69,7 @@ IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, ForceSwapOnDifferenteWebUITypes) { web_contents->GetMainFrame()->GetProcess()->GetID())); } -IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, InPageNavigationsAndReload) { +IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, SameDocumentNavigationsAndReload) { ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUITermsURL)); content::WebUIMessageHandler* test_handler = new TestWebUIMessageHandler; @@ -93,3 +98,18 @@ IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, InPageNavigationsAndReload) { // Verify that after a reload, the test handler has been disallowed. EXPECT_FALSE(test_handler->IsJavascriptAllowed()); } + +// Tests that navigating to chrome://connection-help displays the proper help +// page. +IN_PROC_BROWSER_TEST_F(WebUIImplBrowserTest, ConnectionHelpUI) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kBundledConnectionHelpFeature); + ui_test_utils::NavigateToURL(browser(), + GURL(chrome::kChromeUIConnectionHelpURL)); + content::WaitForLoadStop( + browser()->tab_strip_model()->GetActiveWebContents()); + base::string16 tab_title; + ui_test_utils::GetCurrentTabTitle(browser(), &tab_title); + EXPECT_EQ(base::UTF16ToUTF8(tab_title), + l10n_util::GetStringUTF8(IDS_CONNECTION_HELP_TITLE)); +} diff --git a/chromium/chrome/browser/ui/webui/welcome_handler.cc b/chromium/chrome/browser/ui/webui/welcome_handler.cc index c764d29a70e..d989be56940 100644 --- a/chromium/chrome/browser/ui/webui/welcome_handler.cc +++ b/chromium/chrome/browser/ui/webui/welcome_handler.cc @@ -23,12 +23,18 @@ WelcomeHandler::WelcomeHandler(content::WebUI* web_ui) login_ui_service_(LoginUIServiceFactory::GetForProfile(profile_)), result_(WelcomeResult::DEFAULT) { login_ui_service_->AddObserver(this); - base::RecordAction( - base::UserMetricsAction("Signin_Impression_FromStartPage")); } WelcomeHandler::~WelcomeHandler() { login_ui_service_->RemoveObserver(this); + + // We log that an impression occurred at destruct-time. This can't be done at + // construct-time on some platforms because this page is shown immediately + // after a new installation of Chrome and loads while the user is deciding + // whether or not to opt in to logging. + base::RecordAction( + base::UserMetricsAction("Signin_Impression_FromStartPage")); + UMA_HISTOGRAM_ENUMERATION("Welcome.SignInPromptResult", result_, WelcomeResult::WELCOME_RESULT_MAX); } diff --git a/chromium/chrome/browser/vr/BUILD.gn b/chromium/chrome/browser/vr/BUILD.gn index b3e3d513722..ed0b63c8375 100644 --- a/chromium/chrome/browser/vr/BUILD.gn +++ b/chromium/chrome/browser/vr/BUILD.gn @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/buildflag_header.gni") +import("//chrome/browser/vr/features.gni") import("//chrome/common/features.gni") import("//device/vr/features/features.gni") import("//testing/test.gni") @@ -13,10 +15,15 @@ if (is_android) { assert(enable_vr) +buildflag_header("vr_build_features") { + header = "vr_features.h" + flags = [ "USE_VR_ASSETS_COMPONENT=$use_vr_assets_component" ] +} + static_library("vr_common") { sources = [ - "animation_player.cc", - "animation_player.h", + "animation.cc", + "animation.h", "assets_component_update_status.h", "assets_load_status.h", "assets_loader.cc", @@ -24,17 +31,12 @@ static_library("vr_common") { "browser_ui_interface.h", "content_input_delegate.cc", "content_input_delegate.h", - "controller_mesh.h", "cpu_surface_provider.cc", "cpu_surface_provider.h", "databinding/binding.h", "databinding/binding_base.h", "databinding/vector_binding.h", "databinding/vector_element_binding.h", - "elements/audio_permission_prompt.cc", - "elements/audio_permission_prompt.h", - "elements/audio_permission_prompt_texture.cc", - "elements/audio_permission_prompt_texture.h", "elements/button.cc", "elements/button.h", "elements/content_element.cc", @@ -68,6 +70,12 @@ static_library("vr_common") { "elements/linear_layout.h", "elements/omnibox_formatting.cc", "elements/omnibox_formatting.h", + "elements/omnibox_text_field.cc", + "elements/omnibox_text_field.h", + "elements/prompt.cc", + "elements/prompt.h", + "elements/prompt_texture.cc", + "elements/prompt_texture.h", "elements/rect.cc", "elements/rect.h", "elements/render_text_wrapper.cc", @@ -122,10 +130,6 @@ static_library("vr_common") { "fps_meter.h", "ganesh_surface_provider.cc", "ganesh_surface_provider.h", - "gltf_asset.cc", - "gltf_asset.h", - "gltf_parser.cc", - "gltf_parser.h", "keyboard_delegate.h", "keyboard_ui_interface.h", "macros.h", @@ -135,16 +139,18 @@ static_library("vr_common") { "model/assets.cc", "model/assets.h", "model/camera_model.h", + "model/capturing_state_model.h", "model/color_scheme.cc", "model/color_scheme.h", + "model/controller_model.cc", "model/controller_model.h", "model/modal_prompt_type.cc", "model/modal_prompt_type.h", "model/model.cc", "model/model.h", + "model/native_ui_model.h", "model/omnibox_suggestions.cc", "model/omnibox_suggestions.h", - "model/permissions_model.h", "model/reticle_model.h", "model/speech_recognition_model.h", "model/text_input_info.cc", @@ -181,6 +187,8 @@ static_library("vr_common") { "speech_recognizer.h", "target_property.cc", "target_property.h", + "text_edit_action.cc", + "text_edit_action.h", "text_input_delegate.cc", "text_input_delegate.h", "toolbar_helper.cc", @@ -201,23 +209,17 @@ static_library("vr_common") { "ui_scene_creator.cc", "ui_scene_creator.h", "ui_unsupported_mode.h", + "vr_features.h", "vr_gl_util.cc", "vr_gl_util.h", - "web_contents_event_forwarder.cc", - "web_contents_event_forwarder.h", ] public_deps = [ "//ui/gl", ] - if (enable_gvr_services) { - sources += [ "controller_mesh.cc" ] - - public_deps += [ "//chrome/browser/resources:vr_shell_resources" ] - } - deps = [ + ":vr_build_features", "//base", "//cc/animation", "//cc/paint", @@ -232,8 +234,8 @@ static_library("vr_common") { "//components/vector_icons", "//content/public/browser", "//content/public/common", - "//device/vr:mojo_bindings", - "//device/vr:vr", + "//device/vr", + "//device/vr/public/mojom", "//net", "//skia", "//ui/base", @@ -244,19 +246,22 @@ static_library("vr_common") { test("vr_common_unittests") { sources = [ - "animation_player_unittest.cc", + "animation_unittest.cc", "databinding/binding_unittest.cc", "databinding/vector_binding_unittest.cc", "elements/button_unittest.cc", + "elements/content_element_unittest.cc", "elements/disc_button_unittest.cc", "elements/exit_prompt_unittest.cc", "elements/linear_layout_unittest.cc", "elements/omnibox_formatting_unittest.cc", + "elements/omnibox_text_field_unittest.cc", "elements/rect_unittest.cc", "elements/repositioner_unittest.cc", "elements/scaled_depth_adjuster_unittest.cc", "elements/shadow_unittest.cc", "elements/spinner_unittest.cc", + "elements/text_input_unittest.cc", "elements/text_unittest.cc", "elements/throbber_unittest.cc", "elements/transient_element_unittest.cc", @@ -266,7 +271,7 @@ test("vr_common_unittests") { "elements/vector_icon_unittest.cc", "elements/viewport_aware_root_unittest.cc", "fps_meter_unittest.cc", - "gltf_parser_unittest.cc", + "model/text_input_info_unittest.cc", "pose_util_unittest.cc", "service/vr_device_manager_unittest.cc", "sliding_average_unittest.cc", @@ -278,7 +283,6 @@ test("vr_common_unittests") { "test/run_all_unittests.cc", "test/ui_test.cc", "test/ui_test.h", - "text_input_unittest.cc", "ui_input_manager_unittest.cc", "ui_scene_unittest.cc", "ui_unittest.cc", @@ -355,20 +359,18 @@ source_set("vr_test_support") { "test/mock_browser_ui_interface.h", "test/mock_content_input_delegate.cc", "test/mock_content_input_delegate.h", + "test/mock_keyboard_delegate.cc", + "test/mock_keyboard_delegate.h", "test/mock_render_text.cc", "test/mock_render_text.h", + "test/mock_text_input_delegate.cc", + "test/mock_text_input_delegate.h", "test/mock_ui_browser_interface.cc", "test/mock_ui_browser_interface.h", "test/vr_test_suite.cc", "test/vr_test_suite.h", ] - if (!enable_gvr_services) { - # For testing, we add back controller_mesh.cc on platforms where it isn't - # included by vr_common. - sources += [ "controller_mesh.cc" ] - } - public_deps = [ ":vr_common", ":vr_test_pak", @@ -380,7 +382,7 @@ source_set("vr_test_support") { "//mojo/edk/system", "//skia", "//testing/gtest", - "//ui/accessibility:ax_gen", + "//ui/accessibility:ax_enums_mojo", "//ui/gfx:test_support", # TODO(mthiesse, crbug.com/769373): Remove dependency on device/vr:fakes. @@ -391,7 +393,6 @@ source_set("vr_test_support") { ] data = [ - "test/data/", "$root_out_dir/vr_test.pak", ] } @@ -415,7 +416,6 @@ source_set("vr_gl_test_support") { repack("vr_test_pak") { sources = [ "$root_gen_dir/chrome/generated_resources_en-US.pak", - "$root_gen_dir/chrome/vr_shell_resources.pak", "$root_gen_dir/components/components_resources.pak", "$root_gen_dir/components/strings/components_strings_en-US.pak", ] @@ -424,7 +424,6 @@ repack("vr_test_pak") { deps = [ "//chrome/app:generated_resources", - "//chrome/browser/resources:vr_shell_resources", "//components/resources:components_resources", "//components/strings", ] diff --git a/chromium/chrome/browser/vr/testapp/BUILD.gn b/chromium/chrome/browser/vr/testapp/BUILD.gn index 0aedbbcac45..8a28aac17cc 100644 --- a/chromium/chrome/browser/vr/testapp/BUILD.gn +++ b/chromium/chrome/browser/vr/testapp/BUILD.gn @@ -48,7 +48,7 @@ process_version("assets_component_version_header") { grit("vr_testapp_resources") { source = "vr_testapp_resources.grd" - defines = [ "background_image_available=$is_chrome_branded" ] + defines = [ "is_chrome_branded=$is_chrome_branded" ] outputs = [ "grit/vr_testapp_resources.h", "vr_testapp_resources.pak", diff --git a/chromium/chrome/browser/vr/testapp/vr_testapp_resources.grd b/chromium/chrome/browser/vr/testapp/vr_testapp_resources.grd index 0a01cb0fed4..29935f29ee4 100644 --- a/chromium/chrome/browser/vr/testapp/vr_testapp_resources.grd +++ b/chromium/chrome/browser/vr/testapp/vr_testapp_resources.grd @@ -8,12 +8,20 @@ </outputs> <release seq="1"> <includes> - <if expr="background_image_available"> - <include name="IDR_VR_BACKGROUND_IMAGE" file="../../resources/vr/assets/background.png" type="BINDATA" /> - <include name="IDR_VR_NORMAL_GRADIENT_IMAGE" file="../../resources/vr/assets/normal_gradient.png" type="BINDATA" /> - <include name="IDR_VR_INCOGNITO_GRADIENT_IMAGE" file="../../resources/vr/assets/incognito_gradient.png" type="BINDATA" /> - <include name="IDR_VR_FULLSCREEN_GRADIENT_IMAGE" file="../../resources/vr/assets/fullscreen_gradient.png" type="BINDATA" /> - </if> + <if expr="is_chrome_branded"> + <then> + <include name="IDR_VR_BACKGROUND_IMAGE" file="../../resources/vr/assets/google_chrome/background.png" type="BINDATA" /> + <include name="IDR_VR_NORMAL_GRADIENT_IMAGE" file="../../resources/vr/assets/google_chrome/normal_gradient.png" type="BINDATA" /> + <include name="IDR_VR_INCOGNITO_GRADIENT_IMAGE" file="../../resources/vr/assets/google_chrome/incognito_gradient.png" type="BINDATA" /> + <include name="IDR_VR_FULLSCREEN_GRADIENT_IMAGE" file="../../resources/vr/assets/google_chrome/fullscreen_gradient.png" type="BINDATA" /> + </then> + <else> + <include name="IDR_VR_BACKGROUND_IMAGE" file="../../resources/vr/assets/chromium/background.png" type="BINDATA" /> + <include name="IDR_VR_NORMAL_GRADIENT_IMAGE" file="../../resources/vr/assets/chromium/normal_gradient.png" type="BINDATA" /> + <include name="IDR_VR_INCOGNITO_GRADIENT_IMAGE" file="../../resources/vr/assets/chromium/incognito_gradient.png" type="BINDATA" /> + <include name="IDR_VR_FULLSCREEN_GRADIENT_IMAGE" file="../../resources/vr/assets/chromium/fullscreen_gradient.png" type="BINDATA" /> + </else> + </if> </includes> </release> </grit> diff --git a/chromium/chrome/browser/vr/vector_icons/BUILD.gn b/chromium/chrome/browser/vr/vector_icons/BUILD.gn index 55685ab4890..5414141a212 100644 --- a/chromium/chrome/browser/vr/vector_icons/BUILD.gn +++ b/chromium/chrome/browser/vr/vector_icons/BUILD.gn @@ -8,8 +8,11 @@ aggregate_vector_icons("vr_vector_icons") { icon_directory = "." icons = [ - "sad_tab.icon", "file_download_done.icon", + "reposition.icon", + "sad_tab.icon", + "daydream_controller_home_button.icon", + "daydream_controller_app_button.icon", ] } |