diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-09-29 16:16:15 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-11-09 10:04:06 +0000 |
commit | a95a7417ad456115a1ef2da4bb8320531c0821f1 (patch) | |
tree | edcd59279e486d2fd4a8f88a7ed025bcf925c6e6 /chromium/chrome/browser/extensions | |
parent | 33fc33aa94d4add0878ec30dc818e34e1dd3cc2a (diff) |
BASELINE: Update Chromium to 106.0.5249.126
Change-Id: Ib0bb21c437a7d1686e21c33f2d329f2ac425b7ab
Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/438936
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/chrome/browser/extensions')
280 files changed, 12320 insertions, 5999 deletions
diff --git a/chromium/chrome/browser/extensions/BUILD.gn b/chromium/chrome/browser/extensions/BUILD.gn index 98a0ac50412..400d719eb17 100644 --- a/chromium/chrome/browser/extensions/BUILD.gn +++ b/chromium/chrome/browser/extensions/BUILD.gn @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/chromebox_for_meetings/buildflags.gni") import("//build/config/chromeos/ui_mode.gni") import("//build/config/features.gni") import("//build/config/ozone.gni") @@ -141,6 +142,8 @@ static_library("extensions") { "api/downloads_internal/downloads_internal_api.h", "api/enterprise_hardware_platform/enterprise_hardware_platform_api.cc", "api/enterprise_hardware_platform/enterprise_hardware_platform_api.h", + "api/enterprise_reporting_private/conversion_utils.cc", + "api/enterprise_reporting_private/conversion_utils.h", "api/enterprise_reporting_private/enterprise_reporting_private_api.cc", "api/enterprise_reporting_private/enterprise_reporting_private_api.h", "api/extension_action/extension_action_api.cc", @@ -336,6 +339,10 @@ static_library("extensions") { "api/settings_private/settings_private_event_router.h", "api/settings_private/settings_private_event_router_factory.cc", "api/settings_private/settings_private_event_router_factory.h", + "api/side_panel/side_panel_api.cc", + "api/side_panel/side_panel_api.h", + "api/side_panel/side_panel_service.cc", + "api/side_panel/side_panel_service.h", "api/storage/managed_value_store_cache.cc", "api/storage/managed_value_store_cache.h", "api/storage/policy_value_store.cc", @@ -760,6 +767,7 @@ static_library("extensions") { "//base", "//build:branding_buildflags", "//build:chromeos_buildflags", + "//build/config/chromebox_for_meetings:buildflags", "//chrome:extra_resources", "//chrome:resources", "//chrome:strings", @@ -773,6 +781,7 @@ static_library("extensions") { "//chrome/browser:theme_properties", "//chrome/browser/browsing_data:constants", "//chrome/browser/devtools", + "//chrome/browser/favicon", "//chrome/browser/first_party_sets", "//chrome/browser/image_decoder", "//chrome/browser/media/router", @@ -784,8 +793,10 @@ static_library("extensions") { "//chrome/browser/resource_coordinator:mojo_bindings", "//chrome/browser/safe_browsing", "//chrome/browser/safe_browsing:metrics_collector", + "//chrome/browser/ui/tabs:tab_enums", "//chrome/browser/web_applications", "//components/cbor:cbor", + "//components/device_reauth", "//components/safe_browsing/content/browser", "//components/safe_browsing/core/browser:safe_browsing_metrics_collector", "//components/security_interstitials/content:security_interstitial_page", @@ -809,6 +820,8 @@ static_library("extensions") { "//components/content_settings/core/browser", "//components/cookie_config:cookie_config", "//components/crx_file", + "//components/device_signals/core/browser", + "//components/device_signals/core/common", "//components/dom_distiller/core", "//components/download/content/public", "//components/download/public/common:public", @@ -838,6 +851,7 @@ static_library("extensions") { "//components/password_manager/content/browser", "//components/password_manager/core/browser", "//components/password_manager/core/browser:affiliation", + "//components/password_manager/core/browser:import_results", "//components/password_manager/core/browser/leak_detection", "//components/payments/core", "//components/performance_manager", @@ -855,7 +869,7 @@ static_library("extensions") { "//components/safe_browsing/core/common:safe_browsing_prefs", "//components/safe_browsing/core/common/proto:csd_proto", "//components/search_engines", - "//components/services/app_service/public/mojom", + "//components/services/app_service/public/cpp:app_types", "//components/services/patch/content", "//components/services/unzip/content", "//components/services/unzip/public/cpp", @@ -951,6 +965,8 @@ static_library("extensions") { "api/system_indicator/system_indicator_manager_factory.cc", "api/system_indicator/system_indicator_manager_factory.h", ] + + deps += [ "//components/device_signals/core/common:features" ] } if (is_chromeos) { @@ -969,6 +985,10 @@ static_library("extensions") { "api/file_manager/file_selector.h", "api/file_manager/file_selector_impl.cc", "api/file_manager/file_selector_impl.h", + "api/file_system/consent_provider.cc", + "api/file_system/consent_provider.h", + "api/file_system/request_file_system_notification.cc", + "api/file_system/request_file_system_notification.h", "api/messaging/native_message_built_in_host.cc", "api/messaging/native_message_built_in_host.h", "api/messaging/native_message_echo_host.cc", @@ -982,8 +1002,18 @@ static_library("extensions") { "api/printing/print_job_controller.cc", "api/printing/print_job_controller.h", "api/shared_storage/shared_storage_private_api.h", + + # TODO(crbug.com/1340540): Find an appropriate spot for this. + "chromeos_browser_context_keyed_service_factories.cc", + "chromeos_browser_context_keyed_service_factories.h", "clipboard_extension_helper_chromeos.cc", "clipboard_extension_helper_chromeos.h", + "extension_keeplist_chromeos.cc", + "extension_keeplist_chromeos.h", + "system_display/display_info_provider_chromeos.cc", + "system_display/display_info_provider_chromeos.h", + "system_display/display_info_provider_utils.cc", + "system_display/display_info_provider_utils.h", "system_display/system_display_serialization.cc", "system_display/system_display_serialization.h", ] @@ -1000,13 +1030,16 @@ static_library("extensions") { ] deps += [ + # TODO(crbug.com/1340540): Find an appropriate spot for printing_metrics. + "//chrome/browser/chromeos/extensions/printing_metrics", "//chromeos/printing", "//printing/backend", ] } deps += [ "//chrome/browser/chromeos/extensions:constants", - "//chrome/browser/chromeos/extensions/login_screen", + + # TODO(crbug.com/1340540): Find an appropriate spot for vpn_provider. "//chrome/browser/chromeos/extensions/vpn_provider", "//chromeos/components/disks:prefs", "//chromeos/components/quick_answers/public/cpp:prefs", @@ -1015,6 +1048,7 @@ static_library("extensions") { "//chromeos/crosapi/cpp", "//chromeos/crosapi/mojom", "//chromeos/dbus/missive:missive", + "//chromeos/dbus/power", "//remoting/host/it2me:chrome_os_host", ] } else { @@ -1057,10 +1091,8 @@ static_library("extensions") { "api/file_handlers/non_native_file_system_delegate_chromeos.h", "api/file_manager/file_browser_handler_api_ash.cc", "api/file_manager/file_browser_handler_api_ash.h", - "api/file_system/consent_provider.cc", - "api/file_system/consent_provider.h", - "api/file_system/request_file_system_notification.cc", - "api/file_system/request_file_system_notification.h", + "api/file_system/chrome_file_system_delegate_ash.cc", + "api/file_system/chrome_file_system_delegate_ash.h", "api/image_writer_private/operation_chromeos.cc", "api/image_writer_private/removable_storage_provider_chromeos.cc", "api/input_ime/input_ime_api.cc", @@ -1083,8 +1115,8 @@ static_library("extensions") { "api/settings_private/generated_time_zone_pref_base.cc", "api/settings_private/generated_time_zone_pref_base.h", "api/shared_storage/shared_storage_private_api_ash.cc", - "api/terminal/crostini_startup_status.cc", - "api/terminal/crostini_startup_status.h", + "api/terminal/startup_status.cc", + "api/terminal/startup_status.h", "api/terminal/terminal_private_api.cc", "api/terminal/terminal_private_api.h", "api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc", @@ -1096,10 +1128,6 @@ static_library("extensions") { "extension_assets_manager_chromeos.h", "extension_garbage_collector_chromeos.cc", "extension_garbage_collector_chromeos.h", - "extension_keeplist_ash.cc", - "extension_keeplist_ash.h", - "system_display/display_info_provider_chromeos.cc", - "system_display/display_info_provider_chromeos.h", "updater/chromeos_extension_cache_delegate.cc", "updater/chromeos_extension_cache_delegate.h", "updater/extension_cache_impl.cc", @@ -1114,8 +1142,6 @@ static_library("extensions") { deps += [ "//ash", "//ash/components/arc", - "//ash/components/attestation", - "//ash/components/cryptohome", "//ash/components/disks", "//ash/components/enhanced_network_tts/mojom", "//ash/components/login/auth", @@ -1137,26 +1163,29 @@ static_library("extensions") { "//chrome/browser/devtools", "//chrome/browser/nearby_sharing/common", "//chrome/browser/ui/webui/settings/chromeos/constants:mojom", - "//chromeos/components/chromebox_for_meetings/buildflags", + "//chromeos/ash/components/attestation", + "//chromeos/ash/components/cryptohome", + "//chromeos/ash/components/dbus", + "//chromeos/ash/components/dbus/cicerone:cicerone", + "//chromeos/ash/components/dbus/cicerone:cicerone_proto", + "//chromeos/ash/components/dbus/cros_disks", + "//chromeos/ash/components/dbus/cryptohome", + "//chromeos/ash/components/dbus/image_burner", + "//chromeos/ash/components/dbus/update_engine", + "//chromeos/ash/components/network", + "//chromeos/ash/services/assistant/public/cpp", "//chromeos/components/remote_apps/mojom", - "//chromeos/dbus", - "//chromeos/dbus/cryptohome", - "//chromeos/dbus/image_burner", - "//chromeos/dbus/power", - "//chromeos/dbus/update_engine", - "//chromeos/dbus/util", "//chromeos/language/language_packs", "//chromeos/language/public/mojom", "//chromeos/login/login_state", - "//chromeos/network", "//chromeos/process_proxy", - "//chromeos/services/assistant/public/cpp", "//chromeos/services/machine_learning/public/cpp", "//chromeos/services/machine_learning/public/mojom", "//chromeos/services/media_perception/public/mojom", "//chromeos/services/tts/public/mojom", "//chromeos/system", "//chromeos/ui/base", + "//chromeos/version", "//components/constrained_window", "//components/crash/content/browser/error_reporting", "//components/drive", @@ -1176,6 +1205,10 @@ static_library("extensions") { "//ui/views", ] allow_circular_includes_from += [ "//chrome/browser/ash/crosapi" ] + + if (is_cfm) { + deps += [ "//chromeos/ash/components/chromebox_for_meetings" ] + } } if (is_chromeos_lacros) { @@ -1188,6 +1221,10 @@ static_library("extensions") { "api/file_browser_handler/file_browser_handler_flow_lacros.h", "api/file_manager/file_browser_handler_api_lacros.cc", "api/file_manager/file_browser_handler_api_lacros.h", + "api/file_system/chrome_file_system_delegate_lacros.cc", + "api/file_system/chrome_file_system_delegate_lacros.h", + "api/file_system/volume_list_provider_lacros.cc", + "api/file_system/volume_list_provider_lacros.h", "api/image_writer_private/image_writer_controller_lacros.cc", "api/image_writer_private/image_writer_controller_lacros.h", "api/image_writer_private/operation_nonchromeos.cc", @@ -1196,11 +1233,11 @@ static_library("extensions") { "api/quick_unlock_private/quick_unlock_private_api_lacros.cc", "api/quick_unlock_private/quick_unlock_private_api_lacros.h", "api/shared_storage/shared_storage_private_api_lacros.cc", + "api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.cc", + "api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h", "chrome_kiosk_delegate.cc", "preinstalled_apps.cc", "preinstalled_apps.h", - "system_display/display_info_provider_lacros.cc", - "system_display/display_info_provider_lacros.h", ] deps += [ "//chromeos/lacros", @@ -1210,8 +1247,6 @@ static_library("extensions") { "//components/enterprise/common/proto:connectors_proto", "//components/keep_alive_registry", ] - allow_circular_includes_from += - [ "//chrome/browser/chromeos/extensions/login_screen" ] } if (use_ozone) { @@ -1262,6 +1297,7 @@ static_library("extensions") { "system_display/display_info_provider_win.h", ] deps += [ + "//components/device_signals/core/common/win", "//third_party/iaccessible2", "//third_party/isimpledom", ] diff --git a/chromium/chrome/browser/extensions/api/DEPS b/chromium/chrome/browser/extensions/api/DEPS index c2f7fbe1a7b..aa705a34f93 100644 --- a/chromium/chrome/browser/extensions/api/DEPS +++ b/chromium/chrome/browser/extensions/api/DEPS @@ -2,7 +2,7 @@ include_rules = [ "+apps", "+components/live_caption", "+services/device/public", - + "+components/device_reauth", # Enable remote assistance on Chrome OS "+remoting/host", ] diff --git a/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc b/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc index 1815aa6b3ad..57db92301eb 100644 --- a/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc +++ b/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc @@ -93,8 +93,8 @@ void ActivityLogAPI::OnExtensionActivity(scoped_refptr<Action> activity) { value.Append(base::Value::FromUniquePtrValue(activity_arg.ToValue())); auto event = std::make_unique<Event>( events::ACTIVITY_LOG_PRIVATE_ON_EXTENSION_ACTIVITY, - activity_log_private::OnExtensionActivity::kEventName, - base::Value(std::move(value)).TakeListDeprecated(), browser_context_); + activity_log_private::OnExtensionActivity::kEventName, std::move(value), + browser_context_); EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event)); } diff --git a/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc index 107237f2721..db0ac5335c6 100644 --- a/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc @@ -28,9 +28,9 @@ using api::activity_log_private::ExtensionActivity; typedef testing::Test ActivityLogApiUnitTest; TEST_F(ActivityLogApiUnitTest, ConvertChromeApiAction) { - std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append("hello"); - args->Append("world"); + base::Value::List args; + args.Append("hello"); + args.Append("world"); scoped_refptr<Action> action(new Action(kExtensionId, base::Time::Now(), Action::ACTION_API_CALL, @@ -46,9 +46,9 @@ TEST_F(ActivityLogApiUnitTest, ConvertChromeApiAction) { } TEST_F(ActivityLogApiUnitTest, ConvertDomAction) { - std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append("hello"); - args->Append("world"); + base::Value::List args; + args.Append("hello"); + args.Append("world"); scoped_refptr<Action> action(new Action(kExtensionId, base::Time::Now(), Action::ACTION_DOM_ACCESS, @@ -57,10 +57,9 @@ TEST_F(ActivityLogApiUnitTest, ConvertDomAction) { action->set_args(std::move(args)); action->set_page_url(GURL("http://www.google.com")); action->set_page_title("Title"); - action->mutable_other()->SetIntKey(activity_log_constants::kActionDomVerb, - DomActionType::INSERTED); - action->mutable_other()->SetBoolKey(activity_log_constants::kActionPrerender, - false); + action->mutable_other().Set(activity_log_constants::kActionDomVerb, + DomActionType::INSERTED); + action->mutable_other().Set(activity_log_constants::kActionPrerender, false); ExtensionActivity result = action->ConvertToExtensionActivity(); ASSERT_EQ(kExtensionId, *(result.extension_id.get())); ASSERT_EQ("http://www.google.com/", *(result.page_url.get())); diff --git a/chromium/chrome/browser/extensions/api/alarms/alarms_apitest.cc b/chromium/chrome/browser/extensions/api/alarms/alarms_apitest.cc index fd888e4560c..01f3e0dd0ca 100644 --- a/chromium/chrome/browser/extensions/api/alarms/alarms_apitest.cc +++ b/chromium/chrome/browser/extensions/api/alarms/alarms_apitest.cc @@ -31,7 +31,7 @@ class AlarmsApiTest : public ExtensionApiTest, ASSERT_TRUE(StartEmbeddedTestServer()); } - static std::vector<base::Value> BuildEventArguments(const bool last_message) { + static base::Value::List BuildEventArguments(const bool last_message) { api::test::OnMessage::Info info; info.data = ""; info.last_message = last_message; diff --git a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc index a932bc2648c..c41b33c2595 100644 --- a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc +++ b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc @@ -8,7 +8,6 @@ #include <memory> #include <utility> -#include "base/containers/flat_map.h" #include "base/guid.h" #include "base/metrics/user_metrics.h" #include "base/strings/utf_string_conversions.h" @@ -98,15 +97,15 @@ const char* GetStringFromAddressField(i18n::addressinput::AddressField type) { } // Serializes the AddressUiComponent a map from string to base::Value(). -base::flat_map<std::string, base::Value> AddressUiComponentAsValueMap( +base::Value::Dict AddressUiComponentAsValueMap( const i18n::addressinput::AddressUiComponent& address_ui_component) { - base::flat_map<std::string, base::Value> info; - info.emplace(kFieldNameKey, address_ui_component.name); - info.emplace(kFieldTypeKey, - GetStringFromAddressField(address_ui_component.field)); - info.emplace(kFieldLengthKey, - address_ui_component.length_hint == - i18n::addressinput::AddressUiComponent::HINT_LONG); + base::Value::Dict info; + info.Set(kFieldNameKey, address_ui_component.name); + info.Set(kFieldTypeKey, + GetStringFromAddressField(address_ui_component.field)); + info.Set(kFieldLengthKey, + address_ui_component.length_hint == + i18n::addressinput::AddressUiComponent::HINT_LONG); return info; } @@ -116,9 +115,7 @@ base::flat_map<std::string, base::Value> AddressUiComponentAsValueMap( // number values. void RemoveDuplicatePhoneNumberAtIndex(size_t index, const std::string& country_code, - base::Value* list_value) { - DCHECK(list_value->is_list()); - base::Value::ListView list = list_value->GetListDeprecated(); + base::Value::List& list) { if (list.size() <= index) { NOTREACHED() << "List should have a value at index " << index; return; @@ -138,7 +135,7 @@ void RemoveDuplicatePhoneNumberAtIndex(size_t index, } if (is_duplicate) - list_value->EraseListIter(list.begin() + index); + list.erase(list.begin() + index); } autofill::AutofillManager* GetAutofillManager( @@ -281,7 +278,7 @@ ExtensionFunction::ResponseAction AutofillPrivateSaveAddressFunction::Run() { personal_data->AddProfile(profile); } - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -324,23 +321,23 @@ AutofillPrivateGetAddressComponentsFunction::Run() { /*include_literals=*/false, &lines, &language_code); // Convert std::vector<std::vector<::i18n::addressinput::AddressUiComponent>> // to AddressComponents - base::Value address_components(base::Value::Type::DICTIONARY); - base::Value rows(base::Value::Type::LIST); + base::Value::Dict address_components; + base::Value::List rows; for (auto& line : lines) { - std::vector<base::Value> row_values; + base::Value::List row_values; for (const ::i18n::addressinput::AddressUiComponent& component : line) { - row_values.emplace_back(AddressUiComponentAsValueMap(component)); + row_values.Append(AddressUiComponentAsValueMap(component)); } - base::Value row(base::Value::Type::DICTIONARY); - row.SetKey("row", base::Value(std::move(row_values))); + base::Value::Dict row; + row.Set("row", std::move(row_values)); rows.Append(std::move(row)); } - address_components.SetKey("components", std::move(rows)); - address_components.SetKey("languageCode", base::Value(language_code)); + address_components.Set("components", std::move(rows)); + address_components.Set("languageCode", language_code); - return RespondNow(OneArgument(std::move(address_components))); + return RespondNow(WithArguments(std::move(address_components))); } //////////////////////////////////////////////////////////////////////////////// @@ -417,7 +414,7 @@ ExtensionFunction::ResponseAction AutofillPrivateSaveCreditCardFunction::Run() { if (use_existing_card) { // Only updates when the card info changes. if (existing_card && existing_card->Compare(credit_card) == 0) - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); // Record when nickname is updated. if (credit_card.HasNonEmptyValidNickname() && @@ -437,7 +434,7 @@ ExtensionFunction::ResponseAction AutofillPrivateSaveCreditCardFunction::Run() { } } - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -456,7 +453,7 @@ ExtensionFunction::ResponseAction AutofillPrivateRemoveEntryFunction::Run() { personal_data->RemoveByGUID(parameters->guid); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -472,15 +469,15 @@ AutofillPrivateValidatePhoneNumbersFunction::Run() { api::autofill_private::ValidatePhoneParams* params = ¶meters->params; // Extract the phone numbers into a ListValue. - base::Value phone_numbers(base::Value::Type::LIST); + base::Value::List phone_numbers; for (auto phone_number : params->phone_numbers) { phone_numbers.Append(phone_number); } RemoveDuplicatePhoneNumberAtIndex(params->index_of_new_number, - params->country_code, &phone_numbers); + params->country_code, phone_numbers); - return RespondNow(OneArgument(std::move(phone_numbers))); + return RespondNow(WithArguments(std::move(phone_numbers))); } //////////////////////////////////////////////////////////////////////////////// @@ -499,7 +496,7 @@ ExtensionFunction::ResponseAction AutofillPrivateMaskCreditCardFunction::Run() { personal_data->ResetFullServerCard(parameters->guid); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -557,7 +554,7 @@ AutofillPrivateMigrateCreditCardsFunction::Run() { local_card_migration_manager->GetMigratableCreditCards(); local_card_migration_manager->AttemptToOfferLocalCardMigration( /*is_from_settings_page=*/true); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -573,7 +570,7 @@ AutofillPrivateLogServerCardLinkClickedFunction::Run() { return RespondNow(Error(kErrorDataUnavailable)); personal_data->LogServerCardLinkClicked(); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -599,7 +596,7 @@ AutofillPrivateSetCreditCardFIDOAuthEnabledStateFunction::Run() { credit_card_access_manager->OnSettingsPageFIDOAuthToggled( parameters->enabled); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -653,7 +650,7 @@ ExtensionFunction::ResponseAction AutofillPrivateAddVirtualCardFunction::Run() { virtual_card_enrollment_manager->InitVirtualCardEnroll( *card, autofill::VirtualCardEnrollmentSource::kSettingsPage); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } //////////////////////////////////////////////////////////////////////////////// @@ -692,8 +689,10 @@ AutofillPrivateRemoveVirtualCardFunction::Run() { ->GetFormDataImporter() ->GetVirtualCardEnrollmentManager(); - virtual_card_enrollment_manager->Unenroll(card->instrument_id()); - return RespondNow(NoArguments()); + virtual_card_enrollment_manager->Unenroll( + card->instrument_id(), + /*virtual_card_enrollment_update_response_callback=*/absl::nullopt); + return RespondNow(WithArguments()); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.cc b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.cc index 88ea1f7f579..03695611ef1 100644 --- a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.cc +++ b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.cc @@ -5,7 +5,6 @@ #include "chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h" #include "chrome/browser/extensions/api/autofill_private/autofill_private_event_router.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/extension_system_provider.h" #include "extensions/browser/extensions_browser_client.h" @@ -27,9 +26,9 @@ AutofillPrivateEventRouterFactory::GetInstance() { } AutofillPrivateEventRouterFactory::AutofillPrivateEventRouterFactory() - : BrowserContextKeyedServiceFactory( + : ProfileKeyedServiceFactory( "AutofillPrivateEventRouter", - BrowserContextDependencyManager::GetInstance()) { + ProfileSelections::BuildRedirectedInIncognito()) { DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); } @@ -38,12 +37,6 @@ KeyedService* AutofillPrivateEventRouterFactory::BuildServiceInstanceFor( return AutofillPrivateEventRouter::Create(context); } -content::BrowserContext* -AutofillPrivateEventRouterFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - return ExtensionsBrowserClient::Get()->GetOriginalContext(context); -} - bool AutofillPrivateEventRouterFactory:: ServiceIsCreatedWithBrowserContext() const { return true; diff --git a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h index e5d38f0970f..44f24f3019d 100644 --- a/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h +++ b/chromium/chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_AUTOFILL_PRIVATE_AUTOFILL_PRIVATE_EVENT_ROUTER_FACTORY_H_ #include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace extensions { @@ -15,8 +15,7 @@ class AutofillPrivateEventRouter; // This is a factory class used by the BrowserContextDependencyManager // to instantiate the autofillPrivate event router per profile (since the // extension event router is per profile). -class AutofillPrivateEventRouterFactory - : public BrowserContextKeyedServiceFactory { +class AutofillPrivateEventRouterFactory : public ProfileKeyedServiceFactory { public: // Returns the AutofillPrivateEventRouter for |profile|, creating it if // it is not yet created. @@ -33,8 +32,6 @@ class AutofillPrivateEventRouterFactory protected: // BrowserContextKeyedServiceFactory overrides: - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; private: diff --git a/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc b/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc index 316a9766461..bdbdc398cb8 100644 --- a/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc +++ b/chromium/chrome/browser/extensions/api/automation/automation_apitest.cc @@ -26,6 +26,7 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/tracing_controller.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/content_features.h" #include "content/public/test/browser_test.h" #include "extensions/browser/api/automation_internal/automation_event_router.h" #include "extensions/common/api/automation_internal.h" @@ -33,6 +34,7 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/features.h" #include "ui/accessibility/accessibility_switches.h" #include "ui/accessibility/ax_node.h" #include "ui/accessibility/ax_serializable_tree.h" @@ -209,7 +211,13 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, LineStartOffsets) { << message_; } -IN_PROC_BROWSER_TEST_F(AutomationApiCanvasTest, ImageData) { +// Flaky on Mac: crbug.com/1338036 +#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) +#define MAYBE_ImageData DISABLED_ImageData +#else +#define MAYBE_ImageData ImageData +#endif +IN_PROC_BROWSER_TEST_F(AutomationApiCanvasTest, MAYBE_ImageData) { StartEmbeddedTestServer(); ASSERT_TRUE(RunExtensionTest("automation/tests/tabs", {.page_url = "image_data.html"})) @@ -412,6 +420,38 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) { #endif // !defined(USE_AURA) #if BUILDFLAG(IS_CHROMEOS_ASH) +class AutomationApiFencedFrameTest + : public AutomationApiTest, + public testing::WithParamInterface<bool /* shadow_dom_fenced_frame */> { + protected: + AutomationApiFencedFrameTest() { + feature_list_.InitWithFeaturesAndParameters( + /*enabled_features=*/{{blink::features::kFencedFrames, + {{"implementation_type", + GetParam() ? "shadow_dom" : "mparch"}}}, + {features::kPrivacySandboxAdsAPIsOverride, {}}}, + /*disabled_features=*/{features::kSpareRendererForSitePerProcess}); + } + + ~AutomationApiFencedFrameTest() override = default; + + public: + void SetUpOnMainThread() override { AutomationApiTest::SetUpOnMainThread(); } + + base::test::ScopedFeatureList feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(AutomationApiFencedFrameTest, + AutomationApiFencedFrameTest, + testing::Bool()); + +IN_PROC_BROWSER_TEST_P(AutomationApiFencedFrameTest, DesktopFindInFencedframe) { + StartEmbeddedTestServer(); + ASSERT_TRUE(RunExtensionTest("automation/tests/desktop/fencedframe", + {.page_url = "focus_fencedframe.html"})) + << message_; +} + IN_PROC_BROWSER_TEST_F(AutomationApiTest, Desktop) { ASSERT_TRUE(RunExtensionTest("automation/tests/desktop", {.page_url = "desktop.html"})) diff --git a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc index d4c0c5462a0..8e3b36929dd 100644 --- a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc +++ b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc @@ -16,6 +16,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" +#include "chrome/browser/bookmarks/url_and_id.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h" #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h" @@ -24,6 +25,7 @@ #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/chrome_navigation_ui_data.h" #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" @@ -42,6 +44,7 @@ #include "components/strings/grit/components_strings.h" #include "components/undo/bookmark_undo_service.h" #include "components/user_prefs/user_prefs.h" +#include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" @@ -197,7 +200,7 @@ BookmarkManagerPrivateEventRouter::~BookmarkManagerPrivateEventRouter() { void BookmarkManagerPrivateEventRouter::DispatchEvent( events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args) { + base::Value::List event_args) { EventRouter::Get(browser_context_) ->BroadcastEvent(std::make_unique<Event>(histogram_value, event_name, std::move(event_args))); @@ -260,7 +263,7 @@ BookmarkManagerPrivateDragEventRouter:: void BookmarkManagerPrivateDragEventRouter::DispatchEvent( events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> args) { + base::Value::List args) { EventRouter* event_router = EventRouter::Get(profile_); if (!event_router) return; @@ -337,7 +340,7 @@ ExtensionFunction::ResponseValue ClipboardBookmarkManagerFunction::CopyOrCut( if (cut && HasPermanentNodes(nodes)) return Error(bookmark_keys::kModifySpecialError); bookmarks::CopyToClipboard(model, nodes, cut); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -382,19 +385,17 @@ BookmarkManagerPrivatePasteFunction::RunOnReady() { // No need to test return value, if we got an empty list, we insert at end. if (params->selected_id_list) GetNodesFromVector(model, *params->selected_id_list, &nodes); - int highest_index = -1; - for (size_t i = 0; i < nodes.size(); ++i) { + size_t highest_index = 0; + for (const BookmarkNode* node : nodes) { // + 1 so that we insert after the selection. - int index = parent_node->GetIndexOf(nodes[i]) + 1; - if (index > highest_index) - highest_index = index; + highest_index = + std::max(highest_index, parent_node->GetIndexOf(node).value() + 1); } - size_t insertion_index = (highest_index == -1) - ? parent_node->children().size() - : static_cast<size_t>(highest_index); + if (!highest_index) + highest_index = parent_node->children().size(); - bookmarks::PasteFromClipboard(model, parent_node, insertion_index); - return NoArguments(); + bookmarks::PasteFromClipboard(model, parent_node, highest_index); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -405,7 +406,7 @@ BookmarkManagerPrivateCanPasteFunction::RunOnReady() { PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile()); if (!prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled)) - return OneArgument(base::Value(false)); + return WithArguments(false); BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(GetProfile()); @@ -413,7 +414,7 @@ BookmarkManagerPrivateCanPasteFunction::RunOnReady() { if (!parent_node) return Error(bookmark_keys::kNoParentError); bool can_paste = bookmarks::CanPasteFromClipboard(model, parent_node); - return OneArgument(base::Value(can_paste)); + return WithArguments(can_paste); } ExtensionFunction::ResponseValue @@ -433,7 +434,7 @@ BookmarkManagerPrivateSortChildrenFunction::RunOnReady() { if (!CanBeModified(parent_node, &error)) return Error(error); model->SortChildren(parent_node); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -462,7 +463,7 @@ BookmarkManagerPrivateStartDragFunction::RunOnReady() { GetProfile(), {std::move(nodes), params->drag_node_index, web_contents, source, gfx::Point(params->x, params->y)}); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -501,7 +502,7 @@ BookmarkManagerPrivateDropFunction::RunOnReady() { GetProfile(), *drag_data, drop_parent, drop_index, copy); router->ClearBookmarkNodeData(); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -555,7 +556,7 @@ BookmarkManagerPrivateRemoveTreesFunction::RunOnReady() { return Error(error); } - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -565,7 +566,7 @@ BookmarkManagerPrivateUndoFunction::RunOnReady() { BookmarkUndoServiceFactory::GetForProfile(GetProfile())->undo_manager()-> Undo(); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -575,7 +576,7 @@ BookmarkManagerPrivateRedoFunction::RunOnReady() { BookmarkUndoServiceFactory::GetForProfile(GetProfile())->undo_manager()-> Redo(); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -595,6 +596,7 @@ BookmarkManagerPrivateOpenInNewTabFunction::RunOnReady() { ExtensionTabUtil::OpenTabParams options; options.url = std::make_unique<std::string>(node->url().spec()); options.active = std::make_unique<bool>(params->active); + options.bookmark_id = std::make_unique<int>(node->id()); std::unique_ptr<base::DictionaryValue> result( extensions::ExtensionTabUtil::OpenTab(this, options, user_gesture(), @@ -602,7 +604,7 @@ BookmarkManagerPrivateOpenInNewTabFunction::RunOnReady() { if (!result) return Error(error); - return NoArguments(); + return WithArguments(); } ExtensionFunction::ResponseValue @@ -637,6 +639,18 @@ BookmarkManagerPrivateOpenInNewWindowFunction::RunOnReady() { if (incognito_result == windows_util::IncognitoResult::kError) return Error(std::move(error)); + std::vector<UrlAndId> url_and_ids; + urls.reserve(nodes.size()); + for (const auto* node : nodes) { + if (!base::Contains(urls, node->url())) + continue; // The URL was filtered out; ignore this node. + UrlAndId url_and_id; + url_and_id.url = node->url(); + url_and_id.id = node->id(); + url_and_ids.push_back(url_and_id); + } + DCHECK_EQ(urls.size(), url_and_ids.size()); + DCHECK(!calling_profile->IsOffTheRecord()); Profile* window_profile = incognito_result == windows_util::IncognitoResult::kIncognito @@ -644,8 +658,8 @@ BookmarkManagerPrivateOpenInNewWindowFunction::RunOnReady() { : calling_profile; bool first_tab = true; - for (auto& url : urls) { - NavigateParams navigate_params(window_profile, url, + for (auto& url_and_id : url_and_ids) { + NavigateParams navigate_params(window_profile, url_and_id.url, ui::PAGE_TRANSITION_LINK); navigate_params.window_action = NavigateParams::WindowAction::SHOW_WINDOW; navigate_params.disposition = @@ -653,12 +667,18 @@ BookmarkManagerPrivateOpenInNewWindowFunction::RunOnReady() { : WindowOpenDisposition::NEW_FOREGROUND_TAB; if (params->incognito) navigate_params.disposition = WindowOpenDisposition::OFF_THE_RECORD; - Navigate(&navigate_params); + base::WeakPtr<content::NavigationHandle> handle = + Navigate(&navigate_params); + if (handle) { + ChromeNavigationUIData* ui_data = + static_cast<ChromeNavigationUIData*>(handle->GetNavigationUIData()); + ui_data->set_bookmark_id(url_and_id.id); + } first_tab = false; } - return NoArguments(); + return WithArguments(); } WEB_CONTENTS_USER_DATA_KEY_IMPL(BookmarkManagerPrivateDragEventRouter); diff --git a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h index eef06087328..94d4c3211dd 100644 --- a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h +++ b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h @@ -50,7 +50,7 @@ class BookmarkManagerPrivateEventRouter // Helper to actually dispatch an event to extension listeners. void DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args); + base::Value::List event_args); // Remembers the previous meta info of a node before it was changed. bookmarks::BookmarkNode::MetaInfoMap prev_meta_info_; @@ -122,7 +122,7 @@ class BookmarkManagerPrivateDragEventRouter // Helper to actually dispatch an event to extension listeners. void DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> args); + base::Value::List args); raw_ptr<Profile> profile_; bookmarks::BookmarkNodeData bookmark_drag_data_; diff --git a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_browsertest.cc b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_browsertest.cc new file mode 100644 index 00000000000..05938c1140d --- /dev/null +++ b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_browsertest.cc @@ -0,0 +1,72 @@ +// Copyright 2022 The Chromium Authors. All 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/bookmarks/bookmark_model_factory.h" +#include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/browser/bookmark_node.h" +#include "components/bookmarks/test/bookmark_test_helpers.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/api_test_utils.h" + +using bookmarks::BookmarkModel; +using bookmarks::BookmarkNode; + +namespace extensions { + +class BookmarkManagerPrivateApiBrowsertest : public InProcessBrowserTest { + public: + void SetUp() override { InProcessBrowserTest::SetUp(); } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + model_ = WaitForBookmarkModel(); + } + + BookmarkModel* model() { return model_; } + + private: + BookmarkModel* WaitForBookmarkModel() { + BookmarkModel* model = + BookmarkModelFactory::GetForBrowserContext(browser()->profile()); + bookmarks::test::WaitForBookmarkModelToLoad(model); + return model; + } + + BookmarkModel* model_; +}; + +IN_PROC_BROWSER_TEST_F(BookmarkManagerPrivateApiBrowsertest, + OpenURLInNewWindow) { + const BookmarkNode* node = + model()->AddURL(model()->bookmark_bar_node(), 0, u"Settings", + GURL(chrome::kChromeUISettingsURL)); + std::string node_id = base::NumberToString(node->id()); + + auto new_window_function = + base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); + std::string args = base::StringPrintf(R"([["%s"], false])", node_id.c_str()); + ASSERT_TRUE(api_test_utils::RunFunction(new_window_function.get(), args, + browser()->profile())); +} + +IN_PROC_BROWSER_TEST_F(BookmarkManagerPrivateApiBrowsertest, + OpenURLInNewWindowIncognito) { + const BookmarkNode* node = + model()->AddURL(model()->bookmark_bar_node(), 0, u"Settings", + GURL(chrome::kChromeUIVersionURL)); + std::string node_id = base::NumberToString(node->id()); + + auto new_window_function = + base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); + std::string args = base::StringPrintf(R"([["%s"], true])", node_id.c_str()); + ASSERT_TRUE(api_test_utils::RunFunction(new_window_function.get(), args, + browser()->profile())); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc index 7b2616b3578..5b5bf883213 100644 --- a/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc @@ -127,13 +127,13 @@ TEST_F(BookmarkManagerPrivateApiUnitTest, RunOpenInNewTabFunctionFolder) { } TEST_F(BookmarkManagerPrivateApiUnitTest, RunOpenInNewWindowFunctionFolder) { - auto new_tab_function = + auto new_window_function = base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); std::string node_id = base::NumberToString(model()->bookmark_bar_node()->id()); std::string args = base::StringPrintf(R"([["%s"], false])", node_id.c_str()); EXPECT_EQ("Cannot open a folder in a new window.", - api_test_utils::RunFunctionAndReturnError(new_tab_function.get(), + api_test_utils::RunFunctionAndReturnError(new_window_function.get(), args, profile())); } @@ -143,11 +143,11 @@ TEST_F(BookmarkManagerPrivateApiUnitTest, IncognitoModePrefs::SetAvailability( profile()->GetPrefs(), IncognitoModePrefs::Availability::kDisabled); - auto new_tab_function = + auto new_window_function = base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); std::string args = base::StringPrintf(R"([["%s"], true])", node_id().c_str()); EXPECT_EQ("Incognito mode is disabled.", - api_test_utils::RunFunctionAndReturnError(new_tab_function.get(), + api_test_utils::RunFunctionAndReturnError(new_window_function.get(), args, profile())); } @@ -157,12 +157,12 @@ TEST_F(BookmarkManagerPrivateApiUnitTest, IncognitoModePrefs::SetAvailability( profile()->GetPrefs(), IncognitoModePrefs::Availability::kForced); - auto new_tab_function = + auto new_window_function = base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); std::string args = base::StringPrintf(R"([["%s"], false])", node_id().c_str()); EXPECT_EQ("Incognito mode is forced. Cannot open normal windows.", - api_test_utils::RunFunctionAndReturnError(new_tab_function.get(), + api_test_utils::RunFunctionAndReturnError(new_window_function.get(), args, profile())); } @@ -172,11 +172,11 @@ TEST_F(BookmarkManagerPrivateApiUnitTest, model()->other_node(), 0, u"history", GURL("chrome://history")); std::string node_id = base::NumberToString(node->id()); - auto new_tab_function = + auto new_window_function = base::MakeRefCounted<BookmarkManagerPrivateOpenInNewWindowFunction>(); std::string args = base::StringPrintf(R"([["%s"], true])", node_id.c_str()); EXPECT_EQ("Cannot open URL \"chrome://history/\" in an incognito window.", - api_test_utils::RunFunctionAndReturnError(new_tab_function.get(), + api_test_utils::RunFunctionAndReturnError(new_window_function.get(), args, profile())); } diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc index d694fed2914..10b74a8d915 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc @@ -65,8 +65,8 @@ void PopulateBookmarkTreeNode( if (parent) { out_bookmark_tree_node->parent_id = std::make_unique<std::string>(base::NumberToString(parent->id())); - out_bookmark_tree_node->index = - std::make_unique<int>(parent->GetIndexOf(node)); + out_bookmark_tree_node->index = std::make_unique<int>( + static_cast<int>(parent->GetIndexOf(node).value())); } if (!node->is_folder()) { diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc index 9385e7cefa6..89d3ed0feaf 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc @@ -179,57 +179,57 @@ TEST_F(ExtensionBookmarksTest, GetMetaInfo) { EXPECT_EQ(8u, id_to_meta_info_map.DictSize()); // Verify top level node. - const base::Value* value = NULL; - EXPECT_TRUE(id_to_meta_info_map.Get( - base::NumberToString(model_->other_node()->id()), &value)); - ASSERT_TRUE(NULL != value); - const base::DictionaryValue* dictionary_value = NULL; - EXPECT_TRUE(value->GetAsDictionary(&dictionary_value)); - ASSERT_TRUE(nullptr != dictionary_value); - EXPECT_EQ(0u, dictionary_value->DictSize()); + { + const base::Value* value = nullptr; + EXPECT_TRUE(id_to_meta_info_map.Get( + base::NumberToString(model_->other_node()->id()), &value)); + ASSERT_TRUE(nullptr != value); + ASSERT_TRUE(value->is_dict()); + const base::Value::Dict& dict = value->GetDict(); + EXPECT_EQ(0u, dict.size()); + } // Verify bookmark with two meta info key/value pairs. - value = NULL; - EXPECT_TRUE( - id_to_meta_info_map.Get(base::NumberToString(node_->id()), &value)); - ASSERT_TRUE(NULL != value); - dictionary_value = NULL; - EXPECT_TRUE(value->GetAsDictionary(&dictionary_value)); - ASSERT_TRUE(nullptr != dictionary_value); - EXPECT_EQ(2u, dictionary_value->DictSize()); - std::string string_value; - EXPECT_TRUE(dictionary_value->GetString("some_key1", &string_value)); - EXPECT_EQ("some_value1", string_value); - EXPECT_TRUE(dictionary_value->GetString("some_key2", &string_value)); - EXPECT_EQ("some_value2", string_value); + { + const base::Value* value = nullptr; + EXPECT_TRUE( + id_to_meta_info_map.Get(base::NumberToString(node_->id()), &value)); + ASSERT_TRUE(nullptr != value); + ASSERT_TRUE(value->is_dict()); + const base::Value::Dict& dict = value->GetDict(); + EXPECT_EQ(2u, dict.size()); + ASSERT_TRUE(dict.FindString("some_key1")); + EXPECT_EQ("some_value1", *(dict.FindString("some_key1"))); + ASSERT_TRUE(dict.FindString("some_key2")); + EXPECT_EQ("some_value2", *(dict.FindString("some_key2"))); + } // Verify folder with one meta info key/value pair. - value = NULL; - EXPECT_TRUE( - id_to_meta_info_map.Get(base::NumberToString(folder_->id()), &value)); - ASSERT_TRUE(NULL != value); - dictionary_value = NULL; - EXPECT_TRUE(value->GetAsDictionary(&dictionary_value)); - ASSERT_TRUE(nullptr != dictionary_value); - EXPECT_EQ(1u, dictionary_value->DictSize()); - EXPECT_TRUE(dictionary_value->GetString("some_key1", &string_value)); - EXPECT_EQ("some_value1", string_value); + { + const base::Value* value = nullptr; + EXPECT_TRUE( + id_to_meta_info_map.Get(base::NumberToString(folder_->id()), &value)); + ASSERT_TRUE(nullptr != value); + ASSERT_TRUE(value->is_dict()); + const base::Value::Dict& dict = value->GetDict(); + EXPECT_EQ(1u, dict.size()); + ASSERT_TRUE(dict.FindString("some_key1")); + EXPECT_EQ("some_value1", *(dict.FindString("some_key1"))); + } // Verify bookmark in a subfolder with one meta info key/value pairs. - value = NULL; - EXPECT_TRUE( - id_to_meta_info_map.Get(base::NumberToString(node2_->id()), &value)); - ASSERT_TRUE(NULL != value); - dictionary_value = NULL; - EXPECT_TRUE(value->GetAsDictionary(&dictionary_value)); - ASSERT_TRUE(nullptr != dictionary_value); - EXPECT_EQ(1u, dictionary_value->DictSize()); - string_value.clear(); - EXPECT_FALSE(dictionary_value->GetString("some_key1", &string_value)); - EXPECT_EQ("", string_value); - EXPECT_TRUE(dictionary_value->GetString("some_key2", &string_value)); - EXPECT_EQ("some_value2", string_value); - + { + const base::Value* value = nullptr; + EXPECT_TRUE( + id_to_meta_info_map.Get(base::NumberToString(node2_->id()), &value)); + ASSERT_TRUE(nullptr != value); + ASSERT_TRUE(value->is_dict()); + const base::Value::Dict& dict = value->GetDict(); + EXPECT_EQ(1u, dict.size()); + ASSERT_FALSE(dict.FindString("some_key1")); + ASSERT_TRUE(dict.FindString("some_key2")); + EXPECT_EQ("some_value2", *(dict.FindString("some_key2"))); + } } } // namespace bookmark_api_helpers diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc index 619c9a4d2b9..33287dd0b3d 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc @@ -129,7 +129,6 @@ const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId( const BookmarkNode* BookmarksFunction::CreateBookmarkNode( BookmarkModel* model, const CreateDetails& details, - const BookmarkNode::MetaInfoMap* meta_info, std::string* error) { int64_t parent_id; @@ -172,9 +171,9 @@ const BookmarkNode* BookmarksFunction::CreateBookmarkNode( const BookmarkNode* node; if (url_string.length()) { - node = model->AddURL(parent, index, title, url, meta_info); + node = model->AddNewURL(parent, index, title, url); } else { - node = model->AddFolder(parent, index, title, meta_info); + node = model->AddFolder(parent, index, title); model->SetDateFolderModified(parent, base::Time::Now()); } @@ -248,7 +247,7 @@ BookmarkEventRouter::~BookmarkEventRouter() { void BookmarkEventRouter::DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args) { + base::Value::List event_args) { EventRouter* event_router = EventRouter::Get(browser_context_); if (event_router) { event_router->BroadcastEvent(std::make_unique<extensions::Event>( @@ -598,7 +597,7 @@ ExtensionFunction::ResponseValue BookmarksCreateFunction::RunOnReady() { BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(GetProfile()); const BookmarkNode* node = - CreateBookmarkNode(model, params->bookmark, nullptr, &error); + CreateBookmarkNode(model, params->bookmark, &error); if (!node) return Error(error); diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h index 47f0145abe3..9912ca1e967 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api.h @@ -88,7 +88,7 @@ class BookmarkEventRouter : public bookmarks::BookmarkModelObserver { // Helper to actually dispatch an event to extension listeners. void DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args); + base::Value::List event_args); raw_ptr<content::BrowserContext> browser_context_; raw_ptr<bookmarks::BookmarkModel> model_; @@ -158,7 +158,6 @@ class BookmarksFunction : public ExtensionFunction, const bookmarks::BookmarkNode* CreateBookmarkNode( bookmarks::BookmarkModel* model, const api::bookmarks::CreateDetails& details, - const bookmarks::BookmarkNode::MetaInfoMap* meta_info, std::string* error); // Helper that checks if bookmark editing is enabled. diff --git a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.cc b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.cc index 9e1496598a9..0c541879b59 100644 --- a/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.cc +++ b/chromium/chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.cc @@ -6,13 +6,12 @@ #include "base/memory/singleton.h" #include "base/observer_list.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace extensions { namespace { -class BookmarksApiWatcherFactory : public BrowserContextKeyedServiceFactory { +class BookmarksApiWatcherFactory : public ProfileKeyedServiceFactory { public: static BookmarksApiWatcher* GetForBrowserContext( content::BrowserContext* context) { @@ -25,9 +24,9 @@ class BookmarksApiWatcherFactory : public BrowserContextKeyedServiceFactory { } BookmarksApiWatcherFactory() - : BrowserContextKeyedServiceFactory( + : ProfileKeyedServiceFactory( "BookmarksApiWatcher", - BrowserContextDependencyManager::GetInstance()) {} + ProfileSelections::BuildForRegularAndIncognito()) {} private: // BrowserContextKeyedServiceFactory overrides @@ -35,11 +34,6 @@ class BookmarksApiWatcherFactory : public BrowserContextKeyedServiceFactory { content::BrowserContext* context) const override { return new BookmarksApiWatcher(); } - - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override { - return context; - } }; } // namespace 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 1f60641e7e7..e7f5a97ccc7 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 @@ -14,7 +14,6 @@ #include "base/bind.h" #include "base/strings/stringprintf.h" #include "base/task/thread_pool.h" -#include "base/values.h" #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/account_reconcilor_factory.h" @@ -161,15 +160,13 @@ ExtensionFunction::ResponseAction BrowsingDataSettingsFunction::Run() { // REMOVE_SITE_DATA in browsing_data_remover.h, the former for the unprotected // web, the latter for protected web data. There is no UI control for // extension data. - base::Value origin_types(base::Value::Type::DICTIONARY); - origin_types.SetBoolKey( - extension_browsing_data_api_constants::kUnprotectedWebKey, - isDataTypeSelected(BrowsingDataType::COOKIES, tab)); - origin_types.SetBoolKey( - extension_browsing_data_api_constants::kProtectedWebKey, - isDataTypeSelected(BrowsingDataType::HOSTED_APPS_DATA, tab)); - origin_types.SetBoolKey(extension_browsing_data_api_constants::kExtensionsKey, - false); + base::Value::Dict origin_types; + origin_types.Set(extension_browsing_data_api_constants::kUnprotectedWebKey, + isDataTypeSelected(BrowsingDataType::COOKIES, tab)); + origin_types.Set(extension_browsing_data_api_constants::kProtectedWebKey, + isDataTypeSelected(BrowsingDataType::HOSTED_APPS_DATA, tab)); + origin_types.Set(extension_browsing_data_api_constants::kExtensionsKey, + false); // Fill deletion time period. int period_pref = @@ -183,14 +180,14 @@ ExtensionFunction::ResponseAction BrowsingDataSettingsFunction::Run() { since = time.ToJsTime(); } - base::Value options(base::Value::Type::DICTIONARY); - options.SetKey(extension_browsing_data_api_constants::kOriginTypesKey, - std::move(origin_types)); - options.SetDoubleKey(extension_browsing_data_api_constants::kSinceKey, since); + base::Value::Dict options; + options.Set(extension_browsing_data_api_constants::kOriginTypesKey, + std::move(origin_types)); + options.Set(extension_browsing_data_api_constants::kSinceKey, since); // Fill dataToRemove and dataRemovalPermitted. - base::Value selected(base::Value::Type::DICTIONARY); - base::Value permitted(base::Value::Type::DICTIONARY); + base::Value::Dict selected; + base::Value::Dict permitted; bool delete_site_data = isDataTypeSelected(BrowsingDataType::COOKIES, tab) || @@ -237,23 +234,23 @@ ExtensionFunction::ResponseAction BrowsingDataSettingsFunction::Run() { extension_browsing_data_api_constants::kPasswordsKey, isDataTypeSelected(BrowsingDataType::PASSWORDS, tab)); - base::Value result(base::Value::Type::DICTIONARY); - result.SetKey(extension_browsing_data_api_constants::kOptionsKey, - std::move(options)); - result.SetKey(extension_browsing_data_api_constants::kDataToRemoveKey, - std::move(selected)); - result.SetKey(extension_browsing_data_api_constants::kDataRemovalPermittedKey, - std::move(permitted)); - return RespondNow(OneArgument(std::move(result))); + base::Value::Dict result; + result.Set(extension_browsing_data_api_constants::kOptionsKey, + std::move(options)); + result.Set(extension_browsing_data_api_constants::kDataToRemoveKey, + std::move(selected)); + result.Set(extension_browsing_data_api_constants::kDataRemovalPermittedKey, + std::move(permitted)); + return RespondNow(WithArguments(std::move(result))); } -void BrowsingDataSettingsFunction::SetDetails(base::Value* selected_dict, - base::Value* permitted_dict, +void BrowsingDataSettingsFunction::SetDetails(base::Value::Dict* selected_dict, + base::Value::Dict* permitted_dict, const char* data_type, bool is_selected) { bool is_permitted = IsRemovalPermitted(MaskForKey(data_type), prefs_); - selected_dict->SetBoolKey(data_type, is_selected && is_permitted); - permitted_dict->SetBoolKey(data_type, is_permitted); + selected_dict->Set(data_type, is_selected && is_permitted); + permitted_dict->Set(data_type, is_permitted); } BrowsingDataRemoverFunction::BrowsingDataRemoverFunction() = default; @@ -270,7 +267,7 @@ void BrowsingDataRemoverFunction::OnTaskFinished() { return; synced_data_deletion_.reset(); observation_.Reset(); - Respond(NoArguments()); + Respond(WithArguments()); Release(); // Balanced in StartRemoving. } @@ -282,13 +279,13 @@ ExtensionFunction::ResponseAction BrowsingDataRemoverFunction::Run() { // Grab the initial |options| parameter, and parse out the arguments. EXTENSION_FUNCTION_VALIDATE(args().size() >= 1); EXTENSION_FUNCTION_VALIDATE(args()[0].is_dict()); - const base::Value& options = args()[0]; + const base::Value::Dict& options = args()[0].GetDict(); EXTENSION_FUNCTION_VALIDATE(ParseOriginTypeMask(options, &origin_type_mask_)); // If |ms_since_epoch| isn't set, default it to 0. double ms_since_epoch = - options.FindDoubleKey(extension_browsing_data_api_constants::kSinceKey) + options.FindDouble(extension_browsing_data_api_constants::kSinceKey) .value_or(0); // base::Time takes a double that represents seconds since epoch. JavaScript @@ -298,9 +295,9 @@ ExtensionFunction::ResponseAction BrowsingDataRemoverFunction::Run() { EXTENSION_FUNCTION_VALIDATE(GetRemovalMask(&removal_mask_)); - const base::Value* origins = - options.FindListKey(extension_browsing_data_api_constants::kOriginsKey); - const base::Value* exclude_origins = options.FindListKey( + const base::Value::List* origins = + options.FindList(extension_browsing_data_api_constants::kOriginsKey); + const base::Value::List* exclude_origins = options.FindList( extension_browsing_data_api_constants::kExcludeOriginsKey); // Check that only |origins| or |excludeOrigins| can be set. @@ -402,29 +399,27 @@ void BrowsingDataRemoverFunction::StartRemoving() { } bool BrowsingDataRemoverFunction::ParseOriginTypeMask( - const base::Value& options, + const base::Value::Dict& options, uint64_t* origin_type_mask) { - DCHECK(options.is_dict()); - // Parse the |options| dictionary to generate the origin set mask. Default to // UNPROTECTED_WEB if the developer doesn't specify anything. *origin_type_mask = content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB; const base::Value* origin_type_dict = - options.FindKey(extension_browsing_data_api_constants::kOriginTypesKey); + options.Find(extension_browsing_data_api_constants::kOriginTypesKey); if (!origin_type_dict) return true; if (!origin_type_dict->is_dict()) return false; - const base::Value* option = nullptr; + const base::Value::Dict& origin_type = origin_type_dict->GetDict(); // The developer specified something! Reset to 0 and parse the dictionary. *origin_type_mask = 0; // Unprotected web. - option = origin_type_dict->FindKey( + const base::Value* option = origin_type.Find( extension_browsing_data_api_constants::kUnprotectedWebKey); if (option) { if (!option->is_bool()) @@ -437,8 +432,8 @@ bool BrowsingDataRemoverFunction::ParseOriginTypeMask( } // Protected web. - option = origin_type_dict->FindKey( - extension_browsing_data_api_constants::kProtectedWebKey); + option = + origin_type.Find(extension_browsing_data_api_constants::kProtectedWebKey); if (option) { if (!option->is_bool()) return false; @@ -450,8 +445,8 @@ bool BrowsingDataRemoverFunction::ParseOriginTypeMask( } // Extensions. - option = origin_type_dict->FindKey( - extension_browsing_data_api_constants::kExtensionsKey); + option = + origin_type.Find(extension_browsing_data_api_constants::kExtensionsKey); if (option) { if (!option->is_bool()) return false; @@ -464,12 +459,12 @@ bool BrowsingDataRemoverFunction::ParseOriginTypeMask( return true; } -bool BrowsingDataRemoverFunction::ParseOrigins(const base::Value& list_value, - std::vector<url::Origin>* result, - ResponseValue* error_response) { - DCHECK(list_value.is_list()); - result->reserve(list_value.GetListDeprecated().size()); - for (const auto& value : list_value.GetListDeprecated()) { +bool BrowsingDataRemoverFunction::ParseOrigins( + const base::Value::List& list_value, + std::vector<url::Origin>* result, + ResponseValue* error_response) { + result->reserve(list_value.size()); + for (const auto& value : list_value) { if (!value.is_string()) { *error_response = BadMessage(); return false; diff --git a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.h b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.h index aa309afea3e..fd7ec8100d7 100644 --- a/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.h +++ b/chromium/chrome/browser/extensions/api/browsing_data/browsing_data_api.h @@ -76,8 +76,8 @@ class BrowsingDataSettingsFunction : public ExtensionFunction { // indicating whether the data type is both selected and permitted to be // removed; and a value in the |permitted_dict| with the |data_type| as a // key, indicating only whether the data type is permitted to be removed. - void SetDetails(base::Value* selected_dict, - base::Value* permitted_dict, + void SetDetails(base::Value::Dict* selected_dict, + base::Value::Dict* permitted_dict, const char* data_type, bool is_selected); @@ -127,13 +127,13 @@ class BrowsingDataRemoverFunction // that can be used with the BrowsingDataRemover. // Returns true if parsing was successful. // Pre-condition: `options` is a dictionary. - bool ParseOriginTypeMask(const base::Value& options, + bool ParseOriginTypeMask(const base::Value::Dict& options, uint64_t* origin_type_mask); // Parses the developer-provided list of origins into |result|. // Returns whether or not parsing was successful. In case of parse failure, // |error_response| will contain the error response. - bool ParseOrigins(const base::Value& list_value, + bool ParseOrigins(const base::Value::List& list_value, std::vector<url::Origin>* result, ResponseValue* error_response); diff --git a/chromium/chrome/browser/extensions/api/certificate_provider/OWNERS b/chromium/chrome/browser/extensions/api/certificate_provider/OWNERS index 325f7c3fcd6..5f652fe7f8b 100644 --- a/chromium/chrome/browser/extensions/api/certificate_provider/OWNERS +++ b/chromium/chrome/browser/extensions/api/certificate_provider/OWNERS @@ -1 +1,2 @@ emaxx@chromium.org +fabiansommer@chromium.org diff --git a/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc b/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc index bbb0fd587fa..c0a6fb40d1e 100644 --- a/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc +++ b/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc @@ -498,7 +498,7 @@ ExtensionFunction::ResponseAction CertificateProviderRequestPinFunction::Run() { void CertificateProviderRequestPinFunction::OnInputReceived( const std::string& value) { - std::vector<base::Value> create_results; + base::Value::List create_results; chromeos::CertificateProviderService* const service = chromeos::CertificateProviderServiceFactory::GetForBrowserContext( browser_context()); @@ -508,8 +508,7 @@ void CertificateProviderRequestPinFunction::OnInputReceived( LOG(WARNING) << "PIN request succeeded"; api::certificate_provider::PinResponseDetails details; details.user_input = std::make_unique<std::string>(value); - create_results.emplace_back( - base::Value::FromUniquePtrValue(details.ToValue())); + create_results.Append(base::Value::FromUniquePtrValue(details.ToValue())); } else { // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. LOG(WARNING) << "PIN request canceled"; diff --git a/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc b/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc index a17bf5a807b..c84877724a1 100644 --- a/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc +++ b/chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc @@ -18,6 +18,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/hash/sha1.h" +#include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/path_service.h" #include "base/run_loop.h" @@ -297,7 +298,8 @@ class CertificateProviderApiTest : public extensions::ExtensionApiTest { protected: testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_; - chromeos::CertificateProviderService* cert_provider_service_ = nullptr; + raw_ptr<chromeos::CertificateProviderService> cert_provider_service_ = + nullptr; policy::PolicyMap policy_map_; private: @@ -505,8 +507,8 @@ class CertificateProviderApiMockedExtensionTest return certificate_data; } - content::WebContents* extension_contents_ = nullptr; - const extensions::Extension* extension_ = nullptr; + raw_ptr<content::WebContents> extension_contents_ = nullptr; + raw_ptr<const extensions::Extension> extension_ = nullptr; base::FilePath extension_path_; }; @@ -612,7 +614,7 @@ class CertificateProviderRequestPinTest : public CertificateProviderApiTest { extension_ = LoadExtension(extension_path); } - const extensions::Extension* extension_ = nullptr; + raw_ptr<const extensions::Extension> extension_ = nullptr; std::unique_ptr<ExtensionTestMessageListener> command_request_listener_; }; diff --git a/chromium/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chromium/chrome/browser/extensions/api/chrome_extensions_api_client.cc index 7775713ac64..a3165a89bcd 100644 --- a/chromium/chrome/browser/extensions/api/chrome_extensions_api_client.cc +++ b/chromium/chrome/browser/extensions/api/chrome_extensions_api_client.cc @@ -66,10 +66,16 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) #include "chrome/browser/ash/settings/cros_settings.h" #include "chrome/browser/extensions/api/file_handlers/non_native_file_system_delegate_chromeos.h" +#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h" #include "chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h" #include "chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h" #endif +#if BUILDFLAG(IS_CHROMEOS_LACROS) +#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h" +#include "chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h" +#endif + #if BUILDFLAG(IS_CHROMEOS) #include "chrome/browser/extensions/clipboard_extension_helper_chromeos.h" #endif @@ -163,7 +169,9 @@ bool ChromeExtensionsAPIClient::ShouldHideBrowserNetworkRequest( // Hide requests made by the NTP Instant renderer. auto* instant_service = - InstantServiceFactory::GetForProfile(static_cast<Profile*>(context)); + context + ? InstantServiceFactory::GetForProfile(static_cast<Profile*>(context)) + : nullptr; if (instant_service) { is_sensitive_request |= instant_service->IsInstantProcess(request.render_process_id); @@ -184,10 +192,10 @@ void ChromeExtensionsAPIClient::NotifyWebRequestWithheld( content::RenderFrameHost::FromID(render_process_id, render_frame_id); if (!rfh) return; - // We don't count subframe blocked actions as yet, since there's no way to - // surface this to the user. Ignore these (which is also what we do for - // content scripts). - if (rfh->GetParent()) + // We don't count subframes and prerendering blocked actions as yet, since + // there's no way to surface this to the user. Ignore these (which is also + // what we do for content scripts). + if (!rfh->IsInPrimaryMainFrame()) return; content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(rfh); @@ -350,6 +358,8 @@ ChromeExtensionsAPIClient::CreateVirtualKeyboardDelegate( content::BrowserContext* browser_context) const { #if BUILDFLAG(IS_CHROMEOS_ASH) return std::make_unique<ChromeVirtualKeyboardDelegate>(browser_context); +#elif BUILDFLAG(IS_CHROMEOS_LACROS) + return std::make_unique<LacrosVirtualKeyboardDelegate>(); #else return nullptr; #endif @@ -382,8 +392,15 @@ MetricsPrivateDelegate* ChromeExtensionsAPIClient::GetMetricsPrivateDelegate() { } FileSystemDelegate* ChromeExtensionsAPIClient::GetFileSystemDelegate() { +#if BUILDFLAG(IS_CHROMEOS_ASH) + using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegateAsh; +#elif BUILDFLAG(IS_CHROMEOS_LACROS) + using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegateLacros; +#else + using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegate; +#endif if (!file_system_delegate_) - file_system_delegate_ = std::make_unique<ChromeFileSystemDelegate>(); + file_system_delegate_ = std::make_unique<ChromeFileSystemDelegate_Use>(); return file_system_delegate_.get(); } diff --git a/chromium/chrome/browser/extensions/api/commands/command_service.cc b/chromium/chrome/browser/extensions/api/commands/command_service.cc index 37a16edb85d..0dd3a1d6cd0 100644 --- a/chromium/chrome/browser/extensions/api/commands/command_service.cc +++ b/chromium/chrome/browser/extensions/api/commands/command_service.cc @@ -82,7 +82,9 @@ void MergeSuggestedKeyPrefs( if (extension_prefs->ReadPrefAsDictionary(extension_id, kCommands, ¤t_prefs)) { - std::unique_ptr<base::DictionaryValue> new_prefs(current_prefs->DeepCopy()); + std::unique_ptr<base::DictionaryValue> new_prefs = + base::DictionaryValue::From( + base::Value::ToUniquePtrValue(current_prefs->Clone())); new_prefs->MergeDictionary(suggested_key_prefs.get()); suggested_key_prefs = std::move(new_prefs); } @@ -293,9 +295,9 @@ bool CommandService::SetScope(const std::string& extension_id, Command CommandService::FindCommandByName(const std::string& extension_id, const std::string& command) const { - const base::Value* bindings = - profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands); - for (const auto it : bindings->DictItems()) { + const base::Value::Dict& bindings = + profile_->GetPrefs()->GetValueDict(prefs::kExtensionCommands); + for (const auto it : bindings) { const std::string* extension = it.second.FindStringKey(kExtension); if (!extension || *extension != extension_id) continue; @@ -552,22 +554,19 @@ void CommandService::UpdateExtensionSuggestedCommandPrefs( void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs( const Extension* extension) { ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); - const base::DictionaryValue* current_prefs = nullptr; - extension_prefs->ReadPrefAsDictionary(extension->id(), - kCommands, - ¤t_prefs); + const base::Value::Dict* current_prefs = + extension_prefs->ReadPrefAsDict(extension->id(), kCommands); if (current_prefs) { - std::unique_ptr<base::DictionaryValue> suggested_key_prefs( - current_prefs->DeepCopy()); + base::Value::Dict suggested_key_prefs = current_prefs->Clone(); + const CommandMap* named_commands = CommandsInfo::GetNamedCommands(extension); const Command* browser_action_command = CommandsInfo::GetBrowserActionCommand(extension); - for (base::DictionaryValue::Iterator it(*current_prefs); - !it.IsAtEnd(); it.Advance()) { - if (it.key() == manifest_values::kBrowserActionCommandEvent) { + for (const auto [key, _] : *current_prefs) { + if (key == manifest_values::kBrowserActionCommandEvent) { // The browser action command may be defaulted to an unassigned // accelerator if a browser action is specified by the extension but a // keybinding is not declared. See @@ -575,22 +574,23 @@ void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs( if (!browser_action_command || browser_action_command->accelerator().key_code() == ui::VKEY_UNKNOWN) { - suggested_key_prefs->RemoveKey(it.key()); + suggested_key_prefs.Remove(key); } - } else if (it.key() == manifest_values::kPageActionCommandEvent) { + } else if (key == manifest_values::kPageActionCommandEvent) { if (!CommandsInfo::GetPageActionCommand(extension)) - suggested_key_prefs->RemoveKey(it.key()); - } else if (it.key() == manifest_values::kActionCommandEvent) { + suggested_key_prefs.Remove(key); + } else if (key == manifest_values::kActionCommandEvent) { if (!CommandsInfo::GetActionCommand(extension)) - suggested_key_prefs->RemoveKey(it.key()); + suggested_key_prefs.Remove(key); } else if (named_commands) { - if (named_commands->find(it.key()) == named_commands->end()) - suggested_key_prefs->RemoveKey(it.key()); + if (named_commands->find(key) == named_commands->end()) + suggested_key_prefs.Remove(key); } } - extension_prefs->UpdateExtensionPref(extension->id(), kCommands, - std::move(suggested_key_prefs)); + extension_prefs->UpdateExtensionPref( + extension->id(), kCommands, + std::make_unique<base::Value>(std::move(suggested_key_prefs))); } } @@ -601,20 +601,20 @@ bool CommandService::IsCommandShortcutUserModified( ui::Accelerator suggested_key; absl::optional<bool> suggested_key_was_assigned; ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); - const base::DictionaryValue* commands_prefs = nullptr; - if (extension_prefs->ReadPrefAsDictionary(extension->id(), kCommands, - &commands_prefs)) { - const base::Value* suggested_key_prefs = - commands_prefs->FindDictPath(command_name); + const base::Value::Dict* commands_prefs = + extension_prefs->ReadPrefAsDict(extension->id(), kCommands); + if (commands_prefs) { + const base::Value::Dict* suggested_key_prefs = + commands_prefs->FindDict(command_name); if (suggested_key_prefs) { const std::string* suggested_key_string = - suggested_key_prefs->FindStringKey(kSuggestedKey); + suggested_key_prefs->FindString(kSuggestedKey); if (suggested_key_string) { suggested_key = Command::StringToAccelerator(*suggested_key_string, command_name); } suggested_key_was_assigned = - suggested_key_prefs->FindBoolKey(kSuggestedKeyWasAssigned); + suggested_key_prefs->FindBool(kSuggestedKeyWasAssigned); } } @@ -639,21 +639,17 @@ void CommandService::RemoveKeybindingPrefs(const std::string& extension_id, if (!IsForCurrentPlatform(it.first)) continue; - const base::DictionaryValue* item = nullptr; - it.second.GetAsDictionary(&item); - - std::string extension; - item->GetString(kExtension, &extension); + const base::Value::Dict& dict = it.second.GetDict(); + const std::string* extension = dict.FindString(kExtension); - if (extension == extension_id) { + if (extension && *extension == extension_id) { // If |command_name| is specified, delete only that command. Otherwise, // delete all commands. - std::string command; - item->GetString(kCommandName, &command); - if (!command_name.empty() && command_name != command) + const std::string* command = dict.FindString(kCommandName); + if (command && !command_name.empty() && command_name != *command) continue; - removed_commands.push_back(FindCommandByName(extension_id, command)); + removed_commands.push_back(FindCommandByName(extension_id, *command)); keys_to_remove.push_back(it.first); } } diff --git a/chromium/chrome/browser/extensions/api/content_settings/content_settings_api.cc b/chromium/chrome/browser/extensions/api/content_settings/content_settings_api.cc index b9bcc6e36b9..9d738a4e8e6 100644 --- a/chromium/chrome/browser/extensions/api/content_settings/content_settings_api.cc +++ b/chromium/chrome/browser/extensions/api/content_settings/content_settings_api.cc @@ -53,7 +53,7 @@ namespace pref_keys = extensions::preference_api_constants; namespace { -bool RemoveContentType(std::vector<base::Value>& args, +bool RemoveContentType(base::Value::List& args, ContentSettingsType* content_type) { if (args.empty() || !args[0].is_string()) return false; @@ -112,7 +112,7 @@ ContentSettingsContentSettingClearFunction::Run() { store->ClearContentSettingsForExtensionAndContentType(extension_id(), scope, content_type); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } ExtensionFunction::ResponseAction @@ -166,17 +166,18 @@ ContentSettingsContentSettingGetFunction::Run() { ContentSetting setting = content_type == ContentSettingsType::COOKIES - ? cookie_settings->GetCookieSetting(primary_url, secondary_url, - nullptr) + ? cookie_settings->GetCookieSetting( + primary_url, secondary_url, nullptr, + content_settings::CookieSettings::QueryReason::kSetting) : map->GetContentSetting(primary_url, secondary_url, content_type); - base::Value result(base::Value::Type::DICTIONARY); + base::Value::Dict result; std::string setting_string = content_settings::ContentSettingToString(setting); DCHECK(!setting_string.empty()); - result.SetStringKey(ContentSettingsStore::kContentSettingKey, setting_string); + result.Set(ContentSettingsStore::kContentSettingKey, setting_string); - return RespondNow(OneArgument(std::move(result))); + return RespondNow(WithArguments(std::move(result))); } ExtensionFunction::ResponseAction @@ -301,7 +302,7 @@ ContentSettingsContentSettingSetFunction::Run() { secondary_pattern, content_type, setting, scope); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } ExtensionFunction::ResponseAction @@ -310,7 +311,7 @@ ContentSettingsContentSettingGetResourceIdentifiersFunction::Run() { // plugins have been deprecated since Chrome 87, there are no resource // identifiers for existing settings (but we retain the function for // backwards and potential forwards compatibility). - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chromium/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc index f90c84a85f1..5b419f0c423 100644 --- a/chromium/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc +++ b/chromium/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc @@ -50,6 +50,7 @@ namespace extensions { using ContextType = ExtensionApiTest::ContextType; +using QueryReason = content_settings::CookieSettings::QueryReason; class ExtensionContentSettingsApiTest : public ExtensionApiTest { public: @@ -99,9 +100,10 @@ class ExtensionContentSettingsApiTest : public ExtensionApiTest { // Check default content settings by using an unknown URL. GURL example_url("http://www.example.com"); - EXPECT_TRUE( - cookie_settings->IsFullCookieAccessAllowed(example_url, example_url)); - EXPECT_TRUE(cookie_settings->IsCookieSessionOnly(example_url)); + EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed( + example_url, example_url, QueryReason::kSetting)); + EXPECT_TRUE(cookie_settings->IsCookieSessionOnly(example_url, + QueryReason::kSetting)); EXPECT_EQ(CONTENT_SETTING_ALLOW, map->GetContentSetting(example_url, example_url, ContentSettingsType::IMAGES)); @@ -132,7 +134,8 @@ class ExtensionContentSettingsApiTest : public ExtensionApiTest { // Check content settings for www.google.com GURL url("http://www.google.com"); - EXPECT_FALSE(cookie_settings->IsFullCookieAccessAllowed(url, url)); + EXPECT_FALSE(cookie_settings->IsFullCookieAccessAllowed( + url, url, QueryReason::kSetting)); EXPECT_EQ(CONTENT_SETTING_ALLOW, map->GetContentSetting(url, url, ContentSettingsType::IMAGES)); EXPECT_EQ( @@ -167,8 +170,10 @@ class ExtensionContentSettingsApiTest : public ExtensionApiTest { // Check content settings for www.google.com GURL url("http://www.google.com"); - EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(url, url)); - EXPECT_FALSE(cookie_settings->IsCookieSessionOnly(url)); + EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed( + url, url, QueryReason::kSetting)); + EXPECT_FALSE( + cookie_settings->IsCookieSessionOnly(url, QueryReason::kSetting)); EXPECT_EQ(CONTENT_SETTING_ALLOW, map->GetContentSetting(url, url, ContentSettingsType::IMAGES)); EXPECT_EQ( @@ -204,9 +209,10 @@ class ExtensionContentSettingsApiTest : public ExtensionApiTest { content_settings::CookieSettings* cookie_settings = CookieSettingsFactory::GetForProfile(profile_).get(); + content_settings.push_back(cookie_settings->IsFullCookieAccessAllowed( + url, url, QueryReason::kSetting)); content_settings.push_back( - cookie_settings->IsFullCookieAccessAllowed(url, url)); - content_settings.push_back(cookie_settings->IsCookieSessionOnly(url)); + cookie_settings->IsCookieSessionOnly(url, QueryReason::kSetting)); content_settings.push_back( map->GetContentSetting(url, url, ContentSettingsType::IMAGES)); content_settings.push_back( diff --git a/chromium/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc b/chromium/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc index e15e657eb8c..d20b85b989f 100644 --- a/chromium/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc +++ b/chromium/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc @@ -123,8 +123,7 @@ IN_PROC_BROWSER_TEST_P(ExtensionContextMenuApiTestWithContextType, class ExtensionContextMenuVisibilityApiTest : public ExtensionContextMenuApiTest { public: - ExtensionContextMenuVisibilityApiTest() - : top_level_model_(nullptr), menu_(nullptr), top_level_index_(-1) {} + ExtensionContextMenuVisibilityApiTest() = default; ExtensionContextMenuVisibilityApiTest( const ExtensionContextMenuVisibilityApiTest&) = delete; @@ -155,7 +154,7 @@ class ExtensionContextMenuVisibilityApiTest menu_->extension_items().ConvertToExtensionsCustomCommandId(0), &top_level_model_, &top_level_index_); - EXPECT_GT(top_level_index(), 0); + EXPECT_GT(top_level_index(), 0u); return valid_setup; } @@ -175,14 +174,14 @@ class ExtensionContextMenuVisibilityApiTest void VerifyNumExtensionItemsInMenuModel(int num_items, ui::MenuModel::ItemType type) { int num_found = 0; - for (int i = 0; i < top_level_model_->GetItemCount(); i++) { + for (size_t i = 0; i < top_level_model_->GetItemCount(); ++i) { int command_id = top_level_model_->GetCommandIdAt(i); if (ContextMenuMatcher::IsExtensionsCustomCommandId(command_id) && top_level_model_->GetTypeAt(i) == type) { - num_found++; + ++num_found; } } - ASSERT_TRUE(num_found == num_items); + ASSERT_EQ(num_found, num_items); } // Verifies that the context menu is valid and contains the given number of @@ -196,7 +195,7 @@ class ExtensionContextMenuVisibilityApiTest // Verifies a context menu item's visibility, title, and item type. void VerifyMenuItem(const std::string& title, ui::MenuModel* model, - int index, + size_t index, ui::MenuModel::ItemType type, bool visible) { EXPECT_EQ(base::ASCIIToUTF16(title), model->GetLabelAt(index)); @@ -204,13 +203,13 @@ class ExtensionContextMenuVisibilityApiTest EXPECT_EQ(visible, model->IsVisibleAt(index)); } - int top_level_index() { return top_level_index_; } + size_t top_level_index() const { return top_level_index_; } TestRenderViewContextMenu* menu() { return menu_.get(); } const Extension* extension() { return extension_; } - ui::MenuModel* top_level_model_; + ui::MenuModel* top_level_model_ = nullptr; private: content::WebContents* GetBackgroundPage(const std::string& extension_id) { @@ -221,9 +220,9 @@ class ExtensionContextMenuVisibilityApiTest ProcessManager* process_manager() { return ProcessManager::Get(profile()); } - raw_ptr<const Extension> extension_; + raw_ptr<const Extension> extension_ = nullptr; std::unique_ptr<TestRenderViewContextMenu> menu_; - int top_level_index_; + size_t top_level_index_ = 0; }; // Tests showing a single visible menu item in the top-level menu model, which @@ -287,7 +286,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(2, submodel->GetItemCount()); + EXPECT_EQ(2u, submodel->GetItemCount()); VerifyMenuItem("child1", submodel, 0, ui::MenuModel::TYPE_COMMAND, false); VerifyMenuItem("child2", submodel, 1, ui::MenuModel::TYPE_COMMAND, false); @@ -317,7 +316,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(2, submodel->GetItemCount()); + EXPECT_EQ(2u, submodel->GetItemCount()); // Though the children's internal visibility state remains unchanged, the ui // code will hide the children if the parent is hidden. @@ -347,7 +346,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(2, submodel->GetItemCount()); + EXPECT_EQ(2u, submodel->GetItemCount()); VerifyMenuItem("child1", submodel, 0, ui::MenuModel::TYPE_COMMAND, false); VerifyMenuItem("child2", submodel, 1, ui::MenuModel::TYPE_COMMAND, false); @@ -375,7 +374,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(2, submodel->GetItemCount()); + EXPECT_EQ(2u, submodel->GetItemCount()); VerifyMenuItem("child1", submodel, 0, ui::MenuModel::TYPE_COMMAND, true); VerifyMenuItem("child2", submodel, 1, ui::MenuModel::TYPE_COMMAND, false); @@ -407,7 +406,7 @@ IN_PROC_BROWSER_TEST_F( ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(1, submodel->GetItemCount()); + EXPECT_EQ(1u, submodel->GetItemCount()); // When a parent item is specified by the developer (as opposed to generated), // its visibility is determined by the specified state. @@ -415,7 +414,7 @@ IN_PROC_BROWSER_TEST_F( submodel = submodel->GetSubmenuModelAt(0); ASSERT_TRUE(submodel); - EXPECT_EQ(2, submodel->GetItemCount()); + EXPECT_EQ(2u, submodel->GetItemCount()); VerifyMenuItem("child2", submodel, 0, ui::MenuModel::TYPE_COMMAND, false); VerifyMenuItem("child3", submodel, 1, ui::MenuModel::TYPE_COMMAND, false); @@ -443,7 +442,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(3, submodel->GetItemCount()); + EXPECT_EQ(3u, submodel->GetItemCount()); VerifyMenuItem("item1", submodel, 0, ui::MenuModel::TYPE_COMMAND, false); VerifyMenuItem("item2", submodel, 1, ui::MenuModel::TYPE_COMMAND, false); @@ -472,7 +471,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(1, submodel->GetItemCount()); + EXPECT_EQ(1u, submodel->GetItemCount()); VerifyMenuItem("child1", submodel, 0, ui::MenuModel::TYPE_COMMAND, false); // Update child1 to visible. @@ -526,7 +525,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* submodel = top_level_model_->GetSubmenuModelAt(top_level_index()); ASSERT_TRUE(submodel); - EXPECT_EQ(3, submodel->GetItemCount()); + EXPECT_EQ(3u, submodel->GetItemCount()); VerifyMenuItem("item1", submodel, 0, ui::MenuModel::TYPE_COMMAND, true); VerifyMenuItem("item2", submodel, 1, ui::MenuModel::TYPE_COMMAND, true); @@ -534,7 +533,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuVisibilityApiTest, ui::MenuModel* item3_submodel = submodel->GetSubmenuModelAt(2); ASSERT_TRUE(item3_submodel); - EXPECT_EQ(2, item3_submodel->GetItemCount()); + EXPECT_EQ(2u, item3_submodel->GetItemCount()); // Though the children's internal visibility state remains unchanged, the ui // code will hide the children if the parent is hidden. diff --git a/chromium/chrome/browser/extensions/api/context_menus/extension_context_menu_browsertest.cc b/chromium/chrome/browser/extensions/api/context_menus/extension_context_menu_browsertest.cc index c226e5bf65c..7507e8ece7c 100644 --- a/chromium/chrome/browser/extensions/api/context_menus/extension_context_menu_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/context_menus/extension_context_menu_browsertest.cc @@ -216,7 +216,7 @@ class ExtensionContextMenuBrowserTest return false; MenuModel* model = nullptr; - int index = -1; + size_t index = 0; if (!menu->GetMenuModelAndItemIndex(command_id, &model, &index)) { return false; } @@ -656,7 +656,7 @@ IN_PROC_BROWSER_TEST_P(ExtensionContextMenuLazyTest, TopLevel) { std::unique_ptr<TestRenderViewContextMenu> menu( TestRenderViewContextMenu::Create(GetWebContents(), url, GURL(), GURL())); - int index = 0; + size_t index = 0; MenuModel* model = nullptr; ASSERT_TRUE(menu->GetMenuModelAndItemIndex( @@ -676,7 +676,7 @@ IN_PROC_BROWSER_TEST_P(ExtensionContextMenuLazyTest, TopLevel) { static void ExpectLabelAndType(const char* expected_label, MenuModel::ItemType expected_type, const MenuModel& menu, - int index) { + size_t index) { EXPECT_EQ(expected_type, menu.GetTypeAt(index)); EXPECT_EQ(base::UTF8ToUTF16(expected_label), menu.GetLabelAt(index)); } @@ -698,11 +698,11 @@ static void VerifyMenuForSeparatorsTest(const MenuModel& menu) { // --separator-- // normal3 - int index = 0; + size_t index = 0; #if BUILDFLAG(IS_CHROMEOS_ASH) - ASSERT_EQ(7, menu.GetItemCount()); + ASSERT_EQ(7u, menu.GetItemCount()); #else - ASSERT_EQ(11, menu.GetItemCount()); + ASSERT_EQ(11u, menu.GetItemCount()); #endif // BUILDFLAG(IS_CHROMEOS_ASH) ExpectLabelAndType("radio1", MenuModel::TYPE_RADIO, menu, index++); ExpectLabelAndType("radio2", MenuModel::TYPE_RADIO, menu, index++); @@ -747,7 +747,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionContextMenuPersistentTest, Separators) { // The top-level item should be an "automagic parent" with the extension's // name. MenuModel* model = nullptr; - int index = 0; + size_t index = 0; std::u16string label; ASSERT_TRUE(menu->GetMenuModelAndItemIndex( ContextMenuMatcher::ConvertToExtensionsCustomCommandId(0), diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc b/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc index f30e82d97b9..43eb3bc0da3 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_api.cc @@ -14,7 +14,6 @@ #include "base/json/json_writer.h" #include "base/lazy_instance.h" #include "base/time/time.h" -#include "base/values.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/cookies/cookies_api_constants.h" #include "chrome/browser/extensions/api/cookies/cookies_helpers.h" @@ -118,17 +117,18 @@ void CookiesEventRouter::OnCookieChange(bool otr, const net::CookieChangeInfo& change) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - std::unique_ptr<base::ListValue> args(new base::ListValue()); - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetBoolKey(cookies_api_constants::kRemovedKey, - change.cause != net::CookieChangeCause::INSERTED); + base::Value::List args; + base::Value::Dict dict; + dict.Set(cookies_api_constants::kRemovedKey, + change.cause != net::CookieChangeCause::INSERTED); Profile* profile = otr ? profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true) : profile_->GetOriginalProfile(); api::cookies::Cookie cookie = cookies_helpers::CreateCookie( change.cookie, cookies_helpers::GetStoreIdFromProfile(profile)); - dict.SetKey(cookies_api_constants::kCookieKey, std::move(*cookie.ToValue())); + dict.Set(cookies_api_constants::kCookieKey, + base::Value::FromUniquePtrValue(cookie.ToValue())); // Map the internal cause to an external string. std::string cause_dict_entry; @@ -159,9 +159,9 @@ void CookiesEventRouter::OnCookieChange(bool otr, case net::CookieChangeCause::UNKNOWN_DELETION: NOTREACHED(); } - dict.SetStringKey(cookies_api_constants::kCauseKey, cause_dict_entry); + dict.Set(cookies_api_constants::kCauseKey, cause_dict_entry); - args->Append(std::move(dict)); + args.Append(std::move(dict)); DispatchEvent(profile, events::COOKIES_ON_CHANGED, api::cookies::OnChanged::kEventName, std::move(args), @@ -215,18 +215,16 @@ void CookiesEventRouter::OnConnectionError( MaybeStartListening(); } -void CookiesEventRouter::DispatchEvent( - content::BrowserContext* context, - events::HistogramValue histogram_value, - const std::string& event_name, - std::unique_ptr<base::ListValue> event_args, - const GURL& cookie_domain) { - EventRouter* router = context ? EventRouter::Get(context) : NULL; +void CookiesEventRouter::DispatchEvent(content::BrowserContext* context, + events::HistogramValue histogram_value, + const std::string& event_name, + base::Value::List event_args, + const GURL& cookie_domain) { + EventRouter* router = context ? EventRouter::Get(context) : nullptr; if (!router) return; - auto event = std::make_unique<Event>( - histogram_value, event_name, std::move(*event_args).TakeListDeprecated(), - context); + auto event = std::make_unique<Event>(histogram_value, event_name, + std::move(event_args), context); event->event_url = cookie_domain; router->BroadcastEvent(std::move(event)); } @@ -280,7 +278,7 @@ void CookiesGetFunction::GetCookieListCallback( } // The cookie doesn't exist; return null. - Respond(OneArgument(base::Value())); + Respond(WithArguments(base::Value())); } CookiesGetAllFunction::CookiesGetAllFunction() { @@ -337,7 +335,7 @@ void CookiesGetAllFunction::GetAllCookiesCallback( ArgumentList(api::cookies::GetAll::Results::Create(match_vector)); } else { // TODO(devlin): When can |extension()| be null for this function? - response = NoArguments(); + response = WithArguments(); } Respond(std::move(response)); } @@ -356,7 +354,7 @@ void CookiesGetAllFunction::GetCookieListCallback( ArgumentList(api::cookies::GetAll::Results::Create(match_vector)); } else { // TODO(devlin): When can |extension()| be null for this function? - response = NoArguments(); + response = WithArguments(); } Respond(std::move(response)); } @@ -498,7 +496,7 @@ void CookiesSetFunction::GetCookieListCallback( } } - Respond(value ? std::move(value) : NoArguments()); + Respond(value ? std::move(value) : WithArguments()); } CookiesRemoveFunction::CookiesRemoveFunction() { diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_api.h b/chromium/chrome/browser/extensions/api/cookies/cookies_api.h index 33f64d5b1e5..c0a2701716b 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_api.h +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_api.h @@ -13,6 +13,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" +#include "base/values.h" #include "chrome/browser/ui/browser_list_observer.h" #include "chrome/common/extensions/api/cookies.h" #include "extensions/browser/browser_context_keyed_api_factory.h" @@ -79,7 +80,7 @@ class CookiesEventRouter : public BrowserListObserver { void DispatchEvent(content::BrowserContext* context, events::HistogramValue histogram_value, const std::string& event_name, - std::unique_ptr<base::ListValue> event_args, + base::Value::List event_args, const GURL& cookie_domain); raw_ptr<Profile> profile_; diff --git a/chromium/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chromium/chrome/browser/extensions/api/cookies/cookies_unittest.cc index c2b5547b76f..08eddf075ec 100644 --- a/chromium/chrome/browser/extensions/api/cookies/cookies_unittest.cc +++ b/chromium/chrome/browser/extensions/api/cookies/cookies_unittest.cc @@ -176,10 +176,10 @@ TEST_F(ExtensionCookiesTest, DomainMatching) { for (size_t i = 0; i < std::size(tests); ++i) { // Build up the Params struct. - std::vector<base::Value> args; - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetStringKey(keys::kDomainKey, std::string(tests[i].filter)); - args.emplace_back(std::move(dict)); + base::Value::List args; + base::Value::Dict dict; + dict.Set(keys::kDomainKey, tests[i].filter); + args.Append(std::move(dict)); std::unique_ptr<GetAll::Params> params(GetAll::Params::Create(args)); cookies_helpers::MatchFilter filter(¶ms->details); diff --git a/chromium/chrome/browser/extensions/api/crash_report_private/crash_report_private_apitest.cc b/chromium/chrome/browser/extensions/api/crash_report_private/crash_report_private_apitest.cc index ef734bfc51a..020bde63e9f 100644 --- a/chromium/chrome/browser/extensions/api/crash_report_private/crash_report_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/crash_report_private/crash_report_private_apitest.cc @@ -8,7 +8,7 @@ #include "base/system/sys_info.h" #include "base/test/scoped_feature_list.h" #include "base/test/simple_test_clock.h" -#include "chrome/browser/ash/web_applications/system_web_app_integration_test.h" +#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h" #include "chrome/browser/devtools/devtools_window_testing.h" #include "chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h" #include "chrome/browser/extensions/api/crash_report_private/crash_report_private_api.h" @@ -308,7 +308,7 @@ IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, CalledFromWebContentsInTab) { EXPECT_EQ(report.content, ""); } -using CrashReportPrivateCalledFromSwaTest = SystemWebAppIntegrationTest; +using CrashReportPrivateCalledFromSwaTest = ash::SystemWebAppIntegrationTest; // Test WEB_APP is detected when |CrashReportPrivate| is called from an app // window. 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 969f24f155e..6fad739f111 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 @@ -57,13 +57,13 @@ constexpr const char* kGoogleGstaticAppIds[] = { // ContainsAppIdByHash returns true iff the SHA-256 hash of one of the // elements of |list| equals |hash|. -bool ContainsAppIdByHash(const base::Value& list, +bool ContainsAppIdByHash(const base::Value::List& list, const std::vector<uint8_t>& hash) { if (hash.size() != crypto::kSHA256Length) { return false; } - for (const auto& i : list.GetListDeprecated()) { + for (const auto& i : list) { const std::string& s = i.GetString(); if (s.find('/') == std::string::npos) { // No slashes mean that this is a webauthn RP ID, not a U2F AppID. @@ -122,7 +122,7 @@ CryptotokenPrivateCanOriginAssertAppIdFunction::Run() { } if (origin_url == app_id_url) { - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } // Fetch the eTLD+1 of both. @@ -143,7 +143,7 @@ CryptotokenPrivateCanOriginAssertAppIdFunction::Run() { "Could not find an eTLD for appId *", params->app_id_url))); } if (origin_etldp1 == app_id_etldp1) { - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } // For legacy purposes, allow google.com origins to assert certain // gstatic.com appIds. @@ -151,10 +151,10 @@ CryptotokenPrivateCanOriginAssertAppIdFunction::Run() { if (origin_etldp1 == kGoogleDotCom) { for (const char* id : kGoogleGstaticAppIds) { if (params->app_id_url == id) - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } } - return RespondNow(OneArgument(base::Value(false))); + return RespondNow(WithArguments(false)); } CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction:: @@ -170,12 +170,12 @@ CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction::Run() { Profile* const profile = Profile::FromBrowserContext(browser_context()); const PrefService* const prefs = profile->GetPrefs(); - const base::Value* const permit_attestation = - prefs->GetList(prefs::kSecurityKeyPermitAttestation); + const base::Value::List& permit_attestation = + prefs->GetValueList(prefs::kSecurityKeyPermitAttestation); return RespondNow(ArgumentList( cryptotoken_private::IsAppIdHashInEnterpriseContext::Results::Create( - ContainsAppIdByHash(*permit_attestation, params->app_id_hash)))); + ContainsAppIdByHash(permit_attestation, params->app_id_hash)))); } CryptotokenPrivateCanAppIdGetAttestationFunction:: @@ -200,12 +200,12 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { // prompt is shown. Profile* const profile = Profile::FromBrowserContext(browser_context()); const PrefService* const prefs = profile->GetPrefs(); - const base::Value* const permit_attestation = - prefs->GetList(prefs::kSecurityKeyPermitAttestation); + const base::Value::List& permit_attestation = + prefs->GetValueList(prefs::kSecurityKeyPermitAttestation); - for (const auto& entry : permit_attestation->GetListDeprecated()) { + for (const auto& entry : permit_attestation) { if (entry.GetString() == app_id) - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } // If the origin is blocked, reject attestation. @@ -213,14 +213,14 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { device::fido_filter::Operation::MAKE_CREDENTIAL, origin.Serialize(), /*device=*/absl::nullopt, /*id=*/absl::nullopt) == device::fido_filter::Action::NO_ATTESTATION) { - return RespondNow(OneArgument(base::Value(false))); + return RespondNow(WithArguments(false)); } // If prompting is disabled, allow attestation because that is the historical // behavior. if (!base::FeatureList::IsEnabled( ::features::kSecurityKeyAttestationPrompt)) { - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } #if BUILDFLAG(IS_WIN) @@ -236,7 +236,7 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { device::WinWebAuthnApi::GetDefault()->IsAvailable() && device::WinWebAuthnApi::GetDefault()->Version() >= WEBAUTHN_API_VERSION_2) { - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } #endif // BUILDFLAG(IS_WIN) @@ -272,7 +272,7 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { } void CryptotokenPrivateCanAppIdGetAttestationFunction::Complete(bool result) { - Respond(OneArgument(base::Value(result))); + Respond(WithArguments(result)); } CryptotokenPrivateCanMakeU2fApiRequestFunction:: @@ -294,7 +294,7 @@ CryptotokenPrivateCanMakeU2fApiRequestFunction::Run() { if (!ash::ProfileHelper::IsRegularProfile( Profile::FromBrowserContext(browser_context()))) { DCHECK_EQ(params->options.tab_id, api::tabs::TAB_ID_NONE); - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } #endif @@ -336,7 +336,7 @@ CryptotokenPrivateCanMakeU2fApiRequestFunction::Run() { // (crbug.com/1257293). if (!base::FeatureList::IsEnabled(device::kU2fPermissionPrompt) || u2f_api_origin_trial_enabled) { - return RespondNow(OneArgument(base::Value(true))); + return RespondNow(WithArguments(true)); } permissions::PermissionRequestManager* permission_request_manager = @@ -361,7 +361,7 @@ CryptotokenPrivateCanMakeU2fApiRequestFunction::Run() { } void CryptotokenPrivateCanMakeU2fApiRequestFunction::Complete(bool result) { - Respond(OneArgument(base::Value(result))); + Respond(WithArguments(result)); } ExtensionFunction::ResponseAction @@ -378,7 +378,7 @@ CryptotokenPrivateRecordRegisterRequestFunction::Run() { page_load_metrics::MetricsWebContentsObserver::RecordFeatureUsage( frame, blink::mojom::WebFeature::kU2FCryptotokenRegister); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } ExtensionFunction::ResponseAction @@ -394,7 +394,7 @@ CryptotokenPrivateRecordSignRequestFunction::Run() { page_load_metrics::MetricsWebContentsObserver::RecordFeatureUsage( frame, blink::mojom::WebFeature::kU2FCryptotokenSign); - return RespondNow(NoArguments()); + return RespondNow(WithArguments()); } } // namespace api 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 96ced8a81bc..7cee4fe4494 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 @@ -151,10 +151,10 @@ TEST_F(CryptoTokenPrivateApiTest, IsAppIdHashInEnterpriseContext) { ASSERT_TRUE(GetAppIdHashInEnterpriseContext(rp_id_hash, &result)); EXPECT_FALSE(result); - base::Value::ListStorage permitted_list; - permitted_list.emplace_back(example_com); - profile()->GetPrefs()->Set(prefs::kSecurityKeyPermitAttestation, - base::Value(permitted_list)); + base::Value::List permitted_list; + permitted_list.Append(example_com); + profile()->GetPrefs()->SetList(prefs::kSecurityKeyPermitAttestation, + std::move(permitted_list)); ASSERT_TRUE(GetAppIdHashInEnterpriseContext(example_com_hash, &result)); EXPECT_TRUE(result); @@ -350,10 +350,10 @@ TEST_F(CryptoTokenPermissionTest, AttestationPrompt) { TEST_F(CryptoTokenPermissionTest, PolicyOverridesAttestationPrompt) { const std::string example_com("https://example.com"); - base::Value::ListStorage permitted_list; - permitted_list.emplace_back(example_com); - profile()->GetPrefs()->Set(prefs::kSecurityKeyPermitAttestation, - base::Value(permitted_list)); + base::Value::List permitted_list; + permitted_list.Append(example_com); + profile()->GetPrefs()->SetList(prefs::kSecurityKeyPermitAttestation, + std::move(permitted_list)); // If an appId is configured by enterprise policy then attestation requests // should be permitted without showing a prompt. diff --git a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc index 003d8a4478a..6b758d1c1cd 100644 --- a/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc @@ -10,6 +10,7 @@ #include "base/test/with_feature_override.h" #include "build/build_config.h" #include "chrome/browser/extensions/component_loader.h" +#include "chrome/browser/policy/policy_test_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" @@ -18,6 +19,8 @@ #include "components/embedder_support/switches.h" #include "components/permissions/permission_request_manager.h" #include "components/permissions/test/permission_request_observer.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/policy_constants.h" #include "components/prefs/pref_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" @@ -25,6 +28,7 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "device/fido/features.h" +#include "extensions/browser/extension_registry.h" #include "extensions/browser/pref_names.h" #include "extensions/common/extension_features.h" #include "net/dns/mock_host_resolver.h" @@ -55,10 +59,16 @@ class CryptotokenBrowserTest : public base::test::WithFeatureOverride, CryptotokenBrowserTest() : base::test::WithFeatureOverride( extensions_features::kU2FSecurityKeyAPI) { + // Enable the feature flag to load the cryptoken component extension at + // startup. + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{extensions_features::kLoadCryptoTokenExtension}, + /*disabled_features=*/{ #if BUILDFLAG(IS_WIN) - // Don't dispatch requests to the native Windows API. - scoped_feature_list_.InitAndDisableFeature(device::kWebAuthUseNativeWinApi); + // Don't dispatch requests to the native Windows API. + device::kWebAuthUseNativeWinApi #endif + }); } void SetUpCommandLine(base::CommandLine* command_line) override { @@ -266,9 +276,7 @@ class CryptotokenBrowserTest : public base::test::WithFeatureOverride, return true; } -#if BUILDFLAG(IS_WIN) base::test::ScopedFeatureList scoped_feature_list_; -#endif std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_; }; @@ -405,5 +413,60 @@ IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, SandboxedPageDoesNotSign) { INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(CryptotokenBrowserTest); +// Test that the `kLoadCryptoTokenExtension` feature controls loading of the +// component extension. +class CryptotokenLoadFeatureBrowserTest + : public base::test::WithFeatureOverride, + public InProcessBrowserTest { + protected: + CryptotokenLoadFeatureBrowserTest() + : base::test::WithFeatureOverride( + extensions_features::kLoadCryptoTokenExtension) {} + + void SetUp() override { + ComponentLoader::EnableBackgroundExtensionsForTesting(); + InProcessBrowserTest::SetUp(); + } +}; + +IN_PROC_BROWSER_TEST_P(CryptotokenLoadFeatureBrowserTest, IsLoaded) { + EXPECT_EQ(ExtensionRegistry::Get(browser()->profile()) + ->GenerateInstalledExtensionsSet() + ->GetByID(kCryptoTokenExtensionId) != nullptr, + IsParamFeatureEnabled()); +} + +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(CryptotokenLoadFeatureBrowserTest); + +// Test that `extensions_prefs::kLoadCryptoTokenExtension` also controls loading +// of the component extension. +class CryptotokenLoadPolicyBrowserTest : public policy::PolicyTest { + protected: + void SetUpInProcessBrowserTestFixture() override { + PolicyTest::SetUpInProcessBrowserTestFixture(); + + policy::PolicyMap policies; + policies.Set(policy::key::kLoadCryptoTokenExtension, + policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER, + policy::POLICY_SOURCE_CLOUD, base::Value(true), nullptr); + UpdateProviderPolicy(policies); + } + + void SetUp() override { + ComponentLoader::EnableBackgroundExtensionsForTesting(); + policy::PolicyTest::SetUp(); + } +}; + +IN_PROC_BROWSER_TEST_F(CryptotokenLoadPolicyBrowserTest, IsLoaded) { + // The policy controls the extension even if the feature is disabled. + EXPECT_FALSE(base::FeatureList::IsEnabled( + extensions_features::kLoadCryptoTokenExtension)); + EXPECT_NE(ExtensionRegistry::Get(browser()->profile()) + ->GenerateInstalledExtensionsSet() + ->GetByID(kCryptoTokenExtensionId), + nullptr); +} + } // namespace } // namespace extensions 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 dcb4ff0918a..1553d8ebc41 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 @@ -175,8 +175,7 @@ ExtensionFunction::ResponseValue DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::BuildResponse( api::dashboard_private::Result result, const std::string& error) { // The web store expects an empty string on success. - std::vector<base::Value> args = - ShowPermissionPromptForDelegatedInstall::Results::Create(result); + auto args = ShowPermissionPromptForDelegatedInstall::Results::Create(result); if (result == api::dashboard_private::RESULT_EMPTY_STRING) return ArgumentList(std::move(args)); return ErrorWithArguments(std::move(args), error); diff --git a/chromium/chrome/browser/extensions/api/debugger/debugger_api.cc b/chromium/chrome/browser/extensions/api/debugger/debugger_api.cc index 4b303f27232..a4ece96a9c7 100644 --- a/chromium/chrome/browser/extensions/api/debugger/debugger_api.cc +++ b/chromium/chrome/browser/extensions/api/debugger/debugger_api.cc @@ -824,6 +824,11 @@ ExtensionFunction::ResponseAction DebuggerGetTargetsFunction::Run() { base::Value::List result; Profile* profile = Profile::FromBrowserContext(browser_context()); for (auto& host : list) { + // TODO(crbug.com/1348385): hide all Tab targets for now to avoid + // compatibility problems. Consider exposing them later when they're fully + // supported, and compatibility considerations are better understood. + if (host->GetType() == DevToolsAgentHost::kTypeTab) + continue; if (!ExtensionMayAttachToTargetProfile( profile, include_incognito_information(), *host)) { continue; @@ -831,7 +836,7 @@ ExtensionFunction::ResponseAction DebuggerGetTargetsFunction::Run() { result.Append(SerializeTarget(host)); } - return RespondNow(OneArgument(base::Value(std::move(result)))); + return RespondNow(WithArguments(std::move(result))); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc index b629239fdf4..a568046deba 100644 --- a/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc +++ b/chromium/chrome/browser/extensions/api/debugger/debugger_apitest.cc @@ -156,7 +156,9 @@ testing::AssertionResult DebuggerApiTest::RunAttachFunction( if (id == tab_id) { const base::DictionaryValue& target_dict = base::Value::AsDictionaryValue(target_value); - EXPECT_TRUE(target_dict.GetString("id", &debugger_target_id)); + const std::string* id_str = target_dict.GetDict().FindString("id"); + EXPECT_TRUE(id_str); + debugger_target_id = *id_str; break; } } @@ -263,7 +265,8 @@ class TestInterstitialPage void OnInterstitialClosing() override {} protected: - void PopulateInterstitialStrings(base::Value* load_time_data) override {} + void PopulateInterstitialStrings(base::Value::Dict& load_time_data) override { + } std::unique_ptr<security_interstitials::MetricsHelper> CreateTestMetricsHelper(content::WebContents* web_contents) { @@ -719,6 +722,17 @@ IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, NavigateToForbiddenUrl) { << message_; } +IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, NavigateToUntrustedWebUIUrl) { + ASSERT_TRUE(RunExtensionTest("debugger_navigate_to_untrusted_webui_url")) + << message_; +} + +// Tests that Target.createTarget to WebUI origins are blocked. +IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, CreateTargetToUntrustedWebUI) { + ASSERT_TRUE(RunExtensionTest("debugger_create_target_to_untrusted_webui")) + << message_; +} + IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, IsDeveloperModeTrueHistogram) { profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); base::HistogramTester histograms; diff --git a/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc b/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc index e8206eb6e19..139c7b64c51 100644 --- a/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative/declarative_apitest.cc @@ -121,55 +121,6 @@ class DeclarativeApiTest : public ExtensionApiTest { } }; -// Copied from origin_policy_browsertest.cc. -const base::FilePath::CharType kDataRoot[] = - FILE_PATH_LITERAL("chrome/test/data/origin_policy_browsertest"); - -class DeclarativeApiTestWithOriginPolicy : public DeclarativeApiTest { - protected: - std::u16string NavigateToAndReturnTitle(const char* url) { - EXPECT_TRUE(server()); - EXPECT_TRUE( - ui_test_utils::NavigateToURL(browser(), GURL(server()->GetURL(url)))); - std::u16string title; - ui_test_utils::GetCurrentTabTitle(browser(), &title); - return title; - } - - private: - void SetUpInProcessBrowserTestFixture() override { - server_ = std::make_unique<net::test_server::EmbeddedTestServer>( - net::test_server::EmbeddedTestServer::TYPE_HTTPS); - server_->AddDefaultHandlers(base::FilePath(kDataRoot)); - feature_list_.InitAndEnableFeature(features::kOriginPolicy); - EXPECT_TRUE(server()->Start()); - DeclarativeApiTest::SetUpInProcessBrowserTestFixture(); - } - - void TearDownInProcessBrowserTestFixture() override { server_.reset(); } - - net::test_server::EmbeddedTestServer* server() { return server_.get(); } - - std::unique_ptr<net::test_server::EmbeddedTestServer> server_; - base::test::ScopedFeatureList feature_list_; -}; - -// Regression test for crbug.com/1047275. -IN_PROC_BROWSER_TEST_F(DeclarativeApiTestWithOriginPolicy, - OriginPolicyEnabled) { - // Navigate to a page with an origin policy. It should load correctly. - EXPECT_EQ(u"Page With Policy", - NavigateToAndReturnTitle("/page-with-policy.html")); - - // Load an extension that has the |declarativeWebRequest| permission. - ASSERT_TRUE(RunExtensionTest("declarative/api")) << message_; - - // Future navigations to the page with the origin policy should still work, - // and not throw an interstitial. - EXPECT_EQ(u"Page With Policy", - NavigateToAndReturnTitle("/page-with-policy.html")); -} - IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, DeclarativeApi) { ASSERT_TRUE(RunExtensionTest("declarative/api")) << message_; diff --git a/chromium/chrome/browser/extensions/api/declarative_content/content_condition.cc b/chromium/chrome/browser/extensions/api/declarative_content/content_condition.cc index d85a0981653..121c39a0fee 100644 --- a/chromium/chrome/browser/extensions/api/declarative_content/content_condition.cc +++ b/chromium/chrome/browser/extensions/api/declarative_content/content_condition.cc @@ -38,29 +38,29 @@ std::unique_ptr<ContentCondition> CreateContentCondition( const std::map<std::string, ContentPredicateFactory*>& predicate_factories, const base::Value& api_condition, std::string* error) { - const base::DictionaryValue* api_condition_dict = nullptr; - if (!api_condition.GetAsDictionary(&api_condition_dict)) { + if (!api_condition.is_dict()) { *error = kExpectedDictionary; return nullptr; } + const base::Value::Dict& api_condition_dict = api_condition.GetDict(); + // Verify that we are dealing with a Condition whose type we understand. - std::string instance_type; - if (!api_condition_dict->GetString( - declarative_content_constants::kInstanceType, &instance_type)) { + const std::string* instance_type = api_condition_dict.FindString( + declarative_content_constants::kInstanceType); + if (!instance_type) { *error = kConditionWithoutInstanceType; return nullptr; } - if (instance_type != declarative_content_constants::kPageStateMatcherType) { + if (*instance_type != declarative_content_constants::kPageStateMatcherType) { *error = kExpectedOtherConditionType; return nullptr; } std::vector<std::unique_ptr<const ContentPredicate>> predicates; - for (base::DictionaryValue::Iterator iter(*api_condition_dict); - !iter.IsAtEnd(); iter.Advance()) { - const std::string& predicate_name = iter.key(); - const base::Value& predicate_value = iter.value(); + for (const auto iter : api_condition_dict) { + const std::string& predicate_name = iter.first; + const base::Value& predicate_value = iter.second; if (predicate_name == declarative_content_constants::kInstanceType) continue; 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 ed5bda388af..ca28e3f93f7 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 @@ -8,32 +8,28 @@ #include "components/version_info/version_info.h" #include "content/public/browser/storage_partition.h" #include "content/public/test/browser_test.h" +#include "content/public/test/prerender_test_util.h" #include "extensions/browser/api/declarative/rules_registry.h" #include "extensions/browser/api/declarative/rules_registry_service.h" #include "extensions/browser/extension_action_manager.h" #include "extensions/common/features/feature_channel.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 "ui/gfx/image/image.h" namespace extensions { namespace { -const char kDeclarativeContentManifest[] = - "{\n" - " \"name\": \"Declarative Content apitest\",\n" - " \"version\": \"0.1\",\n" - " \"manifest_version\": 2,\n" - " \"description\": \n" - " \"end-to-end browser test for the declarative Content API\",\n" - " \"background\": {\n" - " \"scripts\": [\"background.js\"]\n" - " },\n" - " \"page_action\": {},\n" - " \"permissions\": [\n" - " \"declarativeContent\"\n" - " ]\n" - "}\n"; +const char kDeclarativeContentManifest[] = R"({ + "name": "Declarative Content apitest", + "version": "0.1", + "manifest_version": 2, + "description": "end-to-end browser test for the declarative Content API", + "background": {"scripts": ["background.js"]}, + "page_action": {}, + "permissions": ["declarativeContent"] +})"; constexpr char kOneByOneImageData[] = "GAAAAAAAAAAQAAAAAAAAADAAAAAAAAAAKAAAAAAAAAACAAAAAQAAAAEAAAAAAAAAAAAAAAAAAA" @@ -44,88 +40,159 @@ class SetIconAPITest : public ExtensionApiTest { SetIconAPITest() // Set the channel to "trunk" since declarativeContent is restricted // to trunk. - : current_channel_(version_info::Channel::UNKNOWN) { - } + : current_channel_(version_info::Channel::UNKNOWN) {} ~SetIconAPITest() override {} + protected: + const Extension* LoadTestExtension() { + ext_dir_.WriteManifest(kDeclarativeContentManifest); + ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), R"( + var declarative = chrome.declarative; + + var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; + var SetIcon = chrome.declarativeContent.SetIcon; + + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + var imageData = ctx.createImageData(1, 1); + + var rule0 = { + conditions: [new PageStateMatcher({pageUrl: {queryContains: "show"}})], + actions: [new SetIcon({"imageData": imageData})] + }; + var testEvent = chrome.declarativeContent.onPageChanged; + + testEvent.removeRules(undefined, function() { + testEvent.addRules([rule0], function() { + chrome.test.sendMessage("ready", function(reply) {}); + }); + }); +)"); + ExtensionTestMessageListener ready("ready"); + const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); + if (!extension) + return nullptr; + + // Wait for declarative rules to be set up. + profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); + EXPECT_TRUE(GetExtensionAction(*extension)); + + EXPECT_TRUE(ready.WaitUntilSatisfied()); + + return extension; + } + + const ExtensionAction* GetExtensionAction(const Extension& extension) { + return ExtensionActionManager::Get(profile())->GetExtensionAction( + extension); + } + + content::WebContents* GetActiveWebContents() { + return browser()->tab_strip_model()->GetWebContentsAt(0); + } + + private: extensions::ScopedCurrentChannel current_channel_; TestExtensionDir ext_dir_; }; IN_PROC_BROWSER_TEST_F(SetIconAPITest, Overview) { - ext_dir_.WriteManifest(kDeclarativeContentManifest); - ext_dir_.WriteFile( - FILE_PATH_LITERAL("background.js"), - "var declarative = chrome.declarative;\n" - "\n" - "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n" - "var SetIcon = chrome.declarativeContent.SetIcon;\n" - "\n" - "var canvas = document.createElement(\'canvas\');\n" - "var ctx = canvas.getContext(\"2d\");" - "var imageData = ctx.createImageData(1, 1);\n" - "\n" - "var rule0 = {\n" - " conditions: [new PageStateMatcher({\n" - " pageUrl: {hostPrefix: \"test1\"}})],\n" - " actions: [new SetIcon({\"imageData\": imageData})]\n" - "}\n" - "\n" - "var testEvent = chrome.declarativeContent.onPageChanged;\n" - "\n" - "testEvent.removeRules(undefined, function() {\n" - " testEvent.addRules([rule0], function() {\n" - " chrome.test.sendMessage(\"ready\", function(reply) {\n" - " })\n" - " });\n" - "});\n"); - ExtensionTestMessageListener ready("ready"); - const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); + const Extension* extension = LoadTestExtension(); ASSERT_TRUE(extension); - // Wait for declarative rules to be set up. - profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); - const ExtensionAction* action = - ExtensionActionManager::Get(browser()->profile()) - ->GetExtensionAction(*extension); + + content::WebContents* const tab = GetActiveWebContents(); + const int tab_id = ExtensionTabUtil::GetTabId(tab); + + // There should be no declarative icon until we navigate to a matched page. + const ExtensionAction* action = GetExtensionAction(*extension); ASSERT_TRUE(action); - ASSERT_TRUE(ready.WaitUntilSatisfied()); - - // Regression test for crbug.com/1231027. - { - scoped_refptr<RulesRegistry> rules_registry = - extensions::RulesRegistryService::Get(browser()->profile()) - ->GetRulesRegistry(RulesRegistryService::kDefaultRulesRegistryID, - "declarativeContent.onPageChanged"); - ASSERT_TRUE(rules_registry); - - std::vector<const api::events::Rule*> rules; - rules_registry->GetAllRules(extension->id(), &rules); - ASSERT_EQ(1u, rules.size()); - ASSERT_EQ(rules[0]->actions.size(), 1u); - - base::Value& action_value = *rules[0]->actions[0]; - base::Value* action_instance_type = action_value.FindPath("instanceType"); - ASSERT_TRUE(action_instance_type); - EXPECT_EQ("declarativeContent.SetIcon", action_instance_type->GetString()); - - base::Value* image_data_value = action_value.FindPath({"imageData", "1"}); - ASSERT_TRUE(image_data_value); - EXPECT_EQ(kOneByOneImageData, image_data_value->GetString()); + EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty()); + EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://example.com/?show"))); + EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty()); + + // Navigating to an unmatched page should reset the icon. + EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://example.com/?hide"))); + EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty()); +} + +// Regression test for crbug.com/1231027. +IN_PROC_BROWSER_TEST_F(SetIconAPITest, Parameter) { + const Extension* extension = LoadTestExtension(); + ASSERT_TRUE(extension); + + scoped_refptr<RulesRegistry> rules_registry = + extensions::RulesRegistryService::Get(browser()->profile()) + ->GetRulesRegistry(RulesRegistryService::kDefaultRulesRegistryID, + "declarativeContent.onPageChanged"); + ASSERT_TRUE(rules_registry); + + std::vector<const api::events::Rule*> rules; + rules_registry->GetAllRules(extension->id(), &rules); + ASSERT_EQ(1u, rules.size()); + ASSERT_EQ(rules[0]->actions.size(), 1u); + + base::Value& action_value = *rules[0]->actions[0]; + base::Value* action_instance_type = action_value.FindPath("instanceType"); + ASSERT_TRUE(action_instance_type); + EXPECT_EQ("declarativeContent.SetIcon", action_instance_type->GetString()); + + base::Value* image_data_value = action_value.FindPath({"imageData", "1"}); + ASSERT_TRUE(image_data_value); + EXPECT_EQ(kOneByOneImageData, image_data_value->GetString()); +} + +class SetIconAPIPrerenderingTest : public SetIconAPITest { + public: + SetIconAPIPrerenderingTest() + : prerender_helper_(base::BindRepeating( + &SetIconAPIPrerenderingTest::GetActiveWebContents, + base::Unretained(this))) {} + ~SetIconAPIPrerenderingTest() override = default; + + protected: + int Prerender(const GURL& url) { return prerender_helper_.AddPrerender(url); } + void Activate(const GURL& url) { prerender_helper_.NavigatePrimaryPage(url); } + + private: + void SetUp() override { + prerender_helper_.SetUp(embedded_test_server()); + ExtensionApiTest::SetUp(); } - content::WebContents* const tab = - browser()->tab_strip_model()->GetWebContentsAt(0); + content::test::PrerenderTestHelper prerender_helper_; +}; + +IN_PROC_BROWSER_TEST_F(SetIconAPIPrerenderingTest, Overview) { + ASSERT_TRUE(embedded_test_server()->Start()); + const Extension* extension = LoadTestExtension(); + ASSERT_TRUE(extension); + + content::WebContents* const tab = GetActiveWebContents(); const int tab_id = ExtensionTabUtil::GetTabId(tab); // There should be no declarative icon until we navigate to a matched page. + const ExtensionAction* action = GetExtensionAction(*extension); + ASSERT_TRUE(action); + EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty()); - EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); + + const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html?show"); + EXPECT_TRUE(NavigateInRenderer(tab, kInitialUrl)); EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty()); - // Navigating to an unmatched page should reset the icon. - EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test2/"))); + // Prerendering an unmatched page should not reset the icon. + const GURL kPrerenderingUrl = + embedded_test_server()->GetURL("/empty.html?hide"); + int host_id = Prerender(kPrerenderingUrl); + ASSERT_NE(content::RenderFrameHost::kNoFrameTreeNodeId, host_id); + EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty()); + + // Activating the unmatched page should reset the icon. + Activate(kPrerenderingUrl); EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty()); + EXPECT_EQ(tab->GetLastCommittedURL(), kPrerenderingUrl); } + } // namespace } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc index e7e52d8a57f..214ffd7f16e 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc @@ -12,7 +12,9 @@ #include "components/version_info/version_info.h" #include "content/public/common/content_features.h" #include "content/public/test/browser_test.h" +#include "content/public/test/prerender_test_util.h" #include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" #include "third_party/blink/public/common/features.h" namespace { @@ -151,4 +153,29 @@ INSTANTIATE_TEST_SUITE_P(DeclarativeNetRequestApiFencedFrameTest, DeclarativeNetRequestApiFencedFrameTest, testing::Bool()); +class DeclarativeNetRequestApiPrerenderingTest + : public DeclarativeNetRequestLazyApiTest { + public: + DeclarativeNetRequestApiPrerenderingTest() = default; + ~DeclarativeNetRequestApiPrerenderingTest() override = default; + + private: + content::test::ScopedPrerenderFeatureList scoped_feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + DeclarativeNetRequestApiPrerenderingTest, + ::testing::Values(ContextType::kPersistentBackground)); +INSTANTIATE_TEST_SUITE_P(EventPage, + DeclarativeNetRequestApiPrerenderingTest, + ::testing::Values(ContextType::kEventPage)); +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + DeclarativeNetRequestApiPrerenderingTest, + ::testing::Values(ContextType::kServiceWorker)); + +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestApiPrerenderingTest, + PrerenderedPageInterception) { + ASSERT_TRUE(RunExtensionTest("prerendering")) << message_; +} + } // namespace 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 beb8fa58755..4a50258cbb2 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 @@ -4904,8 +4904,10 @@ class DeclarativeNetRequestAllowAllRequestsBrowserTest }; }; +// TODO(crbug.com/1345215): Re-enable this test. It was disabled because of +// flakiness. IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestAllowAllRequestsBrowserTest, - Test1) { + DISABLED_Test1) { std::vector<RuleData> rule_data = { {1, 4, "allowAllRequests", "page_with_two_frames\\.html", true, std::vector<std::string>({"main_frame"})}, @@ -4967,8 +4969,10 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestAllowAllRequestsBrowserTest, {requests[1], requests[2], requests[3], requests[4], requests[5]}); } +// TODO(crbug.com/1345215): Re-enable this test. It was disabled because of +// flakiness. IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestAllowAllRequestsBrowserTest, - Test4) { + DISABLED_Test4) { std::vector<RuleData> rule_data = { {1, 6, "allowAllRequests", "page_with_two_frames\\.html", true, std::vector<std::string>({"main_frame"})}, @@ -5018,8 +5022,14 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestAllowAllRequestsBrowserTest, {}, true); } +// TODO(crbug.com/1344372): Re-enable this test on MAC +#if BUILDFLAG(IS_MAC) +#define MAYBE_TestPostNavigationNotMatched DISABLED_TestPostNavigationNotMatched +#else +#define MAYBE_TestPostNavigationNotMatched TestPostNavigationNotMatched +#endif IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestAllowAllRequestsBrowserTest, - TestPostNavigationNotMatched) { + MAYBE_TestPostNavigationNotMatched) { std::vector<RuleData> rule_data = { {1, 6, "allowAllRequests", "page_with_two_frames\\.html", true, std::vector<std::string>({"main_frame"}), @@ -5504,6 +5514,93 @@ class DeclarativeNetRequestSubresourceWebBundlesBrowserTest base::test::ScopedFeatureList feature_list_; }; +// Test for https://crbug.com/1355162. +// Ensure the following happens when DeclarativeNetRequest API blocks a +// WebBundle: +// - A request for the WebBundle fails. +// - A subresource request associated with the bundle fail. +// - A window.load is fired. In other words, any request shouldn't remain +// pending forever. +IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestSubresourceWebBundlesBrowserTest, + WebBundleRequestCanceled) { + // Add a rule to block the bundle. + TestRule rule = CreateGenericRule(); + std::vector<TestRule> rules; + rule.id = kMinValidID; + rule.condition->url_filter = "web_bundle.wbn|"; + rule.priority = 1; + rules.push_back(rule); + ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(rules)); + + const char kPageHtml[] = R"( + <title>Loaded</title> + <body> + <script> + (async () => { + const window_load = new Promise((resolve) => { + window.addEventListener("load", () => { + resolve(); + }); + }); + + const wbn_url = + new URL('./web_bundle.wbn', location.href).toString(); + const pass_js_url = new URL('./pass.js', location.href).toString(); + const script_web_bundle = document.createElement('script'); + script_web_bundle.type = 'webbundle'; + script_web_bundle.textContent = JSON.stringify({ + source: wbn_url, + resources: [pass_js_url] + }); + + const web_bundle_error = new Promise((resolve) => { + script_web_bundle.addEventListener("error", () => { + resolve(); + }); + }); + + document.body.appendChild(script_web_bundle); + + const script_js = document.createElement('script'); + script_js.src = pass_js_url; + + const script_js_error = new Promise((resolve) => { + script_js.addEventListener("error", () => { + resolve(); + }); + }); + + document.body.appendChild(script_js); + + await Promise.all([window_load, web_bundle_error, script_js_error]); + document.title = "success"; + })(); + </script> + </body> + )"; + + std::string web_bundle; + RegisterWebBundleRequestHandler("/web_bundle.wbn", &web_bundle); + RegisterRequestHandler("/test.html", "text/html", kPageHtml); + ASSERT_TRUE(embedded_test_server()->Start()); + + // Create a web bundle, which can be empty in this test. + web_package::WebBundleBuilder builder; + std::vector<uint8_t> bundle = builder.CreateBundle(); + web_bundle = std::string(bundle.begin(), bundle.end()); + + GURL page_url = embedded_test_server()->GetURL("/test.html"); + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + EXPECT_EQ(page_url, web_contents->GetLastCommittedURL()); + + std::u16string expected_title = u"success"; + content::TitleWatcher title_watcher(web_contents, expected_title); + + EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); +} + // Ensure DeclarativeNetRequest API can block the requests for the subresources // inside the web bundle. IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestSubresourceWebBundlesBrowserTest, diff --git a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc index 75be9f5211e..2b3fb999eb8 100644 --- a/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc +++ b/chromium/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc @@ -2150,6 +2150,68 @@ TEST_P(MultipleRulesetsTest, UpdateAndGetEnabledRulesets_RuleCountExceeded) { CheckExtensionAllocationInPrefs(extension()->id(), 200); } +TEST_P(MultipleRulesetsTest, + UpdateAndGetEnabledRulesets_KeepEnabledStaticRulesetsAfterReload) { + AddRuleset(CreateRuleset(kId1, 90, 0, false)); + AddRuleset(CreateRuleset(kId2, 60, 0, false)); + AddRuleset(CreateRuleset(kId3, 150, 0, false)); + + RulesetManagerObserver ruleset_waiter(manager()); + + DeclarativeNetRequestUnittest::LoadAndExpectSuccess( + 300, 0, false /* expect_rulesets_indexed */); + + ruleset_waiter.WaitForExtensionsWithRulesetsCount(0); + + RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId2, kId3}, + absl::nullopt /* expected_error */); + VerifyPublicRulesetIDs(*extension(), {kId2, kId3}); + VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3}); + + // Ensure the set of enabled rulesets persists across extension reloads. + // Regression test for crbug.com/1346185. + const ExtensionId extension_id = extension()->id(); + service()->DisableExtension(extension_id, + disable_reason::DISABLE_USER_ACTION); + + ruleset_waiter.WaitForExtensionsWithRulesetsCount(0); + + service()->EnableExtension(extension_id); + + ruleset_waiter.WaitForExtensionsWithRulesetsCount(1); + + const Extension* extension = + registry()->GetExtensionById(extension_id, ExtensionRegistry::ENABLED); + ASSERT_TRUE(extension); + VerifyPublicRulesetIDs(*extension, {kId2, kId3}); + VerifyGetEnabledRulesetsFunction(*extension, {kId2, kId3}); +} + +// Tests attempting to disable rulesets when there are no rulesets active. +// Regression test for https://crbug.com/1354385. +TEST_P(MultipleRulesetsTest, + UpdateAndGetEnabledRulesets_DisableRulesetsWhenEmptyEnabledRulesets) { + AddRuleset(CreateRuleset(kId1, 90, 0, false)); + AddRuleset(CreateRuleset(kId2, 60, 0, false)); + AddRuleset(CreateRuleset(kId3, 150, 0, false)); + + RulesetManagerObserver ruleset_waiter(manager()); + + DeclarativeNetRequestUnittest::LoadAndExpectSuccess( + 300, 0, false /* expect_rulesets_indexed */); + + ruleset_waiter.WaitForExtensionsWithRulesetsCount(0); + + // Even though rulesets kId2 and kId3 are already disabled, the service + // can't know about that right away because there could be pending calls to + // complete. This means the service will still (appropriately) try and + // disable these rulesets. + RunUpdateEnabledRulesetsFunction(*extension(), {kId2, kId3}, {}, + absl::nullopt /* expected_error */); + ASSERT_FALSE(manager()->GetMatcherForExtension(extension()->id())); + VerifyGetEnabledRulesetsFunction(*extension(), {}); +} + // Test that getAvailableStaticRuleCount returns the correct number of rules an // extension can still enable. TEST_P(MultipleRulesetsTest, GetAvailableStaticRuleCount) { diff --git a/chromium/chrome/browser/extensions/api/desktop_capture/OWNERS b/chromium/chrome/browser/extensions/api/desktop_capture/OWNERS index 39013c0907e..da4c2853d50 100644 --- a/chromium/chrome/browser/extensions/api/desktop_capture/OWNERS +++ b/chromium/chrome/browser/extensions/api/desktop_capture/OWNERS @@ -1,4 +1,3 @@ -# Please send the changes to braveyao@chromium.org first. sergeyu@chromium.org wez@chromium.org -braveyao@chromium.org +eladalon@chromium.org diff --git a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc index 588e510e510..264398347f5 100644 --- a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc +++ b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc @@ -102,8 +102,13 @@ DesktopCaptureChooseDesktopMediaFunction::Run() { if (!target_render_frame_host) return RespondNow(Error(kTargetTabRequiredFromServiceWorker)); - return Execute(params->sources, target_render_frame_host, origin, - target_name); + const bool exclude_system_audio = + params->options && + params->options->system_audio == + api::desktop_capture::SYSTEM_AUDIO_PREFERENCE_ENUM_EXCLUDE; + + return Execute(params->sources, exclude_system_audio, + target_render_frame_host, origin, target_name); } std::string DesktopCaptureChooseDesktopMediaFunction::GetExtensionTargetName() 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 00a662a78ea..d91524ae6df 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 @@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/extensions/api/desktop_capture/desktop_capture_api.h" @@ -275,7 +276,33 @@ IN_PROC_BROWSER_TEST_F(DesktopCaptureApiTest, ServiceWorkerMustSpecifyTab) { ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(), {}, {})) << message_; } -IN_PROC_BROWSER_TEST_F(DesktopCaptureApiTest, FromServiceWorker) { +class DesktopCaptureApiTestWithSystemAudioValue + : public DesktopCaptureApiTest, + public testing::WithParamInterface<std::string> { + public: + DesktopCaptureApiTestWithSystemAudioValue() + : system_audio_preference_(GetParam()) { + DesktopCaptureChooseDesktopMediaFunction::SetPickerFactoryForTests( + &picker_factory_); + } + + ~DesktopCaptureApiTestWithSystemAudioValue() override = default; + + protected: + const std::string system_audio_preference_; +}; + +// |options| itself is optional, as is its member (systemAudio), and so will +// future members be (e.g. selfBrowserSurface). +INSTANTIATE_TEST_SUITE_P(_, + DesktopCaptureApiTestWithSystemAudioValue, + ::testing::Values("", + "{},", + "{systemAudio: \"include\"},", + "{systemAudio: \"exclude\"},")); + +IN_PROC_BROWSER_TEST_P(DesktopCaptureApiTestWithSystemAudioValue, + FromServiceWorker) { static constexpr char kManifest[] = R"({ "name": "Desktop Capture", @@ -285,24 +312,26 @@ IN_PROC_BROWSER_TEST_F(DesktopCaptureApiTest, FromServiceWorker) { "permissions": ["desktopCapture", "tabs"] })"; - static constexpr char kWorker[] = + const std::string worker = base::StringPrintf( R"(chrome.test.runTests([ function tabIdSpecified() { chrome.tabs.query({}, function(tabs) { chrome.test.assertTrue(tabs.length == 1); chrome.desktopCapture.chooseDesktopMedia( ["tab"], tabs[0], + %s function(id) { chrome.test.assertEq("string", typeof id); chrome.test.assertTrue(id != ""); chrome.test.succeed(); }); }); - }]))"; + }]))", + system_audio_preference_.c_str()); TestExtensionDir test_dir; test_dir.WriteManifest(kManifest); - test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kWorker); + test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), worker); // Open a tab to capture. embedded_test_server()->ServeFilesFromDirectory(GetTestResourcesParentDir()); 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 b68d6ca99fe..2a713ccf1d9 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 @@ -81,6 +81,7 @@ void DesktopCaptureChooseDesktopMediaFunctionBase::Cancel() { ExtensionFunction::ResponseAction DesktopCaptureChooseDesktopMediaFunctionBase::Execute( const std::vector<api::desktop_capture::DesktopCaptureSourceType>& sources, + bool exclude_system_audio, content::RenderFrameHost* render_frame_host, const GURL& origin, const std::u16string target_name) { @@ -157,6 +158,7 @@ DesktopCaptureChooseDesktopMediaFunctionBase::Execute( picker_params.app_name = base::UTF8ToUTF16(GetCallerDisplayName()); picker_params.target_name = target_name; picker_params.request_audio = request_audio; + picker_params.exclude_system_audio = exclude_system_audio; picker_controller_ = std::make_unique<DesktopMediaPickerController>(g_picker_factory); picker_params.restricted_by_policy = diff --git a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.h b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.h index b1f067d7119..f97c711188e 100644 --- a/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.h +++ b/chromium/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.h @@ -36,13 +36,18 @@ class DesktopCaptureChooseDesktopMediaFunctionBase : public ExtensionFunction { static const char kTargetNotFoundError[]; - // |web_contents| is the WebContents for which the stream is created, and will - // also be used to determine where to show the picker's UI. + // |exclude_system_audio| is piped from the original call. It is a constraint + // that needs to be applied before the picker is shown to the user, as it + // affects the picker. It is therefore provided to the extension function + // rather than to getUserMedia(), as is the case for other constraints. + // // |origin| is the origin for which the stream is created. + // // |target_name| is the display name of the stream target. ResponseAction Execute( const std::vector<api::desktop_capture::DesktopCaptureSourceType>& sources, + bool exclude_system_audio, content::RenderFrameHost* render_frame_host, const GURL& origin, const std::u16string target_name); diff --git a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.cc index 3f8450294a8..4003cca955f 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.cc @@ -100,6 +100,7 @@ #include "extensions/common/manifest_url_handlers.h" #include "extensions/common/permissions/permissions_data.h" #include "net/base/filename_util.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "storage/browser/blob/shareable_file_reference.h" #include "storage/browser/file_system/external_mount_points.h" #include "storage/browser/file_system/file_system_context.h" @@ -274,6 +275,109 @@ developer::UserSiteSettings ConvertToUserSiteSettings( return user_site_settings; } +std::string GetETldPlusOne(const GURL& site) { + DCHECK(site.is_valid()); + std::string etld_plus_one = + net::registry_controlled_domains::GetDomainAndRegistry( + site, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + return etld_plus_one.empty() ? site.spec() : etld_plus_one; +} + +developer::SiteInfo CreateSiteInfo( + const std::string& site, + absl::optional<developer::UserSiteSet> site_set) { + developer::SiteInfo site_info; + site_info.site = site; + if (site_set.has_value()) + site_info.site_list = *site_set; + else + site_info.num_extensions = 1; + return site_info; +} + +// Adds `site` grouped under `etld_plus_one` into `site_groups`. +void AddSiteToSiteGroups( + std::map<std::string, developer::SiteGroup>* site_groups, + const std::string& site, + const std::string& etld_plus_one, + absl::optional<developer::UserSiteSet> site_set) { + auto [it, inserted] = site_groups->try_emplace(etld_plus_one); + if (inserted) { + it->second.etld_plus_one = etld_plus_one; + it->second.sites.push_back(CreateSiteInfo(site, site_set)); + } else { + auto site_info = std::find_if( + it->second.sites.begin(), it->second.sites.end(), + [site](const developer::SiteInfo& info) { return info.site == site; }); + + if (site_info == it->second.sites.end()) + it->second.sites.push_back(CreateSiteInfo(site, site_set)); + else + site_info->num_extensions++; + } +} + +// Adds an extension's granted host permissions to `site_groups`, +void ProcessSitesForRuntimeHostPermissions( + std::map<std::string, developer::SiteGroup>* site_groups, + const URLPatternSet& user_specified_sites, + const developer::RuntimeHostPermissions& permissions) { + // Track the set of eTLD+1s covered by the extension's granted host + // permissions. + std::set<std::string> etld_plus_ones; + for (const auto& host : permissions.hosts) { + // Convert the host permission pattern back to a URLPattern so it can + // be easily modified for comparing against user specified sites and for + // fetching the eTLD+1. + URLPattern pattern(Extension::kValidHostPermissionSchemes, host.host); + pattern.SetPath(""); + + // Ignore patterns that are empty, are not granted, or match with user + // specified sites. + if (pattern.host().empty() || !host.granted || + user_specified_sites.ContainsPattern(pattern)) { + continue; + } + + pattern.SetMatchSubdomains(false); + pattern.SetScheme("http"); + + std::string etld_plus_one = GetETldPlusOne(GURL(pattern.GetAsString())); + etld_plus_ones.insert(etld_plus_one); + AddSiteToSiteGroups(site_groups, host.host, etld_plus_one, absl::nullopt); + } + + // Increment the extension count for each eTLD+1 covered by this extension's + // host permissions. + for (const auto& etld_plus_one : etld_plus_ones) + (*site_groups)[etld_plus_one].num_extensions++; +} + +// Updates numExtensions counts in `site_groups` for `extension`. Note that this +// should only be called for extensions with effective all hosts access. +void UpdateSiteGroupCountsForExtension( + std::map<std::string, developer::SiteGroup>* site_groups, + const Extension* extension) { + const URLPatternSet extension_patterns = + extension->permissions_data()->GetEffectiveHostPermissions(); + for (auto& entry : *site_groups) { + bool can_run_on_site_group = false; + for (developer::SiteInfo& site_info : entry.second.sites) { + if (site_info.site_list) + continue; + + URLPattern pattern(Extension::kValidHostPermissionSchemes, + site_info.site); + if (extension_patterns.ContainsPattern(pattern)) { + can_run_on_site_group = true; + site_info.num_extensions++; + } + } + if (can_run_on_site_group) + entry.second.num_extensions++; + } +} + } // namespace namespace ChoosePath = api::developer_private::ChoosePath; @@ -386,9 +490,6 @@ DeveloperPrivateEventRouter::DeveloperPrivateEventRouter(Profile* profile) prefs::kExtensionsUIDeveloperMode, base::BindRepeating(&DeveloperPrivateEventRouter::OnProfilePrefChanged, base::Unretained(this))); - notification_registrar_.Add( - this, extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, - content::Source<Profile>(profile_.get())); } DeveloperPrivateEventRouter::~DeveloperPrivateEventRouter() { @@ -525,13 +626,13 @@ void DeveloperPrivateEventRouter::OnExtensionAllowlistWarningStateChanged( } void DeveloperPrivateEventRouter::OnExtensionManagementSettingsChanged() { - std::vector<base::Value> args; - args.push_back(base::Value::FromUniquePtrValue( + base::Value::List args; + args.Append(base::Value::FromUniquePtrValue( DeveloperPrivateAPI::CreateProfileInfo(profile_)->ToValue())); - std::unique_ptr<Event> event( - new Event(events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED, - developer::OnProfileStateChanged::kEventName, std::move(args))); + auto event = std::make_unique<Event>( + events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED, + developer::OnProfileStateChanged::kEventName, std::move(args)); event_router_->BroadcastEvent(std::move(event)); } @@ -541,12 +642,12 @@ void DeveloperPrivateEventRouter::ExtensionWarningsChanged( BroadcastItemStateChanged(developer::EVENT_TYPE_WARNINGS_CHANGED, id); } -void DeveloperPrivateEventRouter::UserPermissionsSettingsChanged( +void DeveloperPrivateEventRouter::OnUserPermissionsSettingsChanged( const PermissionsManager::UserPermissionsSettings& settings) { developer::UserSiteSettings user_site_settings = ConvertToUserSiteSettings(settings); - std::vector<base::Value> args; - args.push_back(base::Value::FromUniquePtrValue(user_site_settings.ToValue())); + base::Value::List args; + args.Append(base::Value::FromUniquePtrValue(user_site_settings.ToValue())); auto event = std::make_unique<Event>( events::DEVELOPER_PRIVATE_ON_USER_SITE_SETTINGS_CHANGED, @@ -554,25 +655,21 @@ void DeveloperPrivateEventRouter::UserPermissionsSettingsChanged( event_router_->BroadcastEvent(std::move(event)); } -void DeveloperPrivateEventRouter::Observe( - int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - DCHECK_EQ(NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, type); - - UpdatedExtensionPermissionsInfo* info = - content::Details<UpdatedExtensionPermissionsInfo>(details).ptr(); +void DeveloperPrivateEventRouter::OnExtensionPermissionsUpdated( + const Extension& extension, + const PermissionSet& permissions, + PermissionsManager::UpdateReason reason) { BroadcastItemStateChanged(developer::EVENT_TYPE_PERMISSIONS_CHANGED, - info->extension->id()); + extension.id()); } void DeveloperPrivateEventRouter::OnProfilePrefChanged() { - std::vector<base::Value> args; - args.push_back(base::Value::FromUniquePtrValue( + base::Value::List args; + args.Append(base::Value::FromUniquePtrValue( DeveloperPrivateAPI::CreateProfileInfo(profile_)->ToValue())); - std::unique_ptr<Event> event( - new Event(events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED, - developer::OnProfileStateChanged::kEventName, std::move(args))); + auto event = std::make_unique<Event>( + events::DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED, + developer::OnProfileStateChanged::kEventName, std::move(args)); event_router_->BroadcastEvent(std::move(event)); // The following properties are updated when dev mode is toggled. @@ -622,8 +719,8 @@ void DeveloperPrivateEventRouter::BroadcastItemStateChangedHelper( std::make_unique<developer::ExtensionInfo>(std::move(infos[0])); } - std::vector<base::Value> args; - args.push_back(base::Value::FromUniquePtrValue(event_data.ToValue())); + base::Value::List args; + args.Append(base::Value::FromUniquePtrValue(event_data.ToValue())); std::unique_ptr<Event> event( new Event(events::DEVELOPER_PRIVATE_ON_ITEM_STATE_CHANGED, developer::OnItemStateChanged::kEventName, std::move(args))); @@ -2235,6 +2332,152 @@ DeveloperPrivateRemoveUserSpecifiedSitesFunction::Run() { return RespondNow(NoArguments()); } +DeveloperPrivateGetUserAndExtensionSitesByEtldFunction:: + DeveloperPrivateGetUserAndExtensionSitesByEtldFunction() = default; +DeveloperPrivateGetUserAndExtensionSitesByEtldFunction:: + ~DeveloperPrivateGetUserAndExtensionSitesByEtldFunction() = default; + +ExtensionFunction::ResponseAction +DeveloperPrivateGetUserAndExtensionSitesByEtldFunction::Run() { + std::map<std::string, developer::SiteGroup> site_groups; + const PermissionsManager::UserPermissionsSettings& settings = + PermissionsManager::Get(browser_context())->GetUserPermissionsSettings(); + URLPatternSet user_specified_sites; + for (const url::Origin& site : settings.permitted_sites) { + user_specified_sites.AddOrigin(Extension::kValidHostPermissionSchemes, + site.GetURL()); + AddSiteToSiteGroups(&site_groups, site.Serialize(), + GetETldPlusOne(site.GetURL()), + developer::USER_SITE_SET_PERMITTED); + } + + for (const url::Origin& site : settings.restricted_sites) { + user_specified_sites.AddOrigin(Extension::kValidHostPermissionSchemes, + site.GetURL()); + AddSiteToSiteGroups(&site_groups, site.Serialize(), + GetETldPlusOne(site.GetURL()), + developer::USER_SITE_SET_RESTRICTED); + } + + std::vector<const Extension*> extensions_with_all_hosts; + ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); + + // Note: we are only counting enabled extensions as the returned extension + // counts will reflect how many extensions can actually run on each site at + // the current moment. + for (const auto& extension : registry->enabled_extensions()) { + // TODO(crbug.com/1331137): Some extensions can access certain sites even if + // the user cannot modify their permissions. These also need to be added to + // another list so the frontend knows that their site access cannot be + // modified. + if (!ui_util::ShouldDisplayInExtensionSettings(*extension) || + !ScriptingPermissionsModifier(browser_context(), extension) + .CanAffectExtension()) { + continue; + } + + developer::RuntimeHostPermissions permissions = + ExtensionInfoGenerator::CreateRuntimeHostPermissionsInfo( + browser_context(), *extension); + + bool has_specific_hosts = + (!permissions.has_all_hosts && + permissions.host_access == developer::HOST_ACCESS_ON_ALL_SITES) || + permissions.host_access == developer::HOST_ACCESS_ON_SPECIFIC_SITES; + + if (permissions.host_access == developer::HOST_ACCESS_ON_ALL_SITES && + permissions.has_all_hosts) { + extensions_with_all_hosts.push_back(extension.get()); + } else if (has_specific_hosts) { + ProcessSitesForRuntimeHostPermissions(&site_groups, user_specified_sites, + permissions); + } + } + + // Specifying a "broad enough" host permission like "*://*.com/*" makes an + // extension "match all hosts". However, the extension does not truly have + // access to all sites, hence we iterate over all populated sites in + // `site_groups` and update the count for the extension for each site that it + // has access to. + for (const Extension* extension : extensions_with_all_hosts) + UpdateSiteGroupCountsForExtension(&site_groups, extension); + + std::vector<developer::SiteGroup> site_group_list; + site_group_list.reserve(site_groups.size()); + for (auto& entry : site_groups) { + // Sort the sites in each SiteGroup in ascending order by site. + std::sort(entry.second.sites.begin(), entry.second.sites.end(), + [](const developer::SiteInfo& a, const developer::SiteInfo& b) { + return b.site > a.site; + }); + site_group_list.push_back(std::move(entry.second)); + } + + return RespondNow( + ArgumentList(developer::GetUserAndExtensionSitesByEtld::Results::Create( + site_group_list))); +} + +DeveloperPrivateGetMatchingExtensionsForSiteFunction:: + DeveloperPrivateGetMatchingExtensionsForSiteFunction() = default; +DeveloperPrivateGetMatchingExtensionsForSiteFunction:: + ~DeveloperPrivateGetMatchingExtensionsForSiteFunction() = default; + +ExtensionFunction::ResponseAction +DeveloperPrivateGetMatchingExtensionsForSiteFunction::Run() { + std::unique_ptr<developer::GetMatchingExtensionsForSite::Params> params( + developer::GetMatchingExtensionsForSite::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); + + URLPattern parsed_site(Extension::kValidHostPermissionSchemes); + if (parsed_site.Parse(params->site) != URLPattern::ParseResult::kSuccess) + return RespondNow(Error("Invalid site: " + params->site)); + + std::vector<developer::MatchingExtensionInfo> matching_extensions; + URLPatternSet site_pattern({parsed_site}); + std::unique_ptr<ExtensionSet> all_extensions = + ExtensionRegistry::Get(browser_context()) + ->GenerateInstalledExtensionsSet(); + for (const auto& extension : *all_extensions) { + const URLPatternSet& extension_withheld_sites = + extension->permissions_data()->withheld_permissions().effective_hosts(); + const URLPatternSet granted_intersection = + URLPatternSet::CreateIntersection( + site_pattern, + extension->permissions_data()->GetEffectiveHostPermissions(), + URLPatternSet::IntersectionBehavior::kDetailed); + const URLPatternSet withheld_intersection = + URLPatternSet::CreateIntersection( + site_pattern, extension_withheld_sites, + URLPatternSet::IntersectionBehavior::kDetailed); + + if (granted_intersection.is_empty() && withheld_intersection.is_empty()) + continue; + + // By default, return ON_CLICK if the extension has requested but does not + // have access to any sites that match `site_pattern`. + developer::HostAccess host_access = developer::HOST_ACCESS_ON_CLICK; + + // If the extension has access to at least one site that matches + // `site_pattern`, return ON_ALL_SITES or ON_SPECIFIC_SITES depending on + // if the extension has any withheld sites. + if (!granted_intersection.is_empty()) { + host_access = extension_withheld_sites.is_empty() + ? developer::HOST_ACCESS_ON_ALL_SITES + : developer::HOST_ACCESS_ON_SPECIFIC_SITES; + } + + developer::MatchingExtensionInfo matching_info; + matching_info.site_access = host_access; + matching_info.id = extension->id(); + matching_extensions.push_back(std::move(matching_info)); + } + + return RespondNow( + ArgumentList(developer::GetMatchingExtensionsForSite::Results::Create( + matching_extensions))); +} + } // namespace api } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.h index 9a6547ad22f..4b971104c70 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.h +++ b/chromium/chrome/browser/extensions/api/developer_private/developer_private_api.h @@ -23,8 +23,6 @@ #include "chrome/common/extensions/api/developer_private.h" #include "chrome/common/extensions/webstore_install_result.h" #include "components/prefs/pref_change_registrar.h" -#include "content/public/browser/notification_observer.h" -#include "content/public/browser/notification_registrar.h" #include "extensions/browser/api/file_system/file_system_api.h" #include "extensions/browser/app_window/app_window_registry.h" #include "extensions/browser/browser_context_keyed_api_factory.h" @@ -72,7 +70,6 @@ class DeveloperPrivateEventRouter : public ExtensionRegistryObserver, public ExtensionAllowlist::Observer, public ExtensionManagement::Observer, public WarningService::Observer, - public content::NotificationObserver, public PermissionsManager::Observer { public: explicit DeveloperPrivateEventRouter(Profile* profile); @@ -142,14 +139,13 @@ class DeveloperPrivateEventRouter : public ExtensionRegistryObserver, void ExtensionWarningsChanged( const ExtensionIdSet& affected_extensions) override; - // content::NotificationObserver: - void Observe(int notification_type, - const content::NotificationSource& source, - const content::NotificationDetails& details) override; - // PermissionsManager::Observer: - void UserPermissionsSettingsChanged( + void OnUserPermissionsSettingsChanged( const PermissionsManager::UserPermissionsSettings& settings) override; + void OnExtensionPermissionsUpdated( + const Extension& extension, + const PermissionSet& permissions, + PermissionsManager::UpdateReason reason) override; // Handles a profile preference change. void OnProfilePrefChanged(); @@ -198,8 +194,6 @@ class DeveloperPrivateEventRouter : public ExtensionRegistryObserver, PrefChangeRegistrar pref_change_registrar_; - content::NotificationRegistrar notification_registrar_; - base::WeakPtrFactory<DeveloperPrivateEventRouter> weak_factory_{this}; }; @@ -210,7 +204,7 @@ class DeveloperPrivateAPI : public BrowserContextKeyedAPI, using UnpackedRetryId = std::string; static BrowserContextKeyedAPIFactory<DeveloperPrivateAPI>* - GetFactoryInstance(); + GetFactoryInstance(); static std::unique_ptr<api::developer_private::ProfileInfo> CreateProfileInfo( Profile* profile); @@ -641,7 +635,6 @@ class DeveloperPrivateChoosePathFunction class DeveloperPrivatePackDirectoryFunction : public DeveloperPrivateAPIFunction, public PackExtensionJob::Client { - public: DECLARE_EXTENSION_FUNCTION("developerPrivate.packDirectory", DEVELOPERPRIVATE_PACKDIRECTORY) @@ -926,6 +919,42 @@ class DeveloperPrivateRemoveUserSpecifiedSitesFunction ResponseAction Run() override; }; +class DeveloperPrivateGetUserAndExtensionSitesByEtldFunction + : public DeveloperPrivateAPIFunction { + public: + DECLARE_EXTENSION_FUNCTION("developerPrivate.getUserAndExtensionSitesByEtld", + DEVELOPERPRIVATE_GETUSERANDEXTENSIONSITESBYETLD) + DeveloperPrivateGetUserAndExtensionSitesByEtldFunction(); + + DeveloperPrivateGetUserAndExtensionSitesByEtldFunction( + const DeveloperPrivateGetUserAndExtensionSitesByEtldFunction&) = delete; + DeveloperPrivateGetUserAndExtensionSitesByEtldFunction& operator=( + const DeveloperPrivateGetUserAndExtensionSitesByEtldFunction&) = delete; + + private: + ~DeveloperPrivateGetUserAndExtensionSitesByEtldFunction() override; + + ResponseAction Run() override; +}; + +class DeveloperPrivateGetMatchingExtensionsForSiteFunction + : public DeveloperPrivateAPIFunction { + public: + DECLARE_EXTENSION_FUNCTION("developerPrivate.getMatchingExtensionsForSite", + DEVELOPERPRIVATE_GETMATCHINGEXTENSIONSFORSITE) + DeveloperPrivateGetMatchingExtensionsForSiteFunction(); + + DeveloperPrivateGetMatchingExtensionsForSiteFunction( + const DeveloperPrivateGetMatchingExtensionsForSiteFunction&) = delete; + DeveloperPrivateGetMatchingExtensionsForSiteFunction& operator=( + const DeveloperPrivateGetMatchingExtensionsForSiteFunction&) = delete; + + private: + ~DeveloperPrivateGetMatchingExtensionsForSiteFunction() override; + + ResponseAction Run() override; +}; + } // namespace api } // namespace extensions 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 15609e7db96..11a868758d4 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 @@ -12,6 +12,7 @@ #include "base/scoped_observation.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" +#include "base/test/values_test_util.h" #include "chrome/browser/extensions/chrome_test_extension_loader.h" #include "chrome/browser/extensions/error_console/error_console.h" #include "chrome/browser/extensions/extension_function_test_utils.h" @@ -97,11 +98,9 @@ bool WasItemChangedEventDispatched( return false; const Event& event = *iter->second; - CHECK(event.event_args); - CHECK_GE(1u, event.event_args->GetListDeprecated().size()); + CHECK_GE(1u, event.event_args.size()); std::unique_ptr<api::developer_private::EventData> event_data = - api::developer_private::EventData::FromValue( - event.event_args->GetListDeprecated()[0]); + api::developer_private::EventData::FromValue(event.event_args[0]); if (!event_data) return false; @@ -124,10 +123,9 @@ bool WasUserSiteSettingsChangedEventDispatched( return false; const Event& event = *iter->second; - CHECK(event.event_args); - CHECK_GE(1u, event.event_args->GetListDeprecated().size()); - auto site_settings = api::developer_private::UserSiteSettings::FromValue( - event.event_args->GetListDeprecated()[0]); + CHECK_GE(1u, event.event_args.size()); + auto site_settings = + api::developer_private::UserSiteSettings::FromValue(event.event_args[0]); if (!site_settings) return false; @@ -159,6 +157,66 @@ void RemoveUserSpecifiedSites(Profile* profile, << function->GetError(); } +void AddExtensionAndGrantPermissions(Profile* profile, + ExtensionService* service, + const Extension& extension) { + PermissionsUpdater updater(profile); + updater.InitializePermissions(&extension); + updater.GrantActivePermissions(&extension); + service->AddExtension(&extension); +} + +void RunAddHostPermission(Profile* profile, + const Extension& extension, + base::StringPiece host, + bool should_succeed, + const char* expected_error) { + SCOPED_TRACE(host); + scoped_refptr<ExtensionFunction> function = + base::MakeRefCounted<api::DeveloperPrivateAddHostPermissionFunction>(); + + std::string args = base::StringPrintf(R"(["%s", "%s"])", + extension.id().c_str(), host.data()); + if (should_succeed) { + EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile)) + << function->GetError(); + } else { + EXPECT_EQ(expected_error, api_test_utils::RunFunctionAndReturnError( + function.get(), args, profile)); + } +} + +void GetMatchingExtensionsForSite( + Profile* profile, + const std::string& site, + std::vector<api::developer_private::MatchingExtensionInfo>* infos) { + scoped_refptr<ExtensionFunction> function = base::MakeRefCounted< + api::DeveloperPrivateGetMatchingExtensionsForSiteFunction>(); + EXPECT_TRUE(api_test_utils::RunFunction( + function.get(), base::StringPrintf(R"(["%s"])", site.c_str()), profile)) + << function->GetError(); + const base::Value::List* results = function->GetResultList(); + ASSERT_EQ(1u, results->size()); + ASSERT_TRUE((*results)[0].is_list()); + + infos->clear(); + for (const auto& value : (*results)[0].GetList()) { + infos->push_back(std::move( + *api::developer_private::MatchingExtensionInfo::FromValue(value))); + } +} + +auto MatchMatchingExtensionInfo( + const std::string& extension_id, + const api::developer_private::HostAccess& host_access) { + return testing::AllOf( + testing::Field(&api::developer_private::MatchingExtensionInfo::id, + extension_id), + testing::Field( + &api::developer_private::MatchingExtensionInfo::site_access, + host_access)); +} + } // namespace class DeveloperPrivateApiUnitTest : public ExtensionServiceTestWithInstall { @@ -518,10 +576,9 @@ TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivatePackFunction) { // Try to pack a final time when omitting (an existing) pem file. We should // get an error. base::DeleteFile(crx_path); - EXPECT_TRUE(pack_args.EraseListIter(pack_args.GetListDeprecated().begin() + - 1u)); // Remove the pem key argument. - EXPECT_TRUE(pack_args.EraseListIter(pack_args.GetListDeprecated().begin() + - 1u)); // Remove the flags argument. + // Remove the pem key and flags arguments. + pack_args.GetList().erase(pack_args.GetList().begin() + 1, + pack_args.GetList().begin() + 3); EXPECT_TRUE(TestPackExtensionFunction( pack_args, api::developer_private::PACK_STATUS_ERROR, 0)); } @@ -1427,50 +1484,37 @@ TEST_F(DeveloperPrivateApiUnitTest, GrantHostPermission) { EXPECT_FALSE(modifier.HasWithheldHostPermissions()); modifier.SetWithholdHostPermissions(true); - auto run_add_host_permission = [this, extension](base::StringPiece host, - bool should_succeed, - const char* expected_error) { - SCOPED_TRACE(host); - scoped_refptr<ExtensionFunction> function = - base::MakeRefCounted<api::DeveloperPrivateAddHostPermissionFunction>(); - - std::string args = base::StringPrintf(R"(["%s", "%s"])", - extension->id().c_str(), host.data()); - if (should_succeed) { - EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile())) - << function->GetError(); - } else { - EXPECT_EQ(expected_error, api_test_utils::RunFunctionAndReturnError( - function.get(), args, profile())); - } - }; - const GURL kExampleCom("https://example.com/"); EXPECT_FALSE(modifier.HasGrantedHostPermission(kExampleCom)); - run_add_host_permission("https://example.com/*", true, nullptr); + RunAddHostPermission(profile(), *extension, "https://example.com/*", + /*should_succeed=*/true, nullptr); EXPECT_TRUE(modifier.HasGrantedHostPermission(kExampleCom)); const GURL kGoogleCom("https://google.com"); const GURL kMapsGoogleCom("https://maps.google.com/"); EXPECT_FALSE(modifier.HasGrantedHostPermission(kGoogleCom)); EXPECT_FALSE(modifier.HasGrantedHostPermission(kMapsGoogleCom)); - run_add_host_permission("https://*.google.com/*", true, nullptr); + RunAddHostPermission(profile(), *extension, "https://*.google.com/*", + /*should_succeed=*/true, nullptr); EXPECT_TRUE(modifier.HasGrantedHostPermission(kGoogleCom)); EXPECT_TRUE(modifier.HasGrantedHostPermission(kMapsGoogleCom)); - run_add_host_permission(kInvalidHost, false, kInvalidHostError); + RunAddHostPermission(profile(), *extension, kInvalidHost, + /*should_succeed=*/false, kInvalidHostError); // Path of the pattern must exactly match "/*". - run_add_host_permission("https://example.com/", false, kInvalidHostError); - run_add_host_permission("https://example.com/foobar", false, - kInvalidHostError); - run_add_host_permission("https://example.com/#foobar", false, - kInvalidHostError); - run_add_host_permission("https://example.com/*foobar", false, - kInvalidHostError); + RunAddHostPermission(profile(), *extension, "https://example.com/", + /*should_succeed=*/false, kInvalidHostError); + RunAddHostPermission(profile(), *extension, "https://example.com/foobar", + /*should_succeed=*/false, kInvalidHostError); + RunAddHostPermission(profile(), *extension, "https://example.com/#foobar", + /*should_succeed=*/false, kInvalidHostError); + RunAddHostPermission(profile(), *extension, "https://example.com/*foobar", + /*should_succeed=*/false, kInvalidHostError); // Cannot grant chrome:-scheme URLs. GURL chrome_host("chrome://settings/*"); - run_add_host_permission(chrome_host.spec(), false, kInvalidHostError); + RunAddHostPermission(profile(), *extension, chrome_host.spec(), + /*should_succeed=*/false, kInvalidHostError); EXPECT_FALSE(modifier.HasGrantedHostPermission(chrome_host)); } @@ -1971,6 +2015,307 @@ TEST_F(DeveloperPrivateApiUnitTest, OnUserSiteSettingsChanged) { EXPECT_TRUE(settings.restricted_sites.empty()); } +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetUserAndExtensionSitesByEtld_UserSites) { + PermissionsManager* manager = PermissionsManager::Get(browser_context()); + + // Add two sites under the eTLD+1 example.com, and one under eTLD+1 google.ca. + manager->AddUserPermittedSite( + url::Origin::Create(GURL("http://a.example.com"))); + manager->AddUserRestrictedSite( + url::Origin::Create(GURL("http://b.example.com"))); + manager->AddUserRestrictedSite(url::Origin::Create(GURL("http://google.ca"))); + + scoped_refptr<ExtensionFunction> function = base::MakeRefCounted< + api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>(); + EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError(); + const base::Value::List* results = function->GetResultList(); + ASSERT_EQ(1u, results->size()); + + EXPECT_THAT((*results)[0], base::test::IsJson(R"([{ + "etldPlusOne": "example.com", + "numExtensions": 0, + "sites": [{ + "siteList": "PERMITTED", + "numExtensions": 0, + "site": "http://a.example.com", + }, { + "siteList": "RESTRICTED", + "numExtensions": 0, + "site": "http://b.example.com", + }] + }, { + "etldPlusOne": "google.ca", + "numExtensions": 0, + "sites": [{ + "siteList": "RESTRICTED", + "numExtensions": 0, + "site": "http://google.ca", + }] + }])")); +} + +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetUserAndExtensionSitesByEtld_UserAndExtensionSites) { + PermissionsManager* manager = PermissionsManager::Get(browser_context()); + manager->AddUserPermittedSite( + url::Origin::Create(GURL("http://images.google.com"))); + manager->AddUserRestrictedSite( + url::Origin::Create(GURL("http://www.asdf.com"))); + + scoped_refptr<const Extension> extension_1 = + ExtensionBuilder("test") + .AddPermission("https://*.google.com/") + .AddPermission("http://www.google.com/") + .AddPermission("http://images.google.com/") + .AddPermission("https://example.com/") + .Build(); + + scoped_refptr<const Extension> extension_2 = + ExtensionBuilder("test_2") + .AddPermission("https://mail.google.com/") + .AddPermission("http://www.google.com/") + .AddPermission("http://www.asdf.com/") + .Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension_1); + AddExtensionAndGrantPermissions(profile(), service(), *extension_2); + + scoped_refptr<ExtensionFunction> function = base::MakeRefCounted< + api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>(); + EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError(); + const base::Value::List* results = function->GetResultList(); + ASSERT_EQ(1u, results->size()); + + // asdf.com and http://www.asdf.com should not have any extensions counted + // because they are associated with user specified sites. + EXPECT_THAT((*results)[0], base::test::IsJson(R"([{ + "etldPlusOne": "asdf.com", + "numExtensions": 0, + "sites": [{ + "siteList": "RESTRICTED", + "numExtensions": 0, + "site": "http://www.asdf.com", + }] + }, { + "etldPlusOne": "example.com", + "numExtensions": 1, + "sites": [{ + "numExtensions": 1, + "site": "https://example.com/*", + }] + }, { + "etldPlusOne": "google.com", + "numExtensions": 2, + "sites": [{ + "siteList": "PERMITTED", + "numExtensions": 0, + "site": "http://images.google.com", + }, { + "numExtensions": 2, + "site": "http://www.google.com/*", + }, { + "numExtensions": 1, + "site": "https://*.google.com/*", + }, { + "numExtensions": 1, + "site": "https://mail.google.com/*", + }] + }])")); +} + +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetUserAndExtensionSitesByEtld_EffectiveAllHosts) { + PermissionsManager* manager = PermissionsManager::Get(browser_context()); + manager->AddUserPermittedSite( + url::Origin::Create(GURL("http://images.google.ca"))); + + scoped_refptr<const Extension> extension_1 = + ExtensionBuilder("specific_hosts") + .AddPermission("https://*.google.ca/") + .AddPermission("http://www.example.com/") + .Build(); + + scoped_refptr<const Extension> extension_2 = + ExtensionBuilder("all_.com").AddPermission("*://*.com/*").Build(); + + scoped_refptr<const Extension> extension_3 = + ExtensionBuilder("all_urls").AddPermission("<all_urls>").Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension_1); + AddExtensionAndGrantPermissions(profile(), service(), *extension_2); + AddExtensionAndGrantPermissions(profile(), service(), *extension_3); + + scoped_refptr<ExtensionFunction> function = base::MakeRefCounted< + api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>(); + EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError(); + const base::Value::List* results = function->GetResultList(); + ASSERT_EQ(1u, results->size()); + + // `extension_2` should not be counted for https://*.google.ca/* as it cannot + // run on .ca sites. + EXPECT_THAT((*results)[0], base::test::IsJson(R"([{ + "etldPlusOne": "example.com", + "numExtensions": 3, + "sites": [{ + "numExtensions": 3, + "site": "http://www.example.com/*", + }] + }, { + "etldPlusOne": "google.ca", + "numExtensions": 2, + "sites": [{ + "numExtensions": 0, + "site": "http://images.google.ca", + "siteList": "PERMITTED", + }, { + "numExtensions": 2, + "site": "https://*.google.ca/*", + }] + }])")); +} + +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetUserAndExtensionSitesByEtld_RuntimeGrantedHosts) { + scoped_refptr<const Extension> extension_1 = + ExtensionBuilder("runtime_hosts").AddPermission("<all_urls>").Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension_1); + + auto get_user_and_extension_sites = [this](const std::string& expected_json) { + scoped_refptr<ExtensionFunction> function = base::MakeRefCounted< + api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>(); + EXPECT_TRUE(RunFunction(function, base::ListValue())) + << function->GetError(); + const base::Value::List* results = function->GetResultList(); + ASSERT_EQ(1u, results->size()); + EXPECT_THAT((*results)[0], base::test::IsJson(expected_json)); + }; + + get_user_and_extension_sites(R"([])"); + + ScriptingPermissionsModifier modifier(profile(), extension_1.get()); + EXPECT_FALSE(modifier.HasWithheldHostPermissions()); + modifier.SetWithholdHostPermissions(true); + + get_user_and_extension_sites(R"([])"); + + const std::string kExampleCom = "https://example.com/*"; + RunAddHostPermission(profile(), *extension_1, kExampleCom, + /*should_succeed=*/true, nullptr); + + get_user_and_extension_sites(R"([{ + "etldPlusOne": "example.com", + "numExtensions": 1, + "sites": [{ + "numExtensions": 1, + "site": "https://example.com/*", + }] + }])"); + + scoped_refptr<const Extension> extension_2 = + ExtensionBuilder("test").AddPermission(kExampleCom).Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension_2); + + get_user_and_extension_sites(R"([{ + "etldPlusOne": "example.com", + "numExtensions": 2, + "sites": [{ + "numExtensions": 2, + "site": "https://example.com/*", + }] + }])"); + + RunUpdateHostAccess(*extension_1, "ON_ALL_SITES"); + get_user_and_extension_sites(R"([{ + "etldPlusOne": "example.com", + "numExtensions": 2, + "sites": [{ + "numExtensions": 2, + "site": "https://example.com/*", + }] + }])"); +} + +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetMatchingExtensionsForSite) { + namespace developer = api::developer_private; + + scoped_refptr<const Extension> extension_1 = + ExtensionBuilder("test").AddPermission("*://mail.google.com/").Build(); + + scoped_refptr<const Extension> extension_2 = + ExtensionBuilder("test_2") + .AddPermission("*://images.google.com/") + .Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension_1); + AddExtensionAndGrantPermissions(profile(), service(), *extension_2); + + std::vector<developer::MatchingExtensionInfo> infos; + GetMatchingExtensionsForSite(profile(), "http://none.com/", &infos); + EXPECT_TRUE(infos.empty()); + + GetMatchingExtensionsForSite(profile(), "http://images.google.com/", &infos); + + // "http://images.google.com/" should only match with `extension_2`. + EXPECT_THAT(infos, testing::UnorderedElementsAre(MatchMatchingExtensionInfo( + extension_2->id(), + developer::HostAccess::HOST_ACCESS_ON_ALL_SITES))); + + service()->DisableExtension(extension_2->id(), + disable_reason::DISABLE_USER_ACTION); + GetMatchingExtensionsForSite(profile(), "*://*.google.com/", &infos); + + // "*://*.google.com/" should only match with both `extension_1` and + // `extension_2`. + EXPECT_THAT(infos, testing::UnorderedElementsAre( + MatchMatchingExtensionInfo( + extension_1->id(), + developer::HostAccess::HOST_ACCESS_ON_ALL_SITES), + MatchMatchingExtensionInfo( + extension_2->id(), + developer::HostAccess::HOST_ACCESS_ON_ALL_SITES))); +} + +// Test that the host access returned by GetMatchingExtensionsForSite reflects +// whether the extension has access to the queried site, or has withheld sites +// in general. +TEST_F(DeveloperPrivateApiUnitTest, + DeveloperPrivateGetMatchingExtensionsForSite_RuntimeGrantedHostAccess) { + namespace developer = api::developer_private; + + scoped_refptr<const Extension> extension = + ExtensionBuilder("test").AddPermission("<all_urls>").Build(); + AddExtensionAndGrantPermissions(profile(), service(), *extension); + + std::vector<developer::MatchingExtensionInfo> infos; + GetMatchingExtensionsForSite(profile(), "http://example.com/", &infos); + + EXPECT_THAT(infos, testing::UnorderedElementsAre(MatchMatchingExtensionInfo( + extension->id(), + developer::HostAccess::HOST_ACCESS_ON_ALL_SITES))); + + ScriptingPermissionsModifier modifier(profile(), extension.get()); + EXPECT_FALSE(modifier.HasWithheldHostPermissions()); + modifier.SetWithholdHostPermissions(true); + + GetMatchingExtensionsForSite(profile(), "http://example.com/", &infos); + EXPECT_THAT(infos, testing::UnorderedElementsAre(MatchMatchingExtensionInfo( + extension->id(), + developer::HostAccess::HOST_ACCESS_ON_CLICK))); + + RunAddHostPermission(profile(), *extension, "*://*.google.com/*", + /*should_succeed=*/true, nullptr); + + GetMatchingExtensionsForSite(profile(), "http://google.com/", &infos); + EXPECT_THAT(infos, + testing::UnorderedElementsAre(MatchMatchingExtensionInfo( + extension->id(), + developer::HostAccess::HOST_ACCESS_ON_SPECIFIC_SITES))); + + GetMatchingExtensionsForSite(profile(), "http://example.com/", &infos); + EXPECT_THAT(infos, testing::UnorderedElementsAre(MatchMatchingExtensionInfo( + extension->id(), + developer::HostAccess::HOST_ACCESS_ON_CLICK))); +} + class DeveloperPrivateApiAllowlistUnitTest : public DeveloperPrivateApiUnitTest { public: 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 c60fcbfdc18..f4143ef7372 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 @@ -4,6 +4,7 @@ #include "base/path_service.h" #include "base/strings/stringprintf.h" +#include "base/test/scoped_feature_list.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/devtools/devtools_window_testing.h" #include "chrome/browser/extensions/api/developer_private/developer_private_api.h" @@ -12,13 +13,20 @@ #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/test/browser_test.h" #include "content/public/test/service_worker_test_helpers.h" #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window_registry.h" #include "extensions/browser/browsertest_util.h" +#include "extensions/browser/extension_host_test_helper.h" +#include "extensions/browser/offscreen_document_host.h" +#include "extensions/browser/process_manager.h" +#include "extensions/common/extension_features.h" #include "extensions/common/manifest_handlers/background_info.h" +#include "extensions/common/mojom/view_type.mojom.h" #include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" namespace extensions { @@ -274,4 +282,103 @@ IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get())); } +class DeveloperPrivateOffscreenDocumentApiTest + : public DeveloperPrivateApiTest { + public: + DeveloperPrivateOffscreenDocumentApiTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionsOffscreenDocuments); + } + ~DeveloperPrivateOffscreenDocumentApiTest() override = default; + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Test that offscreen documents show up in the list of inspectable views and +// can be inspected. +IN_PROC_BROWSER_TEST_F(DeveloperPrivateOffscreenDocumentApiTest, + InspectOffscreenDocument) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + + // Create an offscreen document and wait for it to load. + std::unique_ptr<OffscreenDocumentHost> offscreen_document; + GURL offscreen_url = extension->GetResourceURL("offscreen.html"); + { + ExtensionHostTestHelper offscreen_waiter(profile(), extension->id()); + offscreen_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument); + offscreen_document = std::make_unique<OffscreenDocumentHost>( + *extension, + ProcessManager::Get(profile()) + ->GetSiteInstanceForURL(offscreen_url) + .get(), + offscreen_url); + offscreen_document->CreateRendererSoon(); + offscreen_waiter.WaitForHostCompletedFirstLoad(); + } + + // Get the list of inspectable views for the extension. + auto get_info_function = + base::MakeRefCounted<api::DeveloperPrivateGetExtensionInfoFunction>(); + std::unique_ptr<base::Value> result = + extension_function_test_utils::RunFunctionAndReturnSingleResult( + get_info_function.get(), + content::JsReplace(R"([$1])", extension->id()), browser()); + ASSERT_TRUE(result); + std::unique_ptr<api::developer_private::ExtensionInfo> info = + api::developer_private::ExtensionInfo::FromValue(*result); + ASSERT_TRUE(info); + + // The only inspectable view should be the offscreen document. Validate the + // metadata. + ASSERT_EQ(1u, info->views.size()); + const api::developer_private::ExtensionView& view = info->views[0]; + EXPECT_EQ(api::developer_private::VIEW_TYPE_OFFSCREEN_DOCUMENT, view.type); + content::WebContents* offscreen_contents = + offscreen_document->host_contents(); + EXPECT_EQ(offscreen_url.spec(), view.url); + EXPECT_EQ(offscreen_document->render_process_host()->GetID(), + view.render_process_id); + EXPECT_EQ(offscreen_contents->GetPrimaryMainFrame()->GetRoutingID(), + view.render_view_id); + EXPECT_FALSE(view.incognito); + EXPECT_FALSE(view.is_iframe); + + // The document shouldn't currently be under inspection. + EXPECT_FALSE( + DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents)); + + // Call the API function to inspect the offscreen document. + auto dev_tools_function = + base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); + extension_function_test_utils::RunFunction( + dev_tools_function.get(), + content::JsReplace( + R"([{"renderViewId": $1, + "renderProcessId": $2, + "extensionId": $3 + }])", + view.render_view_id, view.render_process_id, extension->id()), + browser(), api_test_utils::NONE); + + // Validate that the devtools window is now shown. + DevToolsWindow* dev_tools_window = + DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents); + ASSERT_TRUE(dev_tools_window); + + // Tidy up. + DevToolsWindowTesting::CloseDevToolsWindowSync(dev_tools_window); +} + } // namespace extensions 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 ec7ce69a263..ac0befd1ab5 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 @@ -360,45 +360,10 @@ void AddPermissionsInfo(content::BrowserContext* browser_context, extension.GetType())); permissions->simple_permissions = get_permission_messages(api_messages); - auto runtime_host_permissions = - std::make_unique<developer::RuntimeHostPermissions>(); - - ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context); - // "Effective" granted permissions are stored in different prefs, based on - // whether host permissions are withheld. - // TODO(devlin): Create a common helper method to retrieve granted prefs based - // on whether host permissions are withheld? - std::unique_ptr<const PermissionSet> granted_permissions; - // Add the host access data, including the mode and any runtime-granted - // hosts. - if (!permissions_modifier.HasWithheldHostPermissions()) { - granted_permissions = - extension_prefs->GetGrantedPermissions(extension.id()); - runtime_host_permissions->host_access = developer::HOST_ACCESS_ON_ALL_SITES; - } else { - granted_permissions = - extension_prefs->GetRuntimeGrantedPermissions(extension.id()); - if (granted_permissions->effective_hosts().is_empty()) { - runtime_host_permissions->host_access = developer::HOST_ACCESS_ON_CLICK; - } else if (granted_permissions->ShouldWarnAllHosts(false)) { - runtime_host_permissions->host_access = - developer::HOST_ACCESS_ON_ALL_SITES; - } else { - runtime_host_permissions->host_access = - developer::HOST_ACCESS_ON_SPECIFIC_SITES; - } - } - - runtime_host_permissions->hosts = GetSpecificSiteControls( - *granted_permissions, - extension.permissions_data()->withheld_permissions()); - constexpr bool kIncludeApiPermissions = false; - runtime_host_permissions->has_all_hosts = - extension.permissions_data()->withheld_permissions().ShouldWarnAllHosts( - kIncludeApiPermissions) || - granted_permissions->ShouldWarnAllHosts(kIncludeApiPermissions); - - permissions->runtime_host_permissions = std::move(runtime_host_permissions); + permissions->runtime_host_permissions = + std::make_unique<developer::RuntimeHostPermissions>( + ExtensionInfoGenerator::CreateRuntimeHostPermissionsInfo( + browser_context, extension)); } } // namespace @@ -489,6 +454,51 @@ void ExtensionInfoGenerator::CreateExtensionsInfo( } } +developer::RuntimeHostPermissions +ExtensionInfoGenerator::CreateRuntimeHostPermissionsInfo( + content::BrowserContext* browser_context, + const Extension& extension) { + ScriptingPermissionsModifier permissions_modifier( + browser_context, base::WrapRefCounted(&extension)); + developer::RuntimeHostPermissions runtime_host_permissions; + + ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context); + // "Effective" granted permissions are stored in different prefs, based on + // whether host permissions are withheld. + // TODO(devlin): Create a common helper method to retrieve granted prefs based + // on whether host permissions are withheld? + std::unique_ptr<const PermissionSet> granted_permissions; + // Add the host access data, including the mode and any runtime-granted + // hosts. + if (!permissions_modifier.HasWithheldHostPermissions()) { + granted_permissions = + extension_prefs->GetGrantedPermissions(extension.id()); + runtime_host_permissions.host_access = developer::HOST_ACCESS_ON_ALL_SITES; + } else { + granted_permissions = + extension_prefs->GetRuntimeGrantedPermissions(extension.id()); + if (granted_permissions->effective_hosts().is_empty()) { + runtime_host_permissions.host_access = developer::HOST_ACCESS_ON_CLICK; + } else if (granted_permissions->ShouldWarnAllHosts(false)) { + runtime_host_permissions.host_access = + developer::HOST_ACCESS_ON_ALL_SITES; + } else { + runtime_host_permissions.host_access = + developer::HOST_ACCESS_ON_SPECIFIC_SITES; + } + } + + runtime_host_permissions.hosts = GetSpecificSiteControls( + *granted_permissions, + extension.permissions_data()->withheld_permissions()); + constexpr bool kIncludeApiPermissions = false; + runtime_host_permissions.has_all_hosts = + extension.permissions_data()->withheld_permissions().ShouldWarnAllHosts( + kIncludeApiPermissions) || + granted_permissions->ShouldWarnAllHosts(kIncludeApiPermissions); + return runtime_host_permissions; +} + void ExtensionInfoGenerator::CreateExtensionInfoHelper( const Extension& extension, developer::ExtensionState state) { @@ -642,13 +652,18 @@ void ExtensionInfoGenerator::CreateExtensionInfoHelper( } // Location. + bool updates_from_web_store = + extension_management->UpdatesFromWebstore(extension); if (extension.location() == mojom::ManifestLocation::kInternal && - extension_management->UpdatesFromWebstore(extension)) { + updates_from_web_store) { info->location = developer::LOCATION_FROM_STORE; } else if (Manifest::IsUnpackedLocation(extension.location())) { info->location = developer::LOCATION_UNPACKED; + } else if (extension.was_installed_by_default() && + !extension.was_installed_by_oem() && updates_from_web_store) { + info->location = developer::LOCATION_INSTALLED_BY_DEFAULT; } else if (Manifest::IsExternalLocation(extension.location()) && - extension_management->UpdatesFromWebstore(extension)) { + updates_from_web_store) { info->location = developer::LOCATION_THIRD_PARTY; } else { info->location = developer::LOCATION_UNKNOWN; diff --git a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.h b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.h index fbe06dd5c14..a906c676e93 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.h +++ b/chromium/chrome/browser/extensions/api/developer_private/extension_info_generator.h @@ -64,6 +64,12 @@ class ExtensionInfoGenerator { bool include_terminated, ExtensionInfosCallback callback); + // Creates and synchronously returns a RuntimeHostPermissions object with the + // given extension's host permissions. + static api::developer_private::RuntimeHostPermissions + CreateRuntimeHostPermissionsInfo(content::BrowserContext* browser_context, + const Extension& extension); + private: // Creates an ExtensionInfo for the given |extension| and |state|, and // asynchronously adds it to the |list|. @@ -91,7 +97,7 @@ class ExtensionInfoGenerator { raw_ptr<ErrorConsole> error_console_; raw_ptr<ImageLoader> image_loader_; #if BUILDFLAG(ENABLE_SUPERVISED_USERS) - SupervisedUserService* supervised_user_service_; + raw_ptr<SupervisedUserService> supervised_user_service_; #endif // BUILDFLAG(ENABLE_SUPERVISED_USERS) // The number of pending image loads. 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 44a8dd86ce0..68ed5f23ff4 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 @@ -270,7 +270,9 @@ TEST_F(ExtensionInfoGeneratorUnitTest, BasicInfoTest) { .Build()) .Set("permissions", ListBuilder().Append("tabs").Build()) .Build(); - std::unique_ptr<base::DictionaryValue> manifest_copy(manifest->DeepCopy()); + std::unique_ptr<base::DictionaryValue> manifest_copy = + base::DictionaryValue::From( + base::Value::ToUniquePtrValue(manifest->Clone())); scoped_refptr<const Extension> extension = ExtensionBuilder() .SetManifest(std::move(manifest)) @@ -377,6 +379,63 @@ TEST_F(ExtensionInfoGeneratorUnitTest, BasicInfoTest) { EXPECT_FALSE(info->path); } +// Tests that the correct location field is returned for an extension that's +// installed by default. +TEST_F(ExtensionInfoGeneratorUnitTest, ExtensionInfoInstalledByDefault) { + profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); + + std::unique_ptr<base::DictionaryValue> manifest = + DictionaryBuilder() + .Set("name", "installed by default") + .Set("version", "1.2") + .Set("manifest_version", 3) + .Set("update_url", "https://clients2.google.com/service/update2/crx") + .Build(); + + scoped_refptr<const Extension> extension = + ExtensionBuilder() + .SetManifest(std::move(manifest)) + .SetLocation(ManifestLocation::kExternalPref) + .SetPath(data_dir()) + .SetID(crx_file::id_util::GenerateId("alpha")) + .AddFlags(Extension::WAS_INSTALLED_BY_DEFAULT) + .Build(); + service()->AddExtension(extension.get()); + + std::unique_ptr<api::developer_private::ExtensionInfo> info = + GenerateExtensionInfo(extension->id()); + EXPECT_EQ(info->location, developer::LOCATION_INSTALLED_BY_DEFAULT); +} + +// Tests that the correct location field is returned for an extension that's +// installed by the OEM. +TEST_F(ExtensionInfoGeneratorUnitTest, ExtensionInfoInstalledByOem) { + profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); + + std::unique_ptr<base::DictionaryValue> manifest = + DictionaryBuilder() + .Set("name", "installed by OEM") + .Set("version", "1.2") + .Set("manifest_version", 3) + .Set("update_url", "https://clients2.google.com/service/update2/crx") + .Build(); + + scoped_refptr<const Extension> extension = + ExtensionBuilder() + .SetManifest(std::move(manifest)) + .SetLocation(ManifestLocation::kExternalPref) + .SetPath(data_dir()) + .SetID(crx_file::id_util::GenerateId("alpha")) + .AddFlags(Extension::WAS_INSTALLED_BY_DEFAULT | + Extension::WAS_INSTALLED_BY_OEM) + .Build(); + service()->AddExtension(extension.get()); + + std::unique_ptr<api::developer_private::ExtensionInfo> info = + GenerateExtensionInfo(extension->id()); + EXPECT_EQ(info->location, developer::LOCATION_THIRD_PARTY); +} + // Test three generated json outputs. TEST_F(ExtensionInfoGeneratorUnitTest, GenerateExtensionsJSONData) { // Test Extension1 diff --git a/chromium/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc b/chromium/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc index 27fa396e606..87a5db2c54a 100644 --- a/chromium/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc +++ b/chromium/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc @@ -66,6 +66,10 @@ api::developer_private::ViewType ConvertViewType(const mojom::ViewType type) { case mojom::ViewType::kTabContents: developer_private_type = api::developer_private::VIEW_TYPE_TAB_CONTENTS; break; + case mojom::ViewType::kOffscreenDocument: + developer_private_type = + api::developer_private::VIEW_TYPE_OFFSCREEN_DOCUMENT; + break; default: developer_private_type = api::developer_private::VIEW_TYPE_NONE; NOTREACHED(); 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 21d8be994a4..7220ded81ab 100644 --- a/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc +++ b/chromium/chrome/browser/extensions/api/device_permissions_manager_unittest.cc @@ -19,7 +19,7 @@ #include "extensions/browser/extension_prefs.h" #include "extensions/common/extension.h" #include "mojo/public/cpp/bindings/pending_remote.h" -#include "services/device/public/cpp/hid/fake_hid_manager.h" +#include "services/device/public/cpp/test/fake_hid_manager.h" #include "services/device/public/cpp/test/fake_usb_device_manager.h" #include "services/device/public/mojom/hid.mojom.h" #include "services/device/public/mojom/usb_device.mojom.h" diff --git a/chromium/chrome/browser/extensions/api/document_scan/document_scan_api.h b/chromium/chrome/browser/extensions/api/document_scan/document_scan_api.h index 8b45a9602fe..6bbb36508a5 100644 --- a/chromium/chrome/browser/extensions/api/document_scan/document_scan_api.h +++ b/chromium/chrome/browser/extensions/api/document_scan/document_scan_api.h @@ -9,6 +9,7 @@ #include <string> #include <vector> +#include "base/memory/raw_ptr.h" #include "chrome/common/extensions/api/document_scan.h" #include "chromeos/crosapi/mojom/document_scan.mojom-forward.h" #include "extensions/browser/extension_function.h" @@ -49,7 +50,7 @@ class DocumentScanScanFunction : public ExtensionFunction { // Null if the interface is unavailable. // The pointer is constant - if Ash crashes and the mojo connection is lost, // Lacros will automatically be restarted. - crosapi::mojom::DocumentScan* document_scan_ = nullptr; + raw_ptr<crosapi::mojom::DocumentScan> document_scan_ = nullptr; }; } // namespace api diff --git a/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc b/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc index 9b2e75bf4da..1d58b5ad000 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api.cc @@ -33,6 +33,8 @@ #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/download/bubble/download_bubble_controller.h" +#include "chrome/browser/download/bubble/download_bubble_prefs.h" #include "chrome/browser/download/download_core_service.h" #include "chrome/browser/download/download_core_service_factory.h" #include "chrome/browser/download/download_danger_prompt.h" @@ -114,6 +116,10 @@ const char kTooManyListeners[] = "Each extension may have at most one " "onDeterminingFilename listener between all of its renderer execution " "contexts."; +const char kUiDisabled[] = "Another extension has disabled the download UI"; +const char kUiPermission[] = + "downloads.setUiOptions requires the " + "\"downloads.ui\" permission"; const char kUnexpectedDeterminer[] = "Unexpected determineFilename call"; const char kUserGesture[] = "User gesture required"; @@ -441,7 +447,7 @@ bool ShouldExport(const DownloadItem& download_item) { // Set |manager| to the on-record DownloadManager, and |incognito_manager| to // the off-record DownloadManager if one exists and is requested via -// |include_incognito|. This should work regardless of whether |profile| is +// |include_incognito|. This should work regardless of whether |context| is // original or incognito. void GetManagers(content::BrowserContext* context, bool include_incognito, @@ -459,6 +465,40 @@ void GetManagers(content::BrowserContext* context, } } +// Set |service| to the on-record DownloadCoreService, |incognito_service| to +// the off-record DownloadCoreService if one exists and is requested via +// |include_incognito|. This should work regardless of whether |context| is +// original or incognito. +void GetDownloadCoreServices(content::BrowserContext* context, + bool include_incognito, + DownloadCoreService** service, + DownloadCoreService** incognito_service) { + DownloadManager* manager = nullptr; + DownloadManager* incognito_manager = nullptr; + GetManagers(context, include_incognito, &manager, &incognito_manager); + if (manager) { + *service = DownloadCoreServiceFactory::GetForBrowserContext( + manager->GetBrowserContext()); + } + if (incognito_manager) { + *incognito_service = DownloadCoreServiceFactory::GetForBrowserContext( + incognito_manager->GetBrowserContext()); + } +} + +void MaybeSetUiEnabled(DownloadCoreService* service, + DownloadCoreService* incognito_service, + const Extension* extension, + bool enabled) { + if (service) { + service->GetExtensionEventRouter()->SetUiEnabled(extension, enabled); + } + if (incognito_service) { + incognito_service->GetExtensionEventRouter()->SetUiEnabled(extension, + enabled); + } +} + DownloadItem* GetDownload(content::BrowserContext* context, bool include_incognito, int id) { @@ -490,6 +530,7 @@ enum DownloadsFunctionName { DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER = 13, DOWNLOADS_FUNCTION_SET_SHELF_ENABLED = 14, DOWNLOADS_FUNCTION_DETERMINE_FILENAME = 15, + DOWNLOADS_FUNCTION_SET_UI_OPTIONS = 16, // Insert new values here, not at the beginning. DOWNLOADS_FUNCTION_LAST }; @@ -1462,48 +1503,105 @@ ExtensionFunction::ResponseAction DownloadsSetShelfEnabledFunction::Run() { } RecordApiFunctions(DOWNLOADS_FUNCTION_SET_SHELF_ENABLED); - DownloadManager* manager = NULL; - DownloadManager* incognito_manager = NULL; - GetManagers(browser_context(), include_incognito_information(), &manager, - &incognito_manager); - DownloadCoreService* service = NULL; - DownloadCoreService* incognito_service = NULL; - if (manager) { - service = DownloadCoreServiceFactory::GetForBrowserContext( - manager->GetBrowserContext()); - service->GetExtensionEventRouter()->SetShelfEnabled(extension(), - params->enabled); - } - if (incognito_manager) { - incognito_service = DownloadCoreServiceFactory::GetForBrowserContext( - incognito_manager->GetBrowserContext()); - incognito_service->GetExtensionEventRouter()->SetShelfEnabled( - extension(), params->enabled); - } + DownloadCoreService* service = nullptr; + DownloadCoreService* incognito_service = nullptr; + GetDownloadCoreServices(browser_context(), include_incognito_information(), + &service, &incognito_service); + + MaybeSetUiEnabled(service, incognito_service, extension(), params->enabled); + + bool is_bubble_enabled = download::IsDownloadBubbleEnabled( + Profile::FromBrowserContext(browser_context())); BrowserList* browsers = BrowserList::GetInstance(); if (browsers) { - for (auto iter = browsers->begin(); iter != browsers->end(); ++iter) { - const Browser* browser = *iter; + for (auto* browser : *browsers) { DownloadCoreService* current_service = DownloadCoreServiceFactory::GetForBrowserContext(browser->profile()); - if (((current_service == service) || - (current_service == incognito_service)) && - browser->window()->IsDownloadShelfVisible() && - !current_service->IsShelfEnabled()) + // The following code is to hide the download UI explicitly if the UI is + // set to disabled. + bool match_current_service = (current_service == service) || + (current_service == incognito_service); + if (!match_current_service || current_service->IsDownloadUiEnabled()) { + continue; + } + // Calling this API affects the download bubble as well, so extensions + // using this API is still compatible with the new download bubble. This + // API will eventually be deprecated (replaced by the SetUiOptions API + // below). + if (is_bubble_enabled && + browser->window()->GetDownloadBubbleUIController()) { + browser->window()->GetDownloadBubbleUIController()->HideDownloadUi(); + } else if (browser->window()->IsDownloadShelfVisible()) { browser->window()->GetDownloadShelf()->Close(); + } } } if (params->enabled && - ((manager && !service->IsShelfEnabled()) || - (incognito_manager && !incognito_service->IsShelfEnabled()))) { + ((service && !service->IsDownloadUiEnabled()) || + (incognito_service && !incognito_service->IsDownloadUiEnabled()))) { return RespondNow(Error(download_extension_errors::kShelfDisabled)); } return RespondNow(NoArguments()); } +DownloadsSetUiOptionsFunction::DownloadsSetUiOptionsFunction() = default; + +DownloadsSetUiOptionsFunction::~DownloadsSetUiOptionsFunction() = default; + +ExtensionFunction::ResponseAction DownloadsSetUiOptionsFunction::Run() { + std::unique_ptr<downloads::SetUiOptions::Params> params( + downloads::SetUiOptions::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params.get()); + const downloads::UiOptions& options = params->options; + if (!extension()->permissions_data()->HasAPIPermission( + APIPermissionID::kDownloadsUi)) { + return RespondNow(Error(download_extension_errors::kUiPermission)); + } + + RecordApiFunctions(DOWNLOADS_FUNCTION_SET_UI_OPTIONS); + DownloadCoreService* service = nullptr; + DownloadCoreService* incognito_service = nullptr; + GetDownloadCoreServices(browser_context(), include_incognito_information(), + &service, &incognito_service); + + MaybeSetUiEnabled(service, incognito_service, extension(), options.enabled); + + bool is_bubble_enabled = download::IsDownloadBubbleEnabled( + Profile::FromBrowserContext(browser_context())); + + BrowserList* browsers = BrowserList::GetInstance(); + if (browsers) { + for (auto* browser : *browsers) { + DownloadCoreService* current_service = + DownloadCoreServiceFactory::GetForBrowserContext(browser->profile()); + // The following code is to hide the download UI explicitly if the UI is + // set to disabled. + bool match_current_service = (current_service == service) || + (current_service == incognito_service); + if (!match_current_service || current_service->IsDownloadUiEnabled()) { + continue; + } + if (is_bubble_enabled && + browser->window()->GetDownloadBubbleUIController()) { + browser->window()->GetDownloadBubbleUIController()->HideDownloadUi(); + } else if (browser->window()->IsDownloadShelfVisible()) { + browser->window()->GetDownloadShelf()->Close(); + } + } + } + + if (options.enabled && + ((service && !service->IsDownloadUiEnabled()) || + (incognito_service && !incognito_service->IsDownloadUiEnabled()))) { + return RespondNow(Error(download_extension_errors::kUiDisabled)); + } + + return RespondNow(NoArguments()); +} + DownloadsGetFileIconFunction::DownloadsGetFileIconFunction() : icon_extractor_(new DownloadFileIconExtractorImpl()) {} @@ -1583,19 +1681,24 @@ void ExtensionDownloadsEventRouter:: SetDetermineFilenameTimeoutSecondsForTesting(s); } -void ExtensionDownloadsEventRouter::SetShelfEnabled(const Extension* extension, - bool enabled) { - auto iter = shelf_disabling_extensions_.find(extension); - if (iter == shelf_disabling_extensions_.end()) { +void ExtensionDownloadsEventRouter::SetUiEnabled(const Extension* extension, + bool enabled) { + auto iter = ui_disabling_extensions_.find(extension); + if (iter == ui_disabling_extensions_.end()) { if (!enabled) - shelf_disabling_extensions_.insert(extension); + ui_disabling_extensions_.insert(extension); } else if (enabled) { - shelf_disabling_extensions_.erase(extension); + ui_disabling_extensions_.erase(extension); } } -bool ExtensionDownloadsEventRouter::IsShelfEnabled() const { - return shelf_disabling_extensions_.empty(); +bool ExtensionDownloadsEventRouter::IsUiEnabled() const { + return ui_disabling_extensions_.empty(); +} + +bool ExtensionDownloadsEventRouter::IsDownloadObservedByExtension() const { + EventRouter* router = EventRouter::Get(profile_); + return router && router->HasEventListener(downloads::OnChanged::kEventName); } // The method by which extensions hook into the filename determination process @@ -1910,8 +2013,8 @@ void ExtensionDownloadsEventRouter::DispatchEvent( DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!EventRouter::Get(profile_)) return; - std::vector<base::Value> args; - args.push_back(std::move(arg)); + base::Value::List args; + args.Append(std::move(arg)); // The downloads system wants to share on-record events with off-record // extension renderers even in incognito_split_mode because that's how // chrome://downloads works. The "restrict_to_profile" mechanism does not @@ -1936,9 +2039,9 @@ void ExtensionDownloadsEventRouter::OnExtensionUnloaded( const Extension* extension, UnloadedExtensionReason reason) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - auto iter = shelf_disabling_extensions_.find(extension); - if (iter != shelf_disabling_extensions_.end()) - shelf_disabling_extensions_.erase(iter); + auto iter = ui_disabling_extensions_.find(extension); + if (iter != ui_disabling_extensions_.end()) + ui_disabling_extensions_.erase(iter); } void ExtensionDownloadsEventRouter::CheckForHistoryFilesRemoval() { diff --git a/chromium/chrome/browser/extensions/api/downloads/downloads_api.h b/chromium/chrome/browser/extensions/api/downloads/downloads_api.h index 994116d8716..3aef44c043d 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api.h +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api.h @@ -59,6 +59,8 @@ extern const char kOpenPermission[]; extern const char kShelfDisabled[]; extern const char kShelfPermission[]; extern const char kTooManyListeners[]; +extern const char kUiDisabled[]; +extern const char kUiPermission[]; extern const char kUnexpectedDeterminer[]; extern const char kUserGesture[]; @@ -299,6 +301,21 @@ class DownloadsSetShelfEnabledFunction : public ExtensionFunction { ~DownloadsSetShelfEnabledFunction() override; }; +class DownloadsSetUiOptionsFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("downloads.setUiOptions", DOWNLOADS_SETUIOPTIONS) + DownloadsSetUiOptionsFunction(); + + DownloadsSetUiOptionsFunction(const DownloadsSetUiOptionsFunction&) = delete; + DownloadsSetUiOptionsFunction& operator=( + const DownloadsSetUiOptionsFunction&) = delete; + + ResponseAction Run() override; + + protected: + ~DownloadsSetUiOptionsFunction() override; +}; + class DownloadsGetFileIconFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("downloads.getFileIcon", DOWNLOADS_GETFILEICON) @@ -373,8 +390,10 @@ class ExtensionDownloadsEventRouter ~ExtensionDownloadsEventRouter() override; - void SetShelfEnabled(const extensions::Extension* extension, bool enabled); - bool IsShelfEnabled() const; + void SetUiEnabled(const extensions::Extension* extension, bool enabled); + bool IsUiEnabled() const; + + bool IsDownloadObservedByExtension() const; // Called by ChromeDownloadManagerDelegate during the filename determination // process, allows extensions to change the item's target filename. If no @@ -415,7 +434,7 @@ class ExtensionDownloadsEventRouter raw_ptr<Profile> profile_; download::AllDownloadItemNotifier notifier_; - std::set<const extensions::Extension*> shelf_disabling_extensions_; + std::set<const extensions::Extension*> ui_disabling_extensions_; base::Time last_checked_removal_; 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 65b3ff5b730..8c94fbb4dfd 100644 --- a/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc @@ -28,6 +28,9 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "build/build_config.h" +#include "chrome/browser/download/bubble/download_bubble_controller.h" +#include "chrome/browser/download/bubble/download_display.h" +#include "chrome/browser/download/bubble/download_display_controller.h" #include "chrome/browser/download/download_core_service.h" #include "chrome/browser/download/download_core_service_factory.h" #include "chrome/browser/download/download_file_icon_extractor.h" @@ -41,6 +44,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/extensions/extension_action_test_helper.h" #include "chrome/common/extensions/api/downloads.h" #include "chrome/common/pref_names.h" @@ -48,6 +52,8 @@ #include "chrome/test/base/ui_test_utils.h" #include "components/download/public/common/download_item.h" #include "components/prefs/pref_service.h" +#include "components/safe_browsing/content/common/file_type_policies_test_util.h" +#include "components/safe_browsing/core/common/features.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -208,7 +214,8 @@ class DownloadsEventsListener : public EventRouter::TestObserver { Event* new_event = new Event( Profile::FromBrowserContext(event.restrict_to_browser_context), - event.event_name, *event.event_args.get(), base::Time::Now()); + event.event_name, base::Value(event.event_args.Clone()), + base::Time::Now()); events_.push_back(base::WrapUnique(new_event)); if (waiting_ && waiting_for_.get() && new_event->Satisfies(*waiting_for_)) { waiting_ = false; @@ -332,30 +339,11 @@ class DownloadExtensionTest : public ExtensionApiTest { }; void LoadExtension(const char* name, bool enable_file_access = false) { - // Store the created Extension object so that we can attach it to - // ExtensionFunctions. Also load the extension in incognito profiles for - // testing incognito. - extension_ = ExtensionBrowserTest::LoadExtension( - test_data_dir_.AppendASCII(name), - {.allow_in_incognito = true, .allow_file_access = enable_file_access}); - CHECK(extension_); - content::WebContents* tab = chrome::AddSelectedTabWithURL( - current_browser(), - extension_->GetResourceURL("empty.html"), - ui::PAGE_TRANSITION_LINK); - EXPECT_TRUE(content::WaitForLoadStop(tab)); - EventRouter::Get(current_browser()->profile()) - ->AddEventListener(downloads::OnCreated::kEventName, - tab->GetPrimaryMainFrame()->GetProcess(), - GetExtensionId()); - EventRouter::Get(current_browser()->profile()) - ->AddEventListener(downloads::OnChanged::kEventName, - tab->GetPrimaryMainFrame()->GetProcess(), - GetExtensionId()); - EventRouter::Get(current_browser()->profile()) - ->AddEventListener(downloads::OnErased::kEventName, - tab->GetPrimaryMainFrame()->GetProcess(), - GetExtensionId()); + extension_ = LoadExtensionInternal(name, enable_file_access); + } + + void LoadSecondExtension(const char* name) { + second_extension_ = LoadExtensionInternal(name, false); } content::RenderProcessHost* AddFilenameDeterminer() { @@ -468,6 +456,7 @@ class DownloadExtensionTest : public ExtensionApiTest { std::string GetExtensionId() { return extension_->id(); } + std::string GetSecondExtensionId() { return second_extension_->id(); } std::string GetFilename(const char* path) { std::string result = downloads_directory().AppendASCII(path).AsUTF8Unsafe(); @@ -626,14 +615,12 @@ class DownloadExtensionTest : public ExtensionApiTest { } bool RunFunction(ExtensionFunction* function, const std::string& args) { - scoped_refptr<ExtensionFunction> delete_function(function); - SetUpExtensionFunction(function); - bool result = extension_function_test_utils::RunFunction( - function, args, current_browser(), GetFlags()); - if (!result) { - LOG(ERROR) << function->GetError(); - } - return result; + return RunFunctionInternal(extension_, function, args); + } + + bool RunFunctionInSecondExtension(ExtensionFunction* function, + const std::string& args) { + return RunFunctionInternal(second_extension_, function, args); } api_test_utils::RunFunctionFlags GetFlags() { @@ -649,7 +636,7 @@ class DownloadExtensionTest : public ExtensionApiTest { std::unique_ptr<base::Value> RunFunctionAndReturnResult( scoped_refptr<ExtensionFunction> function, const std::string& args) { - SetUpExtensionFunction(function.get()); + SetUpExtensionFunction(extension_, function.get()); return extension_function_test_utils::RunFunctionAndReturnSingleResult( function.get(), args, current_browser(), GetFlags()); } @@ -657,7 +644,15 @@ class DownloadExtensionTest : public ExtensionApiTest { std::string RunFunctionAndReturnError( scoped_refptr<ExtensionFunction> function, const std::string& args) { - SetUpExtensionFunction(function.get()); + SetUpExtensionFunction(extension_, function.get()); + return extension_function_test_utils::RunFunctionAndReturnError( + function.get(), args, current_browser(), GetFlags()); + } + + std::string RunFunctionAndReturnErrorInSecondExtension( + scoped_refptr<ExtensionFunction> function, + const std::string& args) { + SetUpExtensionFunction(second_extension_, function.get()); return extension_function_test_utils::RunFunctionAndReturnError( function.get(), args, current_browser(), GetFlags()); } @@ -665,7 +660,7 @@ class DownloadExtensionTest : public ExtensionApiTest { bool RunFunctionAndReturnString(scoped_refptr<ExtensionFunction> function, const std::string& args, std::string* result_string) { - SetUpExtensionFunction(function.get()); + SetUpExtensionFunction(extension_, function.get()); std::unique_ptr<base::Value> result( RunFunctionAndReturnResult(function, args)); EXPECT_TRUE(result.get()); @@ -689,12 +684,13 @@ class DownloadExtensionTest : public ExtensionApiTest { const Extension* extension() { return extension_; } private: - void SetUpExtensionFunction(ExtensionFunction* function) { - if (extension_) { + void SetUpExtensionFunction(const raw_ptr<const Extension>& extension, + ExtensionFunction* function) { + if (extension) { const GURL url = current_browser_ == incognito_browser_ && - !IncognitoInfo::IsSplitMode(extension_) + !IncognitoInfo::IsSplitMode(extension) ? GURL(url::kAboutBlankURL) - : extension_->GetResourceURL("empty.html"); + : extension->GetResourceURL("empty.html"); // Watch and wait for the navigation to take place. auto observer = std::make_unique<content::TestNavigationObserver>(url); observer->WatchExistingWebContents(); @@ -703,14 +699,56 @@ class DownloadExtensionTest : public ExtensionApiTest { content::WebContents* tab = chrome::AddSelectedTabWithURL( current_browser(), url, ui::PAGE_TRANSITION_LINK); observer->WaitForNavigationFinished(); - function->set_extension(extension_.get()); + function->set_extension(extension.get()); function->SetRenderFrameHost(tab->GetPrimaryMainFrame()); function->set_source_process_id( tab->GetPrimaryMainFrame()->GetProcess()->GetID()); } } + bool RunFunctionInternal(const raw_ptr<const Extension>& extension, + ExtensionFunction* function, + const std::string& args) { + scoped_refptr<ExtensionFunction> delete_function(function); + SetUpExtensionFunction(extension, function); + bool result = extension_function_test_utils::RunFunction( + function, args, current_browser(), GetFlags()); + if (!result) { + LOG(ERROR) << function->GetError(); + } + return result; + } + + raw_ptr<const Extension> LoadExtensionInternal(const char* name, + bool enable_file_access) { + // Store the created Extension object so that we can attach it to + // ExtensionFunctions. Also load the extension in incognito profiles for + // testing incognito. + raw_ptr<const Extension> extension = ExtensionBrowserTest::LoadExtension( + test_data_dir_.AppendASCII(name), + {.allow_in_incognito = true, .allow_file_access = enable_file_access}); + CHECK(extension); + content::WebContents* tab = chrome::AddSelectedTabWithURL( + current_browser(), extension->GetResourceURL("empty.html"), + ui::PAGE_TRANSITION_LINK); + EXPECT_TRUE(content::WaitForLoadStop(tab)); + EventRouter::Get(current_browser()->profile()) + ->AddEventListener(downloads::OnCreated::kEventName, + tab->GetPrimaryMainFrame()->GetProcess(), + extension->id()); + EventRouter::Get(current_browser()->profile()) + ->AddEventListener(downloads::OnChanged::kEventName, + tab->GetPrimaryMainFrame()->GetProcess(), + extension->id()); + EventRouter::Get(current_browser()->profile()) + ->AddEventListener(downloads::OnErased::kEventName, + tab->GetPrimaryMainFrame()->GetProcess(), + extension->id()); + return extension; + } + raw_ptr<const Extension> extension_; + raw_ptr<const Extension> second_extension_; raw_ptr<Browser> incognito_browser_; raw_ptr<Browser> current_browser_; std::unique_ptr<DownloadsEventsListener> events_listener_; @@ -1852,7 +1890,7 @@ class CustomResponse : public net::test_server::HttpResponse { void SendResponse( base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override { base::StringPairs headers = { - //"HTTP/1.1 200 OK\r\n" + // "HTTP/1.1 200 OK\r\n" {"Content-type", "application/octet-stream"}, {"Cache-Control", "max-age=0"}, }; @@ -1967,6 +2005,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, "hOsT", "kEEp-aLivE", "rEfErEr", + "sEt-cOoKiE", "tE", "trAilER", "trANsfer-eNcodiNg", @@ -3194,6 +3233,9 @@ IN_PROC_BROWSER_TEST_F( IN_PROC_BROWSER_TEST_F( DownloadExtensionTest, DownloadExtensionTest_OnDeterminingFilename_SafeOverride) { + safe_browsing::FileTypePoliciesTestOverlay scoped_dangerous = + safe_browsing::ScopedMarkAllFilesDangerousForTesting(); + GoOnTheRecord(); LoadExtension("downloads_split"); AddFilenameDeterminer(); @@ -4574,17 +4616,13 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, EXPECT_TRUE(RunFunction(new DownloadsSetShelfEnabledFunction(), "[false]")); EXPECT_FALSE(DownloadCoreServiceFactory::GetForBrowserContext( current_browser()->profile()) - ->IsShelfEnabled()); + ->IsDownloadUiEnabled()); EXPECT_TRUE(RunFunction(new DownloadsSetShelfEnabledFunction(), "[true]")); EXPECT_TRUE(DownloadCoreServiceFactory::GetForBrowserContext( current_browser()->profile()) - ->IsShelfEnabled()); - // TODO(benjhayden) Test that existing shelves are hidden. - // TODO(benjhayden) Test multiple extensions. - // TODO(benjhayden) Test disabling extensions. + ->IsDownloadUiEnabled()); // TODO(benjhayden) Test that browsers associated with other profiles are not // affected. - // TODO(benjhayden) Test incognito. } // TODO(benjhayden) Figure out why DisableExtension() does not fire @@ -4593,6 +4631,21 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, // TODO(benjhayden) Test that the shelf is shown for download() both with and // without a WebContents. +IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, + DownloadExtensionTest_SetUiOptions) { + LoadExtension("downloads_split"); + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + EXPECT_FALSE(DownloadCoreServiceFactory::GetForBrowserContext( + current_browser()->profile()) + ->IsDownloadUiEnabled()); + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": true}])")); + EXPECT_TRUE(DownloadCoreServiceFactory::GetForBrowserContext( + current_browser()->profile()) + ->IsDownloadUiEnabled()); +} + void OnDangerPromptCreated(DownloadDangerPrompt* prompt) { prompt->InvokeActionForTesting(DownloadDangerPrompt::ACCEPT); } @@ -4608,6 +4661,9 @@ void OnDangerPromptCreated(DownloadDangerPrompt* prompt) { #endif IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, MAYBE_DownloadExtensionTest_AcceptDanger) { + safe_browsing::FileTypePoliciesTestOverlay scoped_dangerous = + safe_browsing::ScopedMarkAllFilesDangerousForTesting(); + // Download a file that will be marked dangerous; click the browser action // button; the browser action poup will call acceptDanger(); when the // DownloadDangerPrompt is created, pretend that the user clicks the Accept @@ -4697,6 +4753,141 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest, result_id))); } +// The DownloadExtensionBubbleEnabledTest relies on the download surface, which +// ChromeOS_ASH doesn't use (see crbug.com/1323505). +#if !BUILDFLAG(IS_CHROMEOS_ASH) +class DownloadExtensionBubbleEnabledTest : public DownloadExtensionTest { + public: + DownloadExtensionBubbleEnabledTest() { + feature_list_.InitAndEnableFeature(safe_browsing::kDownloadBubble); + } + + DownloadDisplay* GetDownloadToolbarButton() { + return current_browser() + ->window() + ->GetDownloadBubbleUIController() + ->GetDownloadDisplayController() + ->download_display_for_testing(); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest, + DownloadExtensionBubbleEnabledTest_SetUiOptions) { + DownloadManager::DownloadVector items; + CreateTwoDownloads(&items); + ScopedItemVectorCanceller delete_items(&items); + LoadExtension("downloads_split"); + + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": true}])")); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); + + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); + + items[0]->Cancel(true); + // Remain hidden on download updates. + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionBubbleEnabledTest, + DownloadExtensionBubbleEnabledTest_SetUiOptionsBeforeDownloadStart) { + LoadExtension("downloads_split"); + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + DownloadManager::DownloadVector items; + CreateTwoDownloads(&items); + ScopedItemVectorCanceller delete_items(&items); + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionBubbleEnabledTest, + DownloadExtensionBubbleEnabledTest_SetUiOptionsShowDetails) { + LoadExtension("downloads_split"); + DownloadManager::DownloadVector items; + CreateTwoDownloads(&items); + ScopedItemVectorCanceller delete_items(&items); + + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); + // Details are not shown because the download item is observed by an + // extension. + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowingDetails()); + + items[0]->Cancel(true); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); + // Details are not shown because the download item is observed by an + // extension. + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowingDetails()); + + DisableExtension(GetExtensionId()); + items[1]->Cancel(true); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); + // Details are shown because the extension is disabled. + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowingDetails()); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionBubbleEnabledTest, + DownloadExtensionBubbleEnabledTest_SetUiOptionsOffTheRecord) { + LoadExtension("downloads_split"); + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + DownloadManager::DownloadVector items; + CreateTwoDownloads(&items); + ScopedItemVectorCanceller delete_items(&items); + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); + + GoOffTheRecord(); + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); + + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": true}])")); + items[0]->Cancel(true); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); + + GoOnTheRecord(); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); +} + +IN_PROC_BROWSER_TEST_F( + DownloadExtensionBubbleEnabledTest, + DownloadExtensionBubbleEnabledTest_SetUiOptionsMultipleExtensions) { + LoadExtension("downloads_split"); + EXPECT_TRUE(RunFunction(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + DownloadManager::DownloadVector items; + CreateTwoDownloads(&items); + ScopedItemVectorCanceller delete_items(&items); + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); + + LoadSecondExtension("downloads_spanning"); + // Returns error because the first extension has disabled the UI. + EXPECT_STREQ(errors::kUiDisabled, RunFunctionAndReturnErrorInSecondExtension( + new DownloadsSetUiOptionsFunction(), + R"([{"enabled": true}])") + .c_str()); + // Two extensions can set the UI to disabled at the same time. No error should + // be returned. + EXPECT_TRUE(RunFunctionInSecondExtension(new DownloadsSetUiOptionsFunction(), + R"([{"enabled": false}])")); + + DisableExtension(GetExtensionId()); + items[0]->Pause(); + // The UI keeps disabled because the second extension has set it to disabled. + EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing()); + + DisableExtension(GetSecondExtensionId()); + items[0]->Cancel(true); + EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing()); +} +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) + class DownloadsApiTest : public ExtensionApiTest { public: DownloadsApiTest() {} diff --git a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/DIR_METADATA b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/DIR_METADATA index 07b250ad60f..fa5cbf478ce 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_device_attributes/DIR_METADATA +++ b/chromium/chrome/browser/extensions/api/enterprise_device_attributes/DIR_METADATA @@ -1,3 +1,3 @@ -monorail { - component: "OS>Software>Enterprise" +buganizer: { + component_id: 620570 } 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 e74209d89f9..6d7f4bef40b 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 @@ -11,7 +11,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chromeos/dbus/session_manager/fake_session_manager_client.h" +#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" #include "chromeos/system/fake_statistics_provider.h" #include "chromeos/system/statistics_provider.h" #include "components/user_manager/user_manager.h" @@ -83,9 +83,9 @@ class EnterpriseDeviceAttributesTest proto->set_device_hostname_template(kHostname); device_policy->Build(); - chromeos::FakeSessionManagerClient::Get()->set_device_policy( + ash::FakeSessionManagerClient::Get()->set_device_policy( device_policy->GetBlob()); - chromeos::FakeSessionManagerClient::Get()->OnPropertyChangeComplete(true); + ash::FakeSessionManagerClient::Get()->OnPropertyChangeComplete(true); } private: diff --git a/chromium/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc b/chromium/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc index e10c67337c9..dc9aa25776c 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc @@ -10,10 +10,10 @@ #include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h" #include "chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chromeos/dbus/shill/shill_device_client.h" -#include "chromeos/dbus/shill/shill_ipconfig_client.h" -#include "chromeos/dbus/shill/shill_profile_client.h" -#include "chromeos/dbus/shill/shill_service_client.h" +#include "chromeos/ash/components/dbus/shill/shill_device_client.h" +#include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h" +#include "chromeos/ash/components/dbus/shill/shill_profile_client.h" +#include "chromeos/ash/components/dbus/shill/shill_service_client.h" #include "content/public/test/browser_test.h" #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" #include "url/gurl.h" @@ -77,14 +77,14 @@ class EnterpriseNetworkingAttributesTest std::get<1>(GetParam())) {} void SetupDisconnectedNetwork() { - chromeos::ShillDeviceClient::TestInterface* shill_device_client = - chromeos::ShillDeviceClient::Get()->GetTestInterface(); - chromeos::ShillIPConfigClient::TestInterface* shill_ipconfig_client = - chromeos::ShillIPConfigClient::Get()->GetTestInterface(); - chromeos::ShillServiceClient::TestInterface* shill_service_client = - chromeos::ShillServiceClient::Get()->GetTestInterface(); - chromeos::ShillProfileClient::TestInterface* shill_profile_client = - chromeos::ShillProfileClient::Get()->GetTestInterface(); + ash::ShillDeviceClient::TestInterface* shill_device_client = + ash::ShillDeviceClient::Get()->GetTestInterface(); + ash::ShillIPConfigClient::TestInterface* shill_ipconfig_client = + ash::ShillIPConfigClient::Get()->GetTestInterface(); + ash::ShillServiceClient::TestInterface* shill_service_client = + ash::ShillServiceClient::Get()->GetTestInterface(); + ash::ShillProfileClient::TestInterface* shill_profile_client = + ash::ShillProfileClient::Get()->GetTestInterface(); shill_service_client->ClearServices(); shill_device_client->ClearDevices(); @@ -125,14 +125,14 @@ class EnterpriseNetworkingAttributesTest kWifiServicePath, shill::kConnectableProperty, base::Value(true)); shill_profile_client->AddService( - chromeos::ShillProfileClient::GetSharedProfilePath(), kWifiServicePath); + ash::ShillProfileClient::GetSharedProfilePath(), kWifiServicePath); base::RunLoop().RunUntilIdle(); } void ConnectNetwork() { - chromeos::ShillServiceClient::TestInterface* shill_service_client = - chromeos::ShillServiceClient::Get()->GetTestInterface(); + ash::ShillServiceClient::TestInterface* shill_service_client = + ash::ShillServiceClient::Get()->GetTestInterface(); shill_service_client->SetServiceProperty(kWifiServicePath, shill::kStateProperty, base::Value(shill::kStateOnline)); diff --git a/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc b/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc index 4b40d6e437e..b922e5144f0 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc @@ -132,13 +132,10 @@ bool IsExtensionAllowed(Profile* profile, const Extension* extension) { // allowed in chrome/common/extensions/api/_permission_features.json return true; } - const base::Value* list = - profile->GetPrefs()->GetList(prefs::kAttestationExtensionAllowlist); - DCHECK_NE(list, nullptr); + const base::Value::List& list = + profile->GetPrefs()->GetValueList(prefs::kAttestationExtensionAllowlist); base::Value value(extension->id()); - return std::find(list->GetListDeprecated().begin(), - list->GetListDeprecated().end(), - value) != list->GetListDeprecated().end(); + return base::Contains(list, value); } } // namespace platform_keys @@ -250,8 +247,8 @@ void EnterprisePlatformKeysGetCertificatesFunction::OnGetCertificates( client_certs.Append(base::Value(std::move(cert))); } - std::vector<base::Value> results; - results.emplace_back(std::move(client_certs)); + base::Value::List results; + results.Append(std::move(client_certs)); Respond(ArgumentList(std::move(results))); } 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 c22d9b597ce..f8f6bdaae70 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 @@ -39,14 +39,13 @@ namespace { const char kUserEmail[] = "test@google.com"; -void FakeRunCheckNotRegister( - chromeos::attestation::AttestationKeyType key_type, - Profile* profile, - ash::attestation::TpmChallengeKeyCallback callback, - const std::string& challenge, - bool register_key, - const std::string& key_name_for_spkac, - const absl::optional<::attestation::DeviceTrustSignals>& signals) { +void FakeRunCheckNotRegister(chromeos::attestation::AttestationKeyType key_type, + Profile* profile, + ash::attestation::TpmChallengeKeyCallback callback, + const std::string& challenge, + bool register_key, + const std::string& key_name_for_spkac, + const absl::optional<std::string>& signals) { EXPECT_FALSE(register_key); std::move(callback).Run( ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse( diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/DEPS b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/DEPS new file mode 100644 index 00000000000..3023fa83c91 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+components/device_signals/core", +] diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.cc b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.cc new file mode 100644 index 00000000000..25798a6b8e3 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.cc @@ -0,0 +1,218 @@ +// Copyright 2022 The Chromium Authors. All 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/enterprise_reporting_private/conversion_utils.h" + +#include "build/build_config.h" + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +#include <memory> +#include <utility> + +#include "base/base64url.h" +#include "base/files/file_path.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "components/device_signals/core/common/common_types.h" +#include "extensions/browser/extension_function.h" + +#if BUILDFLAG(IS_WIN) +#include "base/strings/sys_string_conversions.h" +#include "components/device_signals/core/common/win/win_types.h" +#endif // BUILDFLAG(IS_WIN) + +using SignalCollectionError = device_signals::SignalCollectionError; +using PresenceValue = device_signals::PresenceValue; + +namespace extensions { + +namespace { + +absl::optional<ParsedSignalsError> TryParseError( + const device_signals::SignalsAggregationResponse& response, + const absl::optional<device_signals::BaseSignalResponse>& bundle) { + absl::optional<std::string> error_string; + if (response.top_level_error) { + return ParsedSignalsError{response.top_level_error.value(), + /*is_top_level_error=*/true}; + } + + if (!bundle) { + return ParsedSignalsError{SignalCollectionError::kMissingBundle, + /*is_top_level_error=*/false}; + } + + if (bundle->collection_error) { + return ParsedSignalsError{bundle->collection_error.value(), + /*is_top_level_error=*/false}; + } + + return absl::nullopt; +} + +api::enterprise_reporting_private::PresenceValue ConvertPresenceValue( + PresenceValue presence) { + switch (presence) { + case PresenceValue::kUnspecified: + return api::enterprise_reporting_private::PRESENCE_VALUE_UNSPECIFIED; + case PresenceValue::kAccessDenied: + return api::enterprise_reporting_private::PRESENCE_VALUE_ACCESS_DENIED; + case PresenceValue::kNotFound: + return api::enterprise_reporting_private::PRESENCE_VALUE_NOT_FOUND; + case PresenceValue::kFound: + return api::enterprise_reporting_private::PRESENCE_VALUE_FOUND; + } +} + +std::string EncodeHash(const std::string& byte_string) { + std::string encoded_string; + base::Base64UrlEncode(byte_string, base::Base64UrlEncodePolicy::OMIT_PADDING, + &encoded_string); + return encoded_string; +} + +} // namespace + +std::vector<device_signals::GetFileSystemInfoOptions> +ConvertFileSystemInfoOptions( + const std::vector< + api::enterprise_reporting_private::GetFileSystemInfoOptions>& + api_options) { + std::vector<device_signals::GetFileSystemInfoOptions> converted_options; + for (const auto& api_options_param : api_options) { + device_signals::GetFileSystemInfoOptions converted_param; + converted_param.file_path = + base::FilePath::FromUTF8Unsafe(api_options_param.path); + converted_param.compute_sha256 = api_options_param.compute_sha256; + converted_param.compute_executable_metadata = + api_options_param.compute_executable_metadata; + converted_options.push_back(std::move(converted_param)); + } + return converted_options; +} + +absl::optional<ParsedSignalsError> ConvertFileSystemInfoResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::GetFileSystemInfoResponse>* + arg_list) { + auto error = TryParseError(response, response.file_system_info_response); + if (error) { + return error.value(); + } + + std::vector<api::enterprise_reporting_private::GetFileSystemInfoResponse> + api_responses; + const auto& file_system_signal_values = + response.file_system_info_response.value(); + for (const auto& file_system_item : + file_system_signal_values.file_system_items) { + api::enterprise_reporting_private::GetFileSystemInfoResponse response; + response.path = file_system_item.file_path.AsUTF8Unsafe(); + response.presence = ConvertPresenceValue(file_system_item.presence); + + if (file_system_item.sha256_hash) { + response.sha256_hash = std::make_unique<std::string>( + EncodeHash(file_system_item.sha256_hash.value())); + } + + if (file_system_item.executable_metadata) { + const auto& executable_metadata = + file_system_item.executable_metadata.value(); + + response.is_running = + std::make_unique<bool>(executable_metadata.is_running); + + if (executable_metadata.public_key_sha256) { + response.public_key_sha256 = std::make_unique<std::string>( + EncodeHash(executable_metadata.public_key_sha256.value())); + } + + if (executable_metadata.product_name) { + response.product_name = std::make_unique<std::string>( + executable_metadata.product_name.value()); + } + + if (executable_metadata.version) { + response.version = + std::make_unique<std::string>(executable_metadata.version.value()); + } + } + + api_responses.push_back(std::move(response)); + } + + *arg_list = std::move(api_responses); + return absl::nullopt; +} + +#if BUILDFLAG(IS_WIN) + +absl::optional<ParsedSignalsError> ConvertAvProductsResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::AntiVirusSignal>* arg_list) { + auto error = TryParseError(response, response.av_signal_response); + if (error) { + return error.value(); + } + + std::vector<api::enterprise_reporting_private::AntiVirusSignal> + api_av_signals; + const auto& av_response = response.av_signal_response.value(); + for (const auto& av_product : av_response.av_products) { + api::enterprise_reporting_private::AntiVirusSignal api_av_signal; + api_av_signal.display_name = av_product.display_name; + api_av_signal.product_id = av_product.product_id; + + switch (av_product.state) { + case device_signals::AvProductState::kOn: + api_av_signal.state = api::enterprise_reporting_private:: + AntiVirusProductState::ANTI_VIRUS_PRODUCT_STATE_ON; + break; + case device_signals::AvProductState::kOff: + api_av_signal.state = api::enterprise_reporting_private:: + AntiVirusProductState::ANTI_VIRUS_PRODUCT_STATE_OFF; + break; + case device_signals::AvProductState::kSnoozed: + api_av_signal.state = api::enterprise_reporting_private:: + AntiVirusProductState::ANTI_VIRUS_PRODUCT_STATE_SNOOZED; + break; + case device_signals::AvProductState::kExpired: + api_av_signal.state = api::enterprise_reporting_private:: + AntiVirusProductState::ANTI_VIRUS_PRODUCT_STATE_EXPIRED; + break; + } + + api_av_signals.push_back(std::move(api_av_signal)); + } + + *arg_list = std::move(api_av_signals); + return absl::nullopt; +} + +absl::optional<ParsedSignalsError> ConvertHotfixesResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::HotfixSignal>* arg_list) { + auto error = TryParseError(response, response.hotfix_signal_response); + if (error) { + return error.value(); + } + + std::vector<api::enterprise_reporting_private::HotfixSignal> + api_hotfix_signals; + const auto& hotfix_response = response.hotfix_signal_response.value(); + for (const auto& hotfix : hotfix_response.hotfixes) { + api::enterprise_reporting_private::HotfixSignal api_hotfix; + api_hotfix.hotfix_id = hotfix.hotfix_id; + api_hotfix_signals.push_back(std::move(api_hotfix)); + } + + *arg_list = std::move(api_hotfix_signals); + return absl::nullopt; +} + +#endif // BUILDFLAG(IS_WIN) + +} // namespace extensions + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.h b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.h new file mode 100644 index 00000000000..764aa73af9a --- /dev/null +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.h @@ -0,0 +1,68 @@ +// Copyright 2022 The Chromium Authors. All 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_ENTERPRISE_REPORTING_PRIVATE_CONVERSION_UTILS_H_ +#define CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_CONVERSION_UTILS_H_ + +#include "build/build_config.h" + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +#include <vector> + +#include "chrome/common/extensions/api/enterprise_reporting_private.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace device_signals { +struct GetFileSystemInfoOptions; +struct SignalsAggregationResponse; +enum class SignalCollectionError; +} // namespace device_signals + +namespace extensions { + +struct ParsedSignalsError { + device_signals::SignalCollectionError error; + bool is_top_level_error; +}; + +// Converts GetFileSystemInfoOptions from the Extension API struct definition, +// `api_options`, to the device_signals component definition. +std::vector<device_signals::GetFileSystemInfoOptions> +ConvertFileSystemInfoOptions( + const std::vector< + api::enterprise_reporting_private::GetFileSystemInfoOptions>& + api_options); + +// Parses and converts the File System info signal values from `response` into +// `arg_list`. If any error occurred during signal collection, it will be +// returned and `arg_list` will remain unchanged. +absl::optional<ParsedSignalsError> ConvertFileSystemInfoResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::GetFileSystemInfoResponse>* + arg_list); + +#if BUILDFLAG(IS_WIN) + +// Parses and converts the Antivirus signal values from `response` into +// `arg_list`. If any error occurred during signal collection, it will be +// returned and `arg_list` will remain unchanged. +absl::optional<ParsedSignalsError> ConvertAvProductsResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::AntiVirusSignal>* arg_list); + +// Parses and converts the Hotfix signal values from `response` into +// `arg_list`. If any error occurred during signal collection, it will be +// returned and `arg_list` will remain unchanged. +absl::optional<ParsedSignalsError> ConvertHotfixesResponse( + const device_signals::SignalsAggregationResponse& response, + std::vector<api::enterprise_reporting_private::HotfixSignal>* arg_list); + +#endif // BUILDFLAG(IS_WIN) + +} // namespace extensions + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +#endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_CONVERSION_UTILS_H_ diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.cc b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.cc index 7caa584b10d..ae82bcb654d 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.cc @@ -8,6 +8,7 @@ #include <utility> #include "base/bind.h" +#include "base/callback.h" #include "base/strings/stringprintf.h" #include "base/task/thread_pool.h" #include "base/threading/thread_task_runner_handle.h" @@ -30,6 +31,18 @@ #include "components/reporting/util/statusor.h" #endif +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) +#include "base/strings/string_util.h" +#include "chrome/browser/enterprise/signals/signals_aggregator_factory.h" +#include "chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.h" +#include "components/device_signals/core/browser/metrics_utils.h" +#include "components/device_signals/core/browser/signals_aggregator.h" +#include "components/device_signals/core/browser/signals_types.h" +#include "components/device_signals/core/browser/user_context.h" +#include "components/device_signals/core/common/signals_features.h" // nogncheck +#include "third_party/abseil-cpp/absl/types/optional.h" +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + #include "components/content_settings/core/common/pref_names.h" #include "components/enterprise/browser/controller/browser_dm_token_storage.h" #include "net/cert/x509_util.h" @@ -142,6 +155,40 @@ api::enterprise_reporting_private::ContextInfo ToContextInfo( return info; } +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +device_signals::SignalsAggregationRequest CreateAggregationRequest( + const std::string& user_id, + device_signals::SignalName signal_name) { + device_signals::UserContext user_context; + user_context.user_id = user_id; + + device_signals::SignalsAggregationRequest request; + request.user_context = std::move(user_context); + request.signal_names.emplace(signal_name); + return request; +} + +void StartSignalCollection( + device_signals::SignalsAggregationRequest request, + content::BrowserContext* browser_context, + base::OnceCallback<void(device_signals::SignalsAggregationResponse)> + callback) { + DCHECK(browser_context); + auto* profile = Profile::FromBrowserContext(browser_context); + DCHECK(profile); + auto* signals_aggregator = + enterprise_signals::SignalsAggregatorFactory::GetForProfile(profile); + DCHECK(signals_aggregator); + signals_aggregator->GetSignals(std::move(request), std::move(callback)); +} + +bool CanReturnResponse(content::BrowserContext* browser_context) { + return browser_context && !browser_context->ShutdownStarted(); +} + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + } // namespace #if !BUILDFLAG(IS_CHROMEOS) @@ -608,4 +655,185 @@ void EnterpriseReportingPrivateEnqueueRecordFunction:: } #endif +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +// getFileSystemInfo + +EnterpriseReportingPrivateGetFileSystemInfoFunction:: + EnterpriseReportingPrivateGetFileSystemInfoFunction() = default; +EnterpriseReportingPrivateGetFileSystemInfoFunction:: + ~EnterpriseReportingPrivateGetFileSystemInfoFunction() = default; + +ExtensionFunction::ResponseAction +EnterpriseReportingPrivateGetFileSystemInfoFunction::Run() { + if (!IsNewFunctionEnabled( + enterprise_signals::features::NewEvFunction::kFileSystemInfo)) { + return RespondNow(Error(device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported))); + } + + std::unique_ptr<api::enterprise_reporting_private::GetFileSystemInfo::Params> + params( + api::enterprise_reporting_private::GetFileSystemInfo::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + // Verify that all file paths are UTF8. + bool paths_are_all_utf8 = true; + for (const auto& api_options_param : params->request.options) { + if (!base::IsStringUTF8(api_options_param.path)) { + paths_are_all_utf8 = false; + break; + } + } + EXTENSION_FUNCTION_VALIDATE(paths_are_all_utf8); + + auto aggregation_request = CreateAggregationRequest( + params->request.user_context.user_id, signal_name()); + aggregation_request.file_system_signal_parameters = + ConvertFileSystemInfoOptions(params->request.options); + + StartSignalCollection( + aggregation_request, browser_context(), + base::BindOnce(&EnterpriseReportingPrivateGetFileSystemInfoFunction:: + OnSignalRetrieved, + this)); + + return RespondLater(); +} + +void EnterpriseReportingPrivateGetFileSystemInfoFunction::OnSignalRetrieved( + device_signals::SignalsAggregationResponse response) { + if (!CanReturnResponse(browser_context())) { + // The browser is no longer accepting responses, so just bail. + return; + } + + std::vector<api::enterprise_reporting_private::GetFileSystemInfoResponse> + arg_list; + auto parsed_error = ConvertFileSystemInfoResponse(response, &arg_list); + + if (parsed_error) { + LogSignalCollectionFailed(signal_name(), parsed_error->error, + parsed_error->is_top_level_error); + Respond(Error(device_signals::ErrorToString(parsed_error->error))); + return; + } + + LogSignalCollectionSucceeded(signal_name(), arg_list.size()); + Respond(ArgumentList( + api::enterprise_reporting_private::GetFileSystemInfo::Results::Create( + arg_list))); +} + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +#if BUILDFLAG(IS_WIN) + +// getAvInfo + +EnterpriseReportingPrivateGetAvInfoFunction:: + EnterpriseReportingPrivateGetAvInfoFunction() = default; +EnterpriseReportingPrivateGetAvInfoFunction:: + ~EnterpriseReportingPrivateGetAvInfoFunction() = default; + +ExtensionFunction::ResponseAction +EnterpriseReportingPrivateGetAvInfoFunction::Run() { + if (!IsNewFunctionEnabled( + enterprise_signals::features::NewEvFunction::kAntiVirus)) { + return RespondNow(Error(device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported))); + } + + std::unique_ptr<api::enterprise_reporting_private::GetAvInfo::Params> params( + api::enterprise_reporting_private::GetAvInfo::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + StartSignalCollection( + CreateAggregationRequest(params->user_context.user_id, signal_name()), + browser_context(), + base::BindOnce( + &EnterpriseReportingPrivateGetAvInfoFunction::OnSignalRetrieved, + this)); + + return RespondLater(); +} + +void EnterpriseReportingPrivateGetAvInfoFunction::OnSignalRetrieved( + device_signals::SignalsAggregationResponse response) { + if (!CanReturnResponse(browser_context())) { + // The browser is no longer accepting responses, so just bail. + return; + } + + std::vector<api::enterprise_reporting_private::AntiVirusSignal> arg_list; + auto parsed_error = ConvertAvProductsResponse(response, &arg_list); + + if (parsed_error) { + LogSignalCollectionFailed(signal_name(), parsed_error->error, + parsed_error->is_top_level_error); + Respond(Error(device_signals::ErrorToString(parsed_error->error))); + return; + } + + LogSignalCollectionSucceeded(signal_name(), arg_list.size()); + Respond(ArgumentList( + api::enterprise_reporting_private::GetAvInfo::Results::Create(arg_list))); +} + +// getHotfixes + +EnterpriseReportingPrivateGetHotfixesFunction:: + EnterpriseReportingPrivateGetHotfixesFunction() = default; +EnterpriseReportingPrivateGetHotfixesFunction:: + ~EnterpriseReportingPrivateGetHotfixesFunction() = default; + +ExtensionFunction::ResponseAction +EnterpriseReportingPrivateGetHotfixesFunction::Run() { + if (!IsNewFunctionEnabled( + enterprise_signals::features::NewEvFunction::kHotfix)) { + return RespondNow(Error(device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported))); + } + + std::unique_ptr<api::enterprise_reporting_private::GetHotfixes::Params> + params(api::enterprise_reporting_private::GetHotfixes::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + StartSignalCollection( + CreateAggregationRequest(params->user_context.user_id, signal_name()), + browser_context(), + base::BindOnce( + &EnterpriseReportingPrivateGetHotfixesFunction::OnSignalRetrieved, + this)); + + return RespondLater(); +} + +void EnterpriseReportingPrivateGetHotfixesFunction::OnSignalRetrieved( + device_signals::SignalsAggregationResponse response) { + if (!CanReturnResponse(browser_context())) { + // The browser is no longer accepting responses, so just bail. + return; + } + + std::vector<api::enterprise_reporting_private::HotfixSignal> arg_list; + auto parsed_error = ConvertHotfixesResponse(response, &arg_list); + + if (parsed_error) { + LogSignalCollectionFailed(signal_name(), parsed_error->error, + parsed_error->is_top_level_error); + Respond(Error(device_signals::ErrorToString(parsed_error->error))); + return; + } + + LogSignalCollectionSucceeded(signal_name(), arg_list.size()); + Respond(ArgumentList( + api::enterprise_reporting_private::GetHotfixes::Results::Create( + arg_list))); +} + +#endif // BUILDFLAG(IS_WIN) + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h index bc0a5a7eafd..8afa2b5c177 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h @@ -19,7 +19,9 @@ #include "components/reporting/proto/synced/record.pb.h" #include "components/reporting/proto/synced/record_constants.pb.h" #include "components/reporting/util/statusor.h" -#endif +#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) +#include "components/device_signals/core/browser/signals_types.h" +#endif // BUILDFLAG(IS_CHROMEOS) #include "extensions/browser/extension_function.h" @@ -262,6 +264,87 @@ class EnterpriseReportingPrivateEnqueueRecordFunction #endif +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +class EnterpriseReportingPrivateGetFileSystemInfoFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("enterprise.reportingPrivate.getFileSystemInfo", + ENTERPRISEREPORTINGPRIVATE_GETFILESYSTEMINFO) + + EnterpriseReportingPrivateGetFileSystemInfoFunction(); + EnterpriseReportingPrivateGetFileSystemInfoFunction( + const EnterpriseReportingPrivateGetFileSystemInfoFunction&) = delete; + EnterpriseReportingPrivateGetFileSystemInfoFunction& operator=( + const EnterpriseReportingPrivateGetFileSystemInfoFunction&) = delete; + + private: + ~EnterpriseReportingPrivateGetFileSystemInfoFunction() override; + + // ExtensionFunction + ExtensionFunction::ResponseAction Run() override; + + void OnSignalRetrieved(device_signals::SignalsAggregationResponse response); + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kFileSystemInfo; + } +}; + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +#if BUILDFLAG(IS_WIN) + +class EnterpriseReportingPrivateGetAvInfoFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("enterprise.reportingPrivate.getAvInfo", + ENTERPRISEREPORTINGPRIVATE_GETAVINFO) + + EnterpriseReportingPrivateGetAvInfoFunction(); + EnterpriseReportingPrivateGetAvInfoFunction( + const EnterpriseReportingPrivateGetAvInfoFunction&) = delete; + EnterpriseReportingPrivateGetAvInfoFunction& operator=( + const EnterpriseReportingPrivateGetAvInfoFunction&) = delete; + + private: + ~EnterpriseReportingPrivateGetAvInfoFunction() override; + + // ExtensionFunction + ExtensionFunction::ResponseAction Run() override; + + void OnSignalRetrieved(device_signals::SignalsAggregationResponse response); + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kAntiVirus; + } +}; + +class EnterpriseReportingPrivateGetHotfixesFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("enterprise.reportingPrivate.getHotfixes", + ENTERPRISEREPORTINGPRIVATE_GETHOTFIXES) + + EnterpriseReportingPrivateGetHotfixesFunction(); + EnterpriseReportingPrivateGetHotfixesFunction( + const EnterpriseReportingPrivateGetHotfixesFunction&) = delete; + EnterpriseReportingPrivateGetHotfixesFunction& operator=( + const EnterpriseReportingPrivateGetHotfixesFunction&) = delete; + + private: + ~EnterpriseReportingPrivateGetHotfixesFunction() override; + + // ExtensionFunction + ExtensionFunction::ResponseAction Run() override; + + void OnSignalRetrieved(device_signals::SignalsAggregationResponse response); + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kHotfixes; + } +}; + +#endif // BUILDFLAG(IS_WIN) + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_ENTERPRISE_REPORTING_PRIVATE_API_H_ diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc index 8fc6fa0f42c..aa71b2e544d 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc @@ -2,26 +2,56 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <memory> +#include <string> +#include <utility> + +#include "base/test/scoped_feature_list.h" #include "build/branding_buildflags.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "chrome/browser/enterprise/browser_management/management_service_factory.h" +#include "chrome/browser/extensions/chrome_test_extension_loader.h" #include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/policy/profile_policy_connector.h" +#include "chrome/browser/signin/chrome_signin_client_factory.h" +#include "chrome/browser/signin/chrome_signin_client_test_util.h" +#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h" #include "chrome/test/base/in_process_browser_test.h" +#include "components/policy/core/common/management/management_service.h" +#include "components/signin/public/identity_manager/account_info.h" +#include "components/signin/public/identity_manager/identity_test_environment.h" #include "components/version_info/version_info.h" #include "content/public/test/browser_test.h" #include "extensions/common/extension.h" #include "extensions/test/result_catcher.h" #include "extensions/test/test_extension_dir.h" +#include "services/network/test/test_url_loader_factory.h" + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "base/strings/string_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/policy/chrome_browser_policy_connector.h" +#include "components/device_signals/core/common/signals_features.h" +#include "components/device_signals/core/system_signals/platform_utils.h" // nogncheck +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) #if !BUILDFLAG(IS_CHROMEOS_ASH) +#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h" #include "components/enterprise/browser/controller/fake_browser_dm_token_storage.h" -#endif +#include "components/policy/core/common/cloud/cloud_policy_core.h" +#include "components/policy/core/common/cloud/cloud_policy_store.h" +#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h" +#include "components/policy/core/common/cloud/user_cloud_policy_manager.h" +#include "components/policy/proto/device_management_backend.pb.h" +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS) #include "base/strings/strcat.h" #include "chrome/browser/enterprise/util/affiliation.h" #include "chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h" -#include "chrome/browser/extensions/chrome_test_extension_loader.h" #include "chrome/browser/profiles/profile_manager.h" #endif @@ -42,6 +72,10 @@ namespace extensions { namespace { +#if !BUILDFLAG(IS_CHROMEOS_ASH) +constexpr char kAffiliationId[] = "affiliation-id"; +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) + // Manifest key for the Endpoint Verification extension found at // chrome.google.com/webstore/detail/callobklhcbilhphinckomhgkigmfocg // This extension is authorized to use the enterprise.reportingPrivate API. @@ -83,13 +117,48 @@ constexpr char kManifestTemplate[] = R"( class EnterpriseReportingPrivateApiTest : public extensions::ExtensionApiTest { public: EnterpriseReportingPrivateApiTest() { +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + scoped_features_.InitAndEnableFeature( + enterprise_signals::features::kNewEvSignalsEnabled); +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + #if !BUILDFLAG(IS_CHROMEOS_ASH) browser_dm_token_storage_.SetClientId("client_id"); -#endif + browser_dm_token_storage_.SetEnrollmentToken("enrollment_token"); + browser_dm_token_storage_.SetDMToken("dm_token"); + policy::BrowserDMTokenStorage::SetForTesting(&browser_dm_token_storage_); +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) } ~EnterpriseReportingPrivateApiTest() override = default; +#if !BUILDFLAG(IS_CHROMEOS_ASH) + // Signs in and returns the account ID of the primary account. + AccountInfo SignIn(const std::string& email, bool as_managed = true) { + auto account_info = identity_test_env()->MakePrimaryAccountAvailable( + email, signin::ConsentLevel::kSignin); + EXPECT_TRUE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + + if (as_managed) { + account_info.hosted_domain = "example.com"; + identity_test_env()->UpdateAccountInfoForAccount(account_info); + + safe_browsing::SetProfileDMToken(profile(), "fake_user_dmtoken"); + auto profile_policy_data = + std::make_unique<enterprise_management::PolicyData>(); + profile_policy_data->add_user_affiliation_ids(kAffiliationId); + profile() + ->GetUserCloudPolicyManager() + ->core() + ->store() + ->set_policy_data_for_testing(std::move(profile_policy_data)); + } + + return account_info; + } +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) + void RunTest(const std::string& background_js, bool authorized_manifest_key = true) { ResultCatcher result_catcher; @@ -116,6 +185,69 @@ class EnterpriseReportingPrivateApiTest : public extensions::ExtensionApiTest { } protected: + void SetUpInProcessBrowserTestFixture() override { + extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture(); + + create_services_subscription_ = + BrowserContextDependencyManager::GetInstance() + ->RegisterCreateServicesCallbackForTesting( + base::BindRepeating(&EnterpriseReportingPrivateApiTest:: + OnWillCreateBrowserContextServices, + base::Unretained(this))); + } + + void OnWillCreateBrowserContextServices(content::BrowserContext* context) { + IdentityTestEnvironmentProfileAdaptor:: + SetIdentityTestEnvironmentFactoriesOnBrowserContext(context); + + ChromeSigninClientFactory::GetInstance()->SetTestingFactory( + context, base::BindRepeating(&BuildChromeSigninClientWithURLLoader, + &test_url_loader_factory_)); + } + + void SetUpOnMainThread() override { + extensions::ExtensionApiTest::SetUpOnMainThread(); + identity_test_env_profile_adaptor_ = + std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile()); + + identity_test_env()->SetTestURLLoaderFactory(&test_url_loader_factory_); + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + // Set device org's affiliated IDs. + auto* browser_policy_manager = + g_browser_process->browser_policy_connector() + ->machine_level_user_cloud_policy_manager(); + auto browser_policy_data = + std::make_unique<enterprise_management::PolicyData>(); + browser_policy_data->add_device_affiliation_ids(kAffiliationId); + browser_policy_manager->core()->store()->set_policy_data_for_testing( + std::move(browser_policy_data)); +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + } + + void TearDownOnMainThread() override { + extensions::ExtensionApiTest::TearDownOnMainThread(); + // Must be destroyed before the Profile. + identity_test_env_profile_adaptor_.reset(); + } + + policy::ProfilePolicyConnector* profile_policy_connector() { + return profile()->GetProfilePolicyConnector(); + } + + signin::IdentityTestEnvironment* identity_test_env() { + return identity_test_env_profile_adaptor_->identity_test_env(); + } + + std::unique_ptr<IdentityTestEnvironmentProfileAdaptor> + identity_test_env_profile_adaptor_; + + network::TestURLLoaderFactory test_url_loader_factory_; + + base::CallbackListSubscription create_services_subscription_; + + base::test::ScopedFeatureList scoped_features_; + #if !BUILDFLAG(IS_CHROMEOS_ASH) policy::FakeBrowserDMTokenStorage browser_dm_token_storage_; #endif @@ -382,6 +514,101 @@ IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetCertificate) { });)"); } +#if BUILDFLAG(IS_WIN) + +IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetAvInfo_Success) { + constexpr char kTest[] = R"( + chrome.test.assertEq( + 'function', + typeof chrome.enterprise.reportingPrivate.getAvInfo); + const userContext = {userId: '%s'}; + + chrome.enterprise.reportingPrivate.getAvInfo(userContext, (avProducts) => { + chrome.test.assertNoLastError(); + chrome.test.assertTrue(avProducts instanceof Array); + chrome.test.notifyPass(); + }); + )"; + + AccountInfo account_info = SignIn("some-email@example.com"); + RunTest(base::StringPrintf(kTest, account_info.gaia.c_str())); +} + +IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetHotfixes_Success) { + constexpr char kTest[] = R"( + chrome.test.assertEq( + 'function', + typeof chrome.enterprise.reportingPrivate.getHotfixes); + const userContext = {userId: '%s'}; + + chrome.enterprise.reportingPrivate.getHotfixes(userContext, (hotfixes) => { + chrome.test.assertNoLastError(); + chrome.test.assertTrue(hotfixes instanceof Array); + chrome.test.notifyPass(); + }); + )"; + + AccountInfo account_info = SignIn("some-email@example.com"); + RunTest(base::StringPrintf(kTest, account_info.gaia.c_str())); +} + +#endif // BUILDFLAG(IS_WIN) + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + +IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, + GetFileSystemInfo_Success) { + // Use the test runner process and binary as test parameters, as it will always + // be running. + auto test_runner_file_path = + device_signals::GetProcessExePath(base::Process::Current().Pid()); + + ASSERT_TRUE(test_runner_file_path.has_value()); + ASSERT_FALSE(test_runner_file_path->empty()); + + constexpr char kTest[] = R"( + chrome.test.assertEq( + 'function', + typeof chrome.enterprise.reportingPrivate.getFileSystemInfo); + const userContext = {userId: '%s'}; + + const executablePath = '%s'; + const fileItem = { + path: executablePath, + computeSha256: true, + computeExecutableMetadata: true + }; + + const request = { userContext, options: [fileItem] }; + + chrome.enterprise.reportingPrivate.getFileSystemInfo( + request, + (fileItems) => { + chrome.test.assertNoLastError(); + chrome.test.assertTrue(fileItems instanceof Array); + chrome.test.assertEq(1, fileItems.length); + + const fileItemResponse = fileItems[0]; + chrome.test.assertEq(executablePath, fileItemResponse.path); + chrome.test.assertEq('FOUND', fileItemResponse.presence); + chrome.test.assertTrue(!!fileItemResponse.sha256Hash); + chrome.test.assertTrue(fileItemResponse.isRunning); + + chrome.test.notifyPass(); + }); + )"; + + // Escape all backslashes. + std::string escaped_file_path = test_runner_file_path->AsUTF8Unsafe(); + base::ReplaceSubstringsAfterOffset(&escaped_file_path, 0U, "\\", "\\\\"); + + AccountInfo account_info = SignIn("some-email@example.com"); + RunTest(base::StringPrintf(kTest, account_info.gaia.c_str(), + escaped_file_path.c_str())); +} + +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) + #if BUILDFLAG(IS_CHROMEOS) static void RunTestUsingProfile(const std::string& background_js, Profile* profile) { @@ -522,7 +749,6 @@ using EnterpriseReportingPrivateEnqueueRecordApiTest = ExtensionApiTest; static void SetupAffiliationLacros() { constexpr char kDomain[] = "fake-domain"; - constexpr char kAffiliationId[] = "affiliation-id"; constexpr char kFakeProfileClientId[] = "fake-profile-client-id"; constexpr char kFakeDMToken[] = "fake-dm-token"; enterprise_management::PolicyData profile_policy_data; diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc index 5a073322133..10534443294 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc @@ -12,6 +12,7 @@ #include "base/command_line.h" #include "base/environment.h" #include "base/files/scoped_temp_dir.h" +#include "base/json/json_writer.h" #include "build/build_config.h" #include "chrome/browser/enterprise/signals/signals_common.h" #include "chrome/browser/extensions/api/enterprise_reporting_private/chrome_desktop_report_request_helper.h" @@ -33,6 +34,7 @@ #include "components/reporting/proto/synced/record.pb.h" #include "components/safe_browsing/core/common/safe_browsing_prefs.h" #include "components/version_info/version_info.h" +#include "extensions/browser/api_test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -50,7 +52,19 @@ #include <wrl/client.h> #include "base/test/test_reg_util_win.h" -#endif +#endif // BUILDFLAG(IS_WIN) + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/enterprise/signals/signals_aggregator_factory.h" +#include "components/device_signals/core/browser/mock_signals_aggregator.h" // nogncheck +#include "components/device_signals/core/browser/signals_aggregator.h" // nogncheck +#include "components/device_signals/core/browser/signals_types.h" // nogncheck +#include "components/device_signals/core/common/common_types.h" // nogncheck +#include "components/device_signals/core/common/signals_constants.h" // nogncheck +#include "components/device_signals/core/common/signals_features.h" // nogncheck +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #include "base/nix/xdg_util.h" @@ -63,16 +77,24 @@ using SettingValue = enterprise_signals::SettingValue; using ::testing::_; using ::testing::Eq; using ::testing::Invoke; +using ::testing::IsEmpty; +using ::testing::SizeIs; using ::testing::StrEq; using ::testing::WithArgs; namespace extensions { +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) + +constexpr char kNoError[] = ""; + +#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) + #if !BUILDFLAG(IS_CHROMEOS) namespace { -const char kFakeClientId[] = "fake-client-id"; +constexpr char kFakeClientId[] = "fake-client-id"; } // namespace @@ -1035,38 +1057,18 @@ TEST_P(EnterpriseReportingPrivateGetContextInfoRealTimeURLCheckTest, Test) { } #if BUILDFLAG(IS_CHROMEOS) -class MockMissiveClient : public ::chromeos::FakeMissiveClient { - public: - MockMissiveClient() = default; - ~MockMissiveClient() override = default; - - MockMissiveClient(const MockMissiveClient& other) = delete; - MockMissiveClient& operator=(const MockMissiveClient& other) = delete; - - void Init() override {} - - MissiveClient::TestInterface* GetTestInterface() override { return this; } - - MOCK_METHOD(void, - EnqueueRecord, - (const ::reporting::Priority, - ::reporting::Record, - base::OnceCallback<void(::reporting::Status)>), - (override)); -}; // Test for API enterprise.reportingPrivate.enqueueRecord class EnterpriseReportingPrivateEnqueueRecordFunctionTest : public ExtensionApiUnittest { protected: - static constexpr char kNoError[] = ""; static constexpr char kTestDMTokenValue[] = "test_dm_token_value"; EnterpriseReportingPrivateEnqueueRecordFunctionTest() = default; void SetUp() override { ExtensionApiUnittest::SetUp(); - ::chromeos::MissiveClient::InitializeFake<MockMissiveClient>(); + ::chromeos::MissiveClient::InitializeFake(); function_ = base::MakeRefCounted<EnterpriseReportingPrivateEnqueueRecordFunction>(); const auto record = GetTestRecord(); @@ -1096,6 +1098,18 @@ class EnterpriseReportingPrivateEnqueueRecordFunctionTest return record; } + void VerifyNoRecordsEnqueued(::reporting::Priority priority = + ::reporting::Priority::BACKGROUND_BATCH) { + ::chromeos::MissiveClient::TestInterface* const missive_test_interface = + ::chromeos::MissiveClient::Get()->GetTestInterface(); + ASSERT_TRUE(missive_test_interface); + + const std::vector<::reporting::Record>& records = + missive_test_interface->GetEnqueuedRecords(priority); + + ASSERT_THAT(records, IsEmpty()); + } + std::vector<uint8_t> serialized_record_data_; scoped_refptr<extensions::EnterpriseReportingPrivateEnqueueRecordFunction> function_; @@ -1121,25 +1135,25 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, policy::DMToken::CreateValidTokenForTesting(kTestDMTokenValue); policy::SetDMTokenForTesting(dm_token); - auto* const reporting_client = - static_cast<MockMissiveClient*>(::chromeos::MissiveClient::Get()); - EXPECT_CALL(*reporting_client, EnqueueRecord(_, _, _)) - .WillOnce(WithArgs<1, 2>( - Invoke([&](::reporting::Record record, - base::OnceCallback<void(::reporting::Status)> - completion_callback) { - EXPECT_THAT(record.destination(), - Eq(::reporting::Destination::TELEMETRY_METRIC)); - EXPECT_THAT(record.dm_token(), StrEq(dm_token.value())); - EXPECT_THAT(record.data(), StrEq(GetTestRecord().data())); - - std::move(completion_callback).Run(::reporting::Status::StatusOK()); - }))); - extension_function_test_utils::RunFunction(function_.get(), std::move(params), browser(), extensions::api_test_utils::NONE); EXPECT_EQ(function_->GetError(), kNoError); + + ::chromeos::MissiveClient::TestInterface* const missive_test_interface = + ::chromeos::MissiveClient::Get()->GetTestInterface(); + ASSERT_TRUE(missive_test_interface); + + const std::vector<::reporting::Record>& background_batch_records = + missive_test_interface->GetEnqueuedRecords( + ::reporting::Priority::BACKGROUND_BATCH); + + ASSERT_THAT(background_batch_records, SizeIs(1)); + EXPECT_THAT(background_batch_records[0].destination(), + Eq(::reporting::Destination::TELEMETRY_METRIC)); + EXPECT_THAT(background_batch_records[0].dm_token(), StrEq(dm_token.value())); + EXPECT_THAT(background_batch_records[0].data(), + StrEq(GetTestRecord().data())); } TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, @@ -1163,10 +1177,6 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, policy::SetDMTokenForTesting( policy::DMToken::CreateValidTokenForTesting(kTestDMTokenValue)); - auto* const reporting_client = - static_cast<MockMissiveClient*>(::chromeos::MissiveClient::Get()); - EXPECT_CALL(*reporting_client, EnqueueRecord(_, _, _)).Times(0); - extension_function_test_utils::RunFunction(function_.get(), std::move(params), browser(), extensions::api_test_utils::NONE); @@ -1174,6 +1184,8 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, EXPECT_EQ(function_->GetError(), EnterpriseReportingPrivateEnqueueRecordFunction:: kErrorInvalidEnqueueRecordRequest); + + VerifyNoRecordsEnqueued(); } TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, @@ -1196,10 +1208,6 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, policy::SetDMTokenForTesting( policy::DMToken::CreateValidTokenForTesting(kTestDMTokenValue)); - auto* const reporting_client = - static_cast<MockMissiveClient*>(::chromeos::MissiveClient::Get()); - EXPECT_CALL(*reporting_client, EnqueueRecord(_, _, _)).Times(0); - extension_function_test_utils::RunFunction(function_.get(), std::move(params), browser(), extensions::api_test_utils::NONE); @@ -1207,6 +1215,8 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, EXPECT_EQ(function_->GetError(), EnterpriseReportingPrivateEnqueueRecordFunction:: kErrorProfileNotAffiliated); + + VerifyNoRecordsEnqueued(); } TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, @@ -1226,10 +1236,6 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, // Set up invalid DM token policy::SetDMTokenForTesting(policy::DMToken::CreateInvalidTokenForTesting()); - auto* const reporting_client = - static_cast<MockMissiveClient*>(::chromeos::MissiveClient::Get()); - EXPECT_CALL(*reporting_client, EnqueueRecord(_, _, _)).Times(0); - extension_function_test_utils::RunFunction(function_.get(), std::move(params), browser(), extensions::api_test_utils::NONE); @@ -1237,6 +1243,8 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, EXPECT_EQ(function_->GetError(), EnterpriseReportingPrivateEnqueueRecordFunction:: kErrorCannotAssociateRecordWithUser); + + VerifyNoRecordsEnqueued(); } TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, @@ -1265,10 +1273,6 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, policy::SetDMTokenForTesting( policy::DMToken::CreateValidTokenForTesting(kTestDMTokenValue)); - auto* const reporting_client = - static_cast<MockMissiveClient*>(::chromeos::MissiveClient::Get()); - EXPECT_CALL(*reporting_client, EnqueueRecord(_, _, _)).Times(0); - extension_function_test_utils::RunFunction(function_.get(), std::move(params), browser(), extensions::api_test_utils::NONE); @@ -1276,7 +1280,482 @@ TEST_F(EnterpriseReportingPrivateEnqueueRecordFunctionTest, EXPECT_EQ(function_->GetError(), EnterpriseReportingPrivateEnqueueRecordFunction:: kErrorInvalidEnqueueRecordRequest); + + VerifyNoRecordsEnqueued(); } #endif // BUILDFLAG(IS_CHROMEOS) +#if BUILDFLAG(IS_WIN) + +namespace { + +constexpr char kFakeUserId[] = "fake user id"; + +enterprise_reporting_private::UserContext GetFakeUserContext() { + enterprise_reporting_private::UserContext user_context; + user_context.user_id = kFakeUserId; + return user_context; +} + +std::string GetFakeUserContextJsonParams() { + auto user_context = GetFakeUserContext(); + base::ListValue params; + params.Append(base::Value::FromUniquePtrValue(user_context.ToValue())); + std::string json_value; + base::JSONWriter::Write(params, &json_value); + return json_value; +} + +std::unique_ptr<KeyedService> BuildMockAggregator( + content::BrowserContext* context) { + return std::make_unique< + testing::StrictMock<device_signals::MockSignalsAggregator>>(); +} + +} // namespace + +// Base test class for APIs that require a UserContext parameter and which will +// make use of the SignalsAggregator to retrieve device signals. +class UserContextGatedTest : public ExtensionApiUnittest { + protected: + void SetUp() override { + ExtensionApiUnittest::SetUp(); + + auto* factory = enterprise_signals::SignalsAggregatorFactory::GetInstance(); + mock_aggregator_ = static_cast<device_signals::MockSignalsAggregator*>( + factory->SetTestingFactoryAndUse( + browser()->profile(), base::BindRepeating(&BuildMockAggregator))); + } + + void SetFakeResponse( + const device_signals::SignalsAggregationResponse& response) { + EXPECT_CALL(*mock_aggregator_, GetSignals(_, _)) + .WillOnce( + Invoke([&](const device_signals::SignalsAggregationRequest& request, + device_signals::SignalsAggregator::GetSignalsCallback + callback) { + EXPECT_EQ(request.user_context.user_id, kFakeUserId); + EXPECT_EQ(request.signal_names.size(), 1U); + std::move(callback).Run(response); + })); + } + + virtual void SetFeatureFlag() { + scoped_features_.InitAndEnableFeature( + enterprise_signals::features::kNewEvSignalsEnabled); + } + + device_signals::MockSignalsAggregator* mock_aggregator_; + base::test::ScopedFeatureList scoped_features_; + base::HistogramTester histogram_tester_; +}; + +// Tests for API enterprise.reportingPrivate.getFileSystemInfo +class EnterpriseReportingPrivateGetFileSystemInfoTest + : public UserContextGatedTest { + protected: + void SetUp() override { + UserContextGatedTest::SetUp(); + + SetFeatureFlag(); + + function_ = base::MakeRefCounted< + EnterpriseReportingPrivateGetFileSystemInfoFunction>(); + } + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kFileSystemInfo; + } + + enterprise_reporting_private::GetFileSystemInfoOptions + GetFakeFileSystemOptionsParam() const { + enterprise_reporting_private::GetFileSystemInfoOptions api_param; + api_param.path = "some file path"; + api_param.compute_sha256 = true; + return api_param; + } + + std::string GetFakeRequest() const { + enterprise_reporting_private::GetFileSystemInfoRequest request; + request.user_context = GetFakeUserContext(); + request.options.push_back(GetFakeFileSystemOptionsParam()); + base::ListValue params; + params.Append(base::Value::FromUniquePtrValue(request.ToValue())); + std::string json_value; + base::JSONWriter::Write(params, &json_value); + return json_value; + } + + scoped_refptr<extensions::EnterpriseReportingPrivateGetFileSystemInfoFunction> + function_; +}; + +TEST_F(EnterpriseReportingPrivateGetFileSystemInfoTest, Success) { + device_signals::FileSystemItem fake_file_item; + fake_file_item.file_path = base::FilePath(); + fake_file_item.presence = device_signals::PresenceValue::kFound; + fake_file_item.sha256_hash = "some hashed value"; + + device_signals::FileSystemInfoResponse signal_response; + signal_response.file_system_items.push_back(fake_file_item); + + device_signals::SignalsAggregationResponse expected_response; + expected_response.file_system_info_response = signal_response; + + SetFakeResponse(expected_response); + + auto response = api_test_utils::RunFunctionAndReturnSingleResult( + function_.get(), GetFakeRequest(), profile()); + + EXPECT_EQ(function_->GetError(), kNoError); + + ASSERT_TRUE(response); + ASSERT_TRUE(response->is_list()); + const base::Value::List& list_value = response->GetList(); + ASSERT_EQ(list_value.size(), signal_response.file_system_items.size()); + + const base::Value& file_system_value = list_value.front(); + auto parsed_file_system_signal = + enterprise_reporting_private::GetFileSystemInfoResponse::FromValue( + file_system_value); + ASSERT_TRUE(parsed_file_system_signal); + EXPECT_EQ(parsed_file_system_signal->path, + fake_file_item.file_path.AsUTF8Unsafe()); + EXPECT_EQ(parsed_file_system_signal->presence, + enterprise_reporting_private::PRESENCE_VALUE_FOUND); + EXPECT_EQ(*parsed_file_system_signal->sha256_hash.get(), + "c29tZSBoYXNoZWQgdmFsdWU"); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success.FileSystemInfo.Items", + /*number_of_items=*/1, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetFileSystemInfoTest, TopLevelError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kConsentRequired; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.top_level_error = expected_error; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeRequest(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.FileSystemInfo." + "TopLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetFileSystemInfoTest, CollectionError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kMissingSystemService; + + device_signals::FileSystemInfoResponse signal_response; + signal_response.collection_error = expected_error; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.file_system_info_response = signal_response; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeRequest(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.FileSystemInfo." + "CollectionLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +class EnterpriseReportingPrivateGetFileSystemInfoDisabledTest + : public EnterpriseReportingPrivateGetFileSystemInfoTest { + protected: + // Overwrite this function to disable the feature flag for tests using this + // specific fixture. + void SetFeatureFlag() override { + scoped_features_.InitAndEnableFeatureWithParameters( + enterprise_signals::features::kNewEvSignalsEnabled, + {{"DisableFileSystemInfo", "true"}}); + } +}; + +TEST_F(EnterpriseReportingPrivateGetFileSystemInfoDisabledTest, + FlagDisabled_Test) { + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeRequest(), profile()); + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported)); +} + +// Tests for API enterprise.reportingPrivate.getAvInfo +class EnterpriseReportingPrivateGetAvInfoTest : public UserContextGatedTest { + protected: + void SetUp() override { + UserContextGatedTest::SetUp(); + + SetFeatureFlag(); + + function_ = + base::MakeRefCounted<EnterpriseReportingPrivateGetAvInfoFunction>(); + } + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kAntiVirus; + } + + scoped_refptr<extensions::EnterpriseReportingPrivateGetAvInfoFunction> + function_; +}; + +TEST_F(EnterpriseReportingPrivateGetAvInfoTest, Success) { + device_signals::AvProduct fake_av_product; + fake_av_product.display_name = "Fake display name"; + fake_av_product.state = device_signals::AvProductState::kOff; + fake_av_product.product_id = "fake product id"; + + device_signals::AntiVirusSignalResponse av_response; + av_response.av_products.push_back(fake_av_product); + + device_signals::SignalsAggregationResponse expected_response; + expected_response.av_signal_response = av_response; + + SetFakeResponse(expected_response); + + auto response = api_test_utils::RunFunctionAndReturnSingleResult( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(function_->GetError(), kNoError); + + ASSERT_TRUE(response); + ASSERT_TRUE(response->is_list()); + const base::Value::List& list_value = response->GetList(); + ASSERT_EQ(list_value.size(), av_response.av_products.size()); + + const base::Value& av_value = list_value.front(); + auto parsed_av_signal = + enterprise_reporting_private::AntiVirusSignal::FromValue(av_value); + ASSERT_TRUE(parsed_av_signal); + EXPECT_EQ(parsed_av_signal->display_name, fake_av_product.display_name); + EXPECT_EQ(parsed_av_signal->state, + enterprise_reporting_private::ANTI_VIRUS_PRODUCT_STATE_OFF); + EXPECT_EQ(parsed_av_signal->product_id, fake_av_product.product_id); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success.AntiVirus.Items", + /*number_of_items=*/1, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetAvInfoTest, TopLevelError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kConsentRequired; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.top_level_error = expected_error; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.AntiVirus.TopLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetAvInfoTest, CollectionError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kMissingSystemService; + + device_signals::AntiVirusSignalResponse av_response; + av_response.collection_error = expected_error; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.av_signal_response = av_response; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.AntiVirus." + "CollectionLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +class EnterpriseReportingPrivateGetAvInfoDisabledTest + : public EnterpriseReportingPrivateGetAvInfoTest { + protected: + // Overwrite this function to disable the feature flag for tests using this + // specific fixture. + void SetFeatureFlag() override { + scoped_features_.InitAndEnableFeatureWithParameters( + enterprise_signals::features::kNewEvSignalsEnabled, + {{"DisableAntiVirus", "true"}}); + } +}; + +TEST_F(EnterpriseReportingPrivateGetAvInfoDisabledTest, FlagDisabled_Test) { + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported)); +} + +// Tests for API enterprise.reportingPrivate.getHotfixes +class EnterpriseReportingPrivateGetHotfixesTest : public UserContextGatedTest { + protected: + void SetUp() override { + UserContextGatedTest::SetUp(); + + SetFeatureFlag(); + + function_ = + base::MakeRefCounted<EnterpriseReportingPrivateGetHotfixesFunction>(); + } + + device_signals::SignalName signal_name() { + return device_signals::SignalName::kHotfixes; + } + + scoped_refptr<extensions::EnterpriseReportingPrivateGetHotfixesFunction> + function_; +}; + +TEST_F(EnterpriseReportingPrivateGetHotfixesTest, Success) { + static constexpr char kFakeHotfixId[] = "hotfix id"; + device_signals::HotfixSignalResponse hotfix_response; + hotfix_response.hotfixes.push_back({kFakeHotfixId}); + + device_signals::SignalsAggregationResponse expected_response; + expected_response.hotfix_signal_response = hotfix_response; + + SetFakeResponse(expected_response); + + auto response = api_test_utils::RunFunctionAndReturnSingleResult( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(function_->GetError(), kNoError); + + ASSERT_TRUE(response); + ASSERT_TRUE(response->is_list()); + const base::Value::List& list_value = response->GetList(); + ASSERT_EQ(list_value.size(), hotfix_response.hotfixes.size()); + + const base::Value& hotfix_value = list_value.front(); + auto parsed_hotfix = + enterprise_reporting_private::HotfixSignal::FromValue(hotfix_value); + ASSERT_TRUE(parsed_hotfix); + EXPECT_EQ(parsed_hotfix->hotfix_id, kFakeHotfixId); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Success.Hotfixes.Items", + /*number_of_items=*/1, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetHotfixesTest, TopLevelError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kConsentRequired; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.top_level_error = expected_error; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.Hotfixes.TopLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +TEST_F(EnterpriseReportingPrivateGetHotfixesTest, CollectionError) { + device_signals::SignalCollectionError expected_error = + device_signals::SignalCollectionError::kMissingSystemService; + + device_signals::HotfixSignalResponse hotfix_response; + hotfix_response.collection_error = expected_error; + + device_signals::SignalsAggregationResponse expected_response; + expected_response.hotfix_signal_response = hotfix_response; + SetFakeResponse(expected_response); + + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString(expected_error)); + + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure", signal_name(), 1); + histogram_tester_.ExpectUniqueSample( + "Enterprise.DeviceSignals.Collection.Failure.Hotfixes." + "CollectionLevelError", + /*error=*/expected_error, + /*number_of_occurrences=*/1); +} + +class EnterpriseReportingPrivateGetHotfixesInfoDisabledTest + : public EnterpriseReportingPrivateGetHotfixesTest { + protected: + // Overwrite this function to disable the feature flag for tests using this + // specific fixture. + void SetFeatureFlag() override { + scoped_features_.InitAndEnableFeatureWithParameters( + enterprise_signals::features::kNewEvSignalsEnabled, + {{"DisableHotfix", "true"}}); + } +}; + +TEST_F(EnterpriseReportingPrivateGetHotfixesInfoDisabledTest, + FlagDisabled_Test) { + auto error = api_test_utils::RunFunctionAndReturnError( + function_.get(), GetFakeUserContextJsonParams(), profile()); + EXPECT_EQ(error, function_->GetError()); + EXPECT_EQ(error, device_signals::ErrorToString( + device_signals::SignalCollectionError::kUnsupported)); +} + +#endif // BUILDFLAG(IS_WIN) + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/keychain_data_helper_mac.mm b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/keychain_data_helper_mac.mm index ca810f8158a..05e95beacb9 100644 --- a/chromium/chrome/browser/extensions/api/enterprise_reporting_private/keychain_data_helper_mac.mm +++ b/chromium/chrome/browser/extensions/api/enterprise_reporting_private/keychain_data_helper_mac.mm @@ -15,6 +15,12 @@ namespace extensions { namespace { +// Much of the Keychain API was marked deprecated as of the macOS 13 SDK. +// Removal of its use is tracked in https://crbug.com/1348251 but deprecation +// warnings are disabled in the meanwhile. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Creates an access for a generic password item to share it with other Google // applications with teamid:EQHXZ8M8AV (taken from the signing certificate). OSStatus CreateTargetAccess(NSString* service_name, SecAccessRef* access_ref) { @@ -116,4 +122,6 @@ OSStatus VerifyDefaultKeychainUnlocked(bool* unlocked) { return VerifyKeychainUnlocked(keychain, unlocked); } +#pragma clang diagnostic pop + } // 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 0b7ae5b359e..38901384b55 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 @@ -548,7 +548,8 @@ IN_PROC_BROWSER_TEST_P(BrowserActionApiTestWithContextType, // Go back to first tab, changed title should reappear. browser()->tab_strip_model()->ActivateTabAt( - 0, {TabStripModel::GestureType::kOther}); + 0, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); EXPECT_EQ("Showing icon 2", GetBrowserActionsBar()->GetTooltip(extension->id())); 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 4f2a14feff9..f4ba6142b99 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 @@ -34,6 +34,8 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/download_test_observer.h" +#include "content/public/test/fenced_frame_test_util.h" +#include "content/public/test/test_navigation_observer.h" #include "extensions/browser/extension_action.h" #include "extensions/browser/extension_action_manager.h" #include "extensions/browser/extension_host.h" @@ -72,7 +74,7 @@ namespace { bool IsDownloadSurfaceVisible(BrowserWindow* window) { return base::FeatureList::IsEnabled(safe_browsing::kDownloadBubble) ? window->GetDownloadBubbleUIController() - ->display_controller_for_testing() + ->GetDownloadDisplayController() ->download_display_for_testing() ->IsShowingDetails() : window->IsDownloadShelfVisible(); @@ -432,7 +434,8 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, TabSwitchClosesPopup) { ExtensionHostTestHelper host_helper(profile()); // Change active tabs, the extension popup should close. browser()->tab_strip_model()->ActivateTabAt( - 0, {TabStripModel::GestureType::kOther}); + 0, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); host_helper.WaitForHostDestroyed(); EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup()); @@ -613,8 +616,9 @@ class MainFrameSizeWaiter : public content::WebContentsObserver { // TODO(crbug.com/1249851): Test crashes on Windows #if BUILDFLAG(IS_WIN) #define MAYBE_BrowserActionPopup DISABLED_BrowserActionPopup -#elif BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) -// TODO(crbug.com/1269076): Test is flaky for linux tsan builds +#elif BUILDFLAG(IS_LINUX) && \ + (defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER)) +// TODO(crbug.com/1269076): Test is flaky for linux tsan and asan builds #define MAYBE_BrowserActionPopup DISABLED_BrowserActionPopup #elif BUILDFLAG(IS_MAC) // TODO(crbug.com/1269076): Test is flaky on Mac as well. @@ -786,7 +790,6 @@ class RenderFrameChangedWatcher : public content::WebContentsObserver { IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, BrowserActionPopupWithIframe) { ASSERT_TRUE(embedded_test_server()->Start()); - ASSERT_TRUE(LoadExtension( test_data_dir_.AppendASCII("browser_action/popup_with_iframe"))); const Extension* extension = GetSingleLoadedExtension(); @@ -832,6 +835,71 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, EXPECT_TRUE(ClosePopup()); } +class BrowserActionInteractiveFencedFrameTest + : public BrowserActionInteractiveTest { + public: + ~BrowserActionInteractiveFencedFrameTest() override = default; + + content::test::FencedFrameTestHelper& fenced_frame_test_helper() { + return fenced_frame_test_helper_; + } + + private: + content::test::FencedFrameTestHelper fenced_frame_test_helper_; +}; + +IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveFencedFrameTest, + BrowserActionPopupWithFencedFrame) { + net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); + https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); + https_server.ServeFilesFromSourceDirectory("chrome/test/data"); + ASSERT_TRUE(https_server.Start()); + + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("browser_action/popup_with_fencedframe"))); + const Extension* extension = GetSingleLoadedExtension(); + ASSERT_TRUE(extension) << message_; + + // Simulate a click on the browser action to open the popup. + ASSERT_TRUE(OpenPopupViaToolbar(extension->id())); + + // Find a primary main frame associated in the popup. + extensions::ProcessManager* manager = + extensions::ProcessManager::Get(browser()->profile()); + std::set<content::RenderFrameHost*> hosts = + manager->GetRenderFrameHostsForExtension(extension->id()); + const auto& it = + base::ranges::find_if(hosts, [](content::RenderFrameHost* host) { + return host->IsInPrimaryMainFrame(); + }); + content::RenderFrameHost* primary_rfh = (it != hosts.end()) ? *it : nullptr; + ASSERT_TRUE(primary_rfh); + + // Navigate the popup's fenced frame to a (cross-site) web page via its + // parent, and wait for that page to send a message, which will ensure that + // the page has loaded. + GURL foo_url(https_server.GetURL("a.test", "/popup_fencedframe.html")); + + content::TestNavigationObserver observer( + content::WebContents::FromRenderFrameHost(primary_rfh)); + std::string script = + "document.querySelector('fencedframe').src = '" + foo_url.spec() + "'"; + EXPECT_TRUE(ExecuteScript(primary_rfh, script)); + observer.WaitForNavigationFinished(); + + content::RenderFrameHost* fenced_frame_rfh = + fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame(primary_rfh); + ASSERT_TRUE(fenced_frame_rfh); + + // Confirm that the new page (popup_fencedframe.html) is actually loaded. + content::DOMMessageQueue dom_message_queue(fenced_frame_rfh); + std::string json; + EXPECT_TRUE(dom_message_queue.WaitForMessage(&json)); + EXPECT_EQ("\"DONE\"", json); + + EXPECT_TRUE(ClosePopup()); +} + class NavigatingExtensionPopupInteractiveTest : public BrowserActionInteractiveTest { public: 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 7cd02548061..a105cbb6757 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 @@ -280,8 +280,7 @@ void ExtensionActionAPI::DispatchEventToExtension( return; auto event = std::make_unique<Event>( - histogram_value, event_name, std::move(*event_args).TakeListDeprecated(), - context); + histogram_value, event_name, std::move(event_args->GetList()), context); event->user_gesture = EventRouter::USER_GESTURE_ENABLED; EventRouter::Get(context) ->DispatchEventToExtension(extension_id, std::move(event)); @@ -486,9 +485,9 @@ ExtensionActionSetIconFunction::RunExtensionAction() { ExtensionFunction::ResponseAction ExtensionActionSetTitleFunction::RunExtensionAction() { EXTENSION_FUNCTION_VALIDATE(details_); - std::string title; - EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title)); - extension_action_->SetTitle(tab_id_, title); + const std::string* title = details_->GetDict().FindString("title"); + EXTENSION_FUNCTION_VALIDATE(title); + extension_action_->SetTitle(tab_id_, *title); NotifyChange(); return RespondNow(NoArguments()); } 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 67f4ee4607b..0373a5692d7 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 @@ -18,6 +18,7 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/extensions/extension_action_test_helper.h" +#include "chrome/browser/ui/tabs/tab_enums.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model.h" #include "chrome/test/base/ui_test_utils.h" @@ -388,7 +389,7 @@ IN_PROC_BROWSER_TEST_P(MultiActionAPITest, { content::WebContentsDestroyedWatcher destroyed_watcher(web_contents); tab_strip_model->CloseWebContentsAt(tab_strip_model->active_index(), - TabStripModel::CLOSE_NONE); + TabCloseTypes::CLOSE_NONE); destroyed_watcher.Wait(); } // The title should have been cleared on tab removal as well. @@ -738,7 +739,8 @@ IN_PROC_BROWSER_TEST_P(ActionAndBrowserActionAPITest, ValuesArePersisted) { } // Tests setting the icon dynamically from the background page. -IN_PROC_BROWSER_TEST_P(MultiActionAPICanvasTest, DynamicSetIcon) { +// TODO(crbug.com/1340330): flaky. +IN_PROC_BROWSER_TEST_P(MultiActionAPICanvasTest, DISABLED_DynamicSetIcon) { constexpr char kManifestTemplate[] = R"({ "name": "Test Clicking", diff --git a/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc index 7c8ef6f371e..c223d12d335 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc @@ -222,7 +222,8 @@ IN_PROC_BROWSER_TEST_P(PageActionApiTest, TestTriggerPageAction) { browser(), embedded_test_server()->GetURL("/simple.html"))); chrome::NewTab(browser()); browser()->tab_strip_model()->ActivateTabAt( - 0, {TabStripModel::GestureType::kOther}); + 0, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); // Give the extension time to show the page action on the tab. WaitForPageActionVisibilityChangeTo(1); diff --git a/chromium/chrome/browser/extensions/api/favicon/favicon_util_unittest.cc b/chromium/chrome/browser/extensions/api/favicon/favicon_util_unittest.cc index 5c192857434..f92639f8a73 100644 --- a/chromium/chrome/browser/extensions/api/favicon/favicon_util_unittest.cc +++ b/chromium/chrome/browser/extensions/api/favicon/favicon_util_unittest.cc @@ -22,12 +22,12 @@ TEST(FaviconUtilUnittest, Parse) { {false, "chrome-extension://id/_favicon"}, {false, "chrome-extension://id/_favicon/"}, {false, "chrome-extension://id/_favicon/?"}, - {true, "chrome-extension://id/_favicon?page_url=https://ok.com"}, - {true, "chrome-extension://id/_favicon/?page_url=https://ok.com"}, - {true, "chrome-extension://id/_favicon/?page_url=https://ok.com&size=16"}, + {true, "chrome-extension://id/_favicon?pageUrl=https://ok.com"}, + {true, "chrome-extension://id/_favicon/?pageUrl=https://ok.com"}, + {true, "chrome-extension://id/_favicon/?pageUrl=https://ok.com&size=16"}, {true, - "chrome-extension://id/_favicon/?page_url=https://" - "ok.com&size=16&scale_factor=1.0x&server_fallback=1"}}; + "chrome-extension://id/_favicon/?pageUrl=https://" + "ok.com&size=16&scaleFactor=1.0x&server_fallback=1"}}; for (const auto& test_case : test_cases) { GURL url(test_case.url); chrome::ParsedFaviconPath parsed; diff --git a/chromium/chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.cc b/chromium/chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.cc index 7963fefbc17..dde4f12df87 100644 --- a/chromium/chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.cc +++ b/chromium/chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.cc @@ -12,6 +12,7 @@ #include "base/callback.h" #include "base/files/file_path.h" #include "base/logging.h" +#include "base/memory/raw_ptr.h" #include "base/values.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" @@ -76,7 +77,7 @@ class FileBrowserHandlerExecutorFlow { FileBrowserHandlerFlowFinishedCallback done_; - Profile* profile_; + raw_ptr<Profile> profile_; scoped_refptr<const Extension> extension_; // Inputs owned by the class. @@ -243,8 +244,7 @@ void FileBrowserHandlerExecutorFlow::GrantAccessToFilesAndLaunch( auto event = std::make_unique<extensions::Event>( extensions::events::FILE_BROWSER_HANDLER_ON_EXECUTE, - "fileBrowserHandler.onExecute", - base::Value(std::move(event_args)).TakeListDeprecated(), profile_); + "fileBrowserHandler.onExecute", std::move(event_args), profile_); router->DispatchEventToExtension(extension_->id(), std::move(event)); Finish(true); // Success. diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc index 29346dc0038..e9747993b4c 100644 --- a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc @@ -6,74 +6,48 @@ #include <string> #include <utility> -#include <vector> #include "apps/saved_files_service.h" #include "base/bind.h" #include "base/callback.h" #include "base/check.h" -#include "base/files/file_path.h" +#include "base/notreached.h" #include "base/path_service.h" -#include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "chrome/browser/download/chrome_download_manager_delegate.h" #include "chrome/browser/download/download_core_service.h" #include "chrome/browser/download/download_core_service_factory.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/extensions/api/file_system/file_entry_picker.h" -#include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h" #include "chrome/common/chrome_paths.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/browser_context.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" -#include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" -#include "extensions/browser/api/file_handlers/app_file_handler_util.h" #include "extensions/browser/api/file_system/saved_files_service_interface.h" #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window_registry.h" -#include "extensions/browser/extension_function.h" -#include "extensions/browser/extension_prefs.h" -#include "extensions/browser/extension_system.h" -#include "extensions/browser/extension_util.h" -#include "extensions/common/api/file_system.h" #include "extensions/common/extension.h" -#include "storage/browser/file_system/external_mount_points.h" -#include "storage/browser/file_system/isolated_context.h" -#include "storage/common/file_system/file_system_types.h" #include "storage/common/file_system/file_system_util.h" -#include "third_party/blink/public/common/storage_key/storage_key.h" -#include "ui/shell_dialogs/select_file_dialog.h" #if BUILDFLAG(IS_MAC) #include <CoreFoundation/CoreFoundation.h> #include "base/mac/foundation_util.h" #endif -#if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chrome/browser/ash/file_manager/volume_manager.h" -#include "chrome/browser/extensions/api/file_system/consent_provider.h" +#if BUILDFLAG(IS_CHROMEOS) #include "extensions/browser/event_router.h" -#include "extensions/browser/extension_registry.h" -#include "extensions/common/constants.h" -#include "url/gurl.h" -#include "url/origin.h" -#include "url/url_constants.h" -#endif +#endif // BUILDFLAG(IS_CHROMEOS) namespace extensions { namespace file_system = api::file_system; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) using file_system_api::ConsentProvider; using file_system_api::ConsentProviderDelegate; -namespace { +namespace file_system_api { const char kConsentImpossible[] = "Impossible to ask for user consent as there is no app window visible."; @@ -84,173 +58,27 @@ const char kRequiresFileSystemWriteError[] = const char kSecurityError[] = "Security error."; const char kVolumeNotFoundError[] = "Volume not found."; -// Fills a list of volumes mounted in the system. -bool GetVolumeListForExtension( - const std::vector<base::WeakPtr<file_manager::Volume>>& available_volumes, - ConsentProvider* consent_provider, - const Extension& extension, - std::vector<file_system::Volume>* result_volumes) { - if (!consent_provider) - return false; - - const FileSystemDelegate::GrantVolumesMode mode = - consent_provider->GetGrantVolumesMode(extension); - if (mode == FileSystemDelegate::kGrantNone) - return false; - - // Convert available_volumes to result_volume_list. - for (const auto& volume : available_volumes) { - if (mode == FileSystemDelegate::kGrantAll || - (mode == FileSystemDelegate::kGrantPerVolume && - consent_provider->IsGrantableForVolume(extension, volume))) { - file_system::Volume result_volume; - result_volume.volume_id = volume->volume_id(); - result_volume.writable = !volume->is_read_only(); - result_volumes->push_back(std::move(result_volume)); - } - } - return true; -} - -// Callback called when consent is granted or denied. -void OnConsentReceived(content::BrowserContext* browser_context, - scoped_refptr<ExtensionFunction> requester, - FileSystemDelegate::FileSystemCallback success_callback, - FileSystemDelegate::ErrorCallback error_callback, - const url::Origin& origin, - const base::WeakPtr<file_manager::Volume>& volume, - bool writable, - ConsentProvider::Consent result) { - using file_manager::VolumeManager; - using file_manager::Volume; - - // Render frame host can be gone before this callback method is executed. - if (!requester->render_frame_host()) { - std::move(error_callback).Run(std::string()); - return; - } - +// Returns error message, or null if none. +const char* ConsentResultToError(ConsentProvider::Consent result) { switch (result) { case ConsentProvider::CONSENT_REJECTED: - std::move(error_callback).Run(kSecurityError); - return; - + return kSecurityError; case ConsentProvider::CONSENT_IMPOSSIBLE: - std::move(error_callback).Run(kConsentImpossible); - return; - + return kConsentImpossible; case ConsentProvider::CONSENT_GRANTED: - break; - } - - if (!volume.get()) { - std::move(error_callback).Run(kVolumeNotFoundError); - return; - } - - DCHECK_EQ(origin.scheme(), kExtensionScheme); - scoped_refptr<storage::FileSystemContext> file_system_context = - util::GetStoragePartitionForExtensionId(origin.host(), browser_context) - ->GetFileSystemContext(); - storage::ExternalFileSystemBackend* const backend = - file_system_context->external_backend(); - DCHECK(backend); - - base::FilePath virtual_path; - if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { - std::move(error_callback).Run(kSecurityError); - return; - } - - storage::IsolatedContext* const isolated_context = - storage::IsolatedContext::GetInstance(); - DCHECK(isolated_context); - - const storage::FileSystemURL original_url = - file_system_context->CreateCrackedFileSystemURL( - blink::StorageKey(origin), storage::kFileSystemTypeExternal, - virtual_path); - - // Set a fixed register name, as the automatic one would leak the mount point - // directory. - std::string register_name = "fs"; - const storage::IsolatedContext::ScopedFSHandle file_system = - isolated_context->RegisterFileSystemForPath( - storage::kFileSystemTypeLocalForPlatformApp, - std::string() /* file_system_id */, original_url.path(), - ®ister_name); - if (!file_system.is_valid()) { - std::move(error_callback).Run(kSecurityError); - return; - } - - backend->GrantFileAccessToOrigin(origin, virtual_path); - - // Grant file permissions to the renderer hosting component. - content::ChildProcessSecurityPolicy* policy = - content::ChildProcessSecurityPolicy::GetInstance(); - DCHECK(policy); - - const auto process_id = requester->source_process_id(); - // Read-only permisisons. - policy->GrantReadFile(process_id, volume->mount_path()); - policy->GrantReadFileSystem(process_id, file_system.id()); - - // Additional write permissions. - if (writable) { - policy->GrantCreateReadWriteFile(process_id, volume->mount_path()); - policy->GrantCopyInto(process_id, volume->mount_path()); - policy->GrantWriteFileSystem(process_id, file_system.id()); - policy->GrantDeleteFromFileSystem(process_id, file_system.id()); - policy->GrantCreateFileForFileSystem(process_id, file_system.id()); - } - - std::move(success_callback).Run(file_system.id(), register_name); -} - -} // namespace - -namespace file_system_api { - -void DispatchVolumeListChangeEvent(content::BrowserContext* browser_context) { - DCHECK(browser_context); - EventRouter* const event_router = EventRouter::Get(browser_context); - if (!event_router) // Possible on shutdown. - return; - - ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context); - if (!registry) // Possible on shutdown. - return; - - ConsentProviderDelegate consent_provider_delegate( - Profile::FromBrowserContext(browser_context)); - ConsentProvider consent_provider(&consent_provider_delegate); - - const std::vector<base::WeakPtr<file_manager::Volume>> volume_list = - file_manager::VolumeManager::Get(browser_context)->GetVolumeList(); - - for (const auto& extension : registry->enabled_extensions()) { - file_system::VolumeListChangedEvent event_args; - if (!GetVolumeListForExtension(volume_list, &consent_provider, - *extension.get(), &event_args.volumes)) { - continue; - } - - event_router->DispatchEventToExtension( - extension->id(), - std::make_unique<Event>( - events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED, - file_system::OnVolumeListChanged::kEventName, - file_system::OnVolumeListChanged::Create(event_args))); + return nullptr; } + NOTREACHED(); } } // namespace file_system_api -#endif // BUILDFLAG(IS_CHROMEOS_ASH) +#endif // BUILDFLAG(IS_CHROMEOS) -ChromeFileSystemDelegate::ChromeFileSystemDelegate() {} +/******** ChromeFileSystemDelegate ********/ -ChromeFileSystemDelegate::~ChromeFileSystemDelegate() {} +ChromeFileSystemDelegate::ChromeFileSystemDelegate() = default; + +ChromeFileSystemDelegate::~ChromeFileSystemDelegate() = default; base::FilePath ChromeFileSystemDelegate::GetDefaultDirectory() { base::FilePath documents_dir; @@ -337,18 +165,15 @@ int ChromeFileSystemDelegate::GetDescriptionIdForAcceptType( return 0; } -#if BUILDFLAG(IS_CHROMEOS_ASH) -FileSystemDelegate::GrantVolumesMode -ChromeFileSystemDelegate::GetGrantVolumesMode( +#if BUILDFLAG(IS_CHROMEOS) +bool ChromeFileSystemDelegate::IsGrantable( content::BrowserContext* browser_context, - content::RenderFrameHost* render_frame_host, const Extension& extension) { // Only kiosk apps in kiosk sessions can use this API. // Additionally it is enabled for allowlisted component extensions and apps. ConsentProviderDelegate consent_provider_delegate( Profile::FromBrowserContext(browser_context)); - return ConsentProvider(&consent_provider_delegate) - .GetGrantVolumesMode(extension); + return ConsentProvider(&consent_provider_delegate).IsGrantable(extension); } void ChromeFileSystemDelegate::RequestFileSystem( @@ -358,82 +183,13 @@ void ChromeFileSystemDelegate::RequestFileSystem( std::string volume_id, bool writable, FileSystemCallback success_callback, - ErrorCallback error_callback) { - ConsentProviderDelegate consent_provider_delegate( - Profile::FromBrowserContext(browser_context)); - ConsentProvider consent_provider(&consent_provider_delegate); - - using file_manager::VolumeManager; - using file_manager::Volume; - VolumeManager* const volume_manager = VolumeManager::Get(browser_context); - DCHECK(volume_manager); - - if (writable && - !app_file_handler_util::HasFileSystemWritePermission(&extension)) { - std::move(error_callback).Run(kRequiresFileSystemWriteError); - return; - } - - if (consent_provider.GetGrantVolumesMode(extension) == - FileSystemDelegate::kGrantNone) { - std::move(error_callback).Run(kNotSupportedOnNonKioskSessionError); - return; - } - - base::WeakPtr<file_manager::Volume> volume = - volume_manager->FindVolumeById(volume_id); - if (!volume.get() || - !consent_provider.IsGrantableForVolume(extension, volume)) { - std::move(error_callback).Run(kVolumeNotFoundError); - return; - } - - scoped_refptr<storage::FileSystemContext> file_system_context = - util::GetStoragePartitionForExtensionId(extension.id(), browser_context) - ->GetFileSystemContext(); - storage::ExternalFileSystemBackend* const backend = - file_system_context->external_backend(); - DCHECK(backend); - - base::FilePath virtual_path; - if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { - std::move(error_callback).Run(kSecurityError); - return; - } - - if (writable && (volume->is_read_only())) { - std::move(error_callback).Run(kSecurityError); - return; - } - - ConsentProvider::ConsentCallback callback = - base::BindOnce(&OnConsentReceived, browser_context, requester, - std::move(success_callback), std::move(error_callback), - extension.origin(), volume, writable); - - consent_provider.RequestConsent(extension, requester->render_frame_host(), - volume, writable, std::move(callback)); -} + ErrorCallback error_callback) {} void ChromeFileSystemDelegate::GetVolumeList( content::BrowserContext* browser_context, - const Extension& extension, VolumeListCallback success_callback, - ErrorCallback error_callback) { - ConsentProviderDelegate consent_provider_delegate( - Profile::FromBrowserContext(browser_context)); - ConsentProvider consent_provider(&consent_provider_delegate); - - const std::vector<base::WeakPtr<file_manager::Volume>> volume_list = - file_manager::VolumeManager::Get(browser_context)->GetVolumeList(); - std::vector<file_system::Volume> result_volume_list; - - GetVolumeListForExtension(volume_list, &consent_provider, extension, - &result_volume_list); - std::move(success_callback).Run(result_volume_list); -} - -#endif // BUILDFLAG(IS_CHROMEOS_ASH) + ErrorCallback error_callback) {} +#endif // BUILDFLAG(IS_CHROMEOS) SavedFilesServiceInterface* ChromeFileSystemDelegate::GetSavedFilesService( content::BrowserContext* browser_context) { diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h index 9be9c0da9a4..cde2f6905b1 100644 --- a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h @@ -8,21 +8,38 @@ #include "extensions/browser/api/file_system/file_system_delegate.h" #include <memory> +#include <vector> +#include "base/files/file_path.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "extensions/browser/extension_function.h" +#include "ui/shell_dialogs/select_file_dialog.h" + +#if BUILDFLAG(IS_CHROMEOS) +#include "chrome/browser/extensions/api/file_system/consent_provider.h" +#endif // BUILDFLAG(IS_CHROMEOS) + +namespace content { +class BrowserContext; +} // namespace content namespace extensions { -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) namespace file_system_api { -// Dispatches an event about a mounted or unmounted volume in the system to -// each extension which can request it. -void DispatchVolumeListChangeEvent(content::BrowserContext* browser_context); +extern const char kConsentImpossible[]; +extern const char kNotSupportedOnNonKioskSessionError[]; +extern const char kRequiresFileSystemWriteError[]; +extern const char kSecurityError[]; +extern const char kVolumeNotFoundError[]; + +// Returns error message, or null if none. +const char* ConsentResultToError(ConsentProvider::Consent result); } // namespace file_system_api -#endif // BUILDFLAG(IS_CHROMEOS_ASH) +#endif // BUILDFLAG(IS_CHROMEOS) class ChromeFileSystemDelegate : public FileSystemDelegate { public: @@ -51,11 +68,9 @@ class ChromeFileSystemDelegate : public FileSystemDelegate { base::OnceClosure on_accept, base::OnceClosure on_cancel) override; int GetDescriptionIdForAcceptType(const std::string& accept_type) override; -#if BUILDFLAG(IS_CHROMEOS_ASH) - FileSystemDelegate::GrantVolumesMode GetGrantVolumesMode( - content::BrowserContext* browser_context, - content::RenderFrameHost* render_frame_host, - const Extension& extension) override; +#if BUILDFLAG(IS_CHROMEOS) + bool IsGrantable(content::BrowserContext* browser_context, + const Extension& extension) override; void RequestFileSystem(content::BrowserContext* browser_context, scoped_refptr<ExtensionFunction> requester, const Extension& extension, @@ -64,10 +79,9 @@ class ChromeFileSystemDelegate : public FileSystemDelegate { FileSystemCallback success_callback, ErrorCallback error_callback) override; void GetVolumeList(content::BrowserContext* browser_context, - const Extension& extension, VolumeListCallback success_callback, ErrorCallback error_callback) override; -#endif // BUILDFLAG(IS_CHROMEOS_ASH) +#endif // BUILDFLAG(IS_CHROMEOS) SavedFilesServiceInterface* GetSavedFilesService( content::BrowserContext* browser_context) override; }; diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.cc b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.cc new file mode 100644 index 00000000000..0c594e1c123 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.cc @@ -0,0 +1,269 @@ +// Copyright 2022 The Chromium Authors. All 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/file_system/chrome_file_system_delegate_ash.h" + +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check.h" +#include "base/path_service.h" +#include "chrome/browser/ash/file_manager/volume_manager.h" +#include "chrome/browser/extensions/api/file_system/consent_provider.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/storage_partition.h" +#include "extensions/browser/api/file_handlers/app_file_handler_util.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_util.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "storage/browser/file_system/file_system_backend.h" +#include "storage/browser/file_system/file_system_context.h" +#include "storage/browser/file_system/file_system_url.h" +#include "storage/browser/file_system/isolated_context.h" +#include "storage/common/file_system/file_system_types.h" +#include "third_party/blink/public/common/storage_key/storage_key.h" +#include "url/gurl.h" +#include "url/origin.h" +#include "url/url_constants.h" + +namespace extensions { + +namespace file_system = api::file_system; + +using file_system_api::ConsentProvider; +using file_system_api::ConsentProviderDelegate; + +namespace { + +// Fills a list of volumes mounted in the system. +void FillVolumeList(content::BrowserContext* browser_context, + std::vector<file_system::Volume>* result) { + file_manager::VolumeManager* const volume_manager = + file_manager::VolumeManager::Get(browser_context); + DCHECK(volume_manager); + + const auto& volume_list = volume_manager->GetVolumeList(); + // Convert volume_list to result_volume_list. + for (const auto& volume : volume_list) { + file_system::Volume result_volume; + result_volume.volume_id = volume->volume_id(); + result_volume.writable = !volume->is_read_only(); + result->push_back(std::move(result_volume)); + } +} + +// Callback called when consent is granted or denied. +void OnConsentReceived(content::BrowserContext* browser_context, + scoped_refptr<ExtensionFunction> requester, + FileSystemDelegate::FileSystemCallback success_callback, + FileSystemDelegate::ErrorCallback error_callback, + const url::Origin& origin, + const base::WeakPtr<file_manager::Volume>& volume, + bool writable, + ConsentProvider::Consent result) { + using file_manager::Volume; + using file_manager::VolumeManager; + + // Render frame host can be gone before this callback method is executed. + if (!requester->render_frame_host()) { + std::move(error_callback).Run(std::string()); + return; + } + + const char* consent_err_msg = file_system_api::ConsentResultToError(result); + if (consent_err_msg) { + std::move(error_callback).Run(consent_err_msg); + return; + } + + if (!volume.get()) { + std::move(error_callback).Run(file_system_api::kVolumeNotFoundError); + return; + } + + DCHECK_EQ(origin.scheme(), kExtensionScheme); + scoped_refptr<storage::FileSystemContext> file_system_context = + util::GetStoragePartitionForExtensionId(origin.host(), browser_context) + ->GetFileSystemContext(); + storage::ExternalFileSystemBackend* const backend = + file_system_context->external_backend(); + DCHECK(backend); + + base::FilePath virtual_path; + if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { + std::move(error_callback).Run(file_system_api::kSecurityError); + return; + } + + storage::IsolatedContext* const isolated_context = + storage::IsolatedContext::GetInstance(); + DCHECK(isolated_context); + + const storage::FileSystemURL original_url = + file_system_context->CreateCrackedFileSystemURL( + blink::StorageKey(origin), storage::kFileSystemTypeExternal, + virtual_path); + + // Set a fixed register name, as the automatic one would leak the mount point + // directory. + std::string register_name = "fs"; + const storage::IsolatedContext::ScopedFSHandle file_system = + isolated_context->RegisterFileSystemForPath( + storage::kFileSystemTypeLocalForPlatformApp, + std::string() /* file_system_id */, original_url.path(), + ®ister_name); + if (!file_system.is_valid()) { + std::move(error_callback).Run(file_system_api::kSecurityError); + return; + } + + backend->GrantFileAccessToOrigin(origin, virtual_path); + + // Grant file permissions to the renderer hosting component. + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + DCHECK(policy); + + const auto process_id = requester->source_process_id(); + // Read-only permisisons. + policy->GrantReadFile(process_id, volume->mount_path()); + policy->GrantReadFileSystem(process_id, file_system.id()); + + // Additional write permissions. + if (writable) { + policy->GrantCreateReadWriteFile(process_id, volume->mount_path()); + policy->GrantCopyInto(process_id, volume->mount_path()); + policy->GrantWriteFileSystem(process_id, file_system.id()); + policy->GrantDeleteFromFileSystem(process_id, file_system.id()); + policy->GrantCreateFileForFileSystem(process_id, file_system.id()); + } + + std::move(success_callback).Run(file_system.id(), register_name); +} + +} // namespace + +namespace file_system_api { + +void DispatchVolumeListChangeEventAsh( + content::BrowserContext* browser_context) { + DCHECK(browser_context); + EventRouter* const event_router = EventRouter::Get(browser_context); + if (!event_router) // Possible on shutdown. + return; + + ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context); + if (!registry) // Possible on shutdown. + return; + + ConsentProviderDelegate consent_provider_delegate( + Profile::FromBrowserContext(browser_context)); + ConsentProvider consent_provider(&consent_provider_delegate); + + file_system::VolumeListChangedEvent event_args; + FillVolumeList(browser_context, &event_args.volumes); + for (const auto& extension : registry->enabled_extensions()) { + if (!consent_provider.IsGrantable(*extension.get())) + continue; + + event_router->DispatchEventToExtension( + extension->id(), + std::make_unique<Event>( + events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED, + file_system::OnVolumeListChanged::kEventName, + file_system::OnVolumeListChanged::Create(event_args))); + } +} + +} // namespace file_system_api + +/******** ChromeFileSystemDelegateAsh ********/ + +ChromeFileSystemDelegateAsh::ChromeFileSystemDelegateAsh() = default; + +ChromeFileSystemDelegateAsh::~ChromeFileSystemDelegateAsh() = default; + +void ChromeFileSystemDelegateAsh::RequestFileSystem( + content::BrowserContext* browser_context, + scoped_refptr<ExtensionFunction> requester, + const Extension& extension, + std::string volume_id, + bool writable, + FileSystemCallback success_callback, + ErrorCallback error_callback) { + ConsentProviderDelegate consent_provider_delegate( + Profile::FromBrowserContext(browser_context)); + ConsentProvider consent_provider(&consent_provider_delegate); + + using file_manager::Volume; + using file_manager::VolumeManager; + VolumeManager* const volume_manager = VolumeManager::Get(browser_context); + DCHECK(volume_manager); + + if (writable && + !app_file_handler_util::HasFileSystemWritePermission(&extension)) { + std::move(error_callback) + .Run(file_system_api::kRequiresFileSystemWriteError); + return; + } + + if (!consent_provider.IsGrantable(extension)) { + std::move(error_callback) + .Run(file_system_api::kNotSupportedOnNonKioskSessionError); + return; + } + + base::WeakPtr<file_manager::Volume> volume = + volume_manager->FindVolumeById(volume_id); + if (!volume.get()) { + std::move(error_callback).Run(file_system_api::kVolumeNotFoundError); + return; + } + + scoped_refptr<storage::FileSystemContext> file_system_context = + util::GetStoragePartitionForExtensionId(extension.id(), browser_context) + ->GetFileSystemContext(); + storage::ExternalFileSystemBackend* const backend = + file_system_context->external_backend(); + DCHECK(backend); + + base::FilePath virtual_path; + if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { + std::move(error_callback).Run(file_system_api::kSecurityError); + return; + } + + if (writable && (volume->is_read_only())) { + std::move(error_callback).Run(file_system_api::kSecurityError); + return; + } + + ConsentProvider::ConsentCallback callback = + base::BindOnce(&OnConsentReceived, browser_context, requester, + std::move(success_callback), std::move(error_callback), + extension.origin(), volume, writable); + + consent_provider.RequestConsent(requester->render_frame_host(), extension, + volume->volume_id(), volume->volume_label(), + writable, std::move(callback)); +} + +void ChromeFileSystemDelegateAsh::GetVolumeList( + content::BrowserContext* browser_context, + VolumeListCallback success_callback, + ErrorCallback /*error_callback*/) { + std::vector<file_system::Volume> result_volume_list; + FillVolumeList(browser_context, &result_volume_list); + + std::move(success_callback).Run(result_volume_list); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h new file mode 100644 index 00000000000..93d514bcfc7 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h @@ -0,0 +1,56 @@ +// Copyright 2022 The Chromium Authors. All 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_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_ASH_H_ +#define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_ASH_H_ + +#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h" + +#include <string> + +#include "base/memory/scoped_refptr.h" +#include "extensions/browser/extension_function.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace extensions { + +class Extension; + +namespace file_system_api { + +// Dispatches an event about a mounted or unmounted volume in the system to +// each extension which can request it. +void DispatchVolumeListChangeEventAsh(content::BrowserContext* browser_context); + +} // namespace file_system_api + +class ChromeFileSystemDelegateAsh : public ChromeFileSystemDelegate { + public: + ChromeFileSystemDelegateAsh(); + + ChromeFileSystemDelegateAsh(const ChromeFileSystemDelegateAsh&) = delete; + ChromeFileSystemDelegateAsh& operator=(const ChromeFileSystemDelegateAsh&) = + delete; + + ~ChromeFileSystemDelegateAsh() override; + + // ChromeFileSystemDelegate: + void RequestFileSystem(content::BrowserContext* browser_context, + scoped_refptr<ExtensionFunction> requester, + const Extension& extension, + std::string volume_id, + bool writable, + FileSystemCallback success_callback, + ErrorCallback error_callback) override; + void GetVolumeList(content::BrowserContext* browser_context, + VolumeListCallback success_callback, + ErrorCallback error_callback) override; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_ASH_H_ diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc new file mode 100644 index 00000000000..8b4f913f884 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc @@ -0,0 +1,342 @@ +// Copyright 2022 The Chromium Authors. All 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/file_system/chrome_file_system_delegate_lacros.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check.h" +#include "base/memory/raw_ptr.h" +#include "base/path_service.h" +#include "base/scoped_observation.h" +#include "chrome/browser/extensions/api/file_system/consent_provider.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_observer.h" +#include "chromeos/lacros/lacros_service.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "extensions/browser/api/file_handlers/app_file_handler_util.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/granted_file_entry.h" +#include "extensions/common/extension.h" + +namespace extensions { + +namespace file_system = api::file_system; + +using extensions::app_file_handler_util::CreateFileEntryWithPermissions; +using file_system_api::ConsentProvider; +using file_system_api::ConsentProviderDelegate; + +namespace { + +const char kApiUnavailableError[] = "API unavailable."; +const char kProfileGoneError[] = "Profile gone."; +const char kRenderFrameHostGoneError[] = "Render frame host gone."; + +// Volume list converter that excludes volumes unsupported by lacros-chrome. +void ConvertAndFilterMojomToVolumeList( + const std::vector<crosapi::mojom::VolumePtr>& src_volume_list, + std::vector<file_system::Volume>* dst_volume_list) { + DCHECK(dst_volume_list->empty()); + for (auto& src_volume : src_volume_list) { + if (src_volume->is_available_to_lacros) { + file_system::Volume dst_volume; + dst_volume.volume_id = src_volume->volume_id; + dst_volume.writable = src_volume->writable; + dst_volume_list->emplace_back(std::move(dst_volume)); + } + } +} + +} // namespace + +namespace file_system_api { + +void DispatchVolumeListChangeEventLacros( + content::BrowserContext* browser_context, + const std::vector<crosapi::mojom::VolumePtr>& volume_list) { + DCHECK(browser_context); + EventRouter* const event_router = EventRouter::Get(browser_context); + if (!event_router) // Possible on shutdown. + return; + + ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context); + if (!registry) // Possible on shutdown. + return; + + // TODO(crbug.com/1351493): Simplify usage for IsGrantable(). + ConsentProviderDelegate consent_provider_delegate( + Profile::FromBrowserContext(browser_context)); + ConsentProvider consent_provider(&consent_provider_delegate); + + file_system::VolumeListChangedEvent event_args; + // Note: Events are still fired even if: + // * The *filtered* volume list does not change. + // * The filtered volume list is empty. + // This is done for simplicy: Detecting change in filtered volume list will + // requires caching volume list on Lacros side; preventing empty filtered + // volume list from triggering an event will lead to inconsistencies compared + // to polling via getVolumeList(). + ConvertAndFilterMojomToVolumeList(volume_list, &event_args.volumes); + for (const auto& extension : registry->enabled_extensions()) { + if (!consent_provider.IsGrantable(*extension)) + continue; + + event_router->DispatchEventToExtension( + extension->id(), + std::make_unique<Event>( + events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED, + file_system::OnVolumeListChanged::kEventName, + file_system::OnVolumeListChanged::Create(event_args))); + } +} + +} // namespace file_system_api + +namespace { + +/******** RequestFileSystemExecutor ********/ + +// Executor for chrome.requestFileSystem(), with async steps: +// 1. Crosapi call to get volume info. +// 2. (Potentially) request consent via dialog. +// Sources of complexity: +// * Lifetime: Instances are ref counted, and are kept alive via callback +// binding. +// * Profile: (2) requires |profile_|, which may disappear while awaiting (1)! +// This is handled by observing |profile_|: If it is destroyed then abort +// before (2); else proceeds with (2) and unobserve ASAP. +// * Fulfillment: To ensure the request is fulfilled, one of |success_callback| +// or |error_callback| gets called eventually (via FinishWith*()). +class RequestFileSystemExecutor + : public base::RefCountedThreadSafe<RequestFileSystemExecutor>, + public ProfileObserver { + public: + RequestFileSystemExecutor( + Profile* profile, + scoped_refptr<ExtensionFunction> requester, + const std::string& volume_id, + bool writable, + ChromeFileSystemDelegate::FileSystemCallback success_callback, + ChromeFileSystemDelegate::ErrorCallback error_callback); + RequestFileSystemExecutor(const RequestFileSystemExecutor&) = delete; + RequestFileSystemExecutor& operator=(const RequestFileSystemExecutor&) = + delete; + + // Entry point for executor flow. + void Run(chromeos::LacrosService* lacros_service); + + private: + friend class base::RefCountedThreadSafe<RequestFileSystemExecutor>; + ~RequestFileSystemExecutor() override; + + // ProfileObserver: + void OnProfileWillBeDestroyed(Profile* profile) override; + + // Callback for (1), on receiving volume info from crosapi. + void OnCrosapiGetVolumeMountInfo(crosapi::mojom::VolumePtr crosapi_volume); + + // Callback for (2), on consent granting or denial. + void OnConsentReceived(base::FilePath mount_path, + ConsentProvider::Consent result); + + // Consumes |error_callback_| to pass |error| on error. + void FinishWithError(const std::string& error); + + // Consumes |success_callback_| to pass results on success. + void FinishWithResponse(const std::string& filesystem_id, + const std::string& registered_name); + + // |profile_| can be a raw pointer since its destruction is observed. + base::raw_ptr<Profile> profile_; + base::ScopedObservation<Profile, ProfileObserver> profile_observation_{this}; + scoped_refptr<ExtensionFunction> requester_; + const std::string volume_id_; + const bool want_writable_; + ChromeFileSystemDelegate::FileSystemCallback success_callback_; + ChromeFileSystemDelegate::ErrorCallback error_callback_; +}; + +RequestFileSystemExecutor::RequestFileSystemExecutor( + Profile* profile, + scoped_refptr<ExtensionFunction> requester, + const std::string& volume_id, + bool want_writable, + ChromeFileSystemDelegate::FileSystemCallback success_callback, + ChromeFileSystemDelegate::ErrorCallback error_callback) + : profile_(profile), + requester_(requester), + volume_id_(volume_id), + want_writable_(want_writable), + success_callback_(std::move(success_callback)), + error_callback_(std::move(error_callback)) { + profile_observation_.Observe(profile_); +} + +void RequestFileSystemExecutor::Run(chromeos::LacrosService* lacros_service) { + // All code path from here must lead to either |success_callback_| or + // |error_callback_| getting called. + lacros_service->GetRemote<crosapi::mojom::VolumeManager>() + ->GetVolumeMountInfo( + volume_id_, + base::BindOnce( + &RequestFileSystemExecutor::OnCrosapiGetVolumeMountInfo, this)); +} + +RequestFileSystemExecutor::~RequestFileSystemExecutor() = default; + +void RequestFileSystemExecutor::OnProfileWillBeDestroyed(Profile* profile) { + DCHECK_EQ(profile_, profile); + profile_observation_.Reset(); + profile_ = nullptr; +} + +void RequestFileSystemExecutor::OnCrosapiGetVolumeMountInfo( + crosapi::mojom::VolumePtr crosapi_volume) { + // Profile can be gone before this callback executes, while awaiting crosapi. + if (!profile_) { + FinishWithError(kProfileGoneError); + return; + } + if (!crosapi_volume || !crosapi_volume->is_available_to_lacros) { + FinishWithError(file_system_api::kVolumeNotFoundError); + return; + } + if (want_writable_ && !crosapi_volume->writable) { + FinishWithError(file_system_api::kSecurityError); + return; + } + + // TODO(crbug.com/1351493): Simplify usage for RequestConsent(). + ConsentProviderDelegate consent_provider_delegate(profile_); + ConsentProvider consent_provider(&consent_provider_delegate); + + ConsentProvider::ConsentCallback callback = + base::BindOnce(&RequestFileSystemExecutor::OnConsentReceived, this, + crosapi_volume->mount_path); + + consent_provider.RequestConsent( + requester_->render_frame_host(), *requester_->extension(), + crosapi_volume->volume_id, crosapi_volume->volume_label, want_writable_, + std::move(callback)); + + // Done with |profile_|, so stop observing. + profile_observation_.Reset(); + profile_ = nullptr; +} + +void RequestFileSystemExecutor::OnConsentReceived( + base::FilePath mount_path, + ConsentProvider::Consent result) { + // Render frame host can be gone before this callback executes. + if (!requester_->render_frame_host()) { + FinishWithError(kRenderFrameHostGoneError); + return; + } + + const char* consent_err_msg = file_system_api::ConsentResultToError(result); + if (consent_err_msg) { + FinishWithError(consent_err_msg); + return; + } + + const auto process_id = requester_->source_process_id(); + extensions::GrantedFileEntry granted_file_entry = + CreateFileEntryWithPermissions(process_id, mount_path, + /*can_write=*/want_writable_, + /*can_create=*/want_writable_, + /*can_delete=*/want_writable_); + FinishWithResponse(granted_file_entry.filesystem_id, + granted_file_entry.registered_name); +} + +void RequestFileSystemExecutor::FinishWithError(const std::string& error) { + std::move(error_callback_).Run(error); +} + +void RequestFileSystemExecutor::FinishWithResponse( + const std::string& filesystem_id, + const std::string& registered_name) { + std::move(success_callback_).Run(filesystem_id, registered_name); +} + +} // namespace + +/******** ChromeFileSystemDelegateLacros ********/ + +ChromeFileSystemDelegateLacros::ChromeFileSystemDelegateLacros() = default; + +ChromeFileSystemDelegateLacros::~ChromeFileSystemDelegateLacros() = default; + +void ChromeFileSystemDelegateLacros::RequestFileSystem( + content::BrowserContext* browser_context, + scoped_refptr<ExtensionFunction> requester, + const Extension& extension, + std::string volume_id, + bool writable, + FileSystemCallback success_callback, + ErrorCallback error_callback) { + Profile* profile = Profile::FromBrowserContext(browser_context); + // TODO(crbug.com/1351493): Simplify usage for IsGrantable(). + ConsentProviderDelegate consent_provider_delegate(profile); + ConsentProvider consent_provider(&consent_provider_delegate); + + if (writable && + !app_file_handler_util::HasFileSystemWritePermission(&extension)) { + std::move(error_callback) + .Run(file_system_api::kRequiresFileSystemWriteError); + return; + } + + if (!consent_provider.IsGrantable(extension)) { + std::move(error_callback) + .Run(file_system_api::kNotSupportedOnNonKioskSessionError); + return; + } + + auto* lacros_service = chromeos::LacrosService::Get(); + DCHECK(lacros_service); + if (!lacros_service->IsAvailable<crosapi::mojom::VolumeManager>()) { + std::move(error_callback).Run(kApiUnavailableError); + return; + } + + // The executor object is kept alive by its presence in callbacks, and + // deleted when callbacks are invoked or cleared. + scoped_refptr<RequestFileSystemExecutor> executor = + new RequestFileSystemExecutor(profile, requester, volume_id, writable, + std::move(success_callback), + std::move(error_callback)); + executor->Run(lacros_service); +} + +void ChromeFileSystemDelegateLacros::GetVolumeList( + content::BrowserContext* /*browser_context*/, + VolumeListCallback success_callback, + ErrorCallback error_callback) { + auto* lacros_service = chromeos::LacrosService::Get(); + DCHECK(lacros_service); + if (!lacros_service->IsAvailable<crosapi::mojom::VolumeManager>()) { + std::move(error_callback).Run(kApiUnavailableError); + return; + } + + lacros_service->GetRemote<crosapi::mojom::VolumeManager>()->GetFullVolumeList( + base::BindOnce( + [](VolumeListCallback success_callback, + std::vector<crosapi::mojom::VolumePtr> src_volume_list) { + std::vector<file_system::Volume> filtered_volume_list; + ConvertAndFilterMojomToVolumeList(src_volume_list, + &filtered_volume_list); + std::move(success_callback).Run(filtered_volume_list); + }, + std::move(success_callback))); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h new file mode 100644 index 00000000000..8fb3fbe65c4 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Chromium Authors. All 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_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_ +#define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_ + +#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h" + +#include <string> +#include <vector> + +#include "base/memory/scoped_refptr.h" +#include "chromeos/crosapi/mojom/volume_manager.mojom.h" +#include "extensions/browser/extension_function.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace extensions { + +class Extension; + +namespace file_system_api { + +// Dispatches an event about a mounted or unmounted volume in the system to +// each extension which can request it. +void DispatchVolumeListChangeEventLacros( + content::BrowserContext* browser_context, + const std::vector<crosapi::mojom::VolumePtr>& volume_list); + +} // namespace file_system_api + +class ChromeFileSystemDelegateLacros : public ChromeFileSystemDelegate { + public: + ChromeFileSystemDelegateLacros(); + + ChromeFileSystemDelegateLacros(const ChromeFileSystemDelegateLacros&) = + delete; + ChromeFileSystemDelegateLacros& operator=( + const ChromeFileSystemDelegateLacros&) = delete; + + ~ChromeFileSystemDelegateLacros() override; + + // ChromeFileSystemDelegate: + void RequestFileSystem(content::BrowserContext* browser_context, + scoped_refptr<ExtensionFunction> requester, + const Extension& extension, + std::string volume_id, + bool writable, + FileSystemCallback success_callback, + ErrorCallback error_callback) override; + void GetVolumeList(content::BrowserContext* browser_context, + VolumeListCallback success_callback, + ErrorCallback error_callback) override; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_ diff --git a/chromium/chrome/browser/extensions/api/file_system/consent_provider.cc b/chromium/chrome/browser/extensions/api/file_system/consent_provider.cc index 70ad9360ab5..4e7c22a8c3a 100644 --- a/chromium/chrome/browser/extensions/api/file_system/consent_provider.cc +++ b/chromium/chrome/browser/extensions/api/file_system/consent_provider.cc @@ -5,25 +5,29 @@ #include "chrome/browser/extensions/api/file_system/consent_provider.h" #include <memory> -#include <string> #include "base/bind.h" #include "base/logging.h" #include "base/threading/thread_task_runner_handle.h" -#include "chrome/browser/ash/app_mode/kiosk_app_manager.h" -#include "chrome/browser/ash/file_manager/app_id.h" -#include "chrome/browser/ash/file_manager/volume_manager.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" #include "chrome/browser/extensions/api/file_system/request_file_system_notification.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/ui/views/extensions/request_file_system_dialog_view.h" -#include "components/user_manager/user_manager.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window_registry.h" +#include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/kiosk/kiosk_delegate.h" #include "extensions/common/api/file_system.h" +#include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/kiosk_mode_info.h" -#include "extensions/common/permissions/permissions_data.h" + +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "chrome/browser/ash/file_manager/app_id.h" +#endif // BUILDFLAG(IS_CHROMEOS_ASH) namespace extensions { @@ -32,8 +36,9 @@ namespace { // List of allowlisted component apps and extensions by their ids for // chrome.fileSystem.requestFileSystem. const char* const kRequestFileSystemComponentAllowlist[] = { - file_manager::kFileManagerAppId, file_manager::kAudioPlayerAppId, - file_manager::kImageLoaderExtensionId, +#if BUILDFLAG(IS_CHROMEOS_ASH) + file_manager::kFileManagerAppId, file_manager::kImageLoaderExtensionId, +#endif // BUILDFLAG(IS_CHROMEOS_ASH) // TODO(henryhsu,b/110126438): Remove this extension id, and add it only // for tests. "pkplfbidichfdicaijlchgnapepdginl" // Testing extensions. @@ -65,6 +70,7 @@ void DialogResultToConsent( std::move(callback).Run( file_system_api::ConsentProvider::CONSENT_GRANTED); break; + // The following is wired to both Cancel and Close callbacks. case ui::DIALOG_BUTTON_CANCEL: std::move(callback).Run( file_system_api::ConsentProvider::CONSENT_REJECTED); @@ -76,23 +82,24 @@ void DialogResultToConsent( namespace file_system_api { +/******** ConsentProvider ********/ + ConsentProvider::ConsentProvider(DelegateInterface* delegate) : delegate_(delegate) { DCHECK(delegate_); } -ConsentProvider::~ConsentProvider() { -} +ConsentProvider::~ConsentProvider() = default; -void ConsentProvider::RequestConsent( - const Extension& extension, - content::RenderFrameHost* host, - const base::WeakPtr<file_manager::Volume>& volume, - bool writable, - ConsentCallback callback) { - DCHECK(IsGrantableForVolume(extension, volume)); +void ConsentProvider::RequestConsent(content::RenderFrameHost* host, + const Extension& extension, + const std::string& volume_id, + const std::string& volume_label, + bool writable, + ConsentCallback callback) { + DCHECK(IsGrantable(extension)); - // If a allowlisted component, then no need to ask or inform the user. + // If an allowlisted component, then no need to ask or inform the user. if (extension.location() == mojom::ManifestLocation::kComponent && delegate_->IsAllowlistedComponent(extension)) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -100,77 +107,48 @@ void ConsentProvider::RequestConsent( return; } - // If a allowlisted app or extensions to access Downloads folder, then no - // need to ask or inform the user. - if (volume.get() && - volume->type() == file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY && - delegate_->HasRequestDownloadsPermission(extension)) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), CONSENT_GRANTED)); - return; - } - // If auto-launched kiosk app, then no need to ask user either, but show the // notification. if (delegate_->IsAutoLaunched(extension)) { - delegate_->ShowNotification(extension, volume, writable); + delegate_->ShowNotification(extension.id(), extension.name(), volume_id, + volume_label, writable); base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), CONSENT_GRANTED)); return; } - // If it's a kiosk app running in manual-launch kiosk session, then show - // the confirmation dialog. + // If it's a kiosk app running in manual-launch kiosk session, then show the + // confirmation dialog. if (KioskModeInfo::IsKioskOnly(&extension) && - user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) { + profiles::IsChromeAppKioskSession()) { delegate_->ShowDialog( - extension, host, volume, writable, - base::BindOnce(&DialogResultToConsent, std::move(callback))); + host, extension.id(), extension.name(), volume_id, volume_label, + writable, base::BindOnce(&DialogResultToConsent, std::move(callback))); return; } NOTREACHED() << "Cannot request consent for non-grantable extensions."; } -FileSystemDelegate::GrantVolumesMode ConsentProvider::GetGrantVolumesMode( - const Extension& extension) { +bool ConsentProvider::IsGrantable(const Extension& extension) { const bool is_allowlisted_component = delegate_->IsAllowlistedComponent(extension); const bool is_running_in_kiosk_session = KioskModeInfo::IsKioskOnly(&extension) && - user_manager::UserManager::Get()->IsLoggedInAsKioskApp(); + profiles::IsChromeAppKioskSession(); - if (is_allowlisted_component || is_running_in_kiosk_session) { - return FileSystemDelegate::kGrantAll; - } - - const bool is_allowlisted_non_component = - delegate_->HasRequestDownloadsPermission(extension); - - return is_allowlisted_non_component ? FileSystemDelegate::kGrantPerVolume - : FileSystemDelegate::kGrantNone; + return is_allowlisted_component || is_running_in_kiosk_session; } -bool ConsentProvider::IsGrantableForVolume( - const Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume) { - if (volume.get() && - volume->type() == file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY && - delegate_->HasRequestDownloadsPermission(extension)) { - return true; - } - - return GetGrantVolumesMode(extension) == FileSystemDelegate::kGrantAll; -} +/******** ConsentProviderDelegate ********/ ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile) : profile_(profile) { DCHECK(profile_); } -ConsentProviderDelegate::~ConsentProviderDelegate() { -} +ConsentProviderDelegate::~ConsentProviderDelegate() = default; // static void ConsentProviderDelegate::SetAutoDialogButtonForTest( @@ -179,9 +157,11 @@ void ConsentProviderDelegate::SetAutoDialogButtonForTest( } void ConsentProviderDelegate::ShowDialog( - const Extension& extension, content::RenderFrameHost* host, - const base::WeakPtr<file_manager::Volume>& volume, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable, file_system_api::ConsentProvider::ShowDialogCallback callback) { DCHECK(host); @@ -198,7 +178,7 @@ void ConsentProviderDelegate::ShowDialog( // If there is no web contents handle, then the method is most probably // executed from a background page. if (!web_contents) - web_contents = GetWebContentsForAppId(profile_, extension.id()); + web_contents = GetWebContentsForAppId(profile_, extension_id); if (!web_contents) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -215,33 +195,27 @@ void ConsentProviderDelegate::ShowDialog( return; } - // If the volume is gone, then cancel the dialog. - if (!volume.get()) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), ui::DIALOG_BUTTON_CANCEL)); - return; - } - RequestFileSystemDialogView::ShowDialog( - web_contents, extension.name(), - (volume->volume_label().empty() ? volume->volume_id() - : volume->volume_label()), - writable, std::move(callback)); + web_contents, extension_name, + volume_label.empty() ? volume_id : volume_label, writable, + std::move(callback)); } void ConsentProviderDelegate::ShowNotification( - const Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable) { - ShowNotificationForAutoGrantedRequestFileSystem(profile_, extension, volume, - writable); + ShowNotificationForAutoGrantedRequestFileSystem(profile_, extension_id, + extension_name, volume_id, + volume_label, writable); } bool ConsentProviderDelegate::IsAutoLaunched(const Extension& extension) { - ash::KioskAppManager::App app_info; - return ash::KioskAppManager::Get()->GetApp(extension.id(), &app_info) && - app_info.was_auto_launched_with_zero_delay; + return ExtensionsBrowserClient::Get() + ->GetKioskDelegate() + ->IsAutoLaunchedKioskApp(extension.id()); } bool ConsentProviderDelegate::IsAllowlistedComponent( @@ -253,11 +227,5 @@ bool ConsentProviderDelegate::IsAllowlistedComponent( return false; } -bool ConsentProviderDelegate::HasRequestDownloadsPermission( - const Extension& extension) { - return extension.permissions_data()->HasAPIPermission( - mojom::APIPermissionID::kFileSystemRequestDownloads); -} - } // namespace file_system_api } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/consent_provider.h b/chromium/chrome/browser/extensions/api/file_system/consent_provider.h index e6e7aa99916..1293e8036b1 100644 --- a/chromium/chrome/browser/extensions/api/file_system/consent_provider.h +++ b/chromium/chrome/browser/extensions/api/file_system/consent_provider.h @@ -5,10 +5,10 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CONSENT_PROVIDER_H_ #define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CONSENT_PROVIDER_H_ +#include <string> + #include "base/callback_forward.h" -#include "base/memory/weak_ptr.h" -#include "build/build_config.h" -#include "extensions/browser/api/file_system/file_system_delegate.h" +#include "extensions/common/extension_id.h" #include "ui/base/ui_base_types.h" class Profile; @@ -17,10 +17,6 @@ namespace content { class RenderFrameHost; } // content -namespace file_manager { -class Volume; -} // namespace file_manager - namespace extensions { class Extension; class ScopedSkipRequestFileSystemDialog; @@ -33,8 +29,8 @@ namespace file_system_api { // TestingConsentProviderDelegate. // This class may post callbacks given to it, but does not asynchronously call // itself. It is generally safe to use a temporary ConsentProvider. -// TODO(michaelpg): Make this easier to use by replacing member functions with -// static methods. +// TODO(crbug.com/1351493): Make this easier to use, perhaps by replacing member +// functions with static methods. class ConsentProvider { public: enum Consent { CONSENT_GRANTED, CONSENT_REJECTED, CONSENT_IMPOSSIBLE }; @@ -45,26 +41,26 @@ class ConsentProvider { class DelegateInterface { public: // Shows a dialog for granting permissions. - virtual void ShowDialog(const Extension& extension, - content::RenderFrameHost* host, - const base::WeakPtr<file_manager::Volume>& volume, + virtual void ShowDialog(content::RenderFrameHost* host, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable, ShowDialogCallback callback) = 0; // Shows a notification about permissions automatically granted access. - virtual void ShowNotification( - const Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume, - bool writable) = 0; + virtual void ShowNotification(const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, + bool writable) = 0; // Checks if the extension was launched in auto-launch kiosk mode. virtual bool IsAutoLaunched(const Extension& extension) = 0; // Checks if the extension is a allowlisted component extension or app. virtual bool IsAllowlistedComponent(const Extension& extension) = 0; - - // Checks if the extension has the permission to access Downloads. - virtual bool HasRequestDownloadsPermission(const Extension& extension) = 0; }; explicit ConsentProvider(DelegateInterface* delegate); @@ -74,23 +70,18 @@ class ConsentProvider { ~ConsentProvider(); - // Requests consent for granting |writable| permissions to the |volume| - // volume by the |extension|. Must be called only if the extension is - // grantable, which can be checked with GetGrantVolumesMode() and - // IsGrantableForVolume(). - void RequestConsent(const Extension& extension, - content::RenderFrameHost* host, - const base::WeakPtr<file_manager::Volume>& volume, + // Requests consent for granting |writable| permissions to a volume with + // |volume_id| and |volume_label| by |extension|, which is assumed to be + // grantable (i.e., passes IsGrantable()). + void RequestConsent(content::RenderFrameHost* host, + const Extension& extension, + const std::string& volume_id, + const std::string& volume_label, bool writable, ConsentCallback callback); - // Returns granted access mode for the |extension|. - FileSystemDelegate::GrantVolumesMode GetGrantVolumesMode( - const Extension& extension); - - // Checks whether the |extension| can be granted |volume| access. - bool IsGrantableForVolume(const Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume); + // Checks whether the |extension| can be granted access. + bool IsGrantable(const Extension& extension); private: DelegateInterface* const delegate_; @@ -115,18 +106,21 @@ class ConsentProviderDelegate : public ConsentProvider::DelegateInterface { static void SetAutoDialogButtonForTest(ui::DialogButton button); // ConsentProvider::DelegateInterface overrides: - void ShowDialog( - const Extension& extension, - content::RenderFrameHost* host, - const base::WeakPtr<file_manager::Volume>& volume, - bool writable, - file_system_api::ConsentProvider::ShowDialogCallback callback) override; - void ShowNotification(const Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume, + void ShowDialog(content::RenderFrameHost* host, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, + bool writable, + ConsentProvider::ShowDialogCallback callback) override; + + void ShowNotification(const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable) override; bool IsAutoLaunched(const Extension& extension) override; bool IsAllowlistedComponent(const Extension& extension) override; - bool HasRequestDownloadsPermission(const Extension& extension) override; Profile* const profile_; }; diff --git a/chromium/chrome/browser/extensions/api/file_system/consent_provider_unittest.cc b/chromium/chrome/browser/extensions/api/file_system/consent_provider_unittest.cc index b67115b243a..137f7aa954f 100644 --- a/chromium/chrome/browser/extensions/api/file_system/consent_provider_unittest.cc +++ b/chromium/chrome/browser/extensions/api/file_system/consent_provider_unittest.cc @@ -8,99 +8,92 @@ #include <string> #include "base/bind.h" -#include "base/files/scoped_temp_dir.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" #include "base/run_loop.h" -#include "chrome/browser/ash/file_manager/volume_manager.h" #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" #include "chrome/test/base/testing_browser_process.h" #include "components/prefs/testing_pref_service.h" #include "components/user_manager/scoped_user_manager.h" #include "components/user_manager/user.h" #include "content/public/test/browser_task_environment.h" -#include "extensions/browser/api/file_system/file_system_delegate.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/manifest.h" -#include "extensions/common/permissions/permissions_data.h" #include "testing/gtest/include/gtest/gtest.h" using extensions::file_system_api::ConsentProvider; using extensions::mojom::ManifestLocation; -using file_manager::Volume; namespace extensions { namespace { +// Configurations and results for TestingConsentProviderDelegate, with directly +// accessible fields. +struct TestDelegateState { + // Used to assign a fake dialog response. + ui::DialogButton dialog_button = ui::DIALOG_BUTTON_NONE; + + // Used to assign fake result of detection the auto launch kiosk mode. + bool is_auto_launched = false; + + // Used to set allowlisted components list with a single id. + std::string allowlisted_component_id; + + // Counters to record calls. + int show_dialog_counter = 0; + int show_notification_counter = 0; +}; + +// Test implementation of ConsentProvider::DelegateInterface that exposes +// states to a TestDelegateState instance. class TestingConsentProviderDelegate : public ConsentProvider::DelegateInterface { public: - TestingConsentProviderDelegate() - : show_dialog_counter_(0), - show_notification_counter_(0), - dialog_button_(ui::DIALOG_BUTTON_NONE), - is_auto_launched_(false) {} + explicit TestingConsentProviderDelegate(TestDelegateState* state) + : state_(state) {} TestingConsentProviderDelegate(const TestingConsentProviderDelegate&) = delete; TestingConsentProviderDelegate& operator=( const TestingConsentProviderDelegate&) = delete; - ~TestingConsentProviderDelegate() {} - - // Sets a fake dialog response. - void SetDialogButton(ui::DialogButton button) { dialog_button_ = button; } - - // Sets a fake result of detection the auto launch kiosk mode. - void SetIsAutoLaunched(bool is_auto_launched) { - is_auto_launched_ = is_auto_launched; - } - - // Sets an allowlisted components list with a single id. - void SetComponentAllowlist(const std::string& extension_id) { - allowlisted_component_id_ = extension_id; - } - - int show_dialog_counter() const { return show_dialog_counter_; } - int show_notification_counter() const { return show_notification_counter_; } + ~TestingConsentProviderDelegate() = default; private: - // ConsentProvider::DelegateInterface overrides: - void ShowDialog(const extensions::Extension& extension, - content::RenderFrameHost* host, - const base::WeakPtr<Volume>& volume, + // ConsentProvider::DelegateInterface: + void ShowDialog(content::RenderFrameHost* host, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable, ConsentProvider::ShowDialogCallback callback) override { - ++show_dialog_counter_; - std::move(callback).Run(dialog_button_); + ++state_->show_dialog_counter; + std::move(callback).Run(state_->dialog_button); } - void ShowNotification(const extensions::Extension& extension, - const base::WeakPtr<Volume>& volume, + // ConsentProvider::DelegateInterface: + void ShowNotification(const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable) override { - ++show_notification_counter_; + ++state_->show_notification_counter; } + // ConsentProvider::DelegateInterface: bool IsAutoLaunched(const extensions::Extension& extension) override { - return is_auto_launched_; + return state_->is_auto_launched; } + // ConsentProvider::DelegateInterface: bool IsAllowlistedComponent(const extensions::Extension& extension) override { - return allowlisted_component_id_.compare(extension.id()) == 0; + return state_->allowlisted_component_id.compare(extension.id()) == 0; } - bool HasRequestDownloadsPermission(const Extension& extension) override { - return extension.permissions_data()->HasAPIPermission( - mojom::APIPermissionID::kFileSystemRequestDownloads); - } - - int show_dialog_counter_; - int show_notification_counter_; - ui::DialogButton dialog_button_; - bool is_auto_launched_; - std::string allowlisted_component_id_; + // Use raw_ptr since |state| is owned by owner. + base::raw_ptr<TestDelegateState> state_; }; // Rewrites result of a consent request from |result| to |log|. @@ -123,8 +116,6 @@ class FileSystemApiConsentProviderTest : public testing::Test { scoped_user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>( base::WrapUnique(user_manager_)); - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - download_volume_ = Volume::CreateForDownloads(temp_dir_.GetPath()); } void TearDown() override { @@ -135,13 +126,10 @@ class FileSystemApiConsentProviderTest : public testing::Test { } protected: - base::WeakPtr<Volume> volume_; std::unique_ptr<TestingPrefServiceSimple> testing_pref_service_; ash::FakeChromeUserManager* user_manager_; // Owned by the scope enabler. std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_enabler_; content::BrowserTaskEnvironment task_environment_; - base::ScopedTempDir temp_dir_; - std::unique_ptr<Volume> download_volume_; }; TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { @@ -151,10 +139,10 @@ TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { ExtensionBuilder("Test", ExtensionBuilder::Type::PLATFORM_APP) .SetLocation(ManifestLocation::kComponent) .Build()); - TestingConsentProviderDelegate delegate; + TestDelegateState state; + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*component_extension), - FileSystemDelegate::kGrantNone); + EXPECT_FALSE(provider.IsGrantable(*component_extension)); } // Allowlisted component apps are instantly granted access without asking @@ -164,48 +152,21 @@ TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { ExtensionBuilder("Test", ExtensionBuilder::Type::PLATFORM_APP) .SetLocation(ManifestLocation::kComponent) .Build()); - TestingConsentProviderDelegate delegate; - delegate.SetComponentAllowlist(allowlisted_component_extension->id()); + TestDelegateState state; + state.allowlisted_component_id = allowlisted_component_extension->id(); + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*allowlisted_component_extension), - FileSystemDelegate::kGrantAll); + EXPECT_TRUE(provider.IsGrantable(*allowlisted_component_extension)); ConsentProvider::Consent result = ConsentProvider::CONSENT_IMPOSSIBLE; - provider.RequestConsent(*allowlisted_component_extension.get(), nullptr, - volume_, true /* writable */, + provider.RequestConsent(nullptr, *allowlisted_component_extension.get(), + "Volume ID 1", "Volume Label 1", + true /* writable */, base::BindOnce(&OnConsentReceived, &result)); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(0, delegate.show_dialog_counter()); - EXPECT_EQ(0, delegate.show_notification_counter()); - EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); - } - - // Allowlisted extensions are instantly granted downloads access without - // asking user. - { - scoped_refptr<const Extension> allowlisted_extension( - ExtensionBuilder("Test", ExtensionBuilder::Type::PLATFORM_APP) - .SetLocation(ManifestLocation::kComponent) - .AddPermission("fileSystem.requestDownloads") - .Build()); - TestingConsentProviderDelegate delegate; - ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*allowlisted_extension), - FileSystemDelegate::kGrantPerVolume); - EXPECT_FALSE( - provider.IsGrantableForVolume(*allowlisted_extension, volume_)); - EXPECT_TRUE(provider.IsGrantableForVolume(*allowlisted_extension, - download_volume_->AsWeakPtr())); - - ConsentProvider::Consent result = ConsentProvider::CONSENT_IMPOSSIBLE; - provider.RequestConsent(*allowlisted_extension.get(), nullptr, - download_volume_->AsWeakPtr(), true /* writable */, - base::BindRepeating(&OnConsentReceived, &result)); - base::RunLoop().RunUntilIdle(); - - EXPECT_EQ(0, delegate.show_dialog_counter()); - EXPECT_EQ(0, delegate.show_notification_counter()); + EXPECT_EQ(0, state.show_dialog_counter); + EXPECT_EQ(0, state.show_notification_counter); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } @@ -214,10 +175,10 @@ TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { { scoped_refptr<const Extension> non_component_extension( ExtensionBuilder("Test").Build()); - TestingConsentProviderDelegate delegate; + TestDelegateState state; + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*non_component_extension), - FileSystemDelegate::kGrantNone); + EXPECT_FALSE(provider.IsGrantable(*non_component_extension)); } } @@ -235,20 +196,20 @@ TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { user_manager_->LoginUser( AccountId::FromUserEmail(auto_launch_kiosk_app->id())); - TestingConsentProviderDelegate delegate; - delegate.SetIsAutoLaunched(true); + TestDelegateState state; + state.is_auto_launched = true; + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*auto_launch_kiosk_app), - FileSystemDelegate::kGrantAll); + EXPECT_TRUE(provider.IsGrantable(*auto_launch_kiosk_app)); ConsentProvider::Consent result = ConsentProvider::CONSENT_IMPOSSIBLE; - provider.RequestConsent(*auto_launch_kiosk_app.get(), nullptr, volume_, - true /* writable */, - base::BindOnce(&OnConsentReceived, &result)); + provider.RequestConsent( + nullptr, *auto_launch_kiosk_app.get(), "Volume ID 2", "Volume Label 2", + true /* writable */, base::BindOnce(&OnConsentReceived, &result)); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(0, delegate.show_dialog_counter()); - EXPECT_EQ(1, delegate.show_notification_counter()); + EXPECT_EQ(0, state.show_dialog_counter); + EXPECT_EQ(1, state.show_notification_counter); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } @@ -264,40 +225,42 @@ TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { AccountId::FromUserEmail(manual_launch_kiosk_app->id())); user_manager_->KioskAppLoggedIn(manual_kiosk_app_user); { - TestingConsentProviderDelegate delegate; - delegate.SetDialogButton(ui::DIALOG_BUTTON_OK); + TestDelegateState state; + state.dialog_button = ui::DIALOG_BUTTON_OK; + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - EXPECT_EQ(provider.GetGrantVolumesMode(*manual_launch_kiosk_app), - FileSystemDelegate::kGrantAll); + EXPECT_TRUE(provider.IsGrantable(*manual_launch_kiosk_app)); ConsentProvider::Consent result = ConsentProvider::CONSENT_IMPOSSIBLE; - provider.RequestConsent(*manual_launch_kiosk_app.get(), nullptr, volume_, + provider.RequestConsent(nullptr, *manual_launch_kiosk_app.get(), + "Volume ID 3", "Volume Label 3", true /* writable */, base::BindOnce(&OnConsentReceived, &result)); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(1, delegate.show_dialog_counter()); - EXPECT_EQ(0, delegate.show_notification_counter()); + EXPECT_EQ(1, state.show_dialog_counter); + EXPECT_EQ(0, state.show_notification_counter); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } // Non-component apps in manual-launch kiosk mode will be rejected access // after rejecting by a user. { - TestingConsentProviderDelegate delegate; + TestDelegateState state; + state.dialog_button = ui::DIALOG_BUTTON_CANCEL; + TestingConsentProviderDelegate delegate(&state); ConsentProvider provider(&delegate); - delegate.SetDialogButton(ui::DIALOG_BUTTON_CANCEL); - EXPECT_EQ(provider.GetGrantVolumesMode(*manual_launch_kiosk_app), - FileSystemDelegate::kGrantAll); + EXPECT_TRUE(provider.IsGrantable(*manual_launch_kiosk_app)); ConsentProvider::Consent result = ConsentProvider::CONSENT_IMPOSSIBLE; - provider.RequestConsent(*manual_launch_kiosk_app.get(), nullptr, volume_, + provider.RequestConsent(nullptr, *manual_launch_kiosk_app.get(), + "Volume ID 4", "Volume Label 4", true /* writable */, base::BindOnce(&OnConsentReceived, &result)); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(1, delegate.show_dialog_counter()); - EXPECT_EQ(0, delegate.show_notification_counter()); + EXPECT_EQ(1, state.show_dialog_counter); + EXPECT_EQ(0, state.show_notification_counter); EXPECT_EQ(ConsentProvider::CONSENT_REJECTED, result); } } diff --git a/chromium/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chromium/chrome/browser/extensions/api/file_system/file_system_apitest.cc index 04c1dbc9e06..43c4f0f23a9 100644 --- a/chromium/chrome/browser/extensions/api/file_system/file_system_apitest.cc +++ b/chromium/chrome/browser/extensions/api/file_system/file_system_apitest.cc @@ -9,7 +9,6 @@ #include "base/scoped_observation.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "chrome/browser/apps/platform_apps/app_browsertest_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_paths.h" @@ -731,13 +730,13 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiRestoreDirectoryEntry) { << message_; } -#if !BUILDFLAG(IS_CHROMEOS_ASH) +#if !BUILDFLAG(IS_CHROMEOS) IN_PROC_BROWSER_TEST_F(FileSystemApiTest, RequestFileSystem_NotChromeOS) { ASSERT_TRUE(RunExtensionTest( "api_test/file_system/request_file_system_not_chromeos", {.launch_as_platform_app = true}, {.ignore_manifest_warnings = true})) << message_; } -#endif +#endif // !BUILDFLAG(IS_CHROMEOS) } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chromium/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc index 96bcb6680a6..632e52cd2f1 100644 --- a/chromium/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc @@ -27,14 +27,12 @@ #include "extensions/browser/api/file_system/file_system_api.h" #include "extensions/browser/event_router.h" #include "extensions/common/api/file_system.h" -#include "extensions/common/switches.h" #include "storage/browser/file_system/external_mount_points.h" #include "ui/base/ui_base_types.h" // TODO(michaelpg): Port these tests to app_shell: crbug.com/505926. using file_manager::VolumeManager; -using file_manager::VolumeType; namespace extensions { namespace { @@ -42,7 +40,6 @@ namespace { // Mount point names for chrome.fileSystem.requestFileSystem() tests. const char kWritableMountPointName[] = "writable"; const char kReadOnlyMountPointName[] = "read-only"; -const char kDownloadsMountPointName[] = "downloads"; // Child directory created in each of the mount points. const char kChildDirectory[] = "child-dir"; @@ -143,9 +140,7 @@ class FileSystemApiTestForDrive : public PlatformAppBrowserTest { PlatformAppBrowserTest::SetUpOnMainThread(); } - void TearDown() override { - PlatformAppBrowserTest::TearDown(); - } + void TearDown() override { PlatformAppBrowserTest::TearDown(); } base::FilePath GetDriveMountPoint() { return drivefs_mount_point_; } @@ -213,12 +208,6 @@ class FileSystemApiTestForRequestFileSystem : public PlatformAppBrowserTest { return drive::SetUpUserDataDirectoryForDriveFsTest(); } - void SetUpCommandLine(base::CommandLine* command_line) override { - PlatformAppBrowserTest::SetUpCommandLine(command_line); - command_line->AppendSwitchASCII( - extensions::switches::kAllowlistedExtensionID, kTestingExtensionId); - } - // Sets up fake Drive service for tests (this has to be injected before the // real DriveIntegrationService instance is created.) void SetUpInProcessBrowserTestFixture() override { @@ -235,15 +224,8 @@ class FileSystemApiTestForRequestFileSystem : public PlatformAppBrowserTest { void SetUpOnMainThread() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - CreateTestingFileSystem(kWritableMountPointName, - file_manager::VOLUME_TYPE_TESTING, - false /* read_only */); - CreateTestingFileSystem(kReadOnlyMountPointName, - file_manager::VOLUME_TYPE_TESTING, - true /* read_only */); - CreateTestingFileSystem(kDownloadsMountPointName, - file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY, - false /* read_only */); + CreateTestingFileSystem(kWritableMountPointName, false /* read_only */); + CreateTestingFileSystem(kReadOnlyMountPointName, true /* read_only */); PlatformAppBrowserTest::SetUpOnMainThread(); } @@ -260,7 +242,7 @@ class FileSystemApiTestForRequestFileSystem : public PlatformAppBrowserTest { ASSERT_TRUE(volume_manager); volume_manager->AddVolumeForTesting( base::FilePath("/a/b/c"), file_manager::VOLUME_TYPE_TESTING, - chromeos::DEVICE_TYPE_UNKNOWN, false /* read_only */); + ash::DeviceType::kUnknown, false /* read_only */); } protected: @@ -270,7 +252,6 @@ class FileSystemApiTestForRequestFileSystem : public PlatformAppBrowserTest { // Creates a testing file system in a testing directory. void CreateTestingFileSystem(const std::string& mount_point_name, - VolumeType volume_type, bool read_only) { const base::FilePath mount_point_path = temp_dir_.GetPath().Append(mount_point_name); @@ -283,9 +264,9 @@ class FileSystemApiTestForRequestFileSystem : public PlatformAppBrowserTest { VolumeManager* const volume_manager = VolumeManager::Get(browser()->profile()); ASSERT_TRUE(volume_manager); - volume_manager->AddVolumeForTesting(mount_point_path, volume_type, - chromeos::DEVICE_TYPE_UNKNOWN, - read_only); + volume_manager->AddVolumeForTesting(mount_point_path, + file_manager::VOLUME_TYPE_TESTING, + ash::DeviceType::kUnknown, read_only); } // Simulates entering the kiosk session. @@ -447,7 +428,7 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive, } IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive, - FileSystemApiSaveNewFileWithWriteTest) { + FileSystemApiSaveNewFileWithWriteTest) { base::FilePath test_file = GetDriveMountPoint().AppendASCII("root/save_new.txt"); FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest picker( @@ -458,7 +439,7 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive, } IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive, - FileSystemApiSaveExistingFileWithWriteTest) { + FileSystemApiSaveExistingFileWithWriteTest) { base::FilePath test_file = GetDriveMountPoint().AppendASCII("root/save_existing.txt"); FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest picker( @@ -560,13 +541,4 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem, << message_; } -IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem, - AllowlistedExtensionForDownloads) { - ScopedSkipRequestFileSystemDialog dialog_skipper(ui::DIALOG_BUTTON_CANCEL); - ASSERT_TRUE(RunExtensionTest( - "api_test/file_system/request_downloads_allowed_extension", - {.launch_as_platform_app = true})) - << message_; -} - } // namespace extensions 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 d97609ce30d..18a7403de88 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 @@ -8,25 +8,23 @@ #include <utility> #include "ash/constants/notifier_catalogs.h" -#include "base/callback.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/browser/ash/file_manager/volume_manager.h" +#include "build/chromeos_buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/chrome_app_icon_loader.h" #include "chrome/browser/notifications/notification_display_service.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/app_icon_loader.h" #include "chrome/grit/generated_resources.h" -#include "extensions/common/extension.h" #include "ui/base/l10n/l10n_util.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; namespace extensions { @@ -47,14 +45,14 @@ class AppNotificationLauncher : public AppIconLoaderDelegate, AppNotificationLauncher& operator=(const AppNotificationLauncher&) = delete; void InitAndShow(Profile* profile, - const Extension& extension, + const extensions::ExtensionId& extension_id, std::unique_ptr<message_center::Notification> notification) { profile_ = profile; pending_notification_ = std::move(notification); icon_loader_ = std::make_unique<ChromeAppIconLoader>(profile, kIconSize, this); - icon_loader_->FetchImage(extension.id()); + icon_loader_->FetchImage(extension_id); } // AppIconLoaderDelegate overrides: @@ -90,30 +88,25 @@ class AppNotificationLauncher : public AppIconLoaderDelegate, void ShowNotificationForAutoGrantedRequestFileSystem( Profile* profile, - const Extension& extension, - const base::WeakPtr<Volume>& volume, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable) { DCHECK(profile); - - // If the volume is gone, then do not show the notification. - if (!volume.get()) - return; - static int sequence = 0; // Create globally unique |notification_id| so that notifications are not // suppressed, thus allowing each AppNotificationLauncher instance to // correspond to an actual notification, and properly deallocated on close. - const std::string notification_id = - base::StringPrintf("%s-%s-%d", extension.id().c_str(), - volume->volume_id().c_str(), sequence); + const std::string notification_id = base::StringPrintf( + "%s-%s-%d", extension_id.c_str(), volume_id.c_str(), sequence); ++sequence; message_center::RichNotificationData data; // TODO(mtomasz): Share this code with RequestFileSystemDialogView. const std::u16string display_name = - base::UTF8ToUTF16(!volume->volume_label().empty() ? volume->volume_label() - : volume->volume_id()); + base::UTF8ToUTF16(volume_label.empty() ? volume_id : volume_label); const std::u16string message = l10n_util::GetStringFUTF16( writable ? IDS_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_WRITABLE_MESSAGE @@ -126,16 +119,22 @@ void ShowNotificationForAutoGrantedRequestFileSystem( std::unique_ptr<message_center::Notification> notification(new Notification( message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, - base::UTF8ToUTF16(extension.name()), message, + base::UTF8ToUTF16(extension_name), message, ui::ImageModel(), // Updated asynchronously later. std::u16string(), // display_source GURL(), +#if BUILDFLAG(IS_CHROMEOS_ASH) message_center::NotifierId( message_center::NotifierType::SYSTEM_COMPONENT, notification_id, ash::NotificationCatalogName::kRequestFileSystem), +#endif // BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS_LACROS) + message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, + notification_id), +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) data, app_notification_launcher)); - app_notification_launcher->InitAndShow(profile, extension, + app_notification_launcher->InitAndShow(profile, extension_id, std::move(notification)); } diff --git a/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.h b/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.h index dade350b598..176a7000fe3 100644 --- a/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.h +++ b/chromium/chrome/browser/extensions/api/file_system/request_file_system_notification.h @@ -5,23 +5,20 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_H_ #define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_H_ -#include "base/memory/weak_ptr.h" +#include "extensions/common/extension_id.h" class Profile; -namespace file_manager { -class Volume; -} // namespace file_manager - namespace extensions { -class Extension; // Shows a notification about automatically granted access to a file system, // i.e. the chrome.fileSystem.requestFileSystem() API. void ShowNotificationForAutoGrantedRequestFileSystem( Profile* profile, - const extensions::Extension& extension, - const base::WeakPtr<file_manager::Volume>& volume, + const extensions::ExtensionId& extension_id, + const std::string& extension_name, + const std::string& volume_id, + const std::string& volume_label, bool writable); } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc b/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc new file mode 100644 index 00000000000..fcfe9c61e9e --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc @@ -0,0 +1,33 @@ +// Copyright 2022 The Chromium Authors. All 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/file_system/volume_list_provider_lacros.h" + +#include "base/logging.h" +#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h" +#include "chrome/browser/profiles/profile.h" +#include "chromeos/lacros/lacros_service.h" + +namespace extensions { + +VolumeListProviderLacros::VolumeListProviderLacros(Profile* profile) + : profile_(profile) {} + +VolumeListProviderLacros::~VolumeListProviderLacros() = default; + +void VolumeListProviderLacros::Start() { + auto* lacros_service = chromeos::LacrosService::Get(); + if (!lacros_service->IsAvailable<crosapi::mojom::VolumeManager>()) + return; + lacros_service->GetRemote<crosapi::mojom::VolumeManager>() + ->AddVolumeListObserver(receiver_.BindNewPipeAndPassRemoteWithVersion()); +} + +void VolumeListProviderLacros::OnVolumeListChanged( + std::vector<crosapi::mojom::VolumePtr> volume_list) { + DCHECK(profile_); + file_system_api::DispatchVolumeListChangeEventLacros(profile_, volume_list); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.h b/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.h new file mode 100644 index 00000000000..2c83e544bd6 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.h @@ -0,0 +1,47 @@ +// Copyright 2022 The Chromium Authors. All 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_FILE_SYSTEM_VOLUME_LIST_PROVIDER_LACROS_H_ +#define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_VOLUME_LIST_PROVIDER_LACROS_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/memory/raw_ptr.h" +#include "chromeos/crosapi/mojom/volume_manager.mojom.h" +#include "mojo/public/cpp/bindings/receiver.h" + +class Profile; + +namespace extensions { + +// Class to monitor volume list change from Ash, and dispatch the result to +// extensions that listens to chrome.fileSystem.onVolumeListChange. +class VolumeListProviderLacros : public crosapi::mojom::VolumeListObserver { + public: + using Listener = + base::RepeatingCallback<void(std::vector<crosapi::mojom::VolumePtr>)>; + + explicit VolumeListProviderLacros(Profile* profile); + VolumeListProviderLacros(const VolumeListProviderLacros&) = delete; + VolumeListProviderLacros& operator=(const VolumeListProviderLacros&) = delete; + ~VolumeListProviderLacros() override; + + void Start(); + + private: + // crosapi::mojom::VolumeListObserver: + void OnVolumeListChanged( + std::vector<crosapi::mojom::VolumePtr> volume_list) override; + + // Pointer to owner, so okay to keep as raw pointer. + base::raw_ptr<Profile> profile_; + + // Receives mojo messages from ash-chrome. + mojo::Receiver<crosapi::mojom::VolumeListObserver> receiver_{this}; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_VOLUME_LIST_PROVIDER_LACROS_H_ diff --git a/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.cc b/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.cc index 6674542fc48..16cc434e5b4 100644 --- a/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.cc +++ b/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.cc @@ -288,21 +288,21 @@ ExtensionFunction::ResponseAction FontSettingsGetFontListFunction::Run() { } void FontSettingsGetFontListFunction::FontListHasLoaded( - std::unique_ptr<base::ListValue> list) { - ExtensionFunction::ResponseValue response = CopyFontsToResult(list.get()); + base::Value::List list) { + ExtensionFunction::ResponseValue response = CopyFontsToResult(list); Respond(std::move(response)); } ExtensionFunction::ResponseValue -FontSettingsGetFontListFunction::CopyFontsToResult(base::ListValue* fonts) { +FontSettingsGetFontListFunction::CopyFontsToResult( + const base::Value::List& fonts) { base::Value::List result; - for (const auto& entry : fonts->GetListDeprecated()) { + for (const auto& entry : fonts) { if (!entry.is_list()) { NOTREACHED(); return Error(""); } - const base::Value::ConstListView font_list_value = - entry.GetListDeprecated(); + const base::Value::List& font_list_value = entry.GetList(); if (font_list_value.size() < 2 || !font_list_value[0].is_string() || !font_list_value[1].is_string()) { diff --git a/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.h b/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.h index ddf83ca3288..12f59789afc 100644 --- a/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.h +++ b/chromium/chrome/browser/extensions/api/font_settings/font_settings_api.h @@ -12,6 +12,7 @@ #include <string> #include "base/memory/raw_ptr.h" +#include "base/values.h" #include "chrome/browser/font_pref_change_notifier.h" #include "components/prefs/pref_change_registrar.h" #include "components/prefs/pref_service.h" @@ -153,8 +154,8 @@ class FontSettingsGetFontListFunction : public ExtensionFunction { ResponseAction Run() override; private: - void FontListHasLoaded(std::unique_ptr<base::ListValue> list); - ResponseValue CopyFontsToResult(base::ListValue* fonts); + void FontListHasLoaded(base::Value::List list); + ResponseValue CopyFontsToResult(const base::Value::List& fonts); }; // Base class for extension API functions that clear a browser font pref. diff --git a/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.cc b/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.cc index ab71284b36b..59093f39286 100644 --- a/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.cc +++ b/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.cc @@ -15,7 +15,7 @@ #include "chrome/common/chrome_paths.h" #include "chrome/test/base/mixin_based_in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" -#include "chromeos/dbus/session_manager/fake_session_manager_client.h" +#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" #include "components/prefs/pref_service.h" #include "extensions/browser/extension_registry.h" #include "extensions/test/result_catcher.h" @@ -69,7 +69,7 @@ void ForceInstalledAffiliatedExtensionApiTest:: SetUpInProcessBrowserTestFixture() { // Initialize clients here so they are available during setup. They will be // shutdown in ChromeBrowserMain. - chromeos::SessionManagerClient::InitializeFakeInMemory(); + ash::SessionManagerClient::InitializeFakeInMemory(); // Init the user policy provider. policy_provider_.SetDefaultReturns( @@ -88,9 +88,9 @@ void ForceInstalledAffiliatedExtensionApiTest:: void ForceInstalledAffiliatedExtensionApiTest::SetUpOnMainThread() { // Log in user that was created with // policy::AffiliationTestHelper::PreLoginUser() in the PRE_ test. - const base::Value* users = - g_browser_process->local_state()->GetList("LoggedInUsers"); - if (!users->GetListDeprecated().empty()) { + const base::Value::List& users = + g_browser_process->local_state()->GetValueList("LoggedInUsers"); + if (!users.empty()) { policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id()); } diff --git a/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h b/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h index 95e2dd47abe..f6a400f5f97 100644 --- a/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h +++ b/chromium/chrome/browser/extensions/api/force_installed_affiliated_extension_apitest.h @@ -7,7 +7,6 @@ #include <string> -#include "ash/components/tpm/stub_install_attributes.h" #include "base/values.h" #include "chrome/browser/ash/login/test/cryptohome_mixin.h" #include "chrome/browser/ash/policy/affiliation/affiliation_mixin.h" @@ -15,6 +14,7 @@ #include "chrome/browser/extensions/mixin_based_extension_apitest.h" #include "chrome/browser/policy/extension_force_install_mixin.h" #include "chrome/test/base/mixin_based_in_process_browser_test.h" +#include "chromeos/ash/components/install_attributes/stub_install_attributes.h" #include "components/policy/core/common/mock_configuration_policy_provider.h" #include "extensions/common/extension_id.h" #include "url/gurl.h" diff --git a/chromium/chrome/browser/extensions/api/gcm/gcm_api.cc b/chromium/chrome/browser/extensions/api/gcm/gcm_api.cc index e0419a76dc0..e1363a22442 100644 --- a/chromium/chrome/browser/extensions/api/gcm/gcm_api.cc +++ b/chromium/chrome/browser/extensions/api/gcm/gcm_api.cc @@ -124,8 +124,8 @@ ExtensionFunction::ResponseAction GcmRegisterFunction::Run() { void GcmRegisterFunction::CompleteFunctionWithResult( const std::string& registration_id, gcm::GCMClient::Result gcm_result) { - std::vector<base::Value> result; - result.emplace_back(registration_id); + base::Value::List result; + result.Append(registration_id); const bool succeeded = gcm::GCMClient::SUCCESS == gcm_result; Respond(succeeded @@ -182,8 +182,8 @@ ExtensionFunction::ResponseAction GcmSendFunction::Run() { void GcmSendFunction::CompleteFunctionWithResult( const std::string& message_id, gcm::GCMClient::Result gcm_result) { - std::vector<base::Value> result; - result.emplace_back(message_id); + base::Value::List result; + result.Append(message_id); const bool succeeded = gcm::GCMClient::SUCCESS == gcm_result; Respond(succeeded diff --git a/chromium/chrome/browser/extensions/api/history/history_api.cc b/chromium/chrome/browser/extensions/api/history/history_api.cc index b951eaf0376..bb8d7f73318 100644 --- a/chromium/chrome/browser/extensions/api/history/history_api.cc +++ b/chromium/chrome/browser/extensions/api/history/history_api.cc @@ -143,10 +143,9 @@ HistoryEventRouter::~HistoryEventRouter() { } void HistoryEventRouter::OnURLVisited(history::HistoryService* history_service, - ui::PageTransition transition, - const history::URLRow& row, - base::Time visit_time) { - auto args = OnVisited::Create(GetHistoryItem(row)); + const history::URLRow& url_row, + const history::VisitRow& new_visit) { + auto args = OnVisited::Create(GetHistoryItem(url_row)); DispatchEvent(profile_, events::HISTORY_ON_VISITED, api::history::OnVisited::kEventName, std::move(args)); } @@ -170,7 +169,7 @@ void HistoryEventRouter::OnURLsDeleted( void HistoryEventRouter::DispatchEvent(Profile* profile, events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args) { + base::Value::List event_args) { if (profile && EventRouter::Get(profile)) { auto event = std::make_unique<Event>(histogram_value, event_name, std::move(event_args), profile); diff --git a/chromium/chrome/browser/extensions/api/history/history_api.h b/chromium/chrome/browser/extensions/api/history/history_api.h index 945f0dd95b9..df1ed3057d5 100644 --- a/chromium/chrome/browser/extensions/api/history/history_api.h +++ b/chromium/chrome/browser/extensions/api/history/history_api.h @@ -38,16 +38,15 @@ class HistoryEventRouter : public history::HistoryServiceObserver { private: // history::HistoryServiceObserver. void OnURLVisited(history::HistoryService* history_service, - ui::PageTransition transition, - const history::URLRow& row, - base::Time visit_time) override; + const history::URLRow& url_row, + const history::VisitRow& new_visit) override; void OnURLsDeleted(history::HistoryService* history_service, const history::DeletionInfo& deletion_info) override; void DispatchEvent(Profile* profile, events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args); + base::Value::List event_args); raw_ptr<Profile> profile_; base::ScopedObservation<history::HistoryService, diff --git a/chromium/chrome/browser/extensions/api/i18n/i18n_apitest.cc b/chromium/chrome/browser/extensions/api/i18n/i18n_apitest.cc index f0a2d79d755..35392762ff6 100644 --- a/chromium/chrome/browser/extensions/api/i18n/i18n_apitest.cc +++ b/chromium/chrome/browser/extensions/api/i18n/i18n_apitest.cc @@ -5,7 +5,6 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" -#include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/ui/browser.h" @@ -69,7 +68,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, I18NUpdate) { std::u16string title; ui_test_utils::GetCurrentTabTitle(browser(), &title); - EXPECT_EQ(std::string("FIRSTMESSAGE"), base::UTF16ToUTF8(title)); + EXPECT_EQ(u"FIRSTMESSAGE", title); // Change messages.json file and reload extension. base::CopyFile( @@ -83,7 +82,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, I18NUpdate) { EXPECT_TRUE(catcher.GetNextResult()); ui_test_utils::GetCurrentTabTitle(browser(), &title); - EXPECT_EQ(std::string("SECONDMESSAGE"), base::UTF16ToUTF8(title)); + EXPECT_EQ(u"SECONDMESSAGE", title); } // detectLanguage has some custom hooks that handle the asynchronous response diff --git a/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc b/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc index c08c89bc3a9..e7151a03320 100644 --- a/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc +++ b/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow.cc @@ -5,7 +5,6 @@ #include "chrome/browser/extensions/api/identity/gaia_remote_consent_flow.h" #include "base/bind.h" -#include "base/feature_list.h" #include "base/metrics/histogram_functions.h" #include "base/strings/escape.h" #include "build/chromeos_buildflags.h" @@ -16,7 +15,6 @@ #include "chrome/browser/signin/identity_manager_factory.h" #include "components/signin/core/browser/account_reconcilor.h" #include "components/signin/public/base/multilogin_parameters.h" -#include "components/signin/public/base/signin_switches.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h" #include "content/public/browser/storage_partition.h" @@ -71,8 +69,7 @@ void GaiaRemoteConsentFlow::Start() { WebAuthFlow::GET_AUTH_TOKEN); #if BUILDFLAG(IS_CHROMEOS_LACROS) // `profile_` may be nullptr in tests. - if (profile_ && - base::FeatureList::IsEnabled(switches::kLacrosNonSyncingProfiles)) { + if (profile_) { AccountReconcilorFactory::GetForProfile(profile_) ->GetConsistencyCookieManager() ->AddExtraCookieManager(GetCookieManagerForPartition()); @@ -198,8 +195,7 @@ void GaiaRemoteConsentFlow::SetWebAuthFlowForTesting( web_flow_ = std::move(web_auth_flow); #if BUILDFLAG(IS_CHROMEOS_LACROS) // `profile_` may be nullptr in tests. - if (profile_ && - base::FeatureList::IsEnabled(switches::kLacrosNonSyncingProfiles)) { + if (profile_) { AccountReconcilorFactory::GetForProfile(profile_) ->GetConsistencyCookieManager() ->AddExtraCookieManager(GetCookieManagerForPartition()); @@ -260,8 +256,7 @@ void GaiaRemoteConsentFlow::DetachWebAuthFlow() { #if BUILDFLAG(IS_CHROMEOS_LACROS) // `profile_` may be nullptr in tests. - if (profile_ && - base::FeatureList::IsEnabled(switches::kLacrosNonSyncingProfiles)) { + if (profile_) { AccountReconcilorFactory::GetForProfile(profile_) ->GetConsistencyCookieManager() ->RemoveExtraCookieManager(GetCookieManagerForPartition()); diff --git a/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc b/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc index 8b77aa34d26..e703920143b 100644 --- a/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc +++ b/chromium/chrome/browser/extensions/api/identity/gaia_remote_consent_flow_unittest.cc @@ -9,9 +9,7 @@ #include "base/run_loop.h" #include "base/test/metrics/histogram_tester.h" -#include "base/test/scoped_feature_list.h" #include "build/chromeos_buildflags.h" -#include "components/signin/public/base/signin_switches.h" #include "content/public/test/browser_task_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -112,11 +110,6 @@ class IdentityGaiaRemoteConsentFlowTest : public testing::Test { base::test::TaskEnvironment task_env_; base::HistogramTester histogram_tester_; testing::StrictMock<MockGaiaRemoteConsentFlowDelegate> delegate_; - -#if BUILDFLAG(IS_CHROMEOS_LACROS) - base::test::ScopedFeatureList scoped_feature_list_{ - switches::kLacrosNonSyncingProfiles}; -#endif }; TEST_F(IdentityGaiaRemoteConsentFlowTest, ConsentResult) { diff --git a/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc b/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc index 469ae5a896a..a18afaf7450 100644 --- a/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc +++ b/chromium/chrome/browser/extensions/api/identity/identity_apitest.cc @@ -84,15 +84,21 @@ #include "url/gurl.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "ash/components/tpm/stub_install_attributes.h" #include "chrome/browser/ash/login/users/mock_user_manager.h" #include "chrome/browser/ash/net/network_portal_detector_test_impl.h" -#include "chromeos/network/network_handler.h" -#include "chromeos/network/network_state.h" -#include "chromeos/network/network_state_handler.h" +#include "chromeos/ash/components/install_attributes/stub_install_attributes.h" +#include "chromeos/ash/components/network/network_handler.h" +#include "chromeos/ash/components/network/network_state.h" +#include "chromeos/ash/components/network/network_state_handler.h" +#include "chromeos/login/login_state/login_state.h" #include "components/user_manager/scoped_user_manager.h" #endif +#if BUILDFLAG(IS_CHROMEOS_LACROS) +#include "chromeos/crosapi/mojom/crosapi.mojom.h" +#include "chromeos/startup/browser_init_params.h" +#endif + using guest_view::GuestViewBase; using testing::_; using testing::Return; @@ -116,10 +122,8 @@ const char kGetAuthTokenResultAfterConsentApprovedHistogramName[] = #if BUILDFLAG(IS_CHROMEOS_ASH) void InitNetwork() { - const chromeos::NetworkState* default_network = - chromeos::NetworkHandler::Get() - ->network_state_handler() - ->DefaultNetwork(); + const ash::NetworkState* default_network = + ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); auto* portal_detector = new ash::NetworkPortalDetectorTestImpl(); portal_detector->SetDefaultNetworkForTesting(default_network->guid()); @@ -128,7 +132,7 @@ void InitNetwork() { default_network->guid(), ash::NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE, 204); - chromeos::network_portal_detector::InitializeForTesting(portal_detector); + ash::network_portal_detector::InitializeForTesting(portal_detector); } #endif @@ -168,19 +172,16 @@ class AsyncFunctionRunner { return function->GetError(); } - void WaitForTwoResults(ExtensionFunction* function, - base::Value* first_result, - base::Value* second_result) { + void WaitForOneResult(ExtensionFunction* function, base::Value* result) { RunMessageLoopUntilResponse(); EXPECT_TRUE(function->GetError().empty()) << "Unexpected error: " << function->GetError(); EXPECT_NE(nullptr, function->GetResultList()); const auto& result_list = *function->GetResultList(); - EXPECT_EQ(2ul, result_list.size()); + EXPECT_EQ(1ul, result_list.size()); - *first_result = result_list[0].Clone(); - *second_result = result_list[1].Clone(); + *result = result_list[0].Clone(); } private: @@ -206,11 +207,8 @@ class AsyncExtensionBrowserTest : public ExtensionBrowserTest { return async_function_runner_->WaitForError(function); } - void WaitForTwoResults(ExtensionFunction* function, - base::Value* first_result, - base::Value* second_result) { - return async_function_runner_->WaitForTwoResults(function, first_result, - second_result); + void WaitForOneResult(ExtensionFunction* function, base::Value* result) { + return async_function_runner_->WaitForOneResult(function, result); } private: @@ -420,7 +418,7 @@ class FakeGetAuthTokenFunction : public IdentityGetAuthTokenFunction { } } -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) void StartDeviceAccessTokenRequest() override { // In these tests requests for the device account just funnel through to // requests for the token key account. @@ -990,29 +988,19 @@ class GetAuthTokenFunctionTest Browser* browser, std::string* access_token, std::set<std::string>* granted_scopes) { - EXPECT_TRUE( - utils::RunFunction(function, args, browser, api_test_utils::NONE)); - - EXPECT_TRUE(function->GetError().empty()) - << "Unexpected error: " << function->GetError(); - EXPECT_NE(nullptr, function->GetResultList()); - - const auto& result_list = *function->GetResultList(); - EXPECT_EQ(2ul, result_list.size()); - - const auto& access_token_value = result_list[0]; - const auto& granted_scopes_value = result_list[1]; - EXPECT_TRUE(access_token_value.is_string()); - EXPECT_TRUE(granted_scopes_value.is_list()); - - std::set<std::string> scopes; - for (const auto& scope : granted_scopes_value.GetListDeprecated()) { - EXPECT_TRUE(scope.is_string()); - scopes.insert(scope.GetString()); - } + std::unique_ptr<base::Value> result_value = + utils::RunFunctionAndReturnSingleResult(function, args, browser); + ASSERT_TRUE(result_value); + std::unique_ptr<api::identity::GetAuthTokenResult> result = + api::identity::GetAuthTokenResult::FromValue(*result_value); + ASSERT_TRUE(result); - *access_token = access_token_value.GetString(); - *granted_scopes = std::move(scopes); + EXPECT_NE(nullptr, result->token); + *access_token = *result->token; + EXPECT_NE(nullptr, result->granted_scopes); + std::set<std::string> granted_scopes_map(result->granted_scopes->begin(), + result->granted_scopes->end()); + *granted_scopes = std::move(granted_scopes_map); } void WaitForGetAuthTokenResults( @@ -1020,25 +1008,22 @@ class GetAuthTokenFunctionTest std::string* access_token, std::set<std::string>* granted_scopes, AsyncFunctionRunner* function_runner = nullptr) { - base::Value access_token_value; - base::Value granted_scopes_value; + base::Value result_value; if (function_runner == nullptr) { - WaitForTwoResults(function, &access_token_value, &granted_scopes_value); + WaitForOneResult(function, &result_value); } else { - function_runner->WaitForTwoResults(function, &access_token_value, - &granted_scopes_value); + function_runner->WaitForOneResult(function, &result_value); } - EXPECT_TRUE(access_token_value.is_string()); - EXPECT_TRUE(granted_scopes_value.is_list()); + std::unique_ptr<api::identity::GetAuthTokenResult> result = + api::identity::GetAuthTokenResult::FromValue(result_value); + ASSERT_TRUE(result); - std::set<std::string> scopes; - for (const auto& scope : granted_scopes_value.GetListDeprecated()) { - EXPECT_TRUE(scope.is_string()); - scopes.insert(scope.GetString()); - } - - *access_token = access_token_value.GetString(); - *granted_scopes = std::move(scopes); + ASSERT_NE(nullptr, result->token); + *access_token = *result->token; + ASSERT_NE(nullptr, result->granted_scopes); + std::set<std::string> granted_scopes_map(result->granted_scopes->begin(), + result->granted_scopes->end()); + *granted_scopes = std::move(granted_scopes_map); } private: @@ -1113,9 +1098,8 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, IdentityGetAuthTokenError::State::kSignInFailed, 1); } +// Signin is always allowed on Lacros. #if !BUILDFLAG(IS_CHROMEOS_LACROS) -// TODO(crbug.com/1220066): Remove the lacros exclusion when DICE is disabled on -// Lacros. IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, PRE_InteractiveNotSignedAndSigninNotAllowed) { // kSigninAllowed cannot be set after the profile creation. Use @@ -1251,7 +1235,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); } -// The signin flow is simply not used on ChromeOS. +// The signin flow is simply not used on Ash. #if !BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveMintServiceErrorShowSigninOnlyOnce) { @@ -1304,8 +1288,8 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveLoginCanceled) { std::string error = utils::RunFunctionAndReturnError( func.get(), "[{\"interactive\": true}]", browser()); EXPECT_EQ(std::string(errors::kUserNotSignedIn), error); -// ChromeOS does not support the interactive login flow, so the login UI will -// never be shown on that platform. +// Ash does not support the interactive login flow, so the login UI will never +// be shown on that platform. #if !BUILDFLAG(IS_CHROMEOS_ASH) EXPECT_TRUE(func->login_ui_shown()); #endif @@ -1335,7 +1319,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, } // The interactive login flow is always short-circuited out with failure on -// ChromeOS, so the tests of the interactive login flow being successful are not +// Ash, so the tests of the interactive login flow being successful are not // relevant on that platform. #if !BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, @@ -1542,7 +1526,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveApprovalNoGrant) { IdentityGetAuthTokenError::State::kNoGrant, 1); } -// The signin flow is simply not used on ChromeOS. +// The signin flow is simply not used on Ash. #if !BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveApprovalNoGrantShowSigninUIOnlyOnce) { @@ -1955,8 +1939,8 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveCacheHit) { 1); } -// The interactive login UI is never shown on ChromeOS, so tests of the -// interactive login flow being successful are not relevant on that platform. +// The interactive login UI is never shown on Ash, so tests of the interactive +// login flow being successful are not relevant on that platform. #if !BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, LoginInvalidatesTokenCache) { scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); @@ -2586,7 +2570,7 @@ IN_PROC_BROWSER_TEST_F( 2); } -// The signin flow is simply not used on ChromeOS. +// The signin flow is simply not used on Ash. #if !BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, MultiSecondaryInteractiveInvalidToken) { @@ -2804,22 +2788,103 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, GranularPermissionsResponse) { 1); } +#if BUILDFLAG(IS_CHROMEOS) +enum class DeviceLocalAccountSessionType { kPublic, kAppKiosk, kWebKiosk }; + #if BUILDFLAG(IS_CHROMEOS_ASH) +class GetAuthTokenFunctionDeviceLocalAccountTestPlatformHelper { + public: + explicit GetAuthTokenFunctionDeviceLocalAccountTestPlatformHelper( + DeviceLocalAccountSessionType session_type) + : session_type_(session_type), user_manager_(new ash::MockUserManager) {} + + void SetUpOnMainThread() { + chromeos::LoginState::Get()->SetLoggedInState( + chromeos::LoginState::LoggedInState::LOGGED_IN_ACTIVE, + session_type_ == DeviceLocalAccountSessionType::kPublic + ? chromeos::LoginState::LoggedInUserType:: + LOGGED_IN_USER_PUBLIC_ACCOUNT + : chromeos::LoginState::LoggedInUserType::LOGGED_IN_USER_KIOSK); + EXPECT_CALL(*user_manager_, IsLoggedInAsKioskApp()) + .WillRepeatedly( + Return(session_type_ == DeviceLocalAccountSessionType::kAppKiosk)); + EXPECT_CALL(*user_manager_, IsLoggedInAsWebKioskApp()) + .WillRepeatedly( + Return(session_type_ == DeviceLocalAccountSessionType::kWebKiosk)); + EXPECT_CALL(*user_manager_, IsLoggedInAsPublicAccount()) + .WillRepeatedly( + Return(session_type_ == DeviceLocalAccountSessionType::kPublic)); + EXPECT_CALL(*user_manager_, GetLoggedInUsers()) + .WillRepeatedly( + testing::Invoke(user_manager_, &ash::MockUserManager::GetUsers)); + scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( + base::WrapUnique(user_manager_)); + } + + void TearDownOnMainThread() { scoped_user_manager_.reset(); } + + private: + const DeviceLocalAccountSessionType session_type_; + + // Set up fake install attributes to make the device appeared as + // enterprise-managed. + ash::ScopedStubInstallAttributes test_install_attributes_{ + ash::StubInstallAttributes::CreateCloudManaged("example.com", "fake-id")}; + + // Owned by |scoped_user_manager_|. + ash::MockUserManager* user_manager_; + std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; +}; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +#if BUILDFLAG(IS_CHROMEOS_LACROS) +class GetAuthTokenFunctionDeviceLocalAccountTestPlatformHelper { + public: + explicit GetAuthTokenFunctionDeviceLocalAccountTestPlatformHelper( + DeviceLocalAccountSessionType session_type) { + crosapi::mojom::SessionType init_params_session_type = + crosapi::mojom::SessionType::kUnknown; + switch (session_type) { + case DeviceLocalAccountSessionType::kPublic: + init_params_session_type = crosapi::mojom::SessionType::kPublicSession; + break; + case DeviceLocalAccountSessionType::kAppKiosk: + init_params_session_type = + crosapi::mojom::SessionType::kAppKioskSession; + break; + case DeviceLocalAccountSessionType::kWebKiosk: + init_params_session_type = + crosapi::mojom::SessionType::kWebKioskSession; + break; + } + crosapi::mojom::BrowserInitParamsPtr init_params = + crosapi::mojom::BrowserInitParams::New(); + init_params->session_type = init_params_session_type; + init_params->is_device_enterprised_managed = true; + init_params->device_settings = crosapi::mojom::DeviceSettings::New(); + chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params)); + } + + void SetUpOnMainThread() {} + void TearDownOnMainThread() {} +}; +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + class GetAuthTokenFunctionDeviceLocalAccountTest : public GetAuthTokenFunctionTest { public: - GetAuthTokenFunctionDeviceLocalAccountTest() - : user_manager_(new ash::MockUserManager) {} + explicit GetAuthTokenFunctionDeviceLocalAccountTest( + DeviceLocalAccountSessionType session_type) + : platform_helper_(session_type) {} void SetUpOnMainThread() override { + platform_helper_.SetUpOnMainThread(); GetAuthTokenFunctionTest::SetUpOnMainThread(); - scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( - base::WrapUnique(user_manager_)); } void TearDownOnMainThread() override { GetAuthTokenFunctionTest::TearDownOnMainThread(); - scoped_user_manager_.reset(); + platform_helper_.TearDownOnMainThread(); } protected: @@ -2854,33 +2919,15 @@ class GetAuthTokenFunctionDeviceLocalAccountTest .Build(); } - // Set up fake install attributes to make the device appeared as - // enterprise-managed. - ash::ScopedStubInstallAttributes test_install_attributes_{ - ash::StubInstallAttributes::CreateCloudManaged("example.com", "fake-id")}; - - // Owned by |user_manager_enabler|. - ash::MockUserManager* user_manager_; - std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; + GetAuthTokenFunctionDeviceLocalAccountTestPlatformHelper platform_helper_; }; class GetAuthTokenFunctionPublicSessionTest : public GetAuthTokenFunctionDeviceLocalAccountTest { protected: - void SetUpInProcessBrowserTestFixture() override { - GetAuthTokenFunctionTest::SetUpInProcessBrowserTestFixture(); - - // Set up the user manager to fake a public session. - EXPECT_CALL(*user_manager_, IsLoggedInAsKioskApp()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, IsLoggedInAsWebKioskApp()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, IsLoggedInAsPublicAccount()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*user_manager_, GetLoggedInUsers()) - .WillRepeatedly( - testing::Invoke(user_manager_, &ash::MockUserManager::GetUsers)); - } + GetAuthTokenFunctionPublicSessionTest() + : GetAuthTokenFunctionDeviceLocalAccountTest( + DeviceLocalAccountSessionType::kPublic) {} }; IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest, NonAllowlisted) { @@ -2901,20 +2948,9 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest, NonAllowlisted) { class GetAuthTokenFunctionChromeKioskTest : public GetAuthTokenFunctionDeviceLocalAccountTest { protected: - void SetUpInProcessBrowserTestFixture() override { - GetAuthTokenFunctionTest::SetUpInProcessBrowserTestFixture(); - - // Set up the user manager to fake a Chrome Kiosk session. - EXPECT_CALL(*user_manager_, IsLoggedInAsKioskApp()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*user_manager_, IsLoggedInAsWebKioskApp()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, IsLoggedInAsPublicAccount()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, GetLoggedInUsers()) - .WillRepeatedly( - testing::Invoke(user_manager_, &ash::MockUserManager::GetUsers)); - } + GetAuthTokenFunctionChromeKioskTest() + : GetAuthTokenFunctionDeviceLocalAccountTest( + DeviceLocalAccountSessionType::kAppKiosk) {} }; IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionChromeKioskTest, NonAllowlisted) { @@ -2926,20 +2962,9 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionChromeKioskTest, NonAllowlisted) { class GetAuthTokenFunctionWebKioskTest : public GetAuthTokenFunctionDeviceLocalAccountTest { protected: - void SetUpInProcessBrowserTestFixture() override { - GetAuthTokenFunctionTest::SetUpInProcessBrowserTestFixture(); - - // Set up the user manager to fake a web Kiosk session. - EXPECT_CALL(*user_manager_, IsLoggedInAsKioskApp()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, IsLoggedInAsWebKioskApp()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*user_manager_, IsLoggedInAsPublicAccount()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*user_manager_, GetLoggedInUsers()) - .WillRepeatedly( - testing::Invoke(user_manager_, &ash::MockUserManager::GetUsers)); - } + GetAuthTokenFunctionWebKioskTest() + : GetAuthTokenFunctionDeviceLocalAccountTest( + DeviceLocalAccountSessionType::kWebKiosk) {} }; IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionWebKioskTest, NonAllowlisted) { @@ -2948,7 +2973,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionWebKioskTest, NonAllowlisted) { RunExtensionAndVerifyNoError(/*is_extension_allowlisted=*/false); } -#endif +#endif // BUILDFLAG(IS_CHROMEOS) // There are two parameters, which are stored in a std::pair, for these tests. // @@ -3152,7 +3177,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, 1); } -// The signin flow is not used on ChromeOS. +// The signin flow is not used on Ash. #if !BUILDFLAG(IS_CHROMEOS_ASH) // Tests that Chrome does not have any selected user id value if the account // specified by the extension is not available. @@ -3511,7 +3536,7 @@ class OnSignInChangedEventTest : public IdentityTestWithSignin { // been added. This is because the order of multiple events firing due to the // same underlying state change is undefined in the // chrome.identity.onSignInEventChanged() API. - void AddExpectedEvent(std::vector<base::Value> args) { + void AddExpectedEvent(base::Value::List args) { expected_events_.insert( std::make_unique<Event>(events::IDENTITY_ON_SIGN_IN_CHANGED, api::identity::OnSignInChanged::kEventName, @@ -3526,13 +3551,13 @@ class OnSignInChangedEventTest : public IdentityTestWithSignin { // Search for |event| in the set of expected events. bool found_event = false; - const auto* event_args = event->event_args.get(); + const auto& event_args = event->event_args; for (const auto& expected_event : expected_events_) { EXPECT_EQ(expected_event->histogram_value, event->histogram_value); EXPECT_EQ(expected_event->event_name, event->event_name); - const auto* expected_event_args = expected_event->event_args.get(); - if (*event_args != *expected_event_args) + const auto& expected_event_args = expected_event->event_args; + if (event_args != expected_event_args) continue; expected_events_.erase(expected_event); @@ -3546,11 +3571,11 @@ class OnSignInChangedEventTest : public IdentityTestWithSignin { LOG(INFO) << "Was expecting events with these args:"; for (const auto& expected_event : expected_events_) { - LOG(INFO) << *(expected_event->event_args.get()); + LOG(INFO) << expected_event->event_args; } LOG(INFO) << "But received event with different args:"; - LOG(INFO) << *event_args; + LOG(INFO) << event_args; } } 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 24c2561d4cd..b425e031d91 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 @@ -8,19 +8,14 @@ #include <vector> #include "base/bind.h" -#include "base/feature_list.h" #include "base/location.h" -#include "base/logging.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" -#include "base/strings/string_number_conversions.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/browser_process_platform_part.h" #include "chrome/browser/extensions/api/identity/identity_api.h" -#include "chrome/browser/extensions/api/identity/identity_constants.h" #include "chrome/browser/extensions/api/identity/identity_get_auth_token_error.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/chrome_device_id_helper.h" @@ -30,7 +25,6 @@ #include "chrome/common/extensions/api/identity.h" #include "components/prefs/pref_service.h" #include "components/signin/public/base/signin_pref_names.h" -#include "components/signin/public/base/signin_switches.h" #include "components/signin/public/identity_manager/access_token_info.h" #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" #include "components/signin/public/identity_manager/scope_set.h" @@ -38,20 +32,15 @@ #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "extensions/common/api/oauth2.h" -#include "extensions/common/extension_features.h" -#include "extensions/common/extension_l10n_util.h" #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_urls.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chrome/browser/app_mode/app_mode_utils.h" -#include "chrome/browser/ash/login/session/user_session_manager.h" -#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h" -#include "chrome/browser/device_identity/device_oauth2_token_service.h" -#include "chrome/browser/device_identity/device_oauth2_token_service_factory.h" -#include "components/user_manager/user_manager.h" +#if BUILDFLAG(IS_CHROMEOS) +#include "chrome/browser/policy/chrome_browser_policy_connector.h" +#include "chrome/browser/profiles/profiles_state.h" +#include "components/account_manager_core/account_manager_util.h" #include "google_apis/gaia/gaia_constants.h" #endif @@ -87,13 +76,7 @@ void RecordFunctionResult(const IdentityGetAuthTokenError& error, } // namespace -IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() -#if BUILDFLAG(IS_CHROMEOS_ASH) - : OAuth2AccessTokenManager::Consumer( - kExtensionsIdentityAPIOAuthConsumerName) -#endif -{ -} +IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() = default; IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() { TRACE_EVENT_NESTABLE_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", @@ -237,24 +220,18 @@ void IdentityGetAuthTokenFunction::OnReceivedExtensionAccountInfo( const CoreAccountInfo& account_info) { token_key_.account_info = account_info; -#if BUILDFLAG(IS_CHROMEOS_ASH) - policy::BrowserPolicyConnectorAsh* connector = - g_browser_process->platform_part()->browser_policy_connector_ash(); - bool is_kiosk = user_manager::UserManager::Get()->IsLoggedInAsKioskApp() || - user_manager::UserManager::Get()->IsLoggedInAsWebKioskApp(); - bool is_public_session = - user_manager::UserManager::Get()->IsLoggedInAsPublicAccount(); - - if (connector->IsDeviceEnterpriseManaged() && - (is_kiosk || is_public_session)) { - if (is_public_session) { +#if BUILDFLAG(IS_CHROMEOS) + if (g_browser_process->browser_policy_connector() + ->IsDeviceEnterpriseManaged()) { + if (profiles::IsPublicSession()) { CompleteFunctionWithError(IdentityGetAuthTokenError( IdentityGetAuthTokenError::State::kNotAllowlistedInPublicSession)); return; } - - StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); - return; + if (profiles::IsKioskSession()) { + StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); + return; + } } #endif @@ -325,12 +302,13 @@ void IdentityGetAuthTokenFunction::CompleteFunctionWithResult( const std::set<std::string>& granted_scopes) { RecordFunctionResult(IdentityGetAuthTokenError(), remote_consent_approved_); - base::Value::List granted_scopes_value; - for (const auto& scope : granted_scopes) - granted_scopes_value.Append(scope); + api::identity::GetAuthTokenResult result; + result.token = std::make_unique<std::string>(access_token); + result.granted_scopes = std::make_unique<std::vector<std::string>>( + granted_scopes.begin(), granted_scopes.end()); - CompleteAsyncRun(TwoArguments(base::Value(access_token), - base::Value(std::move(granted_scopes_value)))); + CompleteAsyncRun( + OneArgument(base::Value::FromUniquePtrValue(result.ToValue()))); } void IdentityGetAuthTokenFunction::CompleteFunctionWithError( @@ -404,7 +382,7 @@ void IdentityGetAuthTokenFunction::StartSigninFlow() { void IdentityGetAuthTokenFunction::StartMintTokenFlow( IdentityMintRequestQueue::MintType type) { -#if !BUILDFLAG(IS_CHROMEOS_ASH) +#if !BUILDFLAG(IS_CHROMEOS) // ChromeOS in kiosk mode may start the mint token flow without account. DCHECK(!token_key_.account_info.IsEmpty()); DCHECK(IdentityManagerFactory::GetForProfile(GetProfile()) @@ -469,23 +447,18 @@ void IdentityGetAuthTokenFunction::StartMintToken( if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) { switch (cache_status) { case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND: -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) // Always force minting token for ChromeOS kiosk app and public session. - if (user_manager::UserManager::Get()->IsLoggedInAsPublicAccount()) { + if (profiles::IsPublicSession()) { CompleteFunctionWithError( IdentityGetAuthTokenError(IdentityGetAuthTokenError::State:: kNotAllowlistedInPublicSession)); return; } - - if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp() || - user_manager::UserManager::Get()->IsLoggedInAsWebKioskApp() || - user_manager::UserManager::Get()->IsLoggedInAsPublicAccount()) { + if (profiles::IsKioskSession()) { gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; - policy::BrowserPolicyConnectorAsh* connector = - g_browser_process->platform_part() - ->browser_policy_connector_ash(); - if (connector->IsDeviceEnterpriseManaged()) { + if (g_browser_process->browser_policy_connector() + ->IsDeviceEnterpriseManaged()) { StartDeviceAccessTokenRequest(); } else { StartTokenKeyAccountAccessTokenRequest(); @@ -765,7 +738,9 @@ void IdentityGetAuthTokenFunction::OnGetAccessTokenComplete( const GoogleServiceAuthError& error) { // By the time we get here we should no longer have an outstanding access // token request. - DCHECK(!device_access_token_request_); +#if BUILDFLAG(IS_CHROMEOS) + DCHECK(!device_oauth2_token_fetcher_); +#endif DCHECK(!token_key_account_access_token_fetcher_); if (access_token) { TRACE_EVENT_NESTABLE_ASYNC_END1( @@ -787,21 +762,23 @@ void IdentityGetAuthTokenFunction::OnGetAccessTokenComplete( } } -#if BUILDFLAG(IS_CHROMEOS_ASH) -void IdentityGetAuthTokenFunction::OnGetTokenSuccess( - const OAuth2AccessTokenManager::Request* request, - const OAuth2AccessTokenConsumer::TokenResponse& token_response) { - device_access_token_request_.reset(); - OnGetAccessTokenComplete(token_response.access_token, - token_response.expiration_time, - GoogleServiceAuthError::AuthErrorNone()); -} - -void IdentityGetAuthTokenFunction::OnGetTokenFailure( - const OAuth2AccessTokenManager::Request* request, - const GoogleServiceAuthError& error) { - device_access_token_request_.reset(); - OnGetAccessTokenComplete(absl::nullopt, base::Time(), error); +#if BUILDFLAG(IS_CHROMEOS) +void IdentityGetAuthTokenFunction::OnAccessTokenForDeviceAccountFetchCompleted( + crosapi::mojom::AccessTokenResultPtr result) { + absl::optional<std::string> access_token; + base::Time expiration_time; + GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone(); + if (result->is_access_token_info()) { + access_token = result->get_access_token_info()->access_token; + expiration_time = result->get_access_token_info()->expiration_time; + } else { + DCHECK(result->is_error()); + error = account_manager::FromMojoGoogleServiceAuthError(result->get_error()) + .value_or(GoogleServiceAuthError( + GoogleServiceAuthError::SERVICE_ERROR)); + } + device_oauth2_token_fetcher_.reset(); + OnGetAccessTokenComplete(access_token, expiration_time, error); } #endif @@ -819,7 +796,9 @@ void IdentityGetAuthTokenFunction::OnAccessTokenFetchCompleted( } void IdentityGetAuthTokenFunction::OnIdentityAPIShutdown() { - device_access_token_request_.reset(); +#if BUILDFLAG(IS_CHROMEOS) + device_oauth2_token_fetcher_.reset(); +#endif token_key_account_access_token_fetcher_.reset(); scoped_identity_manager_observation_.Reset(); extensions::IdentityAPI::GetFactoryInstance() @@ -831,18 +810,18 @@ void IdentityGetAuthTokenFunction::OnIdentityAPIShutdown() { IdentityGetAuthTokenError(IdentityGetAuthTokenError::State::kCanceled)); } -#if BUILDFLAG(IS_CHROMEOS_ASH) -// Even though the DeviceOAuth2TokenService may be available on non-ChromeOS -// platforms, its robot account is not made available because it should only be -// used for very specific policy-related things. In fact, the device account on -// desktop isn't scoped for anything other than policy invalidations. +#if BUILDFLAG(IS_CHROMEOS) void IdentityGetAuthTokenFunction::StartDeviceAccessTokenRequest() { - DeviceOAuth2TokenService* service = DeviceOAuth2TokenServiceFactory::Get(); + device_oauth2_token_fetcher_ = std::make_unique<DeviceOAuth2TokenFetcher>(); // Since robot account refresh tokens are scoped down to [any-api] only, // request access token for [any-api] instead of login. - OAuth2AccessTokenManager::ScopeSet scopes; - scopes.insert(GaiaConstants::kAnyApiOAuth2Scope); - device_access_token_request_ = service->StartAccessTokenRequest(scopes, this); + // `Unretained()` is safe because this outlives + // `device_oauth2_token_fetcher_`. + device_oauth2_token_fetcher_->FetchAccessTokenForDeviceAccount( + {GaiaConstants::kAnyApiOAuth2Scope}, + base::BindOnce(&IdentityGetAuthTokenFunction:: + OnAccessTokenForDeviceAccountFetchCompleted, + base::Unretained(this))); } #endif @@ -850,26 +829,6 @@ void IdentityGetAuthTokenFunction::StartTokenKeyAccountAccessTokenRequest() { TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("identity", "GetAccessToken", this); auto* identity_manager = IdentityManagerFactory::GetForProfile(GetProfile()); -#if BUILDFLAG(IS_CHROMEOS_ASH) - if (chrome::IsRunningInForcedAppMode()) { - std::string app_client_id; - std::string app_client_secret; - if (ash::UserSessionManager::GetInstance()->GetAppModeChromeClientOAuthInfo( - &app_client_id, &app_client_secret)) { - token_key_account_access_token_fetcher_ = - identity_manager->CreateAccessTokenFetcherForClient( - token_key_.account_info.account_id, app_client_id, - app_client_secret, kExtensionsIdentityAPIOAuthConsumerName, - signin::ScopeSet(), - base::BindOnce( - &IdentityGetAuthTokenFunction::OnAccessTokenFetchCompleted, - base::Unretained(this)), - signin::AccessTokenFetcher::Mode::kImmediate); - return; - } - } -#endif - token_key_account_access_token_fetcher_ = identity_manager->CreateAccessTokenFetcherForAccount( token_key_.account_info.account_id, 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 fd4958bfe33..a90de50b024 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 @@ -20,10 +20,17 @@ #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_function_histogram_value.h" #include "google_apis/gaia/google_service_auth_error.h" -#include "google_apis/gaia/oauth2_access_token_manager.h" #include "google_apis/gaia/oauth2_mint_token_flow.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "chrome/browser/ash/crosapi/device_oauth2_token_service_ash.h" +#endif + +#if BUILDFLAG(IS_CHROMEOS_LACROS) +#include "chrome/browser/lacros/device_oauth2_token_service_lacros.h" +#endif + namespace signin { class AccessTokenFetcher; struct AccessTokenInfo; @@ -53,9 +60,6 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction, public GaiaRemoteConsentFlow::Delegate, public IdentityMintRequestQueue::Request, public signin::IdentityManager::Observer, -#if BUILDFLAG(IS_CHROMEOS_ASH) - public OAuth2AccessTokenManager::Consumer, -#endif public OAuth2MintTokenFlow::Delegate { public: DECLARE_EXTENSION_FUNCTION("identity.getAuthToken", @@ -81,15 +85,9 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction, // Starts a login access token request. virtual void StartTokenKeyAccountAccessTokenRequest(); -// TODO(blundell): Investigate feasibility of moving the ChromeOS use case -// to use the Identity Service instead of being an -// OAuth2AccessTokenManager::Consumer. -#if BUILDFLAG(IS_CHROMEOS_ASH) - void OnGetTokenSuccess( - const OAuth2AccessTokenManager::Request* request, - const OAuth2AccessTokenConsumer::TokenResponse& token_response) override; - void OnGetTokenFailure(const OAuth2AccessTokenManager::Request* request, - const GoogleServiceAuthError& error) override; +#if BUILDFLAG(IS_CHROMEOS) + void OnAccessTokenForDeviceAccountFetchCompleted( + crosapi::mojom::AccessTokenResultPtr result); #endif void OnAccessTokenFetchCompleted(GoogleServiceAuthError error, @@ -117,10 +115,16 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction, // Exposed for testing. std::string GetSelectedUserId() const; - // Pending request for an access token from the device account (via - // DeviceOAuth2TokenService). - std::unique_ptr<OAuth2AccessTokenManager::Request> - device_access_token_request_; +#if BUILDFLAG(IS_CHROMEOS_ASH) + using DeviceOAuth2TokenFetcher = crosapi::DeviceOAuth2TokenServiceAsh; +#endif +#if BUILDFLAG(IS_CHROMEOS_LACROS) + using DeviceOAuth2TokenFetcher = DeviceOAuth2TokenServiceLacros; +#endif + +#if BUILDFLAG(IS_CHROMEOS) + std::unique_ptr<DeviceOAuth2TokenFetcher> device_oauth2_token_fetcher_; +#endif // Pending fetcher for an access token for |token_key_.account_id| (via // IdentityManager). @@ -194,7 +198,7 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction, void OnRemoteConsentSuccess( const RemoteConsentResolutionData& resolution_data) override; -#if BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS) // Starts a login access token request for device robot account. This method // will be called only in Chrome OS for: // 1. Enterprise kiosk mode. diff --git a/chromium/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chromium/chrome/browser/extensions/api/identity/web_auth_flow.cc index ea9512fd6a8..4f96aeacb0a 100644 --- a/chromium/chrome/browser/extensions/api/identity/web_auth_flow.cc +++ b/chromium/chrome/browser/extensions/api/identity/web_auth_flow.cc @@ -105,19 +105,19 @@ void WebAuthFlow::Start() { base::Base64Encode(random_bytes, &app_window_key_); // identityPrivate.onWebFlowRequest(app_window_key, provider_url_, mode_) - std::unique_ptr<base::ListValue> args(new base::ListValue()); - args->Append(app_window_key_); - args->Append(provider_url_.spec()); + base::Value::List args; + args.Append(app_window_key_); + args.Append(provider_url_.spec()); if (mode_ == WebAuthFlow::INTERACTIVE) - args->Append("interactive"); + args.Append("interactive"); else - args->Append("silent"); - args->Append(GetPartitionName(partition_)); + args.Append("silent"); + args.Append(GetPartitionName(partition_)); auto event = std::make_unique<Event>(events::IDENTITY_PRIVATE_ON_WEB_FLOW_REQUEST, identity_private::OnWebFlowRequest::kEventName, - std::move(*args).TakeListDeprecated(), profile_); + std::move(args), profile_); ExtensionSystem* system = ExtensionSystem::Get(profile_); extensions::ComponentLoader* component_loader = diff --git a/chromium/chrome/browser/extensions/api/idltest/idltest_api.cc b/chromium/chrome/browser/extensions/api/idltest/idltest_api.cc index b54ad539360..3ef76a3cb94 100644 --- a/chromium/chrome/browser/extensions/api/idltest/idltest_api.cc +++ b/chromium/chrome/browser/extensions/api/idltest/idltest_api.cc @@ -17,10 +17,10 @@ namespace { base::Value CopyBinaryValueToIntegerList( const base::Value::BlobStorage& input) { - base::Value::ListStorage list; + base::Value::List list; list.reserve(input.size()); for (int c : input) - list.emplace_back(c); + list.Append(c); return base::Value(std::move(list)); } diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.cc b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.cc index 686d4b08a37..027ce24d140 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.cc @@ -5,6 +5,7 @@ #include "chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h" #include "base/containers/contains.h" +#include "base/memory/raw_ptr.h" #include "base/no_destructor.h" #include "chrome/browser/extensions/api/image_writer_private/error_constants.h" #include "chrome/common/extensions/api/image_writer_private.h" @@ -117,8 +118,9 @@ class ImageWriterControllerLacros::ImageWriterClientLacros // to be valid for the lifetime of this class, as destruction of either // BrowserContext or ImageWriterControllerLacros will result in synchronous // destruction of this class. - content::BrowserContext* const browser_context_; - extensions::image_writer::ImageWriterControllerLacros* const controller_; + const raw_ptr<content::BrowserContext> browser_context_; + const raw_ptr<extensions::image_writer::ImageWriterControllerLacros> + controller_; mojo::Receiver<crosapi::mojom::ImageWriterClient> receiver_{this}; }; diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h index 78d1a3be862..379c028f085 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h +++ b/chromium/chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_IMAGE_WRITER_CONTROLLER_LACROS_H_ #include "base/callback.h" +#include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "chromeos/crosapi/mojom/image_writer.mojom.h" #include "extensions/browser/browser_context_keyed_api_factory.h" @@ -86,7 +87,7 @@ class ImageWriterControllerLacros : public BrowserContextKeyedAPI, // BrowserContextKeyedAPI with |browser_context_| which will handle the // destruction of BrowserContext gracefully by shutting down the service // and removing itself from the factory. - content::BrowserContext* browser_context_; + raw_ptr<content::BrowserContext> browser_context_; // Pending image writer clients by extension id. // For each extension, we only allow one pending remote client to request 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 067f00bf3dc..fab3f50caf0 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 @@ -28,7 +28,7 @@ class ImageWriterPrivateApiTest : public ExtensionApiTest { public: void SetUpInProcessBrowserTestFixture() override { ExtensionApiTest::SetUpInProcessBrowserTestFixture(); - test_utils_.SetUp(true); + test_utils_.SetUp(); ASSERT_TRUE(test_utils_.FillFile(test_utils_.GetImagePath(), image_writer::kImagePattern, diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/operation.h b/chromium/chrome/browser/extensions/api/image_writer_private/operation.h index 5dd62723945..0d6d8732764 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/operation.h +++ b/chromium/chrome/browser/extensions/api/image_writer_private/operation.h @@ -187,7 +187,7 @@ class Operation : public base::RefCountedThreadSafe<Operation> { void UnmountVolumes(base::OnceClosure continuation); // Starts the write after unmounting. void UnmountVolumesCallback(base::OnceClosure continuation, - chromeos::MountError error_code); + ash::MountError error_code); // Starts the ImageBurner write. Note that target_path is the file path of // the device where device_path has been a system device path. void StartWriteOnUIThread(const std::string& target_path, diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/operation_chromeos.cc b/chromium/chrome/browser/extensions/api/image_writer_private/operation_chromeos.cc index 61ed8e2f1da..55db9f49224 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/operation_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/operation_chromeos.cc @@ -10,16 +10,15 @@ #include "base/bind.h" #include "chrome/browser/extensions/api/image_writer_private/error_constants.h" #include "chrome/browser/extensions/api/image_writer_private/operation.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "chromeos/dbus/image_burner/image_burner_client.h" +#include "chromeos/ash/components/dbus/image_burner/image_burner_client.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" namespace extensions { namespace image_writer { +using ::ash::ImageBurnerClient; using ::ash::disks::DiskMountManager; -using chromeos::ImageBurnerClient; using content::BrowserThread; namespace { @@ -31,9 +30,7 @@ void ClearImageBurner() { return; } - chromeos::DBusThreadManager::Get()-> - GetImageBurnerClient()-> - ResetEventHandlers(); + ImageBurnerClient::Get()->ResetEventHandlers(); } } // namespace @@ -65,19 +62,19 @@ void Operation::UnmountVolumes(base::OnceClosure continuation) { } void Operation::UnmountVolumesCallback(base::OnceClosure continuation, - chromeos::MountError error_code) { + ash::MountError error_code) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (error_code != chromeos::MOUNT_ERROR_NONE) { + if (error_code != ash::MountError::kNone) { LOG(ERROR) << "Volume unmounting failed with error code " << error_code; PostTask( base::BindOnce(&Operation::Error, this, error::kUnmountVolumesError)); return; } - const DiskMountManager::DiskMap& disks = + const DiskMountManager::Disks& disks = DiskMountManager::GetInstance()->disks(); - DiskMountManager::DiskMap::const_iterator iter = + DiskMountManager::Disks::const_iterator iter = disks.find(device_path_.value()); if (iter == disks.end()) { @@ -87,7 +84,7 @@ void Operation::UnmountVolumesCallback(base::OnceClosure continuation, return; } - StartWriteOnUIThread(iter->second->file_path(), std::move(continuation)); + StartWriteOnUIThread(iter->get()->file_path(), std::move(continuation)); } void Operation::StartWriteOnUIThread(const std::string& target_path, @@ -95,8 +92,7 @@ void Operation::StartWriteOnUIThread(const std::string& target_path, DCHECK_CURRENTLY_ON(BrowserThread::UI); // TODO(haven): Image Burner cannot handle multiple burns. crbug.com/373575 - ImageBurnerClient* burner = - chromeos::DBusThreadManager::Get()->GetImageBurnerClient(); + ImageBurnerClient* burner = ImageBurnerClient::Get(); burner->SetEventHandlers( base::BindOnce(&Operation::OnBurnFinished, this, std::move(continuation)), diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos.cc b/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos.cc index 87170877c19..04850f040ad 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos.cc @@ -24,25 +24,22 @@ using ::ash::disks::DiskMountManager; scoped_refptr<StorageDeviceList> RemovableStorageProvider::PopulateDeviceList() { DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); - const DiskMountManager::DiskMap& disks = disk_mount_manager->disks(); + const DiskMountManager::Disks& disks = disk_mount_manager->disks(); auto device_list = base::MakeRefCounted<StorageDeviceList>(); - for (DiskMountManager::DiskMap::const_iterator iter = disks.begin(); - iter != disks.end(); - ++iter) { - const Disk& disk = *iter->second; - if (disk.is_parent() && !disk.on_boot_device() && disk.has_media() && - (disk.device_type() == chromeos::DEVICE_TYPE_USB || - disk.device_type() == chromeos::DEVICE_TYPE_SD)) { + for (const auto& disk : disks) { + if (disk->is_parent() && !disk->on_boot_device() && disk->has_media() && + (disk->device_type() == ash::DeviceType::kUSB || + disk->device_type() == ash::DeviceType::kSD)) { api::image_writer_private::RemovableStorageDevice device; - device.storage_unit_id = disk.device_path(); - device.capacity = disk.total_size_in_bytes(); - device.removable = disk.on_removable_device(); - device.vendor = disk.vendor_name(); - device.model = disk.product_name(); + device.storage_unit_id = disk->device_path(); + device.capacity = disk->total_size_in_bytes(); + device.removable = disk->on_removable_device(); + device.vendor = disk->vendor_name(); + device.model = disk->product_name(); if (device.model.empty() && device.vendor.empty()) { - if (disk.device_type() == chromeos::DEVICE_TYPE_USB) { + if (disk->device_type() == ash::DeviceType::kUSB) { device.model = kUnknownUSBDiskModel; } else { device.model = kUnknownSDDiskModel; diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos_unittest.cc b/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos_unittest.cc index 83ad5b19b17..7ac0ef47404 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos_unittest.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/removable_storage_provider_chromeos_unittest.cc @@ -46,7 +46,7 @@ class RemovableStorageProviderChromeOsUnitTest : public testing::Test { } void CreateDisk(const std::string& device_path, - chromeos::DeviceType device_type, + ash::DeviceType device_type, bool is_parent, bool has_media, bool on_boot_device) { @@ -62,13 +62,12 @@ class RemovableStorageProviderChromeOsUnitTest : public testing::Test { void CreateDisk(const std::string& device_path, const std::string& vendor_name, const std::string& product_name, - chromeos::DeviceType device_type, + ash::DeviceType device_type, bool is_parent, bool has_media, bool on_boot_device) { - ash::disks::DiskMountManager::MountPointInfo mount_info( - device_path, kMountPath, chromeos::MOUNT_TYPE_DEVICE, - ash::disks::MOUNT_CONDITION_NONE); + ash::disks::DiskMountManager::MountPoint mount_info{ + device_path, kMountPath, ash::MountType::kDevice}; disk_mount_manager_mock_->CreateDiskEntryForMountDevice( mount_info, kDeviceId, kDeviceName, vendor_name, product_name, device_type, kDeviceSize, is_parent, has_media, on_boot_device, @@ -112,11 +111,11 @@ class RemovableStorageProviderChromeOsUnitTest : public testing::Test { // that are parents, have media and are not boot devices. Other flags are // uninteresting or should not occur for these device types. TEST_F(RemovableStorageProviderChromeOsUnitTest, GetAllDevices) { - CreateDisk(kDevicePathUSB, chromeos::DEVICE_TYPE_USB, true, true, false); - CreateDisk(kDevicePathSD, chromeos::DEVICE_TYPE_SD, true, true, false); - CreateDisk("/dev/NotParent", chromeos::DEVICE_TYPE_USB, false, true, false); - CreateDisk("/dev/NoMedia", chromeos::DEVICE_TYPE_USB, true, false, false); - CreateDisk("/dev/OnBootDevice", chromeos::DEVICE_TYPE_USB, true, true, true); + CreateDisk(kDevicePathUSB, ash::DeviceType::kUSB, true, true, false); + CreateDisk(kDevicePathSD, ash::DeviceType::kSD, true, true, false); + CreateDisk("/dev/NotParent", ash::DeviceType::kUSB, false, true, false); + CreateDisk("/dev/NoMedia", ash::DeviceType::kUSB, true, false, false); + CreateDisk("/dev/OnBootDevice", ash::DeviceType::kUSB, true, true, true); RemovableStorageProvider::GetAllDevices( base::BindOnce(&RemovableStorageProviderChromeOsUnitTest::DevicesCallback, @@ -134,10 +133,8 @@ TEST_F(RemovableStorageProviderChromeOsUnitTest, GetAllDevices) { // Tests that a USB drive with an empty vendor and product gets a generic name. TEST_F(RemovableStorageProviderChromeOsUnitTest, EmptyProductAndModel) { - CreateDisk( - kDevicePathUSB, "", "", chromeos::DEVICE_TYPE_USB, true, true, false); - CreateDisk( - kDevicePathSD, "", "", chromeos::DEVICE_TYPE_SD, true, true, false); + CreateDisk(kDevicePathUSB, "", "", ash::DeviceType::kUSB, true, true, false); + CreateDisk(kDevicePathSD, "", "", ash::DeviceType::kSD, true, true, false); RemovableStorageProvider::GetAllDevices( base::BindOnce(&RemovableStorageProviderChromeOsUnitTest::DevicesCallback, diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.cc b/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.cc index ab8c0ec7152..6e42056ac58 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.cc +++ b/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.cc @@ -21,18 +21,16 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) #include "ash/components/disks/disk.h" #include "chromeos/ash/components/dbus/concierge/concierge_client.h" -#include "chromeos/dbus/dbus_thread_manager.h" // nogncheck -#include "chromeos/dbus/image_burner/fake_image_burner_client.h" +#include "chromeos/ash/components/dbus/dbus_thread_manager.h" // nogncheck +#include "chromeos/ash/components/dbus/image_burner/fake_image_burner_client.h" +#include "chromeos/ash/components/dbus/image_burner/image_burner_client.h" #endif namespace extensions { namespace image_writer { #if BUILDFLAG(IS_CHROMEOS_ASH) -namespace { - -class ImageWriterFakeImageBurnerClient - : public chromeos::FakeImageBurnerClient { +class ImageWriterFakeImageBurnerClient : public ash::FakeImageBurnerClient { public: ImageWriterFakeImageBurnerClient() = default; ~ImageWriterFakeImageBurnerClient() override = default; @@ -65,8 +63,6 @@ class ImageWriterFakeImageBurnerClient BurnFinishedHandler burn_finished_handler_; BurnProgressUpdateHandler burn_progress_update_handler_; }; - -} // namespace #endif MockOperationManager::MockOperationManager(content::BrowserContext* context) @@ -81,8 +77,7 @@ void FakeDiskMountManager::UnmountDeviceRecursively( const std::string& device_path, UnmountDeviceRecursivelyCallbackType callback) { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), chromeos::MOUNT_ERROR_NONE)); + FROM_HERE, base::BindOnce(std::move(callback), ash::MountError::kNone)); } #endif @@ -227,10 +222,6 @@ void ImageWriterTestUtils::RunOnUtilityClientCreation( #endif void ImageWriterTestUtils::SetUp() { - SetUp(false); -} - -void ImageWriterTestUtils::SetUp(bool is_browser_test) { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE( base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &test_image_path_)); @@ -241,29 +232,23 @@ void ImageWriterTestUtils::SetUp(bool is_browser_test) { ASSERT_TRUE(FillFile(test_device_path_, kDevicePattern, kTestFileSize)); #if BUILDFLAG(IS_CHROMEOS_ASH) - if (!chromeos::DBusThreadManager::IsInitialized()) { - if (!is_browser_test) { - // For browser tests, chromeos::InitializeDBus() automatically does the - // same. - chromeos::DBusThreadManager::Initialize(); - ash::ConciergeClient::InitializeFake( - /*fake_cicerone_client=*/nullptr); - } - chromeos::DBusThreadManager::GetSetterForTesting()->SetImageBurnerClient( - std::make_unique<ImageWriterFakeImageBurnerClient>()); + // Browser tests might have already initialized ConciergeClient. + if (!ash::ConciergeClient::Get()) { + ash::ConciergeClient::InitializeFake( + /*fake_cicerone_client=*/nullptr); + concierge_client_initialized_ = true; } + image_burner_client_ = std::make_unique<ImageWriterFakeImageBurnerClient>(); + ash::ImageBurnerClient::SetInstanceForTest(image_burner_client_.get()); FakeDiskMountManager* disk_manager = new FakeDiskMountManager(); ash::disks::DiskMountManager::InitializeForTesting(disk_manager); // Adds a disk entry for test_device_path_ with the same device and file path. disk_manager->CreateDiskEntryForMountDevice( - ash::disks::DiskMountManager::MountPointInfo( - test_device_path_.value(), "/dummy/mount", - chromeos::MOUNT_TYPE_DEVICE, ash::disks::MOUNT_CONDITION_NONE), - "device_id", "device_label", "Vendor", "Product", - chromeos::DEVICE_TYPE_USB, kTestFileSize, true, true, true, false, - kTestFileSystemType); + {test_device_path_.value(), "/dummy/mount", ash::MountType::kDevice}, + "device_id", "device_label", "Vendor", "Product", ash::DeviceType::kUSB, + kTestFileSize, true, true, true, false, kTestFileSystemType); disk_manager->SetupDefaultReplies(); #else ImageWriterUtilityClient::SetFactoryForTesting(&utility_client_factory_); @@ -272,11 +257,12 @@ void ImageWriterTestUtils::SetUp(bool is_browser_test) { void ImageWriterTestUtils::TearDown() { #if BUILDFLAG(IS_CHROMEOS_ASH) - if (chromeos::DBusThreadManager::IsInitialized()) { - // When in browser_tests, this path is not taken. These clients have already - // been shut down by chromeos::ShutdownDBus(). + ash::ImageBurnerClient::SetInstanceForTest(nullptr); + image_burner_client_.reset(); + + if (concierge_client_initialized_) { ash::ConciergeClient::Shutdown(); - chromeos::DBusThreadManager::Shutdown(); + concierge_client_initialized_ = false; } ash::disks::DiskMountManager::Shutdown(); #else diff --git a/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.h b/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.h index 68970d4c5d6..1b5f57d1993 100644 --- a/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.h +++ b/chromium/chrome/browser/extensions/api/image_writer_private/test_utils.h @@ -30,6 +30,10 @@ namespace extensions { namespace image_writer { +#if BUILDFLAG(IS_CHROMEOS_ASH) +class ImageWriterFakeImageBurnerClient; +#endif + const char kDummyExtensionId[] = "DummyExtension"; // Default file size to use in tests. Currently 32kB. @@ -74,7 +78,7 @@ class FakeDiskMountManager : public ash::disks::MockDiskMountManager { UnmountDeviceRecursivelyCallbackType callback) override; private: - DiskMap disks_; + Disks disks_; }; #endif @@ -166,14 +170,7 @@ class ImageWriterTestUtils { const int length); // Set up the test utils, creating temporary folders and such. - // Note that browser tests should use the alternate form and pass "true" as an - // argument. virtual void SetUp(); - // Set up the test utils, creating temporary folders and such. If - // |is_browser_test| is true then it will use alternate initialization - // appropriate for a browser test. This should be run in - // |SetUpInProcessBrowserTestFixture|. - virtual void SetUp(bool is_browser_test); virtual void TearDown(); @@ -186,7 +183,10 @@ class ImageWriterTestUtils { base::FilePath test_image_path_; base::FilePath test_device_path_; -#if !BUILDFLAG(IS_CHROMEOS_ASH) +#if BUILDFLAG(IS_CHROMEOS_ASH) + std::unique_ptr<ImageWriterFakeImageBurnerClient> image_burner_client_; + bool concierge_client_initialized_ = false; +#else scoped_refptr<FakeImageWriterClient> client_; ImageWriterUtilityClient::ImageWriterUtilityClientFactory utility_client_factory_; 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 a2cebdc3fb0..0dfd5fb967a 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 @@ -164,8 +164,8 @@ ExtensionFunction::ResponseAction InputImeSetCompositionFunction::Run() { if (!engine->SetComposition(params.context_id, params.text.c_str(), selection_start, selection_end, params.cursor, segments, &error)) { - std::vector<base::Value> results; - results.emplace_back(false); + base::Value::List results; + results.Append(false); return RespondNow(ErrorWithArguments( std::move(results), InformativeError(error, static_function_name()))); } @@ -184,8 +184,8 @@ ExtensionFunction::ResponseAction InputImeCommitTextFunction::Run() { const CommitText::Params::Parameters& params = parent_params->parameters; if (!engine->CommitText(params.context_id, base::UTF8ToUTF16(params.text), &error)) { - std::vector<base::Value> results; - results.emplace_back(false); + base::Value::List results; + results.Append(false); return RespondNow(ErrorWithArguments( std::move(results), InformativeError(error, static_function_name()))); } diff --git a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc index df016838fa6..941d67ca173 100644 --- a/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc @@ -291,8 +291,8 @@ class ImeObserverChromeOS return; } // Note: this is a private API event. - std::vector<base::Value> args; - args.push_back(base::Value(is_projected)); + base::Value::List args; + args.Append(is_projected); DispatchEventToExtension( extensions::events::INPUT_METHOD_PRIVATE_ON_SCREEN_PROJECTION_CHANGED, @@ -412,7 +412,7 @@ class ImeObserverChromeOS } // Note: this is a private API event. - std::vector<base::Value> bounds_list; + base::Value::List bounds_list; bounds_list.reserve(bounds.size()); for (const auto& bound : bounds) { base::Value bounds_value(base::Value::Type::DICTIONARY); @@ -420,21 +420,40 @@ class ImeObserverChromeOS bounds_value.SetIntKey("y", bound.y()); bounds_value.SetIntKey("w", bound.width()); bounds_value.SetIntKey("h", bound.height()); - bounds_list.push_back(std::move(bounds_value)); + bounds_list.Append(std::move(bounds_value)); } - std::vector<base::Value> args; + base::Value::List args; // The old extension code uses the first parameter to get the bounds of the // first composition character, so for backward compatibility, add it here. - args.push_back(bounds_list[0].Clone()); - args.push_back(base::Value(std::move(bounds_list))); + args.Append(bounds_list[0].Clone()); + args.Append(std::move(bounds_list)); DispatchEventToExtension( extensions::events::INPUT_METHOD_PRIVATE_ON_COMPOSITION_BOUNDS_CHANGED, OnCompositionBoundsChanged::kEventName, std::move(args)); } + void OnCaretBoundsChanged(const gfx::Rect& caret_bounds) override { + if (extension_id_.empty() || + !HasListener(input_method_private::OnCaretBoundsChanged::kEventName)) { + return; + } + + // Note: this is a private API event; + input_method_private::OnCaretBoundsChanged::CaretBounds caret_bounds_arg; + caret_bounds_arg.x = caret_bounds.x(); + caret_bounds_arg.y = caret_bounds.y(); + caret_bounds_arg.w = caret_bounds.width(); + caret_bounds_arg.h = caret_bounds.height(); + + DispatchEventToExtension( + extensions::events::INPUT_METHOD_PRIVATE_ON_CARET_BOUNDS_CHANGED, + input_method_private::OnCaretBoundsChanged::kEventName, + input_method_private::OnCaretBoundsChanged::Create(caret_bounds_arg)); + } + void OnFocus( const std::string& engine_id, int context_id, @@ -594,7 +613,7 @@ class ImeObserverChromeOS void DispatchEventToExtension( extensions::events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> args) { + base::Value::List args) { if (event_name == input_ime::OnActivate::kEventName) { // Send onActivate event regardless of it's listened by the IME. auto event = std::make_unique<extensions::Event>( @@ -984,8 +1003,8 @@ ExtensionFunction::ResponseAction InputImeClearCompositionFunction::Run() { parent_params->parameters; bool success = engine->ClearComposition(params.context_id, &error); - std::vector<base::Value> results; - results.emplace_back(success); + base::Value::List results; + results.Append(success); return RespondNow(success ? ArgumentList(std::move(results)) : ErrorWithArguments( @@ -1077,8 +1096,8 @@ InputImeSetCandidateWindowPropertiesFunction::Run() { if (properties.visible && !engine->SetCandidateWindowVisible(*properties.visible, &error)) { - std::vector<base::Value> results; - results.emplace_back(false); + base::Value::List results; + results.Append(false); return RespondNow(ErrorWithArguments( std::move(results), InformativeError(error, static_function_name()))); } @@ -1168,8 +1187,8 @@ ExtensionFunction::ResponseAction InputImeSetCandidatesFunction::Run() { bool success = engine->SetCandidates(params.context_id, candidates_out, &error); - std::vector<base::Value> results; - results.emplace_back(success); + base::Value::List results; + results.Append(success); return RespondNow(success ? ArgumentList(std::move(results)) : ErrorWithArguments( @@ -1192,8 +1211,8 @@ ExtensionFunction::ResponseAction InputImeSetCursorPositionFunction::Run() { bool success = engine->SetCursorPosition(params.context_id, params.candidate_id, &error); - std::vector<base::Value> results; - results.emplace_back(success); + base::Value::List results; + results.Append(success); return RespondNow(success ? ArgumentList(std::move(results)) : ErrorWithArguments( diff --git a/chromium/chrome/browser/extensions/api/language_settings_private/OWNERS b/chromium/chrome/browser/extensions/api/language_settings_private/OWNERS index 2dbba8f0aab..443a569fed9 100644 --- a/chromium/chrome/browser/extensions/api/language_settings_private/OWNERS +++ b/chromium/chrome/browser/extensions/api/language_settings_private/OWNERS @@ -1,3 +1 @@ -michaelpg@chromium.org - file://chrome/browser/resources/settings/OWNERS 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 cac2da0309a..00869095b0e 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 @@ -81,7 +81,7 @@ base::flat_set<std::string> GetIMEsFromPref(PrefService* prefs, // Returns the set of allowed UI locales. base::flat_set<std::string> GetAllowedLanguages(PrefService* prefs) { const auto& allowed_languages_values = - prefs->GetList(prefs::kAllowedLanguages)->GetListDeprecated(); + prefs->GetValueList(prefs::kAllowedLanguages); return base::MakeFlatSet<std::string>( allowed_languages_values, {}, [](const auto& locale_value) { return locale_value.GetString(); }); @@ -681,7 +681,6 @@ LanguageSettingsPrivateSetTranslateTargetLanguageFunction::Run() { CreateTranslatePrefsForBrowserContext(browser_context()); std::string chrome_language = language_code; - translate_prefs->AddToLanguageList(language_code, false); if (language_code == translate_prefs->GetRecentTargetLanguage()) { return RespondNow(NoArguments()); @@ -828,24 +827,22 @@ LanguageSettingsPrivateAddInputMethodFunction::Run() { std::string input_methods = base::JoinString(input_method_list, ","); prefs->SetString(pref_name, input_methods); - // In LSV2 Update 2, we want to automatically enable "Show input options in - // shelf" when the user has multiple input methods. + // We want to automatically enable "Show input options in shelf" when the user + // has multiple input methods. // We don't want to repeatedly enable it every time the user adds an input // method, as a user may want to intentionally turn it off - so we only enable // it once the user reaches two input methods. - if (base::FeatureList::IsEnabled(ash::features::kLanguageSettingsUpdate2)) { - // As pref_name and input_method_set only refer to the preference related to - // the list of IMEs for which this newly-added IME is in, we need the other - // IME list to calculate the total number of IMEs. - const char* other_ime_list_pref_name = is_component_extension_ime - ? prefs::kLanguageEnabledImes - : prefs::kLanguagePreloadEngines; - base::flat_set<std::string> other_input_method_set( - GetIMEsFromPref(prefs, other_ime_list_pref_name)); - if (input_method_set.size() + other_input_method_set.size() == - kNumImesToAutoEnableImeMenu) { - prefs->SetBoolean(prefs::kLanguageImeMenuActivated, true); - } + // As pref_name and input_method_set only refer to the preference related to + // the list of IMEs for which this newly-added IME is in, we need the other + // IME list to calculate the total number of IMEs. + const char* other_ime_list_pref_name = is_component_extension_ime + ? prefs::kLanguageEnabledImes + : prefs::kLanguagePreloadEngines; + base::flat_set<std::string> other_input_method_set( + GetIMEsFromPref(prefs, other_ime_list_pref_name)); + if (input_method_set.size() + other_input_method_set.size() == + kNumImesToAutoEnableImeMenu) { + prefs->SetBoolean(prefs::kLanguageImeMenuActivated, true); } #endif return RespondNow(NoArguments()); diff --git a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api_unittest.cc index 8554cf307fa..328574de32f 100644 --- a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api_unittest.cc @@ -9,7 +9,6 @@ #include "base/containers/contains.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_refptr.h" -#include "base/strings/utf_string_conversions.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" @@ -115,9 +114,6 @@ class LanguageSettingsPrivateApiTest : public ExtensionServiceTestBase { // Force Windows hybrid spellcheck to be enabled. feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker); #endif // BUILDFLAG(IS_WIN) -#if BUILDFLAG(IS_CHROMEOS_ASH) - feature_list_.InitAndEnableFeature(ash::features::kLanguageSettingsUpdate2); -#endif } #if BUILDFLAG(IS_WIN) @@ -268,11 +264,6 @@ TEST_F(LanguageSettingsPrivateApiTest, SetTranslateTargetLanguageTest) { api_test_utils::RunFunctionAndReturnSingleResult(function.get(), "[\"af\"]", profile()); ASSERT_EQ(translate_prefs_->GetRecentTargetLanguage(), "af"); - - std::vector<std::string> content_languages_after; - translate_prefs_->GetLanguageList(&content_languages_after); - ASSERT_EQ(std::vector<std::string>({"en-US", "en", "af"}), - content_languages_after); } TEST_F(LanguageSettingsPrivateApiTest, GetNeverTranslateLanguagesListTest) { @@ -306,7 +297,26 @@ TEST_F(LanguageSettingsPrivateApiTest, GetNeverTranslateLanguagesListTest) { } } -TEST_F(LanguageSettingsPrivateApiTest, GetLanguageListTest) { +class LanguageSettingsPrivateApiGetLanguageListTest + : public LanguageSettingsPrivateApiTest { + public: + LanguageSettingsPrivateApiGetLanguageListTest() = default; + ~LanguageSettingsPrivateApiGetLanguageListTest() override = default; + + protected: + void InitFeatures() override { +#if BUILDFLAG(IS_WIN) + // Force Windows hybrid spellcheck to be enabled, and disable the delayed + // init feature since that case is tested in + // LanguageSettingsPrivateApiTestDelayInit below. + feature_list_.InitWithFeatures( + /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker}, + /*disabled_features=*/{spellcheck::kWinDelaySpellcheckServiceInit}); +#endif // BUILDFLAG(IS_WIN) + } +}; + +TEST_F(LanguageSettingsPrivateApiGetLanguageListTest, GetLanguageList) { translate::TranslateDownloadManager::GetInstance()->ResetForTesting(); RunGetLanguageListTest(); } @@ -565,9 +575,8 @@ TEST_F(LanguageSettingsPrivateApiTest, GetInputMethodListsTest) { input_method.FindListKey("languageCodes"); ASSERT_NE(nullptr, ime_language_codes_ptr); for (auto& language_code : ime_language_codes_ptr->GetListDeprecated()) { - std::string language_display_name = - base::UTF16ToUTF8(l10n_util::GetDisplayNameForLocale( - language_code.GetString(), "en", true)); + std::u16string language_display_name = l10n_util::GetDisplayNameForLocale( + language_code.GetString(), "en", true); if (!language_display_name.empty()) EXPECT_TRUE(base::Contains(ime_tags_ptr->GetListDeprecated(), base::Value(language_display_name))); diff --git a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.cc b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.cc index dd00dbc5a47..1ca82052423 100644 --- a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.cc +++ b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.cc @@ -6,7 +6,6 @@ #include "chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/extension_system_provider.h" #include "extensions/browser/extensions_browser_client.h" @@ -28,9 +27,9 @@ LanguageSettingsPrivateDelegateFactory::GetInstance() { } LanguageSettingsPrivateDelegateFactory::LanguageSettingsPrivateDelegateFactory() - : BrowserContextKeyedServiceFactory( + : ProfileKeyedServiceFactory( "LanguageSettingsPrivateDelegate", - BrowserContextDependencyManager::GetInstance()) { + ProfileSelections::BuildRedirectedInIncognito()) { DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); DependsOn(SpellcheckServiceFactory::GetInstance()); } @@ -44,12 +43,6 @@ KeyedService* LanguageSettingsPrivateDelegateFactory::BuildServiceInstanceFor( return LanguageSettingsPrivateDelegate::Create(context); } -content::BrowserContext* -LanguageSettingsPrivateDelegateFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - return ExtensionsBrowserClient::Get()->GetOriginalContext(context); -} - bool LanguageSettingsPrivateDelegateFactory:: ServiceIsCreatedWithBrowserContext() const { return true; diff --git a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.h b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.h index 4375dbac6aa..5ca0aae9ab1 100644 --- a/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.h +++ b/chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_LANGUAGE_SETTINGS_PRIVATE_LANGUAGE_SETTINGS_PRIVATE_DELEGATE_FACTORY_H_ #include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace extensions { @@ -17,7 +17,7 @@ class LanguageSettingsPrivateDelegate; // (since the extension event router and language preferences are per browsing // context). class LanguageSettingsPrivateDelegateFactory - : public BrowserContextKeyedServiceFactory { + : public ProfileKeyedServiceFactory { public: LanguageSettingsPrivateDelegateFactory( const LanguageSettingsPrivateDelegateFactory&) = delete; @@ -34,8 +34,6 @@ class LanguageSettingsPrivateDelegateFactory protected: // BrowserContextKeyedServiceFactory overrides: - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; private: 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 238ec361dc6..fffbced3a2e 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 @@ -47,6 +47,7 @@ #include "chrome/common/extensions/extension_metrics.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" #include "components/favicon/core/favicon_service.h" +#include "components/services/app_service/public/cpp/app_launch_util.h" #include "components/webapps/browser/install_result_code.h" #include "components/webapps/browser/installable/installable_manager.h" #include "components/webapps/browser/installable/installable_metrics.h" @@ -71,7 +72,6 @@ #include "chrome/browser/ash/arc/arc_util.h" #include "chrome/browser/ash/login/demo_mode/demo_session.h" #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" -#include "chrome/browser/web_applications/web_app_utils.h" #endif // BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(ENABLE_SUPERVISED_USERS) @@ -336,6 +336,7 @@ class ChromeAppForLinkDelegate : public extensions::AppForLinkDelegate { // These modes are not supported by the extension app backend. case web_app::DisplayMode::kWindowControlsOverlay: case web_app::DisplayMode::kTabbed: + case web_app::DisplayMode::kBorderless: case web_app::DisplayMode::kUndefined: info.launch_type = extensions::api::management::LAUNCH_TYPE_NONE; break; @@ -357,9 +358,9 @@ void LaunchWebApp(const web_app::AppId& app_id, Profile* profile) { DCHECK(provider); absl::optional<web_app::UserDisplayMode> display_mode = provider->registrar().GetAppUserDisplayMode(app_id); - auto launch_container = apps::mojom::LaunchContainer::kLaunchContainerWindow; + auto launch_container = apps::LaunchContainer::kLaunchContainerWindow; if (display_mode == web_app::UserDisplayMode::kBrowser) - launch_container = apps::mojom::LaunchContainer::kLaunchContainerTab; + launch_container = apps::LaunchContainer::kLaunchContainerTab; if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) { // If the profile doesn't have an App Service Proxy available, that means @@ -372,7 +373,7 @@ void LaunchWebApp(const web_app::AppId& app_id, Profile* profile) { apps::AppServiceProxyFactory::GetForProfile(profile)->LaunchAppWithParams( apps::AppLaunchParams(app_id, launch_container, WindowOpenDisposition::NEW_FOREGROUND_TAB, - apps::mojom::LaunchSource::kFromManagementApi)); + apps::LaunchSource::kFromManagementApi)); } void OnWebAppInstallCompleted(InstallOrLaunchWebAppCallback callback, @@ -429,7 +430,7 @@ void ChromeManagementAPIDelegate::LaunchAppFunctionDelegate( // returned. // TODO(crbug.com/1003602): Make AppLaunchParams launch container Optional or // add a "default" launch container enum value. - apps::mojom::LaunchContainer launch_container = + apps::LaunchContainer launch_container = GetLaunchContainer(extensions::ExtensionPrefs::Get(context), extension); Profile* profile = Profile::FromBrowserContext(context); if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) { @@ -442,7 +443,7 @@ void ChromeManagementAPIDelegate::LaunchAppFunctionDelegate( apps::AppServiceProxyFactory::GetForProfile(profile)->LaunchAppWithParams( apps::AppLaunchParams(extension->id(), launch_container, WindowOpenDisposition::NEW_FOREGROUND_TAB, - apps::mojom::LaunchSource::kFromManagementApi)); + apps::LaunchSource::kFromManagementApi)); #if BUILDFLAG(IS_CHROMEOS_ASH) ash::DemoSession::RecordAppLaunchSourceIfInDemoMode( 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 c4fd49636b4..80e3d0b81e1 100644 --- a/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/management/management_api_browsertest.cc @@ -206,8 +206,15 @@ IN_PROC_BROWSER_TEST_P(ExtensionManagementApiTestWithBackgroundType, ASSERT_TRUE(listener2.WaitUntilSatisfied()); } +#if BUILDFLAG(IS_MAC) +// Flaky on Mac: https://crbug.com/1132581 +#define MAYBE_SelfUninstallNoPermissions DISABLED_SelfUninstallNoPermissions +#else +#define MAYBE_SelfUninstallNoPermissions SelfUninstallNoPermissions +#endif + IN_PROC_BROWSER_TEST_P(ExtensionManagementApiTestWithBackgroundType, - SelfUninstallNoPermissions) { + MAYBE_SelfUninstallNoPermissions) { // Wait for the helper script to finish before loading the primary // extension. This ensures that the onUninstall event listener is // added before we proceed to the uninstall step. 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 884e760e2c2..f7078e29359 100644 --- a/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/management/management_api_unittest.cc @@ -47,8 +47,6 @@ #include "chrome/browser/supervised_user/supervised_user_service_factory.h" #include "chrome/browser/supervised_user/supervised_user_test_util.h" #include "content/public/browser/gpu_data_manager.h" -#include "extensions/browser/api/management/management_api_constants.h" -#include "extensions/common/error_utils.h" #endif using extensions::mojom::ManifestLocation; @@ -389,7 +387,7 @@ TEST_F(ManagementApiUnitTest, ManagementUninstall) { // If we try uninstall the extension itself, the uninstall should succeed // (even though we auto-cancel any dialog), because the dialog is never // shown. - uninstall_args.EraseListIter(uninstall_args.GetListDeprecated().begin()); + uninstall_args.GetList().erase(uninstall_args.GetList().begin()); function = new ManagementUninstallSelfFunction(); function->set_extension(extension); EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id)); diff --git a/chromium/chrome/browser/extensions/api/mdns/OWNERS b/chromium/chrome/browser/extensions/api/mdns/OWNERS index 2acc4e2a670..76567a3f634 100644 --- a/chromium/chrome/browser/extensions/api/mdns/OWNERS +++ b/chromium/chrome/browser/extensions/api/mdns/OWNERS @@ -1,2 +1,4 @@ mfoltz@chromium.org -vitalybuka@chromium.org +takumif@chromium.org + + diff --git a/chromium/chrome/browser/extensions/api/mdns/mdns_api.cc b/chromium/chrome/browser/extensions/api/mdns/mdns_api.cc index 41723381457..9df689413d2 100644 --- a/chromium/chrome/browser/extensions/api/mdns/mdns_api.cc +++ b/chromium/chrome/browser/extensions/api/mdns/mdns_api.cc @@ -10,12 +10,14 @@ #include "base/lazy_instance.h" #include "base/strings/stringprintf.h" #include "chrome/browser/extensions/extension_service.h" +#include "components/version_info/channel.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_registry.h" +#include "extensions/common/features/feature_channel.h" #include "extensions/common/mojom/event_dispatcher.mojom.h" namespace extensions { @@ -198,8 +200,14 @@ bool MDnsAPI::IsMDnsAllowed(const std::string& extension_id, ExtensionRegistry::Get(browser_context_) ->enabled_extensions() .GetByID(extension_id); - return (extension && (extension->is_platform_app() || - IsServiceTypeAllowlisted(service_type))); + if (!extension) + return false; + + if (GetCurrentChannel() == version_info::Channel::DEV && + extension->is_extension()) { + return true; + } + return extension->is_platform_app() || IsServiceTypeAllowlisted(service_type); } void MDnsAPI::GetValidOnServiceListListeners( diff --git a/chromium/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc b/chromium/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc index ede617b1402..294015158c8 100644 --- a/chromium/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc @@ -25,6 +25,7 @@ #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/extension_messages.h" +#include "extensions/common/features/feature_channel.h" #include "extensions/common/manifest_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -122,18 +123,14 @@ class EventServiceListSizeMatcher virtual bool MatchAndExplain(const Event& e, testing::MatchResultListener* listener) const { - if (!e.event_args) { - *listener << "event.event_arg is null when it shouldn't be"; - return false; - } - if (e.event_args->GetListDeprecated().size() != 1) { + if (e.event_args.size() != 1) { *listener << "event.event_arg.GetSize() should be 1 but is " - << e.event_args->GetListDeprecated().size(); + << e.event_args.size(); return false; } const base::ListValue* services = nullptr; { - const base::Value& out = e.event_args->GetListDeprecated()[0]; + const base::Value& out = e.event_args[0]; services = static_cast<const base::ListValue*>(&out); } if (!services) { @@ -269,6 +266,10 @@ class MDnsAPIDiscoveryTest : public MDnsAPITest { raw_ptr<MockedMDnsAPI> mdns_api_; }; +class MDnsAPIExtensionTest + : public MDnsAPITest, + public testing::WithParamInterface<version_info::Channel> {}; + TEST_F(MDnsAPIDiscoveryTest, ServiceListenersAddedAndRemoved) { EventRouterFactory::GetInstance()->SetTestingFactory( browser_context(), base::BindRepeating(&MockEventRouterFactoryFunction)); @@ -344,7 +345,10 @@ TEST_F(MDnsAPIMaxServicesTest, OnServiceListDoesNotExceedLimit) { dns_sd_registry()->DispatchMDnsEvent("_testing._tcp.local", services); } -TEST_F(MDnsAPITest, ExtensionRespectsAllowlist) { +TEST_P(MDnsAPIExtensionTest, ExtensionRespectsAllowlist) { + const bool is_dev = GetParam() == version_info::Channel::DEV; + extensions::ScopedCurrentChannel channel_override(GetParam()); + scoped_refptr<extensions::Extension> extension = CreateExtension("Dinosaur networker", false, kExtId); ExtensionRegistry::Get(browser_context())->AddEnabled(extension); @@ -358,16 +362,17 @@ TEST_F(MDnsAPITest, ExtensionRespectsAllowlist) { filter.SetStringKey(kEventFilterServiceTypeKey, "_trex._tcp.local"); ASSERT_TRUE(dns_sd_registry()); - // Test that the extension is able to listen to a non-allowlisted service + // Test that the extension is not able to listen to a non-allowlisted + // service, unless we are on dev channel. EXPECT_CALL(*dns_sd_registry(), RegisterDnsSdListener("_trex._tcp.local")) - .Times(0); + .Times(is_dev ? 1 : 0); EventRouter::Get(browser_context()) ->AddFilteredEventListener(api::mdns::OnServiceList::kEventName, render_process_host(), param.Clone(), absl::nullopt, filter, false); EXPECT_CALL(*dns_sd_registry(), UnregisterDnsSdListener("_trex._tcp.local")) - .Times(0); + .Times(is_dev ? 1 : 0); EventRouter::Get(browser_context()) ->RemoveFilteredEventListener(api::mdns::OnServiceList::kEventName, render_process_host(), param.Clone(), @@ -395,6 +400,11 @@ TEST_F(MDnsAPITest, ExtensionRespectsAllowlist) { } } +INSTANTIATE_TEST_SUITE_P(Channels, + MDnsAPIExtensionTest, + testing::Values(version_info::Channel::DEV, + version_info::Channel::STABLE)); + TEST_F(MDnsAPITest, PlatformAppsNotSubjectToAllowlist) { scoped_refptr<extensions::Extension> extension = CreateExtension("Dinosaur networker", true, kExtId); 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 1eecb573415..b7df0906aa7 100644 --- a/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc +++ b/chromium/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc @@ -51,25 +51,23 @@ ChromeMessagingDelegate::IsNativeMessagingHostAllowed( // All native messaging hosts are allowed if there is no blocklist. if (!pref_service->IsManagedPreference(pref_names::kNativeMessagingBlocklist)) return allow_result; - const base::Value* blocklist = - pref_service->GetList(pref_names::kNativeMessagingBlocklist); - if (!blocklist) - return allow_result; + const base::Value::List& blocklist = + pref_service->GetValueList(pref_names::kNativeMessagingBlocklist); // Check if the name or the wildcard is in the blocklist. base::Value name_value(native_host_name); base::Value wildcard_value("*"); - if (!base::Contains(blocklist->GetListDeprecated(), name_value) && - !base::Contains(blocklist->GetListDeprecated(), wildcard_value)) { + if (!base::Contains(blocklist, name_value) && + !base::Contains(blocklist, wildcard_value)) { return allow_result; } // The native messaging host is blocklisted. Check the allowlist. if (pref_service->IsManagedPreference( pref_names::kNativeMessagingAllowlist)) { - const base::Value* allowlist = - pref_service->GetList(pref_names::kNativeMessagingAllowlist); - if (allowlist && base::Contains(allowlist->GetListDeprecated(), name_value)) + const base::Value::List& allowlist = + pref_service->GetValueList(pref_names::kNativeMessagingAllowlist); + if (base::Contains(allowlist, name_value)) return allow_result; } diff --git a/chromium/chrome/browser/extensions/api/messaging/messaging_apitest.cc b/chromium/chrome/browser/extensions/api/messaging/messaging_apitest.cc index 1edf30fbc9b..18ca5936d38 100644 --- a/chromium/chrome/browser/extensions/api/messaging/messaging_apitest.cc +++ b/chromium/chrome/browser/extensions/api/messaging/messaging_apitest.cc @@ -94,7 +94,7 @@ class MessageSender : public ExtensionHostRegistry::Observer { GURL event_url) { auto event = std::make_unique<Event>( events::TEST_ON_MESSAGE, "test.onMessage", - std::move(*event_args).TakeListDeprecated(), browser_context); + std::move(event_args->GetList()), browser_context); event->event_url = std::move(event_url); return event; } diff --git a/chromium/chrome/browser/extensions/api/messaging/native_message_echo_host.h b/chromium/chrome/browser/extensions/api/messaging/native_message_echo_host.h index cb2ca90042f..510b3f8614e 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_message_echo_host.h +++ b/chromium/chrome/browser/extensions/api/messaging/native_message_echo_host.h @@ -8,6 +8,7 @@ #include <memory> #include <string> +#include "base/memory/raw_ptr.h" #include "extensions/browser/api/messaging/native_message_host.h" namespace base { @@ -52,7 +53,7 @@ class NativeMessageEchoHost : public NativeMessageHost { int message_number_ = 0; // |client_| must outlive this test instance. - Client* client_ = nullptr; + raw_ptr<Client> client_ = nullptr; }; } // namespace extensions 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 fd8c618f857..10915088628 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 @@ -288,25 +288,36 @@ TEST_F(NativeMessagingTest, EchoConnect) { run_loop_->Run(); ASSERT_FALSE(last_message_.empty()); ASSERT_TRUE(last_message_parsed_); + ASSERT_TRUE(last_message_parsed_->is_dict()); std::string expected_url = std::string("chrome-extension://") + - ScopedTestNativeMessagingHost::kExtensionId + "/"; - EXPECT_EQ(1, last_message_parsed_->FindIntKey("id")); - std::string text; - EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text)); - EXPECT_EQ("Hello.", text); - std::string url; - EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url)); - EXPECT_EQ(expected_url, url); + ScopedTestNativeMessagingHost::kExtensionId + "/"; + + { + const base::Value::Dict& dict = last_message_parsed_->GetDict(); + EXPECT_EQ(1, last_message_parsed_->FindIntKey("id")); + const std::string* text = dict.FindStringByDottedPath("echo.text"); + ASSERT_TRUE(text); + EXPECT_EQ("Hello.", *text); + const std::string* url = dict.FindString("caller_url"); + EXPECT_TRUE(url); + EXPECT_EQ(expected_url, *url); + } native_message_host_->OnMessage("{\"foo\": \"bar\"}"); run_loop_ = std::make_unique<base::RunLoop>(); run_loop_->Run(); - EXPECT_EQ(2, last_message_parsed_->FindIntKey("id")); - EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text)); - EXPECT_EQ("bar", text); - EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url)); - EXPECT_EQ(expected_url, url); + + { + const base::Value::Dict& dict = last_message_parsed_->GetDict(); + EXPECT_EQ(2, last_message_parsed_->FindIntKey("id")); + const std::string* text = dict.FindStringByDottedPath("echo.foo"); + ASSERT_TRUE(text); + EXPECT_EQ("bar", *text); + const std::string* url = dict.FindString("caller_url"); + ASSERT_TRUE(url); + EXPECT_EQ(expected_url, *url); + } const base::Value* args = nullptr; ASSERT_TRUE(last_message_parsed_->Get("args", &args)); diff --git a/chromium/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chromium/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc index 6879c4bbe3d..3b99eb71ad2 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc +++ b/chromium/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc @@ -73,8 +73,8 @@ class NativeMessagingApiTest : public NativeMessagingApiTestBase, bool RunTest(const char* extension_name) { if (GetParam() == ContextType::kPersistentBackground) return RunExtensionTest(extension_name); - std::string lazy_exension_name = base::StrCat({extension_name, "/lazy"}); - return RunExtensionTest(lazy_exension_name.c_str()); + std::string lazy_extension_name = base::StrCat({extension_name, "/lazy"}); + return RunExtensionTest(lazy_extension_name.c_str()); } }; diff --git a/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.cc b/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.cc index 009851133bb..88480bd90a0 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.cc +++ b/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.cc @@ -9,7 +9,6 @@ #include "base/check.h" #include "base/json/json_file_value_serializer.h" #include "base/strings/string_util.h" -#include "base/values.h" #include "chrome/common/chrome_features.h" namespace extensions { @@ -54,15 +53,15 @@ std::unique_ptr<NativeMessagingHostManifest> NativeMessagingHostManifest::Load( return nullptr; } - base::DictionaryValue* dictionary; - if (!parsed->GetAsDictionary(&dictionary)) { + if (!parsed->is_dict()) { *error_message = "Invalid manifest file."; return nullptr; } + const base::Value::Dict& dict = parsed->GetDict(); std::unique_ptr<NativeMessagingHostManifest> result( new NativeMessagingHostManifest()); - if (!result->Parse(dictionary, error_message)) { + if (!result->Parse(dict, error_message)) { return nullptr; } @@ -72,45 +71,46 @@ std::unique_ptr<NativeMessagingHostManifest> NativeMessagingHostManifest::Load( NativeMessagingHostManifest::NativeMessagingHostManifest() { } -bool NativeMessagingHostManifest::Parse(base::DictionaryValue* dictionary, +bool NativeMessagingHostManifest::Parse(const base::Value::Dict& dict, std::string* error_message) { - if (!dictionary->GetString("name", &name_) || - !IsValidName(name_)) { + const std::string* name_str = dict.FindString("name"); + if (!name_str || !IsValidName(*name_str)) { *error_message = "Invalid value for name."; return false; } + name_ = *name_str; - if (!dictionary->GetString("description", &description_) || - description_.empty()) { + const std::string* desc_str = dict.FindString("description"); + if (!desc_str || desc_str->empty()) { *error_message = "Invalid value for description."; return false; } + description_ = *desc_str; - std::string type; + const std::string* type = dict.FindString("type"); // stdio is the only host type that's currently supported. - if (!dictionary->GetString("type", &type) || - type != "stdio") { + if (!type || *type != "stdio") { *error_message = "Invalid value for type."; return false; } interface_ = HOST_INTERFACE_STDIO; - std::string path; + const std::string* path = dict.FindString("path"); // JSON parsed checks that all strings are valid UTF8. - if (!dictionary->GetString("path", &path) || - (path_ = base::FilePath::FromUTF8Unsafe(path)).empty()) { + if (!path || (path_ = base::FilePath::FromUTF8Unsafe(*path)).empty()) { *error_message = "Invalid value for path."; return false; } - const base::ListValue* allowed_origins_list; - if (!dictionary->GetList("allowed_origins", &allowed_origins_list)) { + const base::Value::List* allowed_origins_list = + dict.FindList("allowed_origins"); + if (!allowed_origins_list) { *error_message = "Invalid value for allowed_origins. Expected a list of strings."; return false; } allowed_origins_.ClearPatterns(); - for (const auto& entry : allowed_origins_list->GetListDeprecated()) { + for (const auto& entry : *allowed_origins_list) { if (!entry.is_string()) { *error_message = "allowed_origins must be list of strings."; return false; @@ -136,7 +136,7 @@ bool NativeMessagingHostManifest::Parse(base::DictionaryValue* dictionary, if (base::FeatureList::IsEnabled(features::kOnConnectNative)) { if (const base::Value* supports_native_initiated_connections = - dictionary->FindKey("supports_native_initiated_connections")) { + dict.Find("supports_native_initiated_connections")) { if (!supports_native_initiated_connections->is_bool()) { *error_message = "supports_native_initiated_connections must be a boolean."; diff --git a/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h b/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h index 942db33460c..b2e90e82f5c 100644 --- a/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h +++ b/chromium/chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h @@ -9,12 +9,9 @@ #include <string> #include "base/files/file_path.h" +#include "base/values.h" #include "extensions/common/url_pattern_set.h" -namespace base { -class DictionaryValue; -} - namespace extensions { class NativeMessagingHostManifest { @@ -52,7 +49,7 @@ class NativeMessagingHostManifest { // Parses manifest |dictionary|. In case of an error sets |error_message| and // returns false. - bool Parse(base::DictionaryValue* dictionary, std::string* error_message); + bool Parse(const base::Value::Dict& dict, std::string* error_message); std::string name_; std::string description_; diff --git a/chromium/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chromium/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc index 44907c969aa..003fdeb61b0 100644 --- a/chromium/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc +++ b/chromium/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc @@ -5,8 +5,6 @@ #include <memory> #include <utility> -#include "ash/components/cryptohome/cryptohome_parameters.h" -#include "ash/constants/ash_switches.h" #include "base/bind.h" #include "base/callback.h" #include "base/callback_helpers.h" @@ -20,23 +18,6 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chromeos/ash/components/network/onc/network_onc_utils.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "chromeos/dbus/shill/shill_device_client.h" -#include "chromeos/dbus/shill/shill_ipconfig_client.h" -#include "chromeos/dbus/shill/shill_manager_client.h" -#include "chromeos/dbus/shill/shill_profile_client.h" -#include "chromeos/dbus/shill/shill_service_client.h" -#include "chromeos/dbus/userdataauth/cryptohome_misc_client.h" -#include "chromeos/dbus/userdataauth/userdataauth_client.h" -#include "chromeos/network/cellular_metrics_logger.h" -#include "chromeos/network/managed_network_configuration_handler.h" -#include "chromeos/network/network_certificate_handler.h" -#include "chromeos/network/network_handler.h" -#include "chromeos/network/network_handler_test_helper.h" -#include "chromeos/network/network_state.h" -#include "chromeos/network/network_state_handler.h" -#include "chromeos/network/network_type_pattern.h" #include "components/onc/onc_constants.h" #include "components/onc/onc_pref_names.h" #include "components/policy/core/browser/browser_policy_connector.h" @@ -55,16 +36,44 @@ #include "components/user_manager/user_names.h" #include "content/public/test/browser_test.h" #include "content/public/test/test_utils.h" -#include "dbus/object_path.h" #include "extensions/browser/api/networking_private/networking_private_chromeos.h" #include "extensions/browser/api/networking_private/networking_private_delegate_factory.h" #include "extensions/common/switches.h" #include "extensions/common/value_builder.h" #include "extensions/test/extension_test_message_listener.h" #include "testing/gmock/include/gmock/gmock.h" -#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/cros_system_api/dbus/service_constants.h" +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "ash/constants/ash_switches.h" +#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" +#include "chromeos/ash/components/dbus/dbus_thread_manager.h" +#include "chromeos/ash/components/dbus/shill/shill_device_client.h" +#include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h" +#include "chromeos/ash/components/dbus/shill/shill_manager_client.h" +#include "chromeos/ash/components/dbus/shill/shill_profile_client.h" +#include "chromeos/ash/components/dbus/shill/shill_service_client.h" +#include "chromeos/ash/components/dbus/userdataauth/cryptohome_misc_client.h" +#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h" +#include "chromeos/ash/components/network/cellular_metrics_logger.h" +#include "chromeos/ash/components/network/managed_network_configuration_handler.h" +#include "chromeos/ash/components/network/network_certificate_handler.h" +#include "chromeos/ash/components/network/network_handler.h" +#include "chromeos/ash/components/network/network_handler_test_helper.h" +#include "chromeos/ash/components/network/network_state.h" +#include "chromeos/ash/components/network/network_state_handler.h" +#include "chromeos/ash/components/network/network_type_pattern.h" +#include "chromeos/ash/components/network/onc/network_onc_utils.h" +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +#if BUILDFLAG(IS_CHROMEOS_LACROS) +#include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h" +#include "chromeos/crosapi/mojom/test_controller.mojom.h" +#include "chromeos/lacros/lacros_service.h" + +using crosapi::mojom::ShillClientTestInterfaceAsyncWaiter; +#endif + // This tests the Chrome OS implementation of the networkingPrivate API // (NetworkingPrivateChromeOS). Note: The test expectations for chromeos, and // win/mac (NetworkingPrivateServiceClient) are different to reflect the @@ -73,29 +82,224 @@ using testing::Return; using testing::_; -using chromeos::ShillDeviceClient; -using chromeos::ShillIPConfigClient; -using chromeos::ShillManagerClient; -using chromeos::ShillProfileClient; -using chromeos::ShillServiceClient; -using chromeos::UserDataAuthClient; +#if BUILDFLAG(IS_CHROMEOS_ASH) +using ash::ShillDeviceClient; +using ash::ShillIPConfigClient; +using ash::ShillManagerClient; +using ash::ShillProfileClient; +using ash::ShillServiceClient; +using ash::UserDataAuthClient; using extensions::NetworkingPrivateDelegate; using extensions::NetworkingPrivateDelegateFactory; using extensions::NetworkingPrivateChromeOS; +#endif namespace { -const char kUser1ProfilePath[] = "/profile/user1/shill"; -const char kEthernetDevicePath[] = "/device/stub_ethernet_device"; -const char kWifiDevicePath[] = "/device/stub_wifi_device1"; +const char kCellular1ServicePath[] = "stub_cellular1"; const char kCellularDevicePath[] = "/device/stub_cellular_device1"; +const char kEthernetDevicePath[] = "/device/stub_ethernet_device"; const char kIPConfigPath[] = "/ipconfig/ipconfig1"; - +const char kUser1ProfilePath[] = "/profile/user1/shill"; const char kWifi1ServicePath[] = "stub_wifi1"; const char kWifi2ServicePath[] = "stub_wifi2"; -const char kCellular1ServicePath[] = "stub_cellular1"; +const char kWifiDevicePath[] = "/device/stub_wifi_device1"; + +class NetworkingPrivateChromeOSApiTestBase + : public extensions::ExtensionApiTest { + public: + // From extensions::ExtensionApiTest + void SetUpCommandLine(base::CommandLine* command_line) override { + extensions::ExtensionApiTest::SetUpCommandLine(command_line); + // Allowlist the extension ID of the test extension. + command_line->AppendSwitchASCII( + extensions::switches::kAllowlistedExtensionID, + "epcifkihnkjgphfkloaaleeakhpmgdmn"); + } + + bool RunNetworkingSubtest(const std::string& test) { + const std::string arg = + base::StringPrintf("{\"test\": \"%s\"}", test.c_str()); + return RunExtensionTest( + "networking_private/chromeos", + {.custom_arg = arg.c_str(), .launch_as_platform_app = true}); + } + + void ConfigFakeNetwork() { + ClearDevices(); + ClearServices(); + + std::string userhash = GetSanitizedActiveUsername(); + + // Sends a notification about the added profile. + AddProfile(kUser1ProfilePath, userhash); + + // Add IPConfigs + base::DictionaryValue ipconfig; + ipconfig.SetKey(shill::kAddressProperty, base::Value("0.0.0.0")); + ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.1")); + ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(0)); + ipconfig.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv4)); + AddIPConfig(kIPConfigPath, ipconfig); + + // Add Devices + AddDevice(kEthernetDevicePath, shill::kTypeEthernet, + "stub_ethernet_device1"); + + AddDevice(kWifiDevicePath, shill::kTypeWifi, "stub_wifi_device1"); + base::ListValue wifi_ip_configs; + wifi_ip_configs.Append(kIPConfigPath); + SetDeviceProperty(kWifiDevicePath, shill::kIPConfigsProperty, + wifi_ip_configs); + SetDeviceProperty(kWifiDevicePath, shill::kAddressProperty, + base::Value("001122aabbcc")); + + // Add Services + AddService("stub_ethernet", "eth0", shill::kTypeEthernet, + shill::kStateOnline); + SetServiceProperty("stub_ethernet", shill::kProfileProperty, + base::Value(GetSharedProfilePath())); + AddServiceToProfile(GetSharedProfilePath(), "stub_ethernet"); + AddService(kWifi1ServicePath, "wifi1", shill::kTypeWifi, + shill::kStateOnline); + SetServiceProperty(kWifi1ServicePath, shill::kSecurityClassProperty, + base::Value(shill::kSecurityClassWep)); + SetServiceProperty(kWifi1ServicePath, shill::kWifiBSsid, + base::Value("00:01:02:03:04:05")); + SetServiceProperty(kWifi1ServicePath, shill::kSignalStrengthProperty, + base::Value(40)); + SetServiceProperty(kWifi1ServicePath, shill::kProfileProperty, + base::Value(kUser1ProfilePath)); + SetServiceProperty(kWifi1ServicePath, shill::kConnectableProperty, + base::Value(true)); + SetServiceProperty(kWifi1ServicePath, shill::kDeviceProperty, + base::Value(kWifiDevicePath)); + base::DictionaryValue static_ipconfig; + static_ipconfig.SetKey(shill::kAddressProperty, base::Value("1.2.3.4")); + static_ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.0")); + static_ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(1)); + SetServiceProperty(kWifi1ServicePath, shill::kStaticIPConfigProperty, + static_ipconfig); + base::ListValue frequencies1; + frequencies1.Append(2400); + SetServiceProperty(kWifi1ServicePath, shill::kWifiFrequencyListProperty, + frequencies1); + SetServiceProperty(kWifi1ServicePath, shill::kWifiFrequency, + base::Value(2400)); + AddServiceToProfile(kUser1ProfilePath, kWifi1ServicePath); + + AddService(kWifi2ServicePath, "wifi2_PSK", shill::kTypeWifi, + shill::kStateIdle); + SetServiceProperty(kWifi2ServicePath, shill::kSecurityClassProperty, + base::Value(shill::kSecurityClassPsk)); + SetServiceProperty(kWifi2ServicePath, shill::kSignalStrengthProperty, + base::Value(80)); + SetServiceProperty(kWifi2ServicePath, shill::kConnectableProperty, + base::Value(true)); + + base::ListValue frequencies2; + frequencies2.Append(2400); + frequencies2.Append(5000); + SetServiceProperty(kWifi2ServicePath, shill::kWifiFrequencyListProperty, + frequencies2); + SetServiceProperty(kWifi2ServicePath, shill::kWifiFrequency, + base::Value(5000)); + SetServiceProperty(kWifi2ServicePath, shill::kProfileProperty, + base::Value(kUser1ProfilePath)); + AddServiceToProfile(kUser1ProfilePath, kWifi2ServicePath); + + AddService("stub_vpn1", "vpn1", shill::kTypeVPN, shill::kStateOnline); + SetServiceProperty("stub_vpn1", shill::kProviderTypeProperty, + base::Value(shill::kProviderOpenVpn)); + AddServiceToProfile(kUser1ProfilePath, "stub_vpn1"); + + AddService("stub_vpn2", "vpn2", shill::kTypeVPN, shill::kStateOffline); + SetServiceProperty("stub_vpn2", shill::kProviderTypeProperty, + base::Value(shill::kProviderThirdPartyVpn)); + SetServiceProperty("stub_vpn2", shill::kProviderHostProperty, + base::Value("third_party_provider_extension_id")); + AddServiceToProfile(kUser1ProfilePath, "stub_vpn2"); + } + + virtual void SetupCellular() { + // Add a Cellular GSM Device. + AddDevice(kCellularDevicePath, shill::kTypeCellular, + "stub_cellular_device1"); + base::DictionaryValue home_provider; + home_provider.SetStringKey("name", "Cellular1_Provider"); + home_provider.SetStringKey("code", "000000"); + home_provider.SetStringKey("country", "us"); + SetDeviceProperty(kCellularDevicePath, shill::kHomeProviderProperty, + home_provider); + SetDeviceProperty(kCellularDevicePath, shill::kTechnologyFamilyProperty, + base::Value(shill::kNetworkTechnologyGsm)); + SetDeviceProperty(kCellularDevicePath, shill::kMeidProperty, + base::Value("test_meid")); + SetDeviceProperty(kCellularDevicePath, shill::kImeiProperty, + base::Value("test_imei")); + SetDeviceProperty(kCellularDevicePath, shill::kIccidProperty, + base::Value("test_iccid")); + SetDeviceProperty(kCellularDevicePath, shill::kEsnProperty, + base::Value("test_esn")); + SetDeviceProperty(kCellularDevicePath, shill::kMdnProperty, + base::Value("test_mdn")); + SetDeviceProperty(kCellularDevicePath, shill::kMinProperty, + base::Value("test_min")); + SetDeviceProperty(kCellularDevicePath, shill::kModelIdProperty, + base::Value("test_model_id")); + SetSimLocked(kCellularDevicePath, false); + + // Add the Cellular Service. + AddService(kCellular1ServicePath, "cellular1", shill::kTypeCellular, + shill::kStateIdle); + SetServiceProperty(kCellular1ServicePath, + shill::kCellularAllowRoamingProperty, + base::Value(false)); + SetServiceProperty(kCellular1ServicePath, shill::kAutoConnectProperty, + base::Value(true)); + SetServiceProperty(kCellular1ServicePath, shill::kIccidProperty, + base::Value("test_iccid")); + SetServiceProperty(kCellular1ServicePath, shill::kNetworkTechnologyProperty, + base::Value(shill::kNetworkTechnologyGsm)); + SetServiceProperty(kCellular1ServicePath, shill::kActivationStateProperty, + base::Value(shill::kActivationStateNotActivated)); + SetServiceProperty(kCellular1ServicePath, shill::kRoamingStateProperty, + base::Value(shill::kRoamingStateHome)); + + AddServiceToProfile(kUser1ProfilePath, kCellular1ServicePath); + } + + virtual std::string GetSanitizedActiveUsername() = 0; + + virtual void AddDevice(const std::string& device_path, + const std::string& type, + const std::string& name) = 0; + virtual void SetDeviceProperty(const std::string& device_path, + const std::string& name, + const base::Value& value) = 0; + virtual void SetSimLocked(const std::string& device_path, bool enabled) = 0; + virtual void ClearDevices() = 0; + virtual void AddService(const std::string& service_path, + const std::string& name, + const std::string& type, + const std::string& state) = 0; + virtual void ClearServices() = 0; + virtual void SetServiceProperty(const std::string& service_path, + const std::string& property, + const base::Value& value) = 0; + virtual void AddProfile(const std::string& profile_path, + const std::string& userhash) = 0; + + virtual void AddServiceToProfile(const std::string& profile_path, + const std::string& service_path) = 0; + virtual std::string GetSharedProfilePath() = 0; + virtual void AddIPConfig(const std::string& ip_config_path, + const base::Value& properties) = 0; +}; + +#if BUILDFLAG(IS_CHROMEOS_ASH) class UIDelegateStub : public NetworkingPrivateDelegate::UIDelegate { public: static int s_show_account_details_called_; @@ -110,23 +314,56 @@ class UIDelegateStub : public NetworkingPrivateDelegate::UIDelegate { // static int UIDelegateStub::s_show_account_details_called_ = 0; -class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest { +class NetworkingPrivateChromeOSApiTestAsh + : public NetworkingPrivateChromeOSApiTestBase { public: - NetworkingPrivateChromeOSApiTest() {} + NetworkingPrivateChromeOSApiTestAsh() = default; - NetworkingPrivateChromeOSApiTest(const NetworkingPrivateChromeOSApiTest&) = - delete; - NetworkingPrivateChromeOSApiTest& operator=( - const NetworkingPrivateChromeOSApiTest&) = delete; + NetworkingPrivateChromeOSApiTestAsh( + const NetworkingPrivateChromeOSApiTestAsh&) = delete; + NetworkingPrivateChromeOSApiTestAsh& operator=( + const NetworkingPrivateChromeOSApiTestAsh&) = delete; - bool RunNetworkingSubtest(const std::string& test) { - const std::string arg = - base::StringPrintf("{\"test\": \"%s\"}", test.c_str()); - return RunExtensionTest( - "networking_private/chromeos", - {.custom_arg = arg.c_str(), .launch_as_platform_app = true}); + static std::unique_ptr<KeyedService> CreateNetworkingPrivateDelegate( + content::BrowserContext* context) { + std::unique_ptr<NetworkingPrivateDelegate> result( + new NetworkingPrivateChromeOS(context)); + std::unique_ptr<NetworkingPrivateDelegate::UIDelegate> ui_delegate( + new UIDelegateStub); + result->set_ui_delegate(std::move(ui_delegate)); + return result; } + void SetupTether() { + ash::NetworkStateHandler* network_state_handler = + ash::NetworkHandler::Get()->network_state_handler(); + network_state_handler->SetTetherTechnologyState( + ash::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED); + network_state_handler->AddTetherNetworkState( + "tetherGuid1", "tetherName1", "tetherCarrier1", + 50 /* battery_percentage */, 75 /* signal_strength */, + true /* has_connected_to_host */); + network_state_handler->AddTetherNetworkState( + "tetherGuid2", "tetherName2", "tetherCarrier2", + 75 /* battery_percentage */, 100 /* signal_strength */, + false /* has_connected_to_host */); + } + + ShillServiceClient::TestInterface* service_test() { + return network_handler_test_helper_->service_test(); + } + ShillProfileClient::TestInterface* profile_test() { + return network_handler_test_helper_->profile_test(); + } + ShillDeviceClient::TestInterface* device_test() { + return network_handler_test_helper_->device_test(); + } + ShillManagerClient::TestInterface* manager_test() { + return network_handler_test_helper_->manager_test(); + } + + // extensions::ExtensionApiTest overrides: + void SetUpInProcessBrowserTestFixture() override { provider_.SetDefaultReturns( /*is_initialization_complete_return=*/true, @@ -136,12 +373,34 @@ class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest { extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture(); } + void SetUpOnMainThread() override { + extensions::ExtensionApiTest::SetUpOnMainThread(); + content::RunAllPendingInMessageLoop(); + + NetworkingPrivateDelegateFactory::GetInstance()->SetTestingFactory( + profile(), base::BindRepeating(&CreateNetworkingPrivateDelegate)); + + network_handler_test_helper_ = + std::make_unique<ash::NetworkHandlerTestHelper>(); + + ConfigFakeNetwork(); + + PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry()); + PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry()); + network_handler_test_helper_->RegisterPrefs(user_prefs_.registry(), + local_state_.registry()); + + network_handler_test_helper_->InitializePrefs(&user_prefs_, &local_state_); + + base::RunLoop().RunUntilIdle(); + } + + void TearDownOnMainThread() override { network_handler_test_helper_.reset(); } + + // NetworkingPrivateChromeOSApiTestBase overrides: + void SetUpCommandLine(base::CommandLine* command_line) override { - extensions::ExtensionApiTest::SetUpCommandLine(command_line); - // Allowlist the extension ID of the test extension. - command_line->AppendSwitchASCII( - extensions::switches::kAllowlistedExtensionID, - "epcifkihnkjgphfkloaaleeakhpmgdmn"); + NetworkingPrivateChromeOSApiTestBase::SetUpCommandLine(command_line); // TODO(pneubeck): Remove the following hack, once the NetworkingPrivateAPI // uses the ProfileHelper to obtain the userhash crbug/238623. @@ -154,7 +413,7 @@ class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest { sanitized_user); } - void InitializeSanitizedUsername() { + std::string GetSanitizedActiveUsername() override { user_manager::UserManager* user_manager = user_manager::UserManager::Get(); user_manager::User* user = user_manager->GetActiveUser(); CHECK(user); @@ -163,7 +422,7 @@ class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest { request.set_username( cryptohome::CreateAccountIdentifierFromAccountId(user->GetAccountId()) .account_id()); - chromeos::CryptohomeMiscClient::Get()->GetSanitizedUsername( + ash::CryptohomeMiscClient::Get()->GetSanitizedUsername( request, base::BindOnce( [](std::string* out, @@ -172,310 +431,304 @@ class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest { CHECK(result.has_value()); *out = result->sanitized_username(); }, - &userhash_)); - content::RunAllPendingInMessageLoop(); - CHECK(!userhash_.empty()); + &userhash)); + base::RunLoop().RunUntilIdle(); + CHECK(!userhash.empty()); + return userhash; } - void SetupCellular() { + void SetupCellular() override { UIDelegateStub::s_show_account_details_called_ = 0; + NetworkingPrivateChromeOSApiTestBase::SetupCellular(); + base::RunLoop().RunUntilIdle(); + } - // Add a Cellular GSM Device. - device_test()->AddDevice(kCellularDevicePath, shill::kTypeCellular, - "stub_cellular_device1"); - base::DictionaryValue home_provider; - home_provider.SetStringKey("name", "Cellular1_Provider"); - home_provider.SetStringKey("code", "000000"); - home_provider.SetStringKey("country", "us"); - SetDeviceProperty(kCellularDevicePath, shill::kHomeProviderProperty, - home_provider); - SetDeviceProperty(kCellularDevicePath, shill::kTechnologyFamilyProperty, - base::Value(shill::kNetworkTechnologyGsm)); - SetDeviceProperty(kCellularDevicePath, shill::kMeidProperty, - base::Value("test_meid")); - SetDeviceProperty(kCellularDevicePath, shill::kImeiProperty, - base::Value("test_imei")); - SetDeviceProperty(kCellularDevicePath, shill::kIccidProperty, - base::Value("test_iccid")); - SetDeviceProperty(kCellularDevicePath, shill::kEsnProperty, - base::Value("test_esn")); - SetDeviceProperty(kCellularDevicePath, shill::kMdnProperty, - base::Value("test_mdn")); - SetDeviceProperty(kCellularDevicePath, shill::kMinProperty, - base::Value("test_min")); - SetDeviceProperty(kCellularDevicePath, shill::kModelIdProperty, - base::Value("test_model_id")); - device_test()->SetSimLocked(kCellularDevicePath, false); + void AddDevice(const std::string& device_path, + const std::string& type, + const std::string& name) override { + device_test()->AddDevice(device_path, type, name); + } - // Add the Cellular Service. - AddService(kCellular1ServicePath, "cellular1", shill::kTypeCellular, - shill::kStateIdle); - service_test()->SetServiceProperty(kCellular1ServicePath, - shill::kCellularAllowRoamingProperty, - base::Value(false)); - service_test()->SetServiceProperty( - kCellular1ServicePath, shill::kAutoConnectProperty, base::Value(true)); - service_test()->SetServiceProperty(kCellular1ServicePath, - shill::kIccidProperty, - base::Value("test_iccid")); - service_test()->SetServiceProperty( - kCellular1ServicePath, shill::kNetworkTechnologyProperty, - base::Value(shill::kNetworkTechnologyGsm)); - service_test()->SetServiceProperty( - kCellular1ServicePath, shill::kActivationStateProperty, - base::Value(shill::kActivationStateNotActivated)); - service_test()->SetServiceProperty(kCellular1ServicePath, - shill::kRoamingStateProperty, - base::Value(shill::kRoamingStateHome)); - - profile_test()->AddService(kUser1ProfilePath, kCellular1ServicePath); - content::RunAllPendingInMessageLoop(); + void ClearDevices() override { device_test()->ClearDevices(); } + + void SetDeviceProperty(const std::string& device_path, + const std::string& name, + const base::Value& value) override { + device_test()->SetDeviceProperty(device_path, name, value, + /*notify_changed=*/true); } - void SetupTether() { - chromeos::NetworkStateHandler* network_state_handler = - chromeos::NetworkHandler::Get()->network_state_handler(); - network_state_handler->SetTetherTechnologyState( - chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED); - network_state_handler->AddTetherNetworkState( - "tetherGuid1", "tetherName1", "tetherCarrier1", - 50 /* battery_percentage */, 75 /* signal_strength */, - true /* has_connected_to_host */); - network_state_handler->AddTetherNetworkState( - "tetherGuid2", "tetherName2", "tetherCarrier2", - 75 /* battery_percentage */, 100 /* signal_strength */, - false /* has_connected_to_host */); + void SetSimLocked(const std::string& device_path, bool enabled) override { + device_test()->SetSimLocked(device_path, enabled); } void AddService(const std::string& service_path, const std::string& name, const std::string& type, - const std::string& state) { + const std::string& state) override { service_test()->AddService(service_path, service_path + "_guid", name, type, state, true /* add_to_visible */); } - void SetDeviceProperty(const std::string& device_path, - const std::string& name, - const base::Value& value) { - device_test()->SetDeviceProperty(device_path, name, value, - /*notify_changed=*/true); - } + void ClearServices() override { service_test()->ClearServices(); } - static std::unique_ptr<KeyedService> CreateNetworkingPrivateDelegate( - content::BrowserContext* context) { - std::unique_ptr<NetworkingPrivateDelegate> result( - new NetworkingPrivateChromeOS(context)); - std::unique_ptr<NetworkingPrivateDelegate::UIDelegate> ui_delegate( - new UIDelegateStub); - result->set_ui_delegate(std::move(ui_delegate)); - return result; + void SetServiceProperty(const std::string& service_path, + const std::string& property, + const base::Value& value) override { + service_test()->SetServiceProperty(service_path, property, value); } - void SetUpOnMainThread() override { - extensions::ExtensionApiTest::SetUpOnMainThread(); - content::RunAllPendingInMessageLoop(); - - NetworkingPrivateDelegateFactory::GetInstance()->SetTestingFactory( - profile(), base::BindRepeating(&CreateNetworkingPrivateDelegate)); - - InitializeSanitizedUsername(); + void AddIPConfig(const std::string& ip_config_path, + const base::Value& properties) override { + network_handler_test_helper_->ip_config_test()->AddIPConfig(ip_config_path, + properties); + } - network_handler_test_helper_ = - std::make_unique<chromeos::NetworkHandlerTestHelper>(); - device_test()->ClearDevices(); - service_test()->ClearServices(); + void AddProfile(const std::string& profile_path, + const std::string& userhash) override { + profile_test()->AddProfile(profile_path, userhash); + } - // Sends a notification about the added profile. - profile_test()->AddProfile(kUser1ProfilePath, userhash_); + void AddServiceToProfile(const std::string& profile_path, + const std::string& service_path) override { + profile_test()->AddService(profile_path, service_path); + } - // Add IPConfigs - base::DictionaryValue ipconfig; - ipconfig.SetKey(shill::kAddressProperty, base::Value("0.0.0.0")); - ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.1")); - ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(0)); - ipconfig.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv4)); - network_handler_test_helper_->ip_config_test()->AddIPConfig(kIPConfigPath, - ipconfig); + std::string GetSharedProfilePath() override { + return ShillProfileClient::GetSharedProfilePath(); + } - // Add Devices - device_test()->AddDevice(kEthernetDevicePath, shill::kTypeEthernet, - "stub_ethernet_device1"); + protected: + std::unique_ptr<ash::NetworkHandlerTestHelper> network_handler_test_helper_; + testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_; + sync_preferences::TestingPrefServiceSyncable user_prefs_; + TestingPrefServiceSimple local_state_; +}; +#else +class NetworkingPrivateChromeOSApiTestLacros + : public NetworkingPrivateChromeOSApiTestBase { + public: + NetworkingPrivateChromeOSApiTestLacros() {} + + NetworkingPrivateChromeOSApiTestLacros( + const NetworkingPrivateChromeOSApiTestLacros&) = delete; + NetworkingPrivateChromeOSApiTestLacros& operator=( + const NetworkingPrivateChromeOSApiTestLacros&) = delete; + + bool SetUpAsh() { + auto* service = chromeos::LacrosService::Get(); + if (!service->IsAvailable<crosapi::mojom::TestController>() || + service->GetInterfaceVersion(crosapi::mojom::TestController::Uuid_) < + static_cast<int>(crosapi::mojom::TestController::MethodMinVersions:: + kBindShillClientTestInterfaceMinVersion)) { + LOG(ERROR) << "Unsupported ash version."; + return false; + } + crosapi::mojom::TestControllerAsyncWaiter test_controller_waiter{ + service->GetRemote<crosapi::mojom::TestController>().get()}; + + test_controller_waiter.BindShillClientTestInterface( + shill_test_.BindNewPipeAndPassReceiver()); + + ConfigFakeNetwork(); + + return true; + } - device_test()->AddDevice(kWifiDevicePath, shill::kTypeWifi, - "stub_wifi_device1"); - base::ListValue wifi_ip_configs; - wifi_ip_configs.Append(kIPConfigPath); - SetDeviceProperty(kWifiDevicePath, shill::kIPConfigsProperty, - wifi_ip_configs); - SetDeviceProperty(kWifiDevicePath, shill::kAddressProperty, - base::Value("001122aabbcc")); + // NetworkingPrivateChromeOSApiTestBase overrides - // Add Services - AddService("stub_ethernet", "eth0", shill::kTypeEthernet, - shill::kStateOnline); - service_test()->SetServiceProperty( - "stub_ethernet", shill::kProfileProperty, - base::Value(ShillProfileClient::GetSharedProfilePath())); - profile_test()->AddService(ShillProfileClient::GetSharedProfilePath(), - "stub_ethernet"); + std::string GetSanitizedActiveUsername() override { + auto* service = chromeos::LacrosService::Get(); + if (!service->IsAvailable<crosapi::mojom::TestController>() || + service->GetInterfaceVersion(crosapi::mojom::TestController::Uuid_) < + static_cast<int>(crosapi::mojom::TestController::MethodMinVersions:: + kGetSanitizedActiveUsernameMinVersion)) { + LOG(ERROR) << "Unsupported ash version."; + return ""; + } - AddService(kWifi1ServicePath, "wifi1", shill::kTypeWifi, - shill::kStateOnline); - service_test()->SetServiceProperty(kWifi1ServicePath, - shill::kSecurityClassProperty, - base::Value(shill::kSecurityWep)); - service_test()->SetServiceProperty(kWifi1ServicePath, shill::kWifiBSsid, - base::Value("00:01:02:03:04:05")); - service_test()->SetServiceProperty( - kWifi1ServicePath, shill::kSignalStrengthProperty, base::Value(40)); - service_test()->SetServiceProperty(kWifi1ServicePath, - shill::kProfileProperty, - base::Value(kUser1ProfilePath)); - service_test()->SetServiceProperty( - kWifi1ServicePath, shill::kConnectableProperty, base::Value(true)); - service_test()->SetServiceProperty(kWifi1ServicePath, - shill::kDeviceProperty, - base::Value(kWifiDevicePath)); - base::DictionaryValue static_ipconfig; - static_ipconfig.SetKey(shill::kAddressProperty, base::Value("1.2.3.4")); - static_ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.0")); - static_ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(1)); - service_test()->SetServiceProperty( - kWifi1ServicePath, shill::kStaticIPConfigProperty, static_ipconfig); - base::ListValue frequencies1; - frequencies1.Append(2400); - service_test()->SetServiceProperty( - kWifi1ServicePath, shill::kWifiFrequencyListProperty, frequencies1); - service_test()->SetServiceProperty(kWifi1ServicePath, shill::kWifiFrequency, - base::Value(2400)); - profile_test()->AddService(kUser1ProfilePath, kWifi1ServicePath); + crosapi::mojom::TestControllerAsyncWaiter test_controller_waiter{ + service->GetRemote<crosapi::mojom::TestController>().get()}; - AddService(kWifi2ServicePath, "wifi2_PSK", shill::kTypeWifi, - shill::kStateIdle); - service_test()->SetServiceProperty(kWifi2ServicePath, - shill::kSecurityClassProperty, - base::Value(shill::kSecurityPsk)); - service_test()->SetServiceProperty( - kWifi2ServicePath, shill::kSignalStrengthProperty, base::Value(80)); - service_test()->SetServiceProperty( - kWifi2ServicePath, shill::kConnectableProperty, base::Value(true)); + std::string userhash; + test_controller_waiter.GetSanitizedActiveUsername(&userhash); + return userhash; + } - base::ListValue frequencies2; - frequencies2.Append(2400); - frequencies2.Append(5000); - service_test()->SetServiceProperty( - kWifi2ServicePath, shill::kWifiFrequencyListProperty, frequencies2); - service_test()->SetServiceProperty(kWifi2ServicePath, shill::kWifiFrequency, - base::Value(5000)); - service_test()->SetServiceProperty(kWifi2ServicePath, - shill::kProfileProperty, - base::Value(kUser1ProfilePath)); - profile_test()->AddService(kUser1ProfilePath, kWifi2ServicePath); + void AddDevice(const std::string& device_path, + const std::string& type, + const std::string& name) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .AddDevice(device_path, type, name); + } - AddService("stub_vpn1", "vpn1", shill::kTypeVPN, shill::kStateOnline); - service_test()->SetServiceProperty("stub_vpn1", - shill::kProviderTypeProperty, - base::Value(shill::kProviderOpenVpn)); - profile_test()->AddService(kUser1ProfilePath, "stub_vpn1"); + void SetDeviceProperty(const std::string& device_path, + const std::string& name, + const base::Value& value) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .SetDeviceProperty(device_path, name, value.Clone(), + /*notify_changed=*/true); + } - AddService("stub_vpn2", "vpn2", shill::kTypeVPN, shill::kStateOffline); - service_test()->SetServiceProperty( - "stub_vpn2", shill::kProviderTypeProperty, - base::Value(shill::kProviderThirdPartyVpn)); - service_test()->SetServiceProperty( - "stub_vpn2", shill::kProviderHostProperty, - base::Value("third_party_provider_extension_id")); - profile_test()->AddService(kUser1ProfilePath, "stub_vpn2"); + void SetSimLocked(const std::string& device_path, bool enabled) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .SetSimLocked(device_path, enabled); + } - PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry()); - PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry()); - network_handler_test_helper_->RegisterPrefs(user_prefs_.registry(), - local_state_.registry()); + void ClearDevices() override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()).ClearDevices(); + } - network_handler_test_helper_->InitializePrefs(&user_prefs_, &local_state_); + void AddService(const std::string& service_path, + const std::string& name, + const std::string& type, + const std::string& state) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .AddService(service_path, service_path + "_guid", name, type, state, + true /* add_to_visible */); + } - content::RunAllPendingInMessageLoop(); + void ClearServices() override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()).ClearServices(); } - void TearDownOnMainThread() { network_handler_test_helper_.reset(); } + void SetServiceProperty(const std::string& service_path, + const std::string& property, + const base::Value& value) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .SetServiceProperty(service_path, property, value.Clone()); + } - ShillServiceClient::TestInterface* service_test() { - return network_handler_test_helper_->service_test(); + void AddIPConfig(const std::string& ip_config_path, + const base::Value& properties) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .AddIPConfig(ip_config_path, properties.Clone()); } - ShillProfileClient::TestInterface* profile_test() { - return network_handler_test_helper_->profile_test(); + + void AddProfile(const std::string& profile_path, + const std::string& userhash) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .AddProfile(profile_path, userhash); } - ShillDeviceClient::TestInterface* device_test() { - return network_handler_test_helper_->device_test(); + + void AddServiceToProfile(const std::string& profile_path, + const std::string& service_path) override { + ShillClientTestInterfaceAsyncWaiter(shill_test_.get()) + .AddServiceToProfile(profile_path, service_path); } - ShillManagerClient::TestInterface* manager_test() { - return network_handler_test_helper_->manager_test(); + + std::string GetSharedProfilePath() override { + // TODO(crbug.com/): get this information from Ash + const char kSharedProfilePath[] = "/profile/default"; + return kSharedProfilePath; } protected: - std::unique_ptr<chromeos::NetworkHandlerTestHelper> - network_handler_test_helper_; - testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_; - sync_preferences::TestingPrefServiceSyncable user_prefs_; - TestingPrefServiceSimple local_state_; - std::string userhash_; + mojo::Remote<crosapi::mojom::ShillClientTestInterface> shill_test_; }; +#endif + +#if BUILDFLAG(IS_CHROMEOS_LACROS) +using NetworkingPrivateChromeOSApiTest = NetworkingPrivateChromeOSApiTestLacros; +#else +using NetworkingPrivateChromeOSApiTest = NetworkingPrivateChromeOSApiTestAsh; +#endif // Place each subtest into a separate browser test so that the stub networking // library state is reset for each subtest run. This way they won't affect each // other. IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartConnect) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("startConnect")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartDisconnect) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("startDisconnect")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartActivate) { SetupCellular(); EXPECT_TRUE(RunNetworkingSubtest("startActivate")) << message_; EXPECT_EQ(1, UIDelegateStub::s_show_account_details_called_); } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartConnectNonexistent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("startConnectNonexistent")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartDisconnectNonexistent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("startDisconnectNonexistent")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, StartGetPropertiesNonexistent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("startGetPropertiesNonexistent")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetNetworks) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif // Hide stub_wifi2. - service_test()->SetServiceProperty(kWifi2ServicePath, shill::kVisibleProperty, - base::Value(false)); + SetServiceProperty(kWifi2ServicePath, shill::kVisibleProperty, + base::Value(false)); // Add a couple of additional networks that are not configured (saved). AddService("stub_wifi3", "wifi3", shill::kTypeWifi, shill::kStateIdle); AddService("stub_wifi4", "wifi4", shill::kTypeWifi, shill::kStateIdle); content::RunAllPendingInMessageLoop(); + EXPECT_TRUE(RunNetworkingSubtest("getNetworks")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetVisibleNetworks) { EXPECT_TRUE(RunNetworkingSubtest("getVisibleNetworks")) << message_; } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetVisibleNetworksWifi) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("getVisibleNetworksWifi")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, EnabledNetworkTypes) { EXPECT_TRUE(RunNetworkingSubtest("enabledNetworkTypesDisable")) << message_; EXPECT_TRUE(RunNetworkingSubtest("enabledNetworkTypesEnable")) << message_; @@ -488,13 +741,24 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetDeviceStates) { manager_test()->SetTechnologyInitializing("cellular", true); EXPECT_TRUE(RunNetworkingSubtest("getDeviceStates")) << message_; } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, RequestNetworkScan) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("requestNetworkScan")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, RequestNetworkScanCellular) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); EXPECT_TRUE(RunNetworkingSubtest("requestNetworkScanCellular")) << message_; } @@ -502,23 +766,41 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, // Properties are filtered and translated through // ShillToONCTranslator::TranslateWiFiWithState IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetProperties) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("getProperties")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetCellularProperties) { SetupCellular(); EXPECT_TRUE(RunNetworkingSubtest("getPropertiesCellular")) << message_; } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetState) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("getState")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetStateNonExistent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("getStateNonExistent")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, SetCellularProperties) { SetupCellular(); @@ -567,11 +849,18 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, EXPECT_TRUE(RunNetworkingSubtest("createNetworkForPolicyControlledNetwork")); } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, ForgetNetwork) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("forgetNetwork")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, ForgetPolicyControlledNetwork) { constexpr char kUserPolicyBlob[] = @@ -653,45 +942,72 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetManagedProperties) { } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetErrorState) { - chromeos::NetworkHandler::Get()->network_state_handler()->SetErrorForTest( + ash::NetworkHandler::Get()->network_state_handler()->SetErrorForTest( kWifi1ServicePath, "TestErrorState"); EXPECT_TRUE(RunNetworkingSubtest("getErrorState")) << message_; } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnNetworksChangedEventConnect) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("onNetworksChangedEventConnect")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnNetworksChangedEventDisconnect) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("onNetworksChangedEventDisconnect")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnNetworkListChangedEvent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("onNetworkListChangedEvent")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnDeviceStateListChangedEvent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif EXPECT_TRUE(RunNetworkingSubtest("onDeviceStateListChangedEvent")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnDeviceScanningChangedEvent) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); EXPECT_TRUE(RunNetworkingSubtest("onDeviceScanningChangedEvent")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, OnCertificateListsChangedEvent) { ExtensionTestMessageListener listener("eventListenerReady"); listener.SetOnSatisfied(base::BindOnce([](const std::string& message) { - chromeos::NetworkHandler::Get() + ash::NetworkHandler::Get() ->network_certificate_handler() ->AddAuthorityCertificateForTest("authority_cert"); })); @@ -703,10 +1019,10 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetCaptivePortalStatus) { // Ethernet defaults to online. Set wifi1 to idle -> 'Offline', and wifi2 to // redirect-found -> 'Portal'. - service_test()->SetServiceProperty(kWifi1ServicePath, shill::kStateProperty, - base::Value(shill::kStateIdle)); - service_test()->SetServiceProperty(kWifi2ServicePath, shill::kStateProperty, - base::Value(shill::kStateRedirectFound)); + SetServiceProperty(kWifi1ServicePath, shill::kStateProperty, + base::Value(shill::kStateIdle)); + SetServiceProperty(kWifi2ServicePath, shill::kStateProperty, + base::Value(shill::kStateRedirectFound)); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(RunNetworkingSubtest("getCaptivePortalStatus")) << message_; @@ -722,28 +1038,43 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, ExtensionTestMessageListener listener("notifyPortalDetectorObservers"); listener.SetOnSatisfied( base::BindLambdaForTesting([&](const std::string& message) { - service_test()->SetServiceProperty( - kWifi1ServicePath, shill::kStateProperty, - base::Value(shill::kStateRedirectFound)); + SetServiceProperty(kWifi1ServicePath, shill::kStateProperty, + base::Value(shill::kStateRedirectFound)); })); EXPECT_TRUE(RunNetworkingSubtest("captivePortalNotification")) << message_; } +#endif IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, UnlockCellularSim) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); // Lock the SIM - device_test()->SetSimLocked(kCellularDevicePath, true); + SetSimLocked(kCellularDevicePath, true); EXPECT_TRUE(RunNetworkingSubtest("unlockCellularSim")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, SetCellularSimState) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); EXPECT_TRUE(RunNetworkingSubtest("setCellularSimState")) << message_; } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, SelectCellularMobileNetwork) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); // Create fake list of found networks. std::unique_ptr<base::ListValue> found_networks = @@ -765,12 +1096,18 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, CellularSimPuk) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + if (!SetUpAsh()) { + GTEST_SKIP() << "Unsupported ash version."; + } +#endif SetupCellular(); // Lock the SIM - device_test()->SetSimLocked(kCellularDevicePath, true); + SetSimLocked(kCellularDevicePath, true); EXPECT_TRUE(RunNetworkingSubtest("cellularSimPuk")) << message_; } +#if BUILDFLAG(IS_CHROMEOS_ASH) IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetGlobalPolicy) { base::DictionaryValue global_config; global_config.SetKey( @@ -780,7 +1117,7 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetGlobalPolicy) { ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect, base::Value(false)); global_config.SetKey("SomeNewGlobalPolicy", base::Value(false)); - chromeos::NetworkHandler::Get() + ash::NetworkHandler::Get() ->managed_network_configuration_handler() ->SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string() /* no username hash */, base::ListValue(), @@ -816,7 +1153,7 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, } IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetCertificateLists) { - chromeos::NetworkHandler::Get() + ash::NetworkHandler::Get() ->network_certificate_handler() ->AddAuthorityCertificateForTest("authority_cert"); EXPECT_TRUE(RunNetworkingSubtest("getCertificateLists")) << message_; @@ -831,5 +1168,6 @@ IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, Alias) { {.launch_as_platform_app = true})) << message_; } +#endif } // namespace diff --git a/chromium/chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.cc b/chromium/chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.cc index 506ab3b70e6..4f231bd163f 100644 --- a/chromium/chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.cc +++ b/chromium/chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.cc @@ -4,9 +4,9 @@ #include "chrome/browser/extensions/api/networking_private/networking_private_ui_delegate_chromeos.h" -#include "chromeos/network/network_connect.h" -#include "chromeos/network/network_state.h" -#include "chromeos/network/network_state_handler.h" +#include "chromeos/ash/components/network/network_connect.h" +#include "chromeos/ash/components/network/network_state.h" +#include "chromeos/ash/components/network/network_state_handler.h" namespace chromeos { namespace extensions { @@ -17,7 +17,7 @@ NetworkingPrivateUIDelegateChromeOS::~NetworkingPrivateUIDelegateChromeOS() {} void NetworkingPrivateUIDelegateChromeOS::ShowAccountDetails( const std::string& guid) const { - chromeos::NetworkConnect::Get()->ShowCarrierAccountDetail(guid); + ash::NetworkConnect::Get()->ShowCarrierAccountDetail(guid); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.cc b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.cc index e537ef7b9ea..8459f31ef24 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.cc +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.cc @@ -26,9 +26,9 @@ ExtensionNotificationDisplayHelperFactory::GetForProfile(Profile* profile) { ExtensionNotificationDisplayHelperFactory:: ExtensionNotificationDisplayHelperFactory() - : BrowserContextKeyedServiceFactory( + : ProfileKeyedServiceFactory( "ExtensionNotificationDisplayHelperFactory", - BrowserContextDependencyManager::GetInstance()) {} + ProfileSelections::BuildForRegularAndIncognito()) {} ExtensionNotificationDisplayHelperFactory:: ~ExtensionNotificationDisplayHelperFactory() {} @@ -40,10 +40,4 @@ ExtensionNotificationDisplayHelperFactory::BuildServiceInstanceFor( return new ExtensionNotificationDisplayHelper(profile); } -content::BrowserContext* -ExtensionNotificationDisplayHelperFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - return chrome::GetBrowserContextOwnInstanceInIncognito(context); -} - } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h index c7e1ea76eab..76917b632c1 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_NOTIFICATIONS_EXTENSION_NOTIFICATION_DISPLAY_HELPER_FACTORY_H_ #include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" class Profile; @@ -15,7 +15,7 @@ namespace extensions { class ExtensionNotificationDisplayHelper; class ExtensionNotificationDisplayHelperFactory - : public BrowserContextKeyedServiceFactory { + : public ProfileKeyedServiceFactory { public: ExtensionNotificationDisplayHelperFactory( const ExtensionNotificationDisplayHelperFactory&) = delete; @@ -32,8 +32,6 @@ class ExtensionNotificationDisplayHelperFactory // Overridden from BrowserContextKeyedServiceFactory. KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; private: friend struct base::DefaultSingletonTraits< 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 9c7b408f637..a921944fa2a 100644 --- a/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc +++ b/chromium/chrome/browser/extensions/api/notifications/extension_notification_handler.cc @@ -130,8 +130,8 @@ void ExtensionNotificationHandler::SendEvent( if (!event_router) return; - std::unique_ptr<Event> event(new Event( - histogram_value, event_name, std::move(*args).TakeListDeprecated())); + auto event = std::make_unique<Event>(histogram_value, event_name, + std::move(args->GetList())); event->user_gesture = user_gesture; event_router->DispatchEventToExtension(extension_id, std::move(event)); } diff --git a/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc b/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc index c0ae46820a9..1d5c0bc225d 100644 --- a/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc +++ b/chromium/chrome/browser/extensions/api/notifications/notifications_api.cc @@ -533,7 +533,7 @@ NotificationsApiFunction::MapApiTemplateTypeToType( switch (type) { case api::notifications::TEMPLATE_TYPE_NONE: case api::notifications::TEMPLATE_TYPE_BASIC: - return message_center::NOTIFICATION_TYPE_BASE_FORMAT; + return message_center::NOTIFICATION_TYPE_SIMPLE; case api::notifications::TEMPLATE_TYPE_IMAGE: return message_center::NOTIFICATION_TYPE_IMAGE; case api::notifications::TEMPLATE_TYPE_LIST: @@ -543,7 +543,7 @@ NotificationsApiFunction::MapApiTemplateTypeToType( default: // Gracefully handle newer application code that is running on an older // runtime that doesn't recognize the requested template. - return message_center::NOTIFICATION_TYPE_BASE_FORMAT; + return message_center::NOTIFICATION_TYPE_SIMPLE; } } diff --git a/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc b/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc index 3f5385da594..299ca5ded58 100644 --- a/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc +++ b/chromium/chrome/browser/extensions/api/notifications/notifications_apitest.cc @@ -27,6 +27,7 @@ #include "chrome/browser/notifications/notifier_state_tracker_factory.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/test/base/interactive_test_utils.h" +#include "components/services/app_service/public/cpp/app_launch_util.h" #include "content/public/test/browser_test.h" #include "extensions/browser/api/test/test_api.h" #include "extensions/browser/app_window/app_window.h" @@ -166,9 +167,8 @@ class NotificationsApiTest : public extensions::ExtensionApiTest { apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) ->BrowserAppLauncher() ->LaunchAppWithParamsForTesting(apps::AppLaunchParams( - extension->id(), apps::mojom::LaunchContainer::kLaunchContainerNone, - WindowOpenDisposition::NEW_WINDOW, - apps::mojom::LaunchSource::kFromTest)); + extension->id(), apps::LaunchContainer::kLaunchContainerNone, + WindowOpenDisposition::NEW_WINDOW, apps::LaunchSource::kFromTest)); } std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_; diff --git a/chromium/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc b/chromium/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc new file mode 100644 index 00000000000..b64cc18264b --- /dev/null +++ b/chromium/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc @@ -0,0 +1,351 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/offscreen/offscreen_api.h" + +#include <algorithm> + +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_util.h" +#include "components/version_info/channel.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/api/offscreen/offscreen_document_manager.h" +#include "extensions/browser/background_script_executor.h" +#include "extensions/browser/extension_util.h" +#include "extensions/browser/service_worker_task_queue.h" +#include "extensions/browser/test_extension_registry_observer.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/features/feature_channel.h" +#include "extensions/test/extension_background_page_waiter.h" +#include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace extensions { + +namespace { + +// Sets the extension to be enabled in incognito mode. +scoped_refptr<const Extension> SetExtensionIncognitoEnabled( + const Extension& extension, + Profile& profile) { + // Enabling the extension in incognito results in an extension reload; wait + // for that to finish and return the new extension pointer. + TestExtensionRegistryObserver registry_observer( + ExtensionRegistry::Get(&profile), extension.id()); + util::SetIsIncognitoEnabled(extension.id(), &profile, true); + scoped_refptr<const Extension> reloaded_extension = + registry_observer.WaitForExtensionLoaded(); + + if (!reloaded_extension) { + ADD_FAILURE() << "Failed to properly reload extension."; + return nullptr; + } + + EXPECT_TRUE(util::IsIncognitoEnabled(reloaded_extension->id(), &profile)); + return reloaded_extension; +} + +// Wakes up the service worker for the `extension` in the given `profile`. +void WakeUpServiceWorker(const Extension& extension, Profile& profile) { + base::RunLoop run_loop; + auto quit_loop_adapter = + [&run_loop](std::unique_ptr<LazyContextTaskQueue::ContextInfo>) { + run_loop.QuitWhenIdle(); + }; + ServiceWorkerTaskQueue::Get(&profile)->AddPendingTask( + LazyContextId(&profile, extension.id(), extension.url()), + base::BindLambdaForTesting(quit_loop_adapter)); + run_loop.Run(); +} + +} // namespace + +class OffscreenApiTest : public ExtensionApiTest { + public: + OffscreenApiTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionsOffscreenDocuments); + } + ~OffscreenApiTest() override = default; + + // Creates a new offscreen document through an API call, expecting success. + void ProgrammaticallyCreateOffscreenDocument(const Extension& extension, + Profile& profile) { + static constexpr char kScript[] = + R"((async () => { + let message; + try { + await chrome.offscreen.createDocument( + { + url: 'offscreen.html', + reasons: ['TESTING'], + justification: 'testing' + }); + message = 'success'; + } catch (e) { + message = 'Error: ' + e.toString(); + } + chrome.test.sendScriptResult(message); + })();)"; + base::Value result = BackgroundScriptExecutor::ExecuteScript( + &profile, extension.id(), kScript, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + ASSERT_TRUE(result.is_string()); + EXPECT_EQ("success", result.GetString()); + } + + // Closes an offscreen document through an API call, expecting success. + void ProgrammaticallyCloseOffscreenDocument(const Extension& extension, + Profile& profile) { + static constexpr char kScript[] = + R"((async () => { + let message; + try { + await chrome.offscreen.closeDocument(); + message = 'success'; + } catch (e) { + message = 'Error: ' + e.toString(); + } + chrome.test.sendScriptResult(message); + })();)"; + base::Value result = BackgroundScriptExecutor::ExecuteScript( + &profile, extension.id(), kScript, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + ASSERT_TRUE(result.is_string()); + EXPECT_EQ("success", result.GetString()); + } + + // Returns the result of an API call to `offscreen.hasDocument()`. Expects the + // call to not throw an error, independent of whether a document exists. + bool ProgrammaticallyCheckIfHasOffscreenDocument(const Extension& extension, + Profile& profile) { + static constexpr char kScript[] = + R"((async () => { + let result; + try { + result = await chrome.offscreen.hasDocument(); + } catch (e) { + result = 'Error: ' + e.toString(); + } + chrome.test.sendScriptResult(result); + })();)"; + base::Value result = BackgroundScriptExecutor::ExecuteScript( + &profile, extension.id(), kScript, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + EXPECT_TRUE(result.is_bool()) << result; + return result.is_bool() && result.GetBool(); + } + + private: + // The `offscreen` API is currently behind both a feature and a channel + // restriction. + base::test::ScopedFeatureList feature_list_; + ScopedCurrentChannel current_channel_override_{version_info::Channel::CANARY}; +}; + +// Tests the general flow of creating an offscreen document. +IN_PROC_BROWSER_TEST_F(OffscreenApiTest, BasicDocumentManagement) { + ASSERT_TRUE(RunExtensionTest("offscreen/basic_document_management")) + << message_; +} + +// Tests creating, querying, and closing offscreen documents in an incognito +// split mode extension. +IN_PROC_BROWSER_TEST_F(OffscreenApiTest, IncognitoModeHandling_SplitMode) { + // `split` incognito mode is required in order to allow the extension to + // have a separate process in incognito. + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1", + "background": {"service_worker": "background.js"}, + "permissions": ["offscreen"], + "incognito": "split" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// Blank."); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + scoped_refptr<const Extension> extension = + LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + extension = SetExtensionIncognitoEnabled(*extension, *profile()); + ASSERT_TRUE(extension); + + Browser* incognito_browser = CreateIncognitoBrowser(); + ASSERT_TRUE(incognito_browser); + Profile* incognito_profile = incognito_browser->profile(); + + // We're going to be executing scripts in the service worker context, so + // ensure the service worker is active. + // TODO(devlin): Should BackgroundScriptExecutor handle that for us? (Perhaps + // optionally?) + WakeUpServiceWorker(*extension, *profile()); + WakeUpServiceWorker(*extension, *incognito_profile); + + auto has_offscreen_document = [this, extension](Profile& profile) { + bool programmatic = + ProgrammaticallyCheckIfHasOffscreenDocument(*extension, profile); + bool in_manager = + OffscreenDocumentManager::Get(&profile) + ->GetOffscreenDocumentForExtension(*extension) != nullptr; + EXPECT_EQ(programmatic, in_manager) << "Mismatch between manager and API."; + return programmatic && in_manager; + }; + + // Create an offscreen document in the on-the-record profile. Only it should + // have a document; the off-the-record profile is considered distinct. + ProgrammaticallyCreateOffscreenDocument(*extension, *profile()); + EXPECT_TRUE(has_offscreen_document(*profile())); + EXPECT_FALSE(has_offscreen_document(*incognito_profile)); + + // Now, create a new document in the off-the-record profile. + ProgrammaticallyCreateOffscreenDocument(*extension, + *incognito_browser->profile()); + EXPECT_TRUE(has_offscreen_document(*profile())); + EXPECT_TRUE(has_offscreen_document(*incognito_profile)); + + // Close the off-the-record profile - the on-the-record profile's offscreen + // document should remain open. + ProgrammaticallyCloseOffscreenDocument(*extension, *incognito_profile); + EXPECT_TRUE(has_offscreen_document(*profile())); + EXPECT_FALSE(has_offscreen_document(*incognito_profile)); + + // Finally, close the on-the-record profile's document. + ProgrammaticallyCloseOffscreenDocument(*extension, *profile()); + EXPECT_FALSE(has_offscreen_document(*profile())); + EXPECT_FALSE(has_offscreen_document(*incognito_profile)); +} + +// Tests creating, querying, and closing offscreen documents in an incognito +// spanning mode extension. +IN_PROC_BROWSER_TEST_F(OffscreenApiTest, IncognitoModeHandling_SpanningMode) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1", + "background": {"service_worker": "background.js"}, + "permissions": ["offscreen"], + "incognito": "spanning" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// Blank."); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + scoped_refptr<const Extension> extension = + LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + extension = SetExtensionIncognitoEnabled(*extension, *profile()); + ASSERT_TRUE(extension); + + Browser* incognito_browser = CreateIncognitoBrowser(); + ASSERT_TRUE(incognito_browser); + Profile* incognito_profile = incognito_browser->profile(); + + // Wake up the on-the-record service worker (the only one we have, as a + // spanning mode extension). + WakeUpServiceWorker(*extension, *profile()); + + auto has_offscreen_document = [this, extension](Profile& profile) { + bool programmatic = + ProgrammaticallyCheckIfHasOffscreenDocument(*extension, profile); + bool in_manager = + OffscreenDocumentManager::Get(&profile) + ->GetOffscreenDocumentForExtension(*extension) != nullptr; + EXPECT_EQ(programmatic, in_manager) << "Mismatch between manager and API."; + return programmatic && in_manager; + }; + + // There's less to do in a spanning mode extension - by definition, we can't + // call any methods from an incognito profile, so we just have to verify that + // the incognito profile is unaffected. + ProgrammaticallyCreateOffscreenDocument(*extension, *profile()); + EXPECT_TRUE(has_offscreen_document(*profile())); + // Don't use `has_offscreen_document()` since we can't actually check the + // programmatic status, which requires executing script in an incognito + // process. + OffscreenDocumentManager* incognito_manager = + OffscreenDocumentManager::Get(incognito_profile); + EXPECT_EQ(nullptr, + incognito_manager->GetOffscreenDocumentForExtension(*extension)); + + ProgrammaticallyCloseOffscreenDocument(*extension, *profile()); + EXPECT_FALSE(has_offscreen_document(*profile())); + EXPECT_EQ(nullptr, + incognito_manager->GetOffscreenDocumentForExtension(*extension)); +} + +class OffscreenApiTestWithoutFeature : public ExtensionApiTest { + public: + OffscreenApiTestWithoutFeature() = default; + ~OffscreenApiTestWithoutFeature() override = default; + + private: + ScopedCurrentChannel current_channel_override_{ + version_info::Channel::UNKNOWN}; +}; + +// Tests that the `offscreen` API is unavailable if the requisite feature +// (`ExtensionsOffscreenDocuments`) is not enabled. We have this explicit test +// mostly to double-check our registration, since features are prone to typos. +IN_PROC_BROWSER_TEST_F(OffscreenApiTestWithoutFeature, + APIUnavailableWithoutFeature) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1", + "permissions": ["offscreen"], + "background": { "service_worker": "background.js" } + })"; + // The extension validates the `offscreen` API is undefined. + static constexpr char kBackgroundJs[] = + R"(chrome.test.runTests([ + function apiIsUnavailable() { + chrome.test.assertEq(undefined, chrome.offscreen); + chrome.test.succeed(); + }, + ]);)"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); + + ResultCatcher result_catcher; + const Extension* extension = LoadExtension( + test_dir.UnpackedPath(), {.ignore_manifest_warnings = true}); + ASSERT_TRUE(extension); + + EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message(); + + // An install warning should be emitted since the extension requested a + // restricted permission. + const std::vector<InstallWarning>& install_warnings = + extension->install_warnings(); + + // Turn our InstallWarnings into strings for easier testing. + std::vector<std::string> string_warnings; + std::transform(install_warnings.begin(), install_warnings.end(), + std::back_inserter(string_warnings), + [](const InstallWarning& warning) { return warning.message; }); + + static constexpr char kExpectedWarning[] = + "'offscreen' requires the 'ExtensionsOffscreenDocuments' feature flag to " + "be enabled."; + EXPECT_THAT(string_warnings, testing::ElementsAre(kExpectedWarning)); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc b/chromium/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc new file mode 100644 index 00000000000..c8eb402f93c --- /dev/null +++ b/chromium/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc @@ -0,0 +1,315 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/offscreen/offscreen_document_manager.h" + +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_util.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "extensions/browser/disable_reason.h" +#include "extensions/browser/extension_host_test_helper.h" +#include "extensions/browser/extension_util.h" +#include "extensions/browser/offscreen_document_host.h" +#include "extensions/browser/test_extension_registry_observer.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/mojom/view_type.mojom.h" +#include "extensions/test/test_extension_dir.h" + +namespace extensions { + +class OffscreenDocumentManagerBrowserTest : public ExtensionApiTest { + public: + OffscreenDocumentManagerBrowserTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionsOffscreenDocuments); + } + ~OffscreenDocumentManagerBrowserTest() override = default; + + // Creates a new offscreen document with the given `extension`, `url`, + // and `profile`, and waits for it to load. + OffscreenDocumentHost* CreateDocumentAndWaitForLoad( + const Extension& extension, + const GURL& url, + Profile& profile) { + ExtensionHostTestHelper host_waiter(&profile); + host_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument); + OffscreenDocumentHost* offscreen_document = + OffscreenDocumentManager::Get(&profile)->CreateOffscreenDocument( + extension, url); + host_waiter.WaitForHostCompletedFirstLoad(); + + return offscreen_document; + } + + // Same as the above, defaulting to the on-the-record profile. + OffscreenDocumentHost* CreateDocumentAndWaitForLoad( + const Extension& extension, + const GURL& url) { + return CreateDocumentAndWaitForLoad(extension, url, *profile()); + } + + OffscreenDocumentManager* offscreen_document_manager() { + return OffscreenDocumentManager::Get(profile()); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Tests the flow of the OffscreenDocumentManager creating a new offscreen +// document for an extension. +IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest, + CreateOffscreenDocument) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1" + })"; + static constexpr char kOffscreenDocumentHtml[] = + R"(<html> + <body> + <div id="signal">Hello, World</div> + </body> + </html>)"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + kOffscreenDocumentHtml); + + // Note: We wrap `extension` in a refptr because we'll unload it later in the + // test and need to make sure the object isn't deleted. + scoped_refptr<const Extension> extension = + LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + // To start, the manager should not have any offscreen documents registered. + EXPECT_EQ(nullptr, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); + + OffscreenDocumentHost* offscreen_document = nullptr; + { + // Instruct the manager to create a new offscreen document and wait for it + // to load. + ExtensionHostTestHelper host_waiter(profile()); + host_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument); + offscreen_document = offscreen_document_manager()->CreateOffscreenDocument( + *extension, extension->GetResourceURL("offscreen.html")); + ASSERT_TRUE(offscreen_document); + host_waiter.WaitForHostCompletedFirstLoad(); + } + + { + // Check the document loaded properly. Note: general capabilities of + // offscreen documents are exercised more in the OffscreenDocumentHost + // tests, but this helps sanity check that the manager created it properly. + static constexpr char kScript[] = + R"({ + let div = document.getElementById('signal'); + domAutomationController.send(div ? div.innerText : '<no div>'); + })"; + std::string result; + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + offscreen_document->host_contents(), kScript, &result)); + EXPECT_EQ("Hello, World", result); + } + + // The manager should now have a record of a document for the extension. + EXPECT_EQ(offscreen_document, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); + + { + // Disable the extension. This causes it to unload, and the offscreen + // document should be closed. + ExtensionHostTestHelper host_waiter(profile()); + host_waiter.RestrictToHost(offscreen_document); + extension_service()->DisableExtension(extension->id(), + disable_reason::DISABLE_USER_ACTION); + host_waiter.WaitForHostDestroyed(); + // Note: `offscreen_document` is destroyed at this point. + } + + // There should no longer be a document for the extension. + EXPECT_EQ(nullptr, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); +} + +// Tests creating offscreen documents for an incognito split-mode extension. +IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest, + IncognitoOffscreenDocuments) { + // `split` incognito mode is required in order to allow the extension to + // have a separate process in incognito. + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1", + "incognito": "split" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + scoped_refptr<const Extension> extension = + LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + { + // Enable the extension in incognito. This results in an extension reload; + // wait for that to finish and update the `extension` pointer. + TestExtensionRegistryObserver registry_observer( + ExtensionRegistry::Get(profile()), extension->id()); + util::SetIsIncognitoEnabled(extension->id(), browser()->profile(), + /*enabled=*/true); + extension = registry_observer.WaitForExtensionLoaded(); + } + + ASSERT_TRUE(extension); + ASSERT_TRUE(util::IsIncognitoEnabled(extension->id(), profile())); + + const GURL offscreen_url = extension->GetResourceURL("offscreen.html"); + + // Create an on-the-record offscreen document. + OffscreenDocumentHost* on_the_record_host = + CreateDocumentAndWaitForLoad(*extension, offscreen_url); + ASSERT_TRUE(on_the_record_host); + // Ensure the on-the-record context is used. + // Note: Throughout this test, we use + // `OffscreenDocumentHost::host_contents()` to access the BrowserContext + // instead of `OffscreenDocumentHost::browser_context()`; this is to ensure + // that the WebContents is hosted properly. + EXPECT_FALSE(on_the_record_host->host_contents() + ->GetBrowserContext() + ->IsOffTheRecord()); + + // Create an incognito browser and an incognito offscreen document, and + // validate that the proper context is used. + Browser* incognito_browser = CreateIncognitoBrowser(); + ASSERT_TRUE(incognito_browser); + + OffscreenDocumentHost* incognito_host = CreateDocumentAndWaitForLoad( + *extension, offscreen_url, *incognito_browser->profile()); + ASSERT_TRUE(incognito_host); + EXPECT_TRUE( + incognito_host->host_contents()->GetBrowserContext()->IsOffTheRecord()); + + // These should be separate offscreen documents and have separate profiles, + // but the same original profile. + EXPECT_NE(incognito_host, on_the_record_host); + EXPECT_EQ(Profile::FromBrowserContext( + on_the_record_host->host_contents()->GetBrowserContext()), + Profile::FromBrowserContext( + incognito_host->host_contents()->GetBrowserContext()) + ->GetOriginalProfile()); + + // Ensure the offscreen documents are registered with the appropriate + // context. + EXPECT_EQ(on_the_record_host, + OffscreenDocumentManager::Get(profile()) + ->GetOffscreenDocumentForExtension(*extension)); + EXPECT_EQ(incognito_host, + OffscreenDocumentManager::Get(incognito_browser->profile()) + ->GetOffscreenDocumentForExtension(*extension)); + + { + // Shut down the incognito browser. The `incognito_host` should be + // destroyed. + ExtensionHostTestHelper host_waiter(incognito_browser->profile()); + host_waiter.RestrictToHost(incognito_host); + CloseBrowserSynchronously(incognito_browser); + host_waiter.WaitForHostDestroyed(); + // Note: `incognito_host` is destroyed at this point. + } + + // The on-the-record document should remain. + EXPECT_EQ(on_the_record_host, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); +} + +// Tests the flow of closing an existing offscreen document through the +// manager. +IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest, + ClosingDocumentThroughTheManager) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + const GURL offscreen_url = extension->GetResourceURL("offscreen.html"); + + OffscreenDocumentHost* offscreen_document = + CreateDocumentAndWaitForLoad(*extension, offscreen_url); + ASSERT_TRUE(offscreen_document); + + { + ExtensionHostTestHelper host_waiter(profile()); + host_waiter.RestrictToHost(offscreen_document); + offscreen_document_manager()->CloseOffscreenDocumentForExtension( + *extension); + } + + EXPECT_EQ(nullptr, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); +} + +// Tests calling window.close() in an offscreen document closes it (through the +// manager). +IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest, + CallingWindowCloseInAnOffscreenDocumentClosesIt) { + static constexpr char kManifest[] = + R"({ + "name": "Offscreen Document Test", + "manifest_version": 3, + "version": "0.1" + })"; + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), + "<html>offscreen</html>"); + + scoped_refptr<const Extension> extension = + LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad( + *extension, extension->GetResourceURL("offscreen.html")); + ASSERT_TRUE(offscreen_document); + EXPECT_EQ(offscreen_document, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); + + { + // Call window.close() from the offscreen document. This should cause the + // manager to close the document, destroying the host. + ExtensionHostTestHelper host_waiter(profile()); + host_waiter.RestrictToHost(offscreen_document); + ASSERT_TRUE(content::ExecuteScript(offscreen_document->host_contents(), + "window.close();")); + host_waiter.WaitForHostDestroyed(); + } + + EXPECT_EQ(nullptr, + offscreen_document_manager()->GetOffscreenDocumentForExtension( + *extension)); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/omnibox/omnibox_api.cc b/chromium/chrome/browser/extensions/api/omnibox/omnibox_api.cc index 746cc75b889..39df08f5e0f 100644 --- a/chromium/chrome/browser/extensions/api/omnibox/omnibox_api.cc +++ b/chromium/chrome/browser/extensions/api/omnibox/omnibox_api.cc @@ -96,7 +96,7 @@ void ExtensionOmniboxEventRouter::OnInputStarted( Profile* profile, const std::string& extension_id) { auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_STARTED, omnibox::OnInputStarted::kEventName, - std::vector<base::Value>(), profile); + base::Value::List(), profile); EventRouter::Get(profile) ->DispatchEventToExtension(extension_id, std::move(event)); } @@ -110,13 +110,13 @@ bool ExtensionOmniboxEventRouter::OnInputChanged( extension_id, omnibox::OnInputChanged::kEventName)) return false; - auto args(std::make_unique<base::ListValue>()); - args->Append(input); - args->Append(suggest_id); + base::Value::List args; + args.Append(input); + args.Append(suggest_id); - auto event = std::make_unique<Event>( - events::OMNIBOX_ON_INPUT_CHANGED, omnibox::OnInputChanged::kEventName, - std::move(*args).TakeListDeprecated(), profile); + auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_CHANGED, + omnibox::OnInputChanged::kEventName, + std::move(args), profile); event_router->DispatchEventToExtension(extension_id, std::move(event)); return true; } @@ -137,18 +137,18 @@ void ExtensionOmniboxEventRouter::OnInputEntered( extensions::TabHelper::FromWebContents(web_contents)-> active_tab_permission_granter()->GrantIfRequested(extension); - auto args(std::make_unique<base::ListValue>()); - args->Append(input); + base::Value::List args; + args.Append(input); if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB) - args->Append(kForegroundTabDisposition); + args.Append(kForegroundTabDisposition); else if (disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) - args->Append(kBackgroundTabDisposition); + args.Append(kBackgroundTabDisposition); else - args->Append(kCurrentTabDisposition); + args.Append(kCurrentTabDisposition); - auto event = std::make_unique<Event>( - events::OMNIBOX_ON_INPUT_ENTERED, omnibox::OnInputEntered::kEventName, - std::move(*args).TakeListDeprecated(), profile); + auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_ENTERED, + omnibox::OnInputEntered::kEventName, + std::move(args), profile); EventRouter::Get(profile) ->DispatchEventToExtension(extension_id, std::move(event)); @@ -160,7 +160,7 @@ void ExtensionOmniboxEventRouter::OnInputCancelled( Profile* profile, const std::string& extension_id) { auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_CANCELLED, omnibox::OnInputCancelled::kEventName, - std::vector<base::Value>(), profile); + base::Value::List(), profile); EventRouter::Get(profile) ->DispatchEventToExtension(extension_id, std::move(event)); } @@ -169,13 +169,12 @@ void ExtensionOmniboxEventRouter::OnDeleteSuggestion( Profile* profile, const std::string& extension_id, const std::string& suggestion_text) { - auto args(std::make_unique<base::ListValue>()); - args->Append(suggestion_text); + base::Value::List args; + args.Append(suggestion_text); - auto event = - std::make_unique<Event>(events::OMNIBOX_ON_DELETE_SUGGESTION, - omnibox::OnDeleteSuggestion::kEventName, - std::move(*args).TakeListDeprecated(), profile); + auto event = std::make_unique<Event>(events::OMNIBOX_ON_DELETE_SUGGESTION, + omnibox::OnDeleteSuggestion::kEventName, + std::move(args), profile); EventRouter::Get(profile)->DispatchEventToExtension(extension_id, std::move(event)); diff --git a/chromium/chrome/browser/extensions/api/omnibox/omnibox_unittest.cc b/chromium/chrome/browser/extensions/api/omnibox/omnibox_unittest.cc index 2921ab41c82..b239bf215b7 100644 --- a/chromium/chrome/browser/extensions/api/omnibox/omnibox_unittest.cc +++ b/chromium/chrome/browser/extensions/api/omnibox/omnibox_unittest.cc @@ -77,7 +77,7 @@ TEST(ExtensionOmniboxTest, DescriptionStylesSimple) { styles_expected.push_back(ACMatchClassification(9, kNone)); std::unique_ptr<SendSuggestions::Params> params( - SendSuggestions::Params::Create(list->GetListDeprecated())); + SendSuggestions::Params::Create(list->GetList())); EXPECT_TRUE(params); ASSERT_FALSE(params->suggest_results.empty()); CompareClassification(styles_expected, StyleTypesToACMatchClassifications( @@ -109,7 +109,7 @@ TEST(ExtensionOmniboxTest, DescriptionStylesSimple) { .Build(); std::unique_ptr<SendSuggestions::Params> swapped_params( - SendSuggestions::Params::Create(swap_list->GetListDeprecated())); + SendSuggestions::Params::Create(swap_list->GetList())); EXPECT_TRUE(swapped_params); ASSERT_FALSE(swapped_params->suggest_results.empty()); CompareClassification( @@ -173,7 +173,7 @@ TEST(ExtensionOmniboxTest, DescriptionStylesCombine) { styles_expected.push_back(ACMatchClassification(9, kMatch | kDim)); std::unique_ptr<SendSuggestions::Params> params( - SendSuggestions::Params::Create(list->GetListDeprecated())); + SendSuggestions::Params::Create(list->GetList())); EXPECT_TRUE(params); ASSERT_FALSE(params->suggest_results.empty()); CompareClassification(styles_expected, StyleTypesToACMatchClassifications( @@ -221,7 +221,7 @@ TEST(ExtensionOmniboxTest, DescriptionStylesCombine) { .Build(); std::unique_ptr<SendSuggestions::Params> moved_params( - SendSuggestions::Params::Create(moved_list->GetListDeprecated())); + SendSuggestions::Params::Create(moved_list->GetList())); EXPECT_TRUE(moved_params); ASSERT_FALSE(moved_params->suggest_results.empty()); CompareClassification(styles_expected, StyleTypesToACMatchClassifications( @@ -280,7 +280,7 @@ TEST(ExtensionOmniboxTest, DescriptionStylesCombine2) { styles_expected.push_back(ACMatchClassification(5, kNone)); std::unique_ptr<SendSuggestions::Params> params( - SendSuggestions::Params::Create(list->GetListDeprecated())); + SendSuggestions::Params::Create(list->GetList())); EXPECT_TRUE(params); ASSERT_FALSE(params->suggest_results.empty()); CompareClassification(styles_expected, StyleTypesToACMatchClassifications( @@ -332,7 +332,7 @@ TEST(ExtensionOmniboxTest, DefaultSuggestResult) { .Build(); std::unique_ptr<SetDefaultSuggestion::Params> params( - SetDefaultSuggestion::Params::Create(list->GetListDeprecated())); + SetDefaultSuggestion::Params::Create(list->GetList())); EXPECT_TRUE(params); } diff --git a/chromium/chrome/browser/extensions/api/omnibox/suggestion_parser.cc b/chromium/chrome/browser/extensions/api/omnibox/suggestion_parser.cc index 66ea40e725e..33de6e9e538 100644 --- a/chromium/chrome/browser/extensions/api/omnibox/suggestion_parser.cc +++ b/chromium/chrome/browser/extensions/api/omnibox/suggestion_parser.cc @@ -140,25 +140,25 @@ void ConstructResultFromValue( std::move(callback).Run(std::move(result)); }; - if (value_or_error.error) { - run_callback_with_error(std::move(*value_or_error.error)); + if (!value_or_error.has_value()) { + run_callback_with_error(std::move(value_or_error.error())); return; } - DCHECK(value_or_error.value); + DCHECK(value_or_error.has_value()); // From this point on, we hope that everything is valid (e.g., that we don't // get non-dictionary values or unexpected top-level types. But, if we did, // emit a generic error. constexpr char kGenericError[] = "Invalid XML"; - if (!value_or_error.value->is_dict()) { + if (!value_or_error->is_dict()) { run_callback_with_error(kGenericError); return; } std::vector<const base::Value*> entries; - const base::Value& root_node = *value_or_error.value; + const base::Value& root_node = *value_or_error; if (has_multiple_entries) { if (!PopulateEntriesFromNode(root_node, &entries)) { run_callback_with_error(kGenericError); 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 5f25da1f0a2..ae20864acfc 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 @@ -9,32 +9,20 @@ #include <utility> #include "base/bind.h" -#include "base/callback_helpers.h" #include "base/files/file_util.h" #include "base/task/thread_pool.h" #include "build/chromeos_buildflags.h" -#include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/extension_util.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/profiles/profiles_state.h" #include "components/sessions/content/session_tab_helper.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" -#include "content/public/browser/notification_details.h" -#include "content/public/browser/notification_source.h" -#include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #include "content/public/common/mhtml_generation_params.h" #include "extensions/common/extension_messages.h" #include "extensions/common/permissions/permissions_data.h" -#if BUILDFLAG(IS_CHROMEOS) -#include "chrome/browser/chromeos/extensions/public_session_permission_helper.h" -#include "extensions/common/permissions/api_permission_set.h" -#endif - using content::BrowserThread; using content::ChildProcessSecurityPolicy; using content::WebContents; @@ -51,9 +39,6 @@ const char kTemporaryFileError[] = "Failed to create a temporary file."; const char kTabClosedError[] = "Cannot find the tab for this request."; const char kPageCaptureNotAllowed[] = "Don't have permissions required to capture this page."; -#if BUILDFLAG(IS_CHROMEOS) -const char kUserDenied[] = "User denied request."; -#endif constexpr base::TaskTraits kCreateTemporaryFileTaskTraits = { // Requires IO. base::MayBlock(), @@ -91,31 +76,6 @@ ExtensionFunction::ResponseAction PageCaptureSaveAsMHTMLFunction::Run() { params_ = SaveAsMHTML::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(params_.get()); -#if BUILDFLAG(IS_CHROMEOS) - // In Public Sessions, extensions (and apps) are force-installed by admin - // policy so the user does not get a chance to review the permissions for - // these extensions. This is not acceptable from a security/privacy - // standpoint, so when an extension uses the PageCapture API for the first - // time, we show the user a dialog where they can choose whether to allow - // the extension access to the API. - // TODO(https://crbug.com/1269409): This bypasses the CanCaptureCurrentPage() - // check below, which means we don't check certain restrictions. - if (profiles::ArePublicSessionRestrictionsEnabled()) { - WebContents* web_contents = GetWebContents(); - if (!web_contents) { - return RespondNow(Error(kTabClosedError)); - } - - permission_helper::HandlePermissionRequest( - *extension(), {mojom::APIPermissionID::kPageCapture}, web_contents, - base::BindOnce( - &PageCaptureSaveAsMHTMLFunction::ResolvePermissionRequest, - this), // Callback increments refcount. - permission_helper::PromptFactory()); - return RespondLater(); - } -#endif - std::string error; if (!CanCaptureCurrentPage(&error)) { return RespondNow(Error(std::move(error))); @@ -187,21 +147,6 @@ void PageCaptureSaveAsMHTMLFunction::OnServiceWorkerAck() { Release(); // Balanced in Run() } -#if BUILDFLAG(IS_CHROMEOS) -void PageCaptureSaveAsMHTMLFunction::ResolvePermissionRequest( - const PermissionIDSet& allowed_permissions) { - if (allowed_permissions.ContainsID( - extensions::mojom::APIPermissionID::kPageCapture)) { - base::ThreadPool::PostTask( - FROM_HERE, kCreateTemporaryFileTaskTraits, - base::BindOnce(&PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile, - this)); - } else { - ReturnFailure(kUserDenied); - } -} -#endif - void PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile() { bool success = base::CreateTemporaryFile(&mhtml_path_); content::GetIOThreadTaskRunner({})->PostTask( diff --git a/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.h b/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.h index 09aa6d5a574..c31adf73f03 100644 --- a/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.h +++ b/chromium/chrome/browser/extensions/api/page_capture/page_capture_api.h @@ -25,10 +25,6 @@ class WebContents; namespace extensions { -#if BUILDFLAG(IS_CHROMEOS) -class PermissionIDSet; -#endif - class PageCaptureSaveAsMHTMLFunction : public ExtensionFunction { public: PageCaptureSaveAsMHTMLFunction(); @@ -51,11 +47,6 @@ class PageCaptureSaveAsMHTMLFunction : public ExtensionFunction { ResponseAction Run() override; bool OnMessageReceived(const IPC::Message& message) override; -#if BUILDFLAG(IS_CHROMEOS) - // Resolves the API permission request in Public Sessions. - void ResolvePermissionRequest(const PermissionIDSet& allowed_permissions); -#endif - // Returns whether or not the extension has permission to capture the current // page. Sets |*error| to an error value on failure. bool CanCaptureCurrentPage(std::string* error); diff --git a/chromium/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc b/chromium/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc index 15672ba7592..0a39556adcd 100644 --- a/chromium/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc +++ b/chromium/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc @@ -4,37 +4,15 @@ #include <atomic> -#include "base/base_switches.h" #include "base/command_line.h" -#include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" -#include "chrome/browser/extensions/active_tab_permission_granter.h" #include "chrome/browser/extensions/api/page_capture/page_capture_api.h" -#include "chrome/browser/extensions/extension_action_runner.h" #include "chrome/browser/extensions/extension_apitest.h" -#include "chrome/browser/extensions/extension_util.h" -#include "chrome/browser/extensions/tab_helper.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/test/base/ui_test_utils.h" -#include "chromeos/login/login_state/scoped_test_public_session_login_state.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" -#include "content/public/test/test_utils.h" -#include "extensions/browser/extension_dialog_auto_confirm.h" -#include "extensions/common/permissions/permission_set.h" -#include "extensions/common/permissions/permissions_data.h" -#include "extensions/common/url_pattern_set.h" -#include "extensions/test/extension_test_message_listener.h" -#include "extensions/test/result_catcher.h" #include "net/dns/mock_host_resolver.h" #include "third_party/blink/public/common/switches.h" -#if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chromeos/login/login_state/login_state.h" -#endif // BUILDFLAG(IS_CHROMEOS_ASH) - namespace extensions { using ContextType = ExtensionApiTest::ContextType; @@ -47,7 +25,7 @@ class PageCaptureSaveAsMHTMLDelegate } virtual ~PageCaptureSaveAsMHTMLDelegate() { - PageCaptureSaveAsMHTMLFunction::SetTestDelegate(NULL); + PageCaptureSaveAsMHTMLFunction::SetTestDelegate(nullptr); } void OnTemporaryFileCreated( @@ -130,28 +108,4 @@ IN_PROC_BROWSER_TEST_P(ExtensionPageCaptureApiTest, SaveAsMHTMLWithFileAccess) { WaitForFileCleanup(&delegate); } -#if BUILDFLAG(IS_CHROMEOS_ASH) -IN_PROC_BROWSER_TEST_P(ExtensionPageCaptureApiTest, - PublicSessionRequestAllowed) { - ASSERT_TRUE(StartEmbeddedTestServer()); - PageCaptureSaveAsMHTMLDelegate delegate; - chromeos::ScopedTestPublicSessionLoginState login_state; - // Resolve Permission dialog with Allow. - ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT); - ASSERT_TRUE(RunTest("page_capture")) << message_; - WaitForFileCleanup(&delegate); -} - -IN_PROC_BROWSER_TEST_P(ExtensionPageCaptureApiTest, - PublicSessionRequestDenied) { - ASSERT_TRUE(StartEmbeddedTestServer()); - PageCaptureSaveAsMHTMLDelegate delegate; - chromeos::ScopedTestPublicSessionLoginState login_state; - // Resolve Permission dialog with Deny. - ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::CANCEL); - ASSERT_TRUE(RunTest("page_capture", "REQUEST_DENIED")) << message_; - EXPECT_EQ(0, delegate.temp_file_count()); -} -#endif // BUILDFLAG(IS_CHROMEOS_ASH) - } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/OWNERS b/chromium/chrome/browser/extensions/api/passwords_private/OWNERS index eaa48b0e3d3..53d53538317 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/OWNERS +++ b/chromium/chrome/browser/extensions/api/passwords_private/OWNERS @@ -1,2 +1,3 @@ file://chrome/browser/resources/settings/OWNERS +mamir@chromium.org vsemeniuk@google.com diff --git a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc index fcd63f8338a..07031d70233 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc @@ -15,8 +15,11 @@ #include "base/bind.h" #include "base/containers/flat_set.h" +#include "base/feature_list.h" #include "base/memory/ref_counted.h" +#include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" +#include "base/ranges/algorithm.h" #include "base/strings/escape.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/sequenced_task_runner_handle.h" @@ -26,8 +29,10 @@ #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h" #include "chrome/browser/password_manager/account_password_store_factory.h" #include "chrome/browser/password_manager/bulk_leak_check_service_factory.h" +#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h" #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/sync_service_factory.h" #include "chrome/common/extensions/api/passwords_private.h" #include "chrome/grit/generated_resources.h" #include "components/keyed_service/core/service_access_type.h" @@ -37,11 +42,16 @@ #include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" #include "components/password_manager/core/browser/leak_detection/encryption_utils.h" #include "components/password_manager/core/browser/password_change_success_tracker.h" +#include "components/password_manager/core/browser/password_feature_manager_impl.h" #include "components/password_manager/core/browser/password_form.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" +#include "components/password_manager/core/browser/password_manager_util.h" +#include "components/password_manager/core/browser/password_scripts_fetcher.h" #include "components/password_manager/core/browser/ui/credential_utils.h" #include "components/password_manager/core/browser/ui/insecure_credentials_manager.h" #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" #include "components/password_manager/core/browser/well_known_change_password_util.h" +#include "components/password_manager/core/common/password_manager_features.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_service.h" #include "components/url_formatter/elide_url.h" @@ -50,6 +60,7 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/time_format.h" #include "url/gurl.h" +#include "url/origin.h" namespace extensions { @@ -61,14 +72,17 @@ using password_manager::InsecureType; using password_manager::LeakCheckCredential; using password_manager::PasswordChangeSuccessTracker; using password_manager::PasswordForm; +using password_manager::PasswordScriptsFetcher; +using password_manager::metrics_util::PasswordCheckScriptsCacheState; using ui::TimeFormat; -using InsecureCredentialsView = - password_manager::InsecureCredentialsManager::CredentialsView; using SavedPasswordsView = password_manager::SavedPasswordsPresenter::SavedPasswordsView; using State = password_manager::BulkLeakCheckService::State; +constexpr char kPasswordCheckScriptsCacheStateUmaKey[] = + "PasswordManager.BulkCheck.ScriptsCacheState"; + std::unique_ptr<std::string> GetChangePasswordUrl(const GURL& url) { return std::make_unique<std::string>( password_manager::CreateChangePasswordUrl(url).spec()); @@ -179,20 +193,6 @@ std::string FormatElapsedTime(base::Time time) { TimeFormat::FORMAT_ELAPSED, TimeFormat::LENGTH_LONG, elapsed_time, true)); } -base::Time GetTimeWhenWasPhishedOrLeaked(const CredentialUIEntry& entry) { - base::Time compromise_time; - if (entry.IsLeaked()) { - compromise_time = - entry.password_issues.at(InsecureType::kLeaked).create_time; - } - if (entry.IsPhished()) { - compromise_time = - std::max(compromise_time, - entry.password_issues.at(InsecureType::kPhished).create_time); - } - return compromise_time; -} - api::passwords_private::CompromiseType GetCompromiseType( const CredentialUIEntry& entry) { if (entry.IsLeaked() && entry.IsPhished()) { @@ -235,8 +235,7 @@ void OrderInsecureCredentials(std::vector<CredentialUIEntry>& credentials) { // |results|. Now sort both groups by their creation date so that most recent // compromises appear first in both lists. auto create_time_cmp = [](auto& lhs, auto& rhs) { - return GetTimeWhenWasPhishedOrLeaked(lhs) > - GetTimeWhenWasPhishedOrLeaked(rhs); + return lhs.GetLastLeakedOrPhishedTime() > rhs.GetLastLeakedOrPhishedTime(); }; std::sort(credentials.begin(), non_phished, create_time_cmp); std::sort(non_phished, credentials.end(), create_time_cmp); @@ -246,9 +245,9 @@ api::passwords_private::CompromisedInfo CreateCompromiseInfo( const CredentialUIEntry& form) { api::passwords_private::CompromisedInfo compromise_info; compromise_info.compromise_time = - GetTimeWhenWasPhishedOrLeaked(form).ToJsTimeIgnoringNull(); + form.GetLastLeakedOrPhishedTime().ToJsTimeIgnoringNull(); compromise_info.elapsed_time_since_compromise = - FormatElapsedTime(GetTimeWhenWasPhishedOrLeaked(form)); + FormatElapsedTime(form.GetLastLeakedOrPhishedTime()); compromise_info.compromise_type = GetCompromiseType(form); compromise_info.is_muted = IsCredentialMuted(form); return compromise_info; @@ -258,8 +257,15 @@ api::passwords_private::CompromisedInfo CreateCompromiseInfo( PasswordCheckDelegate::PasswordCheckDelegate( Profile* profile, - password_manager::SavedPasswordsPresenter* presenter) + password_manager::SavedPasswordsPresenter* presenter, + IdGenerator<password_manager::CredentialUIEntry, + int, + password_manager::CredentialUIEntry::Less>* id_generator) : profile_(profile), + password_feature_manager_( + std::make_unique<password_manager::PasswordFeatureManagerImpl>( + profile->GetPrefs(), + SyncServiceFactory::GetForProfile(profile))), saved_passwords_presenter_(presenter), insecure_credentials_manager_(presenter, PasswordStoreFactory::GetForProfile( @@ -271,33 +277,28 @@ PasswordCheckDelegate::PasswordCheckDelegate( bulk_leak_check_service_adapter_( presenter, BulkLeakCheckServiceFactory::GetForProfile(profile_), - profile_->GetPrefs()) { + profile_->GetPrefs()), + id_generator_(id_generator) { + DCHECK(id_generator); observed_saved_passwords_presenter_.Observe(saved_passwords_presenter_.get()); observed_insecure_credentials_manager_.Observe( &insecure_credentials_manager_); observed_bulk_leak_check_service_.Observe( BulkLeakCheckServiceFactory::GetForProfile(profile_)); - - // Instructs the provider to initialize and build its cache. - // This will soon after invoke OnCompromisedCredentialsChanged(). Calls to - // GetCompromisedCredentials() that might happen until then will return an - // empty list. - insecure_credentials_manager_.Init(); } PasswordCheckDelegate::~PasswordCheckDelegate() = default; -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> PasswordCheckDelegate::GetCompromisedCredentials() { std::vector<CredentialUIEntry> ordered_credentials = insecure_credentials_manager_.GetInsecureCredentialEntries(); OrderInsecureCredentials(ordered_credentials); - std::vector<api::passwords_private::InsecureCredential> - compromised_credentials; + std::vector<api::passwords_private::PasswordUiEntry> compromised_credentials; compromised_credentials.reserve(ordered_credentials.size()); for (const auto& credential : ordered_credentials) { - api::passwords_private::InsecureCredential api_credential = + api::passwords_private::PasswordUiEntry api_credential = ConstructInsecureCredential(credential); api_credential.compromised_info = std::make_unique<api::passwords_private::CompromisedInfo>( @@ -308,12 +309,12 @@ PasswordCheckDelegate::GetCompromisedCredentials() { return compromised_credentials; } -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> PasswordCheckDelegate::GetWeakCredentials() { std::vector<CredentialUIEntry> weak_credentials = insecure_credentials_manager_.GetWeakCredentialEntries(); - std::vector<api::passwords_private::InsecureCredential> api_credentials; + std::vector<api::passwords_private::PasswordUiEntry> api_credentials; api_credentials.reserve(weak_credentials.size()); for (auto& weak_credential : weak_credentials) { api_credentials.push_back(ConstructInsecureCredential(weak_credential)); @@ -322,43 +323,8 @@ PasswordCheckDelegate::GetWeakCredentials() { return api_credentials; } -absl::optional<api::passwords_private::InsecureCredential> -PasswordCheckDelegate::GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential) const { - const CredentialUIEntry* entry = FindMatchingEntry(credential); - if (!entry) - return absl::nullopt; - - credential.password = - std::make_unique<std::string>(base::UTF16ToUTF8(entry->password)); - return credential; -} - -bool PasswordCheckDelegate::ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) { - // Try to obtain the original CredentialUIEntry. Return false if fails. - const CredentialUIEntry* entry = FindMatchingEntry(credential); - if (!entry) - return false; - - CredentialUIEntry credential_to_edit = *entry; - credential_to_edit.password = base::UTF8ToUTF16(new_password); - return saved_passwords_presenter_->EditSavedCredentials(credential_to_edit); -} - -bool PasswordCheckDelegate::RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { - // Try to obtain the original CredentialUIEntry. Return false if fails. - const CredentialUIEntry* entry = FindMatchingEntry(credential); - if (!entry) - return false; - - return saved_passwords_presenter_->RemoveCredential(*entry); -} - bool PasswordCheckDelegate::MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { // Try to obtain the original CredentialUIEntry. Return false if fails. const CredentialUIEntry* entry = FindMatchingEntry(credential); if (!entry) @@ -368,7 +334,7 @@ bool PasswordCheckDelegate::MuteInsecureCredential( } bool PasswordCheckDelegate::UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { // Try to obtain the original CredentialUIEntry. Return false if fails. const CredentialUIEntry* entry = FindMatchingEntry(credential); if (!entry) @@ -380,7 +346,7 @@ bool PasswordCheckDelegate::UnmuteInsecureCredential( // Records that a change password flow was started for |credential| and // whether |is_manual_flow| applies to the flow. void PasswordCheckDelegate::RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) { // If the |credential| does not have a |change_password_url|, skip it. if (!credential.change_password_url) @@ -398,6 +364,15 @@ void PasswordCheckDelegate::RecordChangePasswordFlowStarted( } } +void PasswordCheckDelegate::RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) { + if (PasswordScriptsFetcher* fetcher = GetPasswordScriptsFetcher()) { + fetcher->RefreshScriptsIfNecessary(std::move(callback)); + return; + } + std::move(callback).Run(); +} + void PasswordCheckDelegate::StartPasswordCheck( StartPasswordCheckCallback callback) { // If the delegate isn't initialized yet, enqueue the callback and return @@ -407,13 +382,61 @@ void PasswordCheckDelegate::StartPasswordCheck( return; } - // Also return early if the check is already running. - if (bulk_leak_check_service_adapter_.GetBulkLeakCheckState() == - State::kRunning) { + // Also return early if the check is already running or scripts are fetching. + if (are_scripts_fetching_ || + bulk_leak_check_service_adapter_.GetBulkLeakCheckState() == + State::kRunning) { std::move(callback).Run(State::kRunning); return; } + // If automated password change from password check in settings is enabled, + // we make sure that the cache is warm prior to analyzing passwords. + if (base::FeatureList::IsEnabled( + password_manager::features::kPasswordChange)) { + if (GetPasswordScriptsFetcher()->IsCacheStale()) { + are_scripts_fetching_ = true; + // The UMA metric for a stale cache is recorded on callback. + GetPasswordScriptsFetcher()->RefreshScriptsIfNecessary( + base::BindOnce(&PasswordCheckDelegate::OnPasswordScriptsFetched, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); + return; + } + UMA_HISTOGRAM_ENUMERATION(kPasswordCheckScriptsCacheStateUmaKey, + PasswordCheckScriptsCacheState::kCacheFresh); + } + + // Otherwise, call directly. + StartPasswordAnalyses(std::move(callback)); +} + +void PasswordCheckDelegate::OnPasswordScriptsFetched( + StartPasswordCheckCallback callback) { + DCHECK(are_scripts_fetching_); + are_scripts_fetching_ = false; + if (PasswordsPrivateEventRouter* event_router = + PasswordsPrivateEventRouterFactory::GetForProfile(profile_)) { + // Only update if at least one credential now has a startable script. + std::vector<api::passwords_private::PasswordUiEntry> credentials = + GetCompromisedCredentials(); + if (base::ranges::any_of( + credentials, + &api::passwords_private::PasswordUiEntry::has_startable_script)) { + UMA_HISTOGRAM_ENUMERATION( + kPasswordCheckScriptsCacheStateUmaKey, + PasswordCheckScriptsCacheState::kCacheStaleAndUiUpdate); + event_router->OnCompromisedCredentialsChanged(std::move(credentials)); + } else { + UMA_HISTOGRAM_ENUMERATION( + kPasswordCheckScriptsCacheStateUmaKey, + PasswordCheckScriptsCacheState::kCacheStaleAndNoUiUpdate); + } + } + StartPasswordAnalyses(std::move(callback)); +} + +void PasswordCheckDelegate::StartPasswordAnalyses( + StartPasswordCheckCallback callback) { // Start the weakness check, and notify observers once done. insecure_credentials_manager_.StartWeakCheck(base::BindOnce( &PasswordCheckDelegate::RecordAndNotifyAboutCompletedWeakPasswordCheck, @@ -504,8 +527,7 @@ void PasswordCheckDelegate::OnSavedPasswordsChanged(SavedPasswordsView) { NotifyPasswordCheckStatusChanged(); } -void PasswordCheckDelegate::OnInsecureCredentialsChanged( - InsecureCredentialsView credentials) { +void PasswordCheckDelegate::OnInsecureCredentialsChanged() { if (auto* event_router = PasswordsPrivateEventRouterFactory::GetForProfile(profile_)) { event_router->OnCompromisedCredentialsChanged(GetCompromisedCredentials()); @@ -553,13 +575,12 @@ void PasswordCheckDelegate::OnCredentialDone( } const CredentialUIEntry* PasswordCheckDelegate::FindMatchingEntry( - const api::passwords_private::InsecureCredential& credential) const { - const CredentialUIEntry* entry = - insecure_credential_id_generator_.TryGetKey(credential.id); + const api::passwords_private::PasswordUiEntry& credential) const { + const CredentialUIEntry* entry = id_generator_->TryGetKey(credential.id); if (!entry) return nullptr; - if (credential.signon_realm != entry->signon_realm || + if (credential.urls.signon_realm != entry->signon_realm || credential.username != base::UTF16ToUTF8(entry->username) || (credential.password && *credential.password != base::UTF16ToUTF8(entry->password))) { @@ -602,58 +623,61 @@ void PasswordCheckDelegate::NotifyPasswordCheckStatusChanged() { } } -api::passwords_private::InsecureCredential +api::passwords_private::PasswordUiEntry PasswordCheckDelegate::ConstructInsecureCredential( const CredentialUIEntry& entry) { - api::passwords_private::InsecureCredential api_credential; + api::passwords_private::PasswordUiEntry api_credential; auto facet = password_manager::FacetURI::FromPotentiallyInvalidSpec( entry.signon_realm); - if (facet.IsValidAndroidFacetURI()) { - api_credential.is_android_credential = true; - // |formatted_orgin|, |detailed_origin| and |change_password_url| need - // special handling for Android. Here we use affiliation information - // instead of the origin. + api_credential.is_android_credential = facet.IsValidAndroidFacetURI(); + api_credential.id = id_generator_->GenerateId(entry); + api_credential.username = base::UTF16ToUTF8(entry.username); + api_credential.urls = CreateUrlCollectionFromCredential(entry); + api_credential.stored_in = StoreSetFromCredential(entry); + if (api_credential.is_android_credential) { + // |change_password_url| need special handling for Android. Here we use + // affiliation information instead of the origin. if (!entry.app_display_name.empty()) { - api_credential.formatted_origin = entry.app_display_name; - api_credential.detailed_origin = entry.app_display_name; api_credential.change_password_url = GetChangePasswordUrl(GURL(entry.affiliated_web_realm)); - } else { - // In case no affiliation information could be obtained show the - // formatted package name to the user. An empty change_password_url will - // be handled by the frontend, by not including a link in this case. - api_credential.formatted_origin = l10n_util::GetStringFUTF8( - IDS_SETTINGS_PASSWORDS_ANDROID_APP, - base::UTF8ToUTF16(facet.android_package_name())); - api_credential.detailed_origin = facet.android_package_name(); } } else { - api_credential.is_android_credential = false; - api_credential.formatted_origin = - base::UTF16ToUTF8(url_formatter::FormatUrl( - entry.url.GetWithEmptyPath(), - url_formatter::kFormatUrlOmitDefaults | - url_formatter::kFormatUrlOmitHTTPS | - url_formatter::kFormatUrlOmitTrivialSubdomains | - url_formatter::kFormatUrlTrimAfterHost, - base::UnescapeRule::SPACES, nullptr, nullptr, nullptr)); - api_credential.detailed_origin = - base::UTF16ToUTF8(url_formatter::FormatUrlForSecurityDisplay( - entry.url.GetWithEmptyPath())); api_credential.change_password_url = GetChangePasswordUrl(entry.url); } - - api_credential.id = insecure_credential_id_generator_.GenerateId(entry); - api_credential.signon_realm = entry.signon_realm; - api_credential.username = base::UTF16ToUTF8(entry.username); - + // For the time being, the automated password change is restricted to + // compromised credentials. In the future, this requirement may be relaxed. + if ((entry.IsPhished() || entry.IsLeaked()) && + IsAutomatedPasswordChangeFromSettingsEnabled() && + !entry.username.empty()) { + GURL url = api_credential.is_android_credential + ? GURL(entry.affiliated_web_realm) + : entry.url; + api_credential.has_startable_script = + !url.is_empty() && GetPasswordScriptsFetcher()->IsScriptAvailable( + url::Origin::Create(entry.url)); + } else { + api_credential.has_startable_script = false; + } return api_credential; } PasswordChangeSuccessTracker* -PasswordCheckDelegate::GetPasswordChangeSuccessTracker() { +PasswordCheckDelegate::GetPasswordChangeSuccessTracker() const { return password_manager::PasswordChangeSuccessTrackerFactory:: GetForBrowserContext(profile_); } +PasswordScriptsFetcher* PasswordCheckDelegate::GetPasswordScriptsFetcher() + const { + return PasswordScriptsFetcherFactory::GetForBrowserContext(profile_); +} + +bool PasswordCheckDelegate::IsAutomatedPasswordChangeFromSettingsEnabled() + const { + return password_feature_manager_ + ->AreRequirementsForAutomatedPasswordChangeFulfilled() && + base::FeatureList::IsEnabled( + password_manager::features::kPasswordChange); +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.h b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.h index 1280671f85a..484d407a013 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate.h @@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORD_CHECK_DELEGATE_H_ #define CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORD_CHECK_DELEGATE_H_ +#include <memory> + #include "base/callback.h" #include "base/callback_helpers.h" #include "base/memory/raw_ptr.h" @@ -28,6 +30,8 @@ class Profile; namespace password_manager { class PasswordChangeSuccessTracker; +class PasswordFeatureManager; +class PasswordScriptsFetcher; } // namespace password_manager namespace extensions { @@ -45,9 +49,15 @@ class PasswordCheckDelegate public: using StartPasswordCheckCallback = PasswordsPrivateDelegate::StartPasswordCheckCallback; - - PasswordCheckDelegate(Profile* profile, - password_manager::SavedPasswordsPresenter* presenter); + using RefreshScriptsIfNecessaryCallback = + PasswordsPrivateDelegate::RefreshScriptsIfNecessaryCallback; + + PasswordCheckDelegate( + Profile* profile, + password_manager::SavedPasswordsPresenter* presenter, + IdGenerator<password_manager::CredentialUIEntry, + int, + password_manager::CredentialUIEntry::Less>* id_generator); PasswordCheckDelegate(const PasswordCheckDelegate&) = delete; PasswordCheckDelegate& operator=(const PasswordCheckDelegate&) = delete; ~PasswordCheckDelegate() override; @@ -55,48 +65,36 @@ class PasswordCheckDelegate // Obtains information about compromised credentials. This includes the last // time a check was run, as well as all compromised credentials that are // present in the password store. - std::vector<api::passwords_private::InsecureCredential> + std::vector<api::passwords_private::PasswordUiEntry> GetCompromisedCredentials(); // Obtains information about weak credentials. - std::vector<api::passwords_private::InsecureCredential> GetWeakCredentials(); - - // Requests the plaintext password for |credential|. If successful, this - // returns |credential| with its |password| member set. This can fail if no - // matching insecure credential can be found in the password store. - absl::optional<api::passwords_private::InsecureCredential> - GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential) const; - - // Attempts to change the stored password of |credential| to |new_password|. - // Returns whether the change succeeded. - bool ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password); - - // Attempts to remove |credential| from the password store. Returns whether - // the remove succeeded. - bool RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential); - - // Attempts to mute |credential| from the password store. Returns whether + std::vector<api::passwords_private::PasswordUiEntry> GetWeakCredentials(); + + // Attempts to mute `credential` from the password store. Returns whether // the mute succeeded. bool MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential); + const api::passwords_private::PasswordUiEntry& credential); - // Attempts to unmute |credential| from the password store. Returns whether + // Attempts to unmute `credential` from the password store. Returns whether // the unmute succeeded. bool UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential); + const api::passwords_private::PasswordUiEntry& credential); - // Records that a change password flow was started for |credential| and - // whether |is_manual_flow| applies to the flow. + // Records that a change password flow was started for `credential` and + // whether `is_manual_flow` applies to the flow. void RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow); - // Requests to start a check for insecure passwords. Invokes |callback| once a - // check is running or the request was stopped via StopPasswordCheck(). + // Refreshes the cache for automatic password change scripts if that is stale + // and runs `callback` once that is complete. + void RefreshScriptsIfNecessary(RefreshScriptsIfNecessaryCallback callback); + + // Checks that all preconditions for running a password check are fulfilled + // and, once that is the case, launches the password check. Invokes `callback` + // once a check is running or the request was stopped via + // `StopPasswordCheck()`. void StartPasswordCheck( StartPasswordCheckCallback callback = base::DoNothing()); // Stops checking for insecure passwords. @@ -119,9 +117,7 @@ class PasswordCheckDelegate // password_manager::InsecureCredentialsManager::Observer: // Invokes PasswordsPrivateEventRouter::OnInsecureCredentialsChanged if // a valid pointer can be obtained. - void OnInsecureCredentialsChanged( - password_manager::InsecureCredentialsManager::CredentialsView credentials) - override; + void OnInsecureCredentialsChanged() override; // password_manager::InsecureCredentialsManager::Observer: // Invokes PasswordsPrivateEventRouter::OnWeakCredentialsChanged if a valid @@ -135,13 +131,21 @@ class PasswordCheckDelegate password_manager::IsLeaked is_leaked) override; // Tries to find the matching CredentialUIEntry for |credential|. It - // performs a look-up in |insecure_credential_id_generator_| using - // |credential.id|. If a matching value exists it also verifies that signon - // realm, username and when possible password match. - // Returns a pointer to the matching CredentialUIEntry on success or - // nullptr otherwise. + // performs a look-up in |id_generator_| using |credential.id|. If a matching + // value exists it also verifies that signon realm, username and when possible + // password match. Returns a pointer to the matching CredentialUIEntry on + // success or nullptr otherwise. const password_manager::CredentialUIEntry* FindMatchingEntry( - const api::passwords_private::InsecureCredential& credential) const; + const api::passwords_private::PasswordUiEntry& credential) const; + + // Reacts to a refreshed password scripts cache. Checks whether any of the + // compromised credentials have a password script and only then calls the + // event router to update the frontend. + void OnPasswordScriptsFetched(StartPasswordCheckCallback callback); + + // Starts the analyses of whether credentials are compromised and/or weak. + // Assumes that `StartPasswordCheck()` was called prior. + void StartPasswordAnalyses(StartPasswordCheckCallback callback); // Invoked when a compromised password check completes. Records the current // timestamp in `kLastTimePasswordCheckCompleted` pref. @@ -151,24 +155,36 @@ class PasswordCheckDelegate // in `last_completed_weak_check_`. void RecordAndNotifyAboutCompletedWeakPasswordCheck(); - // Tries to notify the PasswordsPrivateEventRouter that the password check - // status has changed. Invoked after OnSavedPasswordsChanged and - // OnStateChanged. + // Tries to notify the `PasswordsPrivateEventRouter` that the password check + // status has changed. Invoked after `OnSavedPasswordsChanged` and + // `OnStateChanged`. void NotifyPasswordCheckStatusChanged(); - // Constructs |InsecureCredential| from |CredentialUIEntry|. - api::passwords_private::InsecureCredential ConstructInsecureCredential( + // Constructs `PasswordUiEntry` from `CredentialUIEntry`. + api::passwords_private::PasswordUiEntry ConstructInsecureCredential( const password_manager::CredentialUIEntry& entry); - // Obtain a raw pointer to the |PasswordChangeSuccessTracker| associated - // with |profile_|. + // Returns a raw pointer to the `PasswordChangeSuccessTracker` associated + // with `profile_`. password_manager::PasswordChangeSuccessTracker* - GetPasswordChangeSuccessTracker(); + GetPasswordChangeSuccessTracker() const; + + // Returns a raw pointer to the `PasswordScriptsFetcher` associated with + // `profile_`. + password_manager::PasswordScriptsFetcher* GetPasswordScriptsFetcher() const; + + // Returns whether automatic password changes are enabled from settings. + bool IsAutomatedPasswordChangeFromSettingsEnabled() const; // Raw pointer to the underlying profile. Needs to outlive this instance. raw_ptr<Profile> profile_ = nullptr; - // Used by |insecure_credentials_manager_| to obtain the list of saved + // A password feature manager instance used to determine whether to offer + // automated password changes. + const std::unique_ptr<password_manager::PasswordFeatureManager> + password_feature_manager_; + + // Used by `insecure_credentials_manager_` to obtain the list of saved // passwords. raw_ptr<password_manager::SavedPasswordsPresenter> saved_passwords_presenter_ = nullptr; @@ -184,45 +200,48 @@ class PasswordCheckDelegate // when the delegate obtains the list of saved passwords for the first time. bool is_initialized_ = false; - // List of callbacks that were passed to StartPasswordCheck() prior to the + // List of callbacks that were passed to `StartPasswordCheck()` prior to the // delegate being initialized. These will be run when either initialization - // finishes, or StopPasswordCheck() gets invoked before hand. + // finishes, or `StopPasswordCheck()` gets invoked before hand. std::vector<StartPasswordCheckCallback> start_check_callbacks_; // Remembers the progress of the ongoing check. Null if no check is currently // running. base::WeakPtr<PasswordCheckProgress> password_check_progress_; + // Remembers whether scripts are fetching right now. + bool are_scripts_fetching_ = false; + // Remembers whether a password check is running right now. bool is_check_running_ = false; // Store when the last weak check was completed. base::Time last_completed_weak_check_; - // A scoped observer for |saved_passwords_presenter_|. + // A scoped observer for `saved_passwords_presenter_`. base::ScopedObservation<password_manager::SavedPasswordsPresenter, password_manager::SavedPasswordsPresenter::Observer> observed_saved_passwords_presenter_{this}; - // A scoped observer for |insecure_credentials_manager_|. + // A scoped observer for `insecure_credentials_manager_`. base::ScopedObservation< password_manager::InsecureCredentialsManager, password_manager::InsecureCredentialsManager::Observer> observed_insecure_credentials_manager_{this}; - // A scoped observer for the BulkLeakCheckService. + // A scoped observer for the `BulkLeakCheckService`. base::ScopedObservation< password_manager::BulkLeakCheckServiceInterface, password_manager::BulkLeakCheckServiceInterface::Observer> observed_bulk_leak_check_service_{this}; // An id generator for insecure credentials. Required to match - // api::passwords_private::InsecureCredential instances passed to the UI - // with the underlying CredentialUIEntry they are based on. - IdGenerator<password_manager::CredentialUIEntry, - int, - password_manager::CredentialUIEntry::Less> - insecure_credential_id_generator_; + // `api::passwords_private::PasswordUiEntry` instances passed to the UI + // with the underlying `CredentialUIEntry` they are based on. + raw_ptr<IdGenerator<password_manager::CredentialUIEntry, + int, + password_manager::CredentialUIEntry::Less>> + id_generator_; base::WeakPtrFactory<PasswordCheckDelegate> weak_ptr_factory_{this}; }; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate_unittest.cc b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate_unittest.cc index 375950c8f8f..57fe62002de 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate_unittest.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/password_check_delegate_unittest.cc @@ -19,12 +19,17 @@ #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" +#include "base/test/gmock_move_support.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" +#include "base/test/scoped_feature_list.h" #include "base/time/time.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h" #include "chrome/browser/password_manager/bulk_leak_check_service_factory.h" #include "chrome/browser/password_manager/password_manager_test_util.h" +#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h" +#include "chrome/browser/sync/sync_service_factory.h" #include "chrome/common/extensions/api/passwords_private.h" #include "chrome/test/base/testing_profile.h" #include "components/keyed_service/core/keyed_service.h" @@ -33,17 +38,22 @@ #include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" #include "components/password_manager/core/browser/leak_detection/leak_detection_delegate_interface.h" #include "components/password_manager/core/browser/mock_password_change_success_tracker.h" +#include "components/password_manager/core/browser/mock_password_scripts_fetcher.h" #include "components/password_manager/core/browser/password_change_success_tracker.h" #include "components/password_manager/core/browser/password_form.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_manager_test_utils.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" #include "components/password_manager/core/browser/well_known_change_password_util.h" +#include "components/password_manager/core/common/password_manager_features.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/identity_test_environment.h" +#include "components/sync/driver/test_sync_service.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" #include "content/public/browser/browser_context.h" #include "content/public/test/browser_task_environment.h" #include "extensions/browser/event_router.h" @@ -54,6 +64,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/origin.h" namespace extensions { @@ -74,22 +85,24 @@ constexpr char16_t kWeakPassword1[] = u"123456"; constexpr char16_t kWeakPassword2[] = u"111111"; using api::passwords_private::CompromisedInfo; -using api::passwords_private::InsecureCredential; using api::passwords_private::PasswordCheckStatus; +using api::passwords_private::PasswordUiEntry; +using api::passwords_private::UrlCollection; using password_manager::BulkLeakCheckDelegateInterface; using password_manager::BulkLeakCheckService; -using password_manager::InsecureCredentialTypeFlags; using password_manager::InsecureType; using password_manager::InsecurityMetadata; using password_manager::IsLeaked; using password_manager::IsMuted; using password_manager::LeakCheckCredential; using password_manager::MockPasswordChangeSuccessTracker; +using password_manager::MockPasswordScriptsFetcher; using password_manager::PasswordChangeSuccessTracker; using password_manager::PasswordChangeSuccessTrackerFactory; using password_manager::PasswordForm; using password_manager::SavedPasswordsPresenter; using password_manager::TestPasswordStore; +using password_manager::metrics_util::PasswordCheckScriptsCacheState; using password_manager::prefs::kLastTimePasswordCheckCompleted; using signin::IdentityTestEnvironment; using ::testing::AllOf; @@ -101,10 +114,13 @@ using ::testing::IsNull; using ::testing::Mock; using ::testing::Pair; using ::testing::Pointee; +using ::testing::Return; using ::testing::UnorderedElementsAre; using MockStartPasswordCheckCallback = base::MockCallback<PasswordCheckDelegate::StartPasswordCheckCallback>; +using MockRefreshScriptsIfNecessaryCallback = base::MockCallback< + PasswordsPrivateDelegate::RefreshScriptsIfNecessaryCallback>; PasswordsPrivateEventRouter* CreateAndUsePasswordsPrivateEventRouter( Profile* profile) { @@ -140,6 +156,17 @@ MockPasswordChangeSuccessTracker* CreateAndUsePasswordChangeSuccessTracker( }))); } +MockPasswordScriptsFetcher* CreateAndUsePasswordScriptsFetcher( + Profile* profile) { + return static_cast<MockPasswordScriptsFetcher*>( + PasswordScriptsFetcherFactory::GetInstance() + ->SetTestingSubclassFactoryAndUse( + profile, base::BindRepeating([](content::BrowserContext*) { + return std::make_unique< + testing::NiceMock<MockPasswordScriptsFetcher>>(); + }))); +} + BulkLeakCheckService* CreateAndUseBulkLeakCheckService( signin::IdentityManager* identity_manager, Profile* profile) { @@ -154,6 +181,13 @@ BulkLeakCheckService* CreateAndUseBulkLeakCheckService( }))); } +syncer::TestSyncService* CreateAndUseSyncService(Profile* profile) { + return SyncServiceFactory::GetInstance()->SetTestingSubclassFactoryAndUse( + profile, base::BindLambdaForTesting([](content::BrowserContext*) { + return std::make_unique<syncer::TestSyncService>(); + })); +} + PasswordForm MakeSavedPassword(base::StringPiece signon_realm, base::StringPiece16 username, base::StringPiece16 password = kPassword1, @@ -196,22 +230,10 @@ PasswordForm MakeSavedAndroidPassword( return form; } -// Creates matcher for a given insecure credential. -auto ExpectInsecureCredential( - const std::string& formatted_origin, - const std::string& detailed_origin, - const absl::optional<std::string>& change_password_url, - const std::u16string& username) { - auto change_password_url_field_matcher = - change_password_url.has_value() - ? Field(&InsecureCredential::change_password_url, - Pointee(change_password_url.value())) - : Field(&InsecureCredential::change_password_url, IsNull()); - return AllOf( - Field(&InsecureCredential::formatted_origin, formatted_origin), - Field(&InsecureCredential::detailed_origin, detailed_origin), - change_password_url_field_matcher, - Field(&InsecureCredential::username, base::UTF16ToASCII(username))); +auto ExpectUrls(const std::string& formatted_origin, + const std::string& detailed_origin) { + return AllOf(Field(&UrlCollection::shown, formatted_origin), + Field(&UrlCollection::link, detailed_origin)); } // Creates matcher for a given compromised info. @@ -228,6 +250,22 @@ auto ExpectCompromisedInfo( } // Creates matcher for a given compromised credential +auto ExpectWeakCredential( + const std::string& formatted_origin, + const std::string& detailed_origin, + const absl::optional<std::string>& change_password_url, + const std::u16string& username) { + auto change_password_url_field_matcher = + change_password_url.has_value() + ? Field(&PasswordUiEntry::change_password_url, + Pointee(change_password_url.value())) + : Field(&PasswordUiEntry::change_password_url, IsNull()); + return AllOf(Field(&PasswordUiEntry::username, base::UTF16ToASCII(username)), + Field(&PasswordUiEntry::urls, + ExpectUrls(formatted_origin, detailed_origin))); +} + +// Creates matcher for a given compromised credential auto ExpectCompromisedCredential( const std::string& formatted_origin, const std::string& detailed_origin, @@ -236,14 +274,30 @@ auto ExpectCompromisedCredential( base::TimeDelta elapsed_time_since_compromise, const std::string& elapsed_time_since_compromise_str, api::passwords_private::CompromiseType compromise_type) { - return AllOf(ExpectInsecureCredential(formatted_origin, detailed_origin, - change_password_url, username), - Field(&InsecureCredential::compromised_info, + auto change_password_url_field_matcher = + change_password_url.has_value() + ? Field(&PasswordUiEntry::change_password_url, + Pointee(change_password_url.value())) + : Field(&PasswordUiEntry::change_password_url, IsNull()); + return AllOf(Field(&PasswordUiEntry::username, base::UTF16ToASCII(username)), + change_password_url_field_matcher, + Field(&PasswordUiEntry::urls, + ExpectUrls(formatted_origin, detailed_origin)), + Field(&PasswordUiEntry::compromised_info, Pointee(ExpectCompromisedInfo( elapsed_time_since_compromise, elapsed_time_since_compromise_str, compromise_type)))); } +// Creates a simplified matcher that only checks the username name and +// whether a startable script exists. +auto ExpectCredentialWithScriptInfo(const std::u16string& username, + bool has_startable_script) { + return AllOf( + Field(&PasswordUiEntry::username, base::UTF16ToASCII(username)), + Field(&PasswordUiEntry::has_startable_script, has_startable_script)); +} + class PasswordCheckDelegateTest : public ::testing::Test { public: PasswordCheckDelegateTest() { @@ -263,9 +317,18 @@ class PasswordCheckDelegateTest : public ::testing::Test { MockPasswordChangeSuccessTracker& password_change_success_tracker() { return *password_change_success_tracker_; } + MockPasswordScriptsFetcher& password_scripts_fetcher() { + return *password_scripts_fetcher_; + } + syncer::TestSyncService& sync_service() { return *sync_service_; } SavedPasswordsPresenter& presenter() { return presenter_; } PasswordCheckDelegate& delegate() { return delegate_; } + PasswordCheckDelegate CreateDelegate(SavedPasswordsPresenter* presenter) { + return PasswordCheckDelegate(&profile_, presenter, + &credential_id_generator_); + } + private: content::BrowserTaskEnvironment task_env_{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; @@ -282,33 +345,21 @@ class PasswordCheckDelegateTest : public ::testing::Test { CreateAndUseTestPasswordStore(&profile_); raw_ptr<MockPasswordChangeSuccessTracker> password_change_success_tracker_ = CreateAndUsePasswordChangeSuccessTracker(&profile_); + raw_ptr<MockPasswordScriptsFetcher> password_scripts_fetcher_ = + CreateAndUsePasswordScriptsFetcher(&profile_); + raw_ptr<syncer::TestSyncService> sync_service_ = + CreateAndUseSyncService(&profile_); + IdGenerator<password_manager::CredentialUIEntry, + int, + password_manager::CredentialUIEntry::Less> + credential_id_generator_; SavedPasswordsPresenter presenter_{store_}; - PasswordCheckDelegate delegate_{&profile_, &presenter_}; + PasswordCheckDelegate delegate_{&profile_, &presenter_, + &credential_id_generator_}; }; } // namespace -TEST_F(PasswordCheckDelegateTest, VerifyCastingOfCompromisedCredentialTypes) { - static_assert( - static_cast<int>(api::passwords_private::COMPROMISE_TYPE_NONE) == - static_cast<int>(InsecureCredentialTypeFlags::kSecure), - ""); - static_assert( - static_cast<int>(api::passwords_private::COMPROMISE_TYPE_LEAKED) == - static_cast<int>(InsecureCredentialTypeFlags::kCredentialLeaked), - ""); - static_assert( - static_cast<int>(api::passwords_private::COMPROMISE_TYPE_PHISHED) == - static_cast<int>(InsecureCredentialTypeFlags::kCredentialPhished), - ""); - static_assert( - static_cast<int>( - api::passwords_private::COMPROMISE_TYPE_PHISHED_AND_LEAKED) == - static_cast<int>(InsecureCredentialTypeFlags::kCredentialLeaked | - InsecureCredentialTypeFlags::kCredentialPhished), - ""); -} - // Verify that GetWeakCredentials() correctly represents weak credentials. TEST_F(PasswordCheckDelegateTest, GetWeakCredentialsFillsFieldsCorrectly) { store().AddLogin(MakeSavedPassword(kExampleCom, kUsername1, kWeakPassword1)); @@ -321,11 +372,12 @@ TEST_F(PasswordCheckDelegateTest, GetWeakCredentialsFillsFieldsCorrectly) { EXPECT_THAT( delegate().GetWeakCredentials(), UnorderedElementsAre( - ExpectInsecureCredential( - "example.com", "https://example.com", + ExpectWeakCredential( + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername1), - ExpectInsecureCredential( - "Example App", "Example App", + ExpectWeakCredential( + "Example App", + "https://play.google.com/store/apps/details?id=com.example.app", "https://example.com/.well-known/change-password", kUsername2))); } @@ -353,8 +405,8 @@ TEST_F(PasswordCheckDelegateTest, WeakCheckWhenUserSignedOut) { EXPECT_THAT( delegate().GetWeakCredentials(), - ElementsAre(ExpectInsecureCredential( - "example.com", "https://example.com", + ElementsAre(ExpectWeakCredential( + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername1))); EXPECT_EQ(api::passwords_private::PASSWORD_CHECK_STATE_SIGNED_OUT, delegate().GetPasswordCheckStatus().state); @@ -386,22 +438,22 @@ TEST_F(PasswordCheckDelegateTest, GetCompromisedCredentialsOrders) { EXPECT_THAT( delegate().GetCompromisedCredentials(), ElementsAre(ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername2, base::Minutes(2), "2 minutes ago", api::passwords_private::COMPROMISE_TYPE_PHISHED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername1, base::Minutes(4), "4 minutes ago", api::passwords_private::COMPROMISE_TYPE_PHISHED), ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername1, base::Minutes(1), "1 minute ago", api::passwords_private::COMPROMISE_TYPE_LEAKED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername2, base::Minutes(3), "3 minutes ago", api::passwords_private::COMPROMISE_TYPE_LEAKED))); @@ -432,22 +484,22 @@ TEST_F(PasswordCheckDelegateTest, GetCompromisedCredentialsHandlesTimes) { EXPECT_THAT( delegate().GetCompromisedCredentials(), ElementsAre(ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername1, base::Seconds(59), "Just now", api::passwords_private::COMPROMISE_TYPE_LEAKED), ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername2, base::Seconds(60), "1 minute ago", api::passwords_private::COMPROMISE_TYPE_LEAKED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername1, base::Days(100), "3 months ago", api::passwords_private::COMPROMISE_TYPE_LEAKED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername2, base::Days(800), "2 years ago", api::passwords_private::COMPROMISE_TYPE_LEAKED))); @@ -483,22 +535,22 @@ TEST_F(PasswordCheckDelegateTest, delegate().GetCompromisedCredentials(), ElementsAre( ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername1, base::Minutes(1), "1 minute ago", api::passwords_private::COMPROMISE_TYPE_PHISHED_AND_LEAKED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername1, base::Minutes(3), "3 minutes ago", api::passwords_private::COMPROMISE_TYPE_PHISHED), ExpectCompromisedCredential( - "example.org", "http://www.example.org", + "example.org", "http://www.example.org/", "http://www.example.org/.well-known/change-password", kUsername2, base::Minutes(4), "4 minutes ago", api::passwords_private::COMPROMISE_TYPE_PHISHED_AND_LEAKED), ExpectCompromisedCredential( - "example.com", "https://example.com", + "example.com", "https://example.com/", "https://example.com/.well-known/change-password", kUsername2, base::Minutes(2), "2 minutes ago", api::passwords_private::COMPROMISE_TYPE_LEAKED))); @@ -525,20 +577,23 @@ TEST_F(PasswordCheckDelegateTest, GetCompromisedCredentialsInjectsAndroid) { // password store. EXPECT_THAT( delegate().GetCompromisedCredentials(), - ElementsAre(ExpectCompromisedCredential( - "Example App", "Example App", - "https://example.com/.well-known/change-password", - kUsername2, base::Days(3), "3 days ago", - api::passwords_private::COMPROMISE_TYPE_PHISHED), - ExpectCompromisedCredential( - "App (com.example.app)", "com.example.app", absl::nullopt, - kUsername1, base::Days(4), "4 days ago", - api::passwords_private::COMPROMISE_TYPE_PHISHED), - ExpectCompromisedCredential( - "example.com", "https://example.com", - "https://example.com/.well-known/change-password", - kUsername1, base::Minutes(5), "5 minutes ago", - api::passwords_private::COMPROMISE_TYPE_LEAKED))); + ElementsAre( + ExpectCompromisedCredential( + "Example App", + "https://play.google.com/store/apps/details?id=com.example.app", + "https://example.com/.well-known/change-password", kUsername2, + base::Days(3), "3 days ago", + api::passwords_private::COMPROMISE_TYPE_PHISHED), + ExpectCompromisedCredential( + "app.example.com", + "https://play.google.com/store/apps/details?id=com.example.app", + absl::nullopt, kUsername1, base::Days(4), "4 days ago", + api::passwords_private::COMPROMISE_TYPE_PHISHED), + ExpectCompromisedCredential( + "example.com", "https://example.com/", + "https://example.com/.well-known/change-password", kUsername1, + base::Minutes(5), "5 minutes ago", + api::passwords_private::COMPROMISE_TYPE_LEAKED))); } // Test that a change to compromised credential notifies observers. @@ -568,214 +623,6 @@ TEST_F(PasswordCheckDelegateTest, OnGetCompromisedCredentials) { event_router_observer().events().at(kEventName)->histogram_value); } -TEST_F(PasswordCheckDelegateTest, GetPlaintextInsecurePasswordRejectsWrongId) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(0, credential.id); - - // Purposefully set a wrong id and verify that trying to get a plaintext - // password fails. - credential.id = 1; - EXPECT_EQ(absl::nullopt, - delegate().GetPlaintextInsecurePassword(std::move(credential))); -} - -TEST_F(PasswordCheckDelegateTest, - GetPlaintextInsecurePasswordRejectsWrongSignonRealm) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - - RunUntilIdle(); - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(kExampleCom, credential.signon_realm); - - // Purposefully set a wrong signon realm and verify that trying to get a - // plaintext password fails. - credential.signon_realm = kExampleOrg; - EXPECT_EQ(absl::nullopt, - delegate().GetPlaintextInsecurePassword(std::move(credential))); -} - -TEST_F(PasswordCheckDelegateTest, - GetPlaintextInsecurePasswordRejectsWrongUsername) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(base::UTF16ToASCII(kUsername1), credential.username); - - // Purposefully set a wrong username and verify that trying to get a - // plaintext password fails. - credential.signon_realm = base::UTF16ToASCII(kUsername2); - EXPECT_EQ(absl::nullopt, - delegate().GetPlaintextInsecurePassword(std::move(credential))); -} - -TEST_F(PasswordCheckDelegateTest, - GetPlaintextInsecurePasswordReturnsCorrectPassword) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(0, credential.id); - EXPECT_EQ(kExampleCom, credential.signon_realm); - EXPECT_EQ(base::UTF16ToASCII(kUsername1), credential.username); - EXPECT_EQ(nullptr, credential.password); - - absl::optional<InsecureCredential> opt_credential = - delegate().GetPlaintextInsecurePassword(std::move(credential)); - ASSERT_TRUE(opt_credential.has_value()); - EXPECT_EQ(0, opt_credential->id); - EXPECT_EQ(kExampleCom, opt_credential->signon_realm); - EXPECT_EQ(base::UTF16ToASCII(kUsername1), opt_credential->username); - EXPECT_EQ(base::UTF16ToASCII(kPassword1), *opt_credential->password); -} - -// Test that changing a insecure password fails if the ids don't match. -TEST_F(PasswordCheckDelegateTest, ChangeInsecureCredentialIdMismatch) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(0, credential.id); - credential.id = 1; - - EXPECT_FALSE(delegate().ChangeInsecureCredential(credential, "new_pass")); -} - -// Test that changing a insecure password fails if the underlying insecure -// credential no longer exists. -TEST_F(PasswordCheckDelegateTest, ChangeInsecureCredentialStaleData) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - - store().RemoveLogin(form); - RunUntilIdle(); - - EXPECT_FALSE(delegate().ChangeInsecureCredential(credential, "new_pass")); -} - -// Test that changing a insecure password succeeds. -TEST_F(PasswordCheckDelegateTest, ChangeInsecureCredentialSuccess) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(0, credential.id); - EXPECT_EQ(kExampleCom, credential.signon_realm); - EXPECT_EQ(base::UTF16ToASCII(kUsername1), credential.username); - EXPECT_EQ(kPassword1, - store().stored_passwords().at(kExampleCom).at(0).password_value); - - EXPECT_TRUE(delegate().ChangeInsecureCredential( - credential, base::UTF16ToASCII(kPassword2))); - RunUntilIdle(); - - EXPECT_EQ(kPassword2, - store().stored_passwords().at(kExampleCom).at(0).password_value); -} - -// Test that changing a insecure password removes duplicates from store. -// https://crbug.com/1334160 -TEST_F(PasswordCheckDelegateTest, - DISABLED_ChangeInsecureCredentialRemovesDupes) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - - PasswordForm duplicate_form = MakeSavedPassword( - kExampleCom, kUsername1, kPassword1, u"different_element"); - AddIssueToForm(&duplicate_form, InsecureType::kLeaked); - store().AddLogin(duplicate_form); - - RunUntilIdle(); - - EXPECT_EQ(2u, store().stored_passwords().at(kExampleCom).size()); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_TRUE(delegate().ChangeInsecureCredential( - credential, base::UTF16ToASCII(kPassword2))); - RunUntilIdle(); - - EXPECT_EQ(1u, store().stored_passwords().at(kExampleCom).size()); - EXPECT_EQ(kPassword2, - store().stored_passwords().at(kExampleCom).at(0).password_value); -} - -// Test that removing a insecure password fails if the ids don't match. -TEST_F(PasswordCheckDelegateTest, RemoveInsecureCredentialIdMismatch) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_EQ(0, credential.id); - credential.id = 1; - - EXPECT_FALSE(delegate().RemoveInsecureCredential(credential)); -} - -// Test that removing a insecure password fails if the underlying insecure -// credential no longer exists. -TEST_F(PasswordCheckDelegateTest, RemoveInsecureCredentialStaleData) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - store().RemoveLogin(form); - RunUntilIdle(); - - EXPECT_FALSE(delegate().RemoveInsecureCredential(credential)); -} - -// Test that removing a insecure password succeeds. -TEST_F(PasswordCheckDelegateTest, RemoveInsecureCredentialSuccess) { - PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); - AddIssueToForm(&form, InsecureType::kLeaked); - store().AddLogin(form); - RunUntilIdle(); - - InsecureCredential credential = - std::move(delegate().GetCompromisedCredentials().at(0)); - EXPECT_TRUE(delegate().RemoveInsecureCredential(credential)); - RunUntilIdle(); - EXPECT_TRUE(store().IsEmpty()); - - // Expect another removal of the same credential to fail. - EXPECT_FALSE(delegate().RemoveInsecureCredential(credential)); -} - // Test that muting a insecure password succeeds. TEST_F(PasswordCheckDelegateTest, MuteInsecureCredentialSuccess) { PasswordForm form = MakeSavedPassword(kExampleCom, kUsername1); @@ -783,7 +630,7 @@ TEST_F(PasswordCheckDelegateTest, MuteInsecureCredentialSuccess) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); EXPECT_TRUE(delegate().MuteInsecureCredential(credential)); RunUntilIdle(); @@ -806,7 +653,7 @@ TEST_F(PasswordCheckDelegateTest, MuteInsecureCredentialStaleData) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); store().RemoveLogin(form); RunUntilIdle(); @@ -821,7 +668,7 @@ TEST_F(PasswordCheckDelegateTest, MuteInsecureCredentialIdMismatch) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); EXPECT_EQ(0, credential.id); credential.id = 1; @@ -836,7 +683,7 @@ TEST_F(PasswordCheckDelegateTest, UnmuteInsecureCredentialSuccess) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); EXPECT_TRUE(delegate().UnmuteInsecureCredential(credential)); RunUntilIdle(); @@ -859,7 +706,7 @@ TEST_F(PasswordCheckDelegateTest, UnmuteInsecureCredentialStaleData) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); store().RemoveLogin(form); RunUntilIdle(); @@ -874,7 +721,7 @@ TEST_F(PasswordCheckDelegateTest, UnmuteInsecureCredentialIdMismatch) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); EXPECT_EQ(0, credential.id); credential.id = 1; @@ -889,7 +736,7 @@ TEST_F(PasswordCheckDelegateTest, RecordChangePasswordFlowStartedManual) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); ASSERT_EQ(base::UTF16ToASCII(kUsername1), credential.username); @@ -910,7 +757,7 @@ TEST_F(PasswordCheckDelegateTest, RecordChangePasswordFlowStartedAutomated) { store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); ASSERT_EQ(base::UTF16ToASCII(kUsername1), credential.username); @@ -925,6 +772,18 @@ TEST_F(PasswordCheckDelegateTest, RecordChangePasswordFlowStartedAutomated) { /*is_manual_flow=*/false); } +TEST_F(PasswordCheckDelegateTest, RefreshScriptsIfNecessary) { + base::OnceClosure refresh_callback = base::DoNothing(); + EXPECT_CALL(password_scripts_fetcher(), RefreshScriptsIfNecessary) + .WillOnce(MoveArg<0>(&refresh_callback)); + + MockRefreshScriptsIfNecessaryCallback callback; + delegate().RefreshScriptsIfNecessary(callback.Get()); + + EXPECT_CALL(callback, Run); + std::move(refresh_callback).Run(); +} + TEST_F(PasswordCheckDelegateTest, RecordChangePasswordFlowStartedForAppWithWebRealm) { // Create an insecure credential. @@ -934,7 +793,7 @@ TEST_F(PasswordCheckDelegateTest, store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); ASSERT_EQ(base::UTF16ToASCII(kUsername2), credential.username); @@ -957,7 +816,7 @@ TEST_F(PasswordCheckDelegateTest, store().AddLogin(form); RunUntilIdle(); - InsecureCredential credential = + PasswordUiEntry credential = std::move(delegate().GetCompromisedCredentials().at(0)); ASSERT_EQ(base::UTF16ToASCII(kUsername1), credential.username); @@ -1265,8 +1124,8 @@ TEST_F(PasswordCheckDelegateTest, OnCredentialDoneUpdatesProgress) { delegate().StartPasswordCheck(); EXPECT_EQ(events::PASSWORDS_PRIVATE_ON_PASSWORD_CHECK_STATUS_CHANGED, event_iter->second->histogram_value); - auto status = PasswordCheckStatus::FromValue( - event_iter->second->event_args->GetListDeprecated().front()); + auto status = + PasswordCheckStatus::FromValue(event_iter->second->event_args.front()); EXPECT_EQ(api::passwords_private::PASSWORD_CHECK_STATE_RUNNING, status->state); EXPECT_EQ(0, *status->already_processed); @@ -1275,8 +1134,8 @@ TEST_F(PasswordCheckDelegateTest, OnCredentialDoneUpdatesProgress) { static_cast<BulkLeakCheckDelegateInterface*>(service())->OnFinishedCredential( LeakCheckCredential(kUsername1, kPassword1), IsLeaked(false)); - status = PasswordCheckStatus::FromValue( - event_iter->second->event_args->GetListDeprecated().front()); + status = + PasswordCheckStatus::FromValue(event_iter->second->event_args.front()); EXPECT_EQ(api::passwords_private::PASSWORD_CHECK_STATE_RUNNING, status->state); EXPECT_EQ(2, *status->already_processed); @@ -1285,8 +1144,8 @@ TEST_F(PasswordCheckDelegateTest, OnCredentialDoneUpdatesProgress) { static_cast<BulkLeakCheckDelegateInterface*>(service())->OnFinishedCredential( LeakCheckCredential(kUsername2, kPassword2), IsLeaked(false)); - status = PasswordCheckStatus::FromValue( - event_iter->second->event_args->GetListDeprecated().front()); + status = + PasswordCheckStatus::FromValue(event_iter->second->event_args.front()); EXPECT_EQ(api::passwords_private::PASSWORD_CHECK_STATE_RUNNING, status->state); EXPECT_EQ(4, *status->already_processed); @@ -1326,7 +1185,7 @@ TEST_F(PasswordCheckDelegateTest, // Use a local delegate instead of |delegate()| so that the Password Store can // be set-up prior to constructing the object. SavedPasswordsPresenter new_presenter(&store()); - PasswordCheckDelegate delegate(&profile(), &new_presenter); + PasswordCheckDelegate delegate = CreateDelegate(&new_presenter); new_presenter.Init(); delegate.StartPasswordCheck(callback1.Get()); delegate.StartPasswordCheck(callback2.Get()); @@ -1366,4 +1225,210 @@ TEST_F(PasswordCheckDelegateTest, WellKnownChangePasswordUrl_androidrealm) { password_manager::kWellKnownChangePasswordPath); } +TEST_F(PasswordCheckDelegateTest, HasStartableScript) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kPasswordChange); + base::HistogramTester histogram_tester; + + identity_test_env().MakeAccountAvailable(kTestEmail); + // Enable password sync. + sync_service().SetActiveDataTypes(syncer::ModelTypeSet(syncer::PASSWORDS)); + + // Add two forms, but only one already has a known issue. + PasswordForm form1 = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + AddIssueToForm(&form1, InsecureType::kLeaked); + store().AddLogin(form1); + const url::Origin origin1 = url::Origin::Create(GURL(kExampleCom)); + + PasswordForm form2 = MakeSavedPassword(kExampleOrg, kUsername2, kPassword2); + store().AddLogin(form2); + const url::Origin origin2 = url::Origin::Create(GURL(kExampleOrg)); + + RunUntilIdle(); + + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable(origin1)) + .WillOnce(Return(false)); + + // Only the form with the known issue shows up and does not have a startable + // script. + EXPECT_THAT(delegate().GetCompromisedCredentials(), + UnorderedElementsAre(ExpectCredentialWithScriptInfo( + kUsername1, /*has_startable_script=*/false))); + + // Simulate a stale cache. + EXPECT_CALL(password_scripts_fetcher(), IsCacheStale).WillOnce(Return(true)); + + base::OnceClosure refresh_callback; + EXPECT_CALL(password_scripts_fetcher(), RefreshScriptsIfNecessary) + .WillOnce(MoveArg<0>(&refresh_callback)); + + MockStartPasswordCheckCallback start_callback1; + delegate().StartPasswordCheck(start_callback1.Get()); + + // Starting another check will indicate that the first one is still running. + MockStartPasswordCheckCallback start_callback2; + EXPECT_CALL(start_callback2, Run(BulkLeakCheckService::State::kRunning)); + delegate().StartPasswordCheck(start_callback2.Get()); + + EXPECT_CALL(start_callback1, Run(BulkLeakCheckService::State::kRunning)); + + // From now on, always return that scripts are available. + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable) + .WillRepeatedly(Return(true)); + + // Signal that scripts are fetched. + std::move(refresh_callback).Run(); + + static_cast<BulkLeakCheckDelegateInterface*>(service())->OnFinishedCredential( + LeakCheckCredential(kUsername1, kPassword1), IsLeaked(true)); + static_cast<BulkLeakCheckDelegateInterface*>(service())->OnFinishedCredential( + LeakCheckCredential(kUsername2, kPassword2), IsLeaked(true)); + RunUntilIdle(); + + EXPECT_THAT( + delegate().GetCompromisedCredentials(), + UnorderedElementsAre(ExpectCredentialWithScriptInfo( + kUsername1, /*has_startable_script=*/true), + ExpectCredentialWithScriptInfo( + kUsername2, /*has_startable_script=*/true))); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.BulkCheck.ScriptsCacheState", + PasswordCheckScriptsCacheState::kCacheStaleAndUiUpdate, 1); +} + +TEST_F(PasswordCheckDelegateTest, HasStartableScript_SyncDisabled) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kPasswordChange); + base::HistogramTester histogram_tester; + + identity_test_env().MakeAccountAvailable(kTestEmail); + // Disable password sync. + sync_service().SetActiveDataTypes(syncer::ModelTypeSet()); + + PasswordForm form1 = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + AddIssueToForm(&form1, InsecureType::kLeaked); + store().AddLogin(form1); + const url::Origin origin1 = url::Origin::Create(GURL(kExampleCom)); + + RunUntilIdle(); + + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable) + .WillRepeatedly(Return(true)); + + EXPECT_THAT(delegate().GetCompromisedCredentials(), + UnorderedElementsAre(ExpectCredentialWithScriptInfo( + kUsername1, /*has_startable_script=*/false))); + histogram_tester.ExpectTotalCount( + "PasswordManager.BulkCheck.ScriptsCacheState", 0u); +} + +TEST_F(PasswordCheckDelegateTest, HasStartableScript_FeatureDisabled) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature( + password_manager::features::kPasswordChange); + base::HistogramTester histogram_tester; + + identity_test_env().MakeAccountAvailable(kTestEmail); + // Enable password sync. + sync_service().SetActiveDataTypes(syncer::ModelTypeSet(syncer::PASSWORDS)); + + PasswordForm form1 = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + AddIssueToForm(&form1, InsecureType::kLeaked); + store().AddLogin(form1); + const url::Origin origin1 = url::Origin::Create(GURL(kExampleCom)); + + RunUntilIdle(); + + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable) + .WillRepeatedly(Return(true)); + + EXPECT_THAT(delegate().GetCompromisedCredentials(), + UnorderedElementsAre(ExpectCredentialWithScriptInfo( + kUsername1, /*has_startable_script=*/false))); + histogram_tester.ExpectTotalCount( + "PasswordManager.BulkCheck.ScriptsCacheState", 0u); +} + +TEST_F(PasswordCheckDelegateTest, HasStartableScript_CacheFresh) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kPasswordChange); + base::HistogramTester histogram_tester; + + identity_test_env().MakeAccountAvailable(kTestEmail); + // Enable password sync. + sync_service().SetActiveDataTypes(syncer::ModelTypeSet(syncer::PASSWORDS)); + + PasswordForm form1 = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + AddIssueToForm(&form1, InsecureType::kLeaked); + store().AddLogin(form1); + const url::Origin origin1 = url::Origin::Create(GURL(kExampleCom)); + + RunUntilIdle(); + + EXPECT_CALL(password_scripts_fetcher(), IsCacheStale).WillOnce(Return(false)); + EXPECT_CALL(password_scripts_fetcher(), RefreshScriptsIfNecessary).Times(0); + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable) + .WillRepeatedly(Return(true)); + + delegate().StartPasswordCheck(); + event_router_observer().ClearEvents(); + + RunUntilIdle(); + + // Check that no update event was fired. + EXPECT_FALSE(base::Contains( + event_router_observer().events(), + api::passwords_private::OnCompromisedCredentialsChanged::kEventName)); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.BulkCheck.ScriptsCacheState", + PasswordCheckScriptsCacheState::kCacheFresh, 1); +} + +TEST_F(PasswordCheckDelegateTest, + HasStartableScript_CredentialListUpdateAfterScriptsFetched) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kPasswordChange); + base::HistogramTester histogram_tester; + + identity_test_env().MakeAccountAvailable(kTestEmail); + // Enable password sync. + sync_service().SetActiveDataTypes(syncer::ModelTypeSet(syncer::PASSWORDS)); + + PasswordForm form1 = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + AddIssueToForm(&form1, InsecureType::kLeaked); + store().AddLogin(form1); + const url::Origin origin1 = url::Origin::Create(GURL(kExampleCom)); + + RunUntilIdle(); + + EXPECT_CALL(password_scripts_fetcher(), IsCacheStale).WillOnce(Return(true)); + base::OnceClosure refresh_callback; + EXPECT_CALL(password_scripts_fetcher(), RefreshScriptsIfNecessary) + .WillOnce(MoveArg<0>(&refresh_callback)); + + delegate().StartPasswordCheck(); + + EXPECT_CALL(password_scripts_fetcher(), IsScriptAvailable) + .WillRepeatedly(Return(true)); + + event_router_observer().ClearEvents(); + + // Signal that scripts are fetched. + std::move(refresh_callback).Run(); + RunUntilIdle(); + + // Check that an update event was fired after the scripts were fetched. + EXPECT_EQ(events::PASSWORDS_PRIVATE_ON_COMPROMISED_CREDENTIALS_INFO_CHANGED, + event_router_observer() + .events() + .at(api::passwords_private::OnCompromisedCredentialsChanged:: + kEventName) + ->histogram_value); + histogram_tester.ExpectUniqueSample( + "PasswordManager.BulkCheck.ScriptsCacheState", + PasswordCheckScriptsCacheState::kCacheStaleAndUiUpdate, 1); +} + } // namespace extensions 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 8ac904a0f2d..d266eaf8371 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 @@ -22,6 +22,7 @@ #include "components/sync/driver/sync_service.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_function_registry.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace extensions { @@ -52,15 +53,17 @@ ResponseAction PasswordsPrivateChangeSavedPasswordFunction::Run() { api::passwords_private::ChangeSavedPassword::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(parameters); - if (!GetDelegate(browser_context()) - ->ChangeSavedPassword(parameters->ids, parameters->params)) { - return RespondNow(Error( - "Could not change the password. Either the password is empty, the user " - "is not authenticated, vector of ids is empty or no matching password " - "could be found at least for one of the ids.")); + auto new_id = GetDelegate(browser_context()) + ->ChangeSavedPassword(parameters->id, parameters->params); + if (new_id.has_value()) { + return RespondNow(ArgumentList( + api::passwords_private::ChangeSavedPassword::Results::Create( + new_id.value()))); } - - return RespondNow(NoArguments()); + return RespondNow(Error( + "Could not change the password. Either the password is empty, the user " + "is not authenticated or no matching password could be found for the " + "id.")); } // PasswordsPrivateRemoveSavedPasswordFunction @@ -68,16 +71,8 @@ ResponseAction PasswordsPrivateRemoveSavedPasswordFunction::Run() { auto parameters = api::passwords_private::RemoveSavedPassword::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(parameters); - GetDelegate(browser_context())->RemoveSavedPasswords({parameters->id}); - return RespondNow(NoArguments()); -} - -// PasswordsPrivateRemoveSavedPasswordsFunction -ResponseAction PasswordsPrivateRemoveSavedPasswordsFunction::Run() { - auto parameters = - api::passwords_private::RemoveSavedPasswords::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(parameters); - GetDelegate(browser_context())->RemoveSavedPasswords(parameters->ids); + GetDelegate(browser_context()) + ->RemoveSavedPassword(parameters->id, parameters->from_stores); return RespondNow(NoArguments()); } @@ -86,16 +81,7 @@ ResponseAction PasswordsPrivateRemovePasswordExceptionFunction::Run() { auto parameters = api::passwords_private::RemovePasswordException::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(parameters); - GetDelegate(browser_context())->RemovePasswordExceptions({parameters->id}); - return RespondNow(NoArguments()); -} - -// PasswordsPrivateRemovePasswordExceptionsFunction -ResponseAction PasswordsPrivateRemovePasswordExceptionsFunction::Run() { - auto parameters = - api::passwords_private::RemovePasswordExceptions::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(parameters); - GetDelegate(browser_context())->RemovePasswordExceptions(parameters->ids); + GetDelegate(browser_context())->RemovePasswordException(parameters->id); return RespondNow(NoArguments()); } @@ -138,6 +124,40 @@ void PasswordsPrivateRequestPlaintextPasswordFunction::GotPassword( ->id))); } +// PasswordsPrivateRequestCredentialDetailsFunction +ResponseAction PasswordsPrivateRequestCredentialDetailsFunction::Run() { + auto parameters = + api::passwords_private::RequestCredentialDetails::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(parameters); + + GetDelegate(browser_context()) + ->RequestCredentialDetails( + parameters->id, + base::BindOnce(&PasswordsPrivateRequestCredentialDetailsFunction:: + GotPasswordUiEntry, + this), + GetSenderWebContents()); + + // GotPasswordUiEntry() might have responded before we reach this point. + return did_respond() ? AlreadyResponded() : RespondLater(); +} + +void PasswordsPrivateRequestCredentialDetailsFunction::GotPasswordUiEntry( + absl::optional<api::passwords_private::PasswordUiEntry> password_ui_entry) { + if (password_ui_entry) { + Respond(ArgumentList( + api::passwords_private::RequestCredentialDetails::Results::Create( + std::move(*password_ui_entry)))); + return; + } + + Respond(Error(base::StringPrintf( + "Could not obtain password entry. Either the user is not " + "authenticated or no credential with id = %d could be found.", + api::passwords_private::RequestCredentialDetails::Params::Create(args()) + ->id))); +} + // PasswordsPrivateGetSavedPasswordListFunction ResponseAction PasswordsPrivateGetSavedPasswordListFunction::Run() { // GetList() can immediately call GotList() (which would Respond() before @@ -197,8 +217,25 @@ ResponseAction PasswordsPrivateMovePasswordsToAccountFunction::Run() { // PasswordsPrivateImportPasswordsFunction ResponseAction PasswordsPrivateImportPasswordsFunction::Run() { - GetDelegate(browser_context())->ImportPasswords(GetSenderWebContents()); - return RespondNow(NoArguments()); + auto parameters = + api::passwords_private::ImportPasswords::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(parameters); + GetDelegate(browser_context()) + ->ImportPasswords( + parameters->to_store, + base::BindOnce( + &PasswordsPrivateImportPasswordsFunction::ImportRequestCompleted, + this), + GetSenderWebContents()); + + // `ImportRequestCompleted()` might respond before we reach this point. + return did_respond() ? AlreadyResponded() : RespondLater(); +} + +void PasswordsPrivateImportPasswordsFunction::ImportRequestCompleted( + const api::passwords_private::ImportResults& result) { + Respond(ArgumentList( + api::passwords_private::ImportPasswords::Results::Create(result))); } // PasswordsPrivateExportPasswordsFunction @@ -217,7 +254,7 @@ void PasswordsPrivateExportPasswordsFunction::ExportRequestCompleted( if (error.empty()) Respond(NoArguments()); else - Error(error); + Respond(Error(error)); } // PasswordsPrivateCancelExportPasswordsFunction @@ -270,87 +307,6 @@ ResponseAction PasswordsPrivateGetWeakCredentialsFunction::Run() { GetDelegate(browser_context())->GetWeakCredentials()))); } -// PasswordsPrivateGetPlaintextInsecurePasswordFunction: -PasswordsPrivateGetPlaintextInsecurePasswordFunction:: - ~PasswordsPrivateGetPlaintextInsecurePasswordFunction() = default; - -ResponseAction PasswordsPrivateGetPlaintextInsecurePasswordFunction::Run() { - auto parameters = - api::passwords_private::GetPlaintextInsecurePassword::Params::Create( - args()); - EXTENSION_FUNCTION_VALIDATE(parameters); - - GetDelegate(browser_context()) - ->GetPlaintextInsecurePassword( - std::move(parameters->credential), parameters->reason, - GetSenderWebContents(), - base::BindOnce(&PasswordsPrivateGetPlaintextInsecurePasswordFunction:: - GotCredential, - this)); - - // GotCredential() might respond before we reach this point. - return did_respond() ? AlreadyResponded() : RespondLater(); -} - -void PasswordsPrivateGetPlaintextInsecurePasswordFunction::GotCredential( - absl::optional<api::passwords_private::InsecureCredential> credential) { - if (!credential) { - Respond( - Error("Could not obtain plaintext insecure password. Either the user " - "is not authenticated or no matching password could be found.")); - return; - } - - Respond(ArgumentList( - api::passwords_private::GetPlaintextInsecurePassword::Results::Create( - *credential))); -} - -// PasswordsPrivateChangeInsecureCredentialFunction: -PasswordsPrivateChangeInsecureCredentialFunction:: - ~PasswordsPrivateChangeInsecureCredentialFunction() = default; - -ResponseAction PasswordsPrivateChangeInsecureCredentialFunction::Run() { - auto parameters = - api::passwords_private::ChangeInsecureCredential::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(parameters); - - if (parameters->new_password.empty()) { - return RespondNow( - Error("Could not change the insecure credential. The new password " - "can't be empty.")); - } - - if (!GetDelegate(browser_context()) - ->ChangeInsecureCredential(parameters->credential, - parameters->new_password)) { - return RespondNow(Error( - "Could not change the insecure credential. Either the user is not " - "authenticated or no matching password could be found.")); - } - - return RespondNow(NoArguments()); -} - -// PasswordsPrivateRemoveInsecureCredentialFunction: -PasswordsPrivateRemoveInsecureCredentialFunction:: - ~PasswordsPrivateRemoveInsecureCredentialFunction() = default; - -ResponseAction PasswordsPrivateRemoveInsecureCredentialFunction::Run() { - auto parameters = - api::passwords_private::RemoveInsecureCredential::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(parameters); - - if (!GetDelegate(browser_context()) - ->RemoveInsecureCredential(parameters->credential)) { - return RespondNow( - Error("Could not remove the insecure credential. Probably no matching " - "password could be found.")); - } - - return RespondNow(NoArguments()); -} - // PasswordsPrivateMuteInsecureCredentialFunction: PasswordsPrivateMuteInsecureCredentialFunction:: ~PasswordsPrivateMuteInsecureCredentialFunction() = default; @@ -405,6 +361,24 @@ ResponseAction PasswordsPrivateRecordChangePasswordFlowStartedFunction::Run() { return RespondNow(NoArguments()); } +// PasswordsPrivateRefreshScriptsIfNecessaryFunction: +PasswordsPrivateRefreshScriptsIfNecessaryFunction:: + ~PasswordsPrivateRefreshScriptsIfNecessaryFunction() = default; + +ResponseAction PasswordsPrivateRefreshScriptsIfNecessaryFunction::Run() { + GetDelegate(browser_context()) + ->RefreshScriptsIfNecessary(base::BindOnce( + &PasswordsPrivateRefreshScriptsIfNecessaryFunction::OnRefreshed, + base::RetainedRef(this))); + + // OnRefreshed() might respond before we reach this point. + return did_respond() ? AlreadyResponded() : RespondLater(); +} + +void PasswordsPrivateRefreshScriptsIfNecessaryFunction::OnRefreshed() { + Respond(NoArguments()); +} + // PasswordsPrivateStartPasswordCheckFunction: PasswordsPrivateStartPasswordCheckFunction:: ~PasswordsPrivateStartPasswordCheckFunction() = default; @@ -445,6 +419,35 @@ ResponseAction PasswordsPrivateGetPasswordCheckStatusFunction::Run() { GetDelegate(browser_context())->GetPasswordCheckStatus()))); } +// PasswordsPrivateStartAutomatedPasswordChangeFunction: +PasswordsPrivateStartAutomatedPasswordChangeFunction:: + ~PasswordsPrivateStartAutomatedPasswordChangeFunction() = default; + +ResponseAction PasswordsPrivateStartAutomatedPasswordChangeFunction::Run() { + auto parameters = + api::passwords_private::StartAutomatedPasswordChange::Params::Create( + args()); + EXTENSION_FUNCTION_VALIDATE(parameters); + + // Forward the call to the delegate. + GetDelegate(browser_context()) + ->StartAutomatedPasswordChange( + parameters->credential, + base::BindOnce(&PasswordsPrivateStartAutomatedPasswordChangeFunction:: + OnResultReceived, + base::RetainedRef(this))); + + // `OnResultReceived()` might respond before we reach this point. + return did_respond() ? AlreadyResponded() : RespondLater(); +} + +void PasswordsPrivateStartAutomatedPasswordChangeFunction::OnResultReceived( + bool success) { + Respond(ArgumentList( + api::passwords_private::StartAutomatedPasswordChange::Results::Create( + success))); +} + // PasswordsPrivateIsAccountStoreDefaultFunction ResponseAction PasswordsPrivateIsAccountStoreDefaultFunction::Run() { return RespondNow(OneArgument( 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 01878f1dc2d..ec17329c1b7 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 @@ -53,18 +53,6 @@ class PasswordsPrivateRemoveSavedPasswordFunction : public ExtensionFunction { ResponseAction Run() override; }; -class PasswordsPrivateRemoveSavedPasswordsFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removeSavedPasswords", - PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS) - - protected: - ~PasswordsPrivateRemoveSavedPasswordsFunction() override = default; - - // ExtensionFunction overrides. - ResponseAction Run() override; -}; - class PasswordsPrivateRemovePasswordExceptionFunction : public ExtensionFunction { public: @@ -78,19 +66,6 @@ class PasswordsPrivateRemovePasswordExceptionFunction ResponseAction Run() override; }; -class PasswordsPrivateRemovePasswordExceptionsFunction - : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removePasswordExceptions", - PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS) - - protected: - ~PasswordsPrivateRemovePasswordExceptionsFunction() override = default; - - // ExtensionFunction overrides. - ResponseAction Run() override; -}; - class PasswordsPrivateUndoRemoveSavedPasswordOrExceptionFunction : public ExtensionFunction { public: @@ -122,6 +97,23 @@ class PasswordsPrivateRequestPlaintextPasswordFunction void GotPassword(absl::optional<std::u16string> password); }; +class PasswordsPrivateRequestCredentialDetailsFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("passwordsPrivate.requestCredentialDetails", + PASSWORDSPRIVATE_REQUESTCREDENTIALDETAILS) + protected: + ~PasswordsPrivateRequestCredentialDetailsFunction() override = default; + + // ExtensionFunction overrides. + ResponseAction Run() override; + + private: + void GotPasswordUiEntry( + absl::optional<api::passwords_private::PasswordUiEntry> + password_ui_entry); +}; + class PasswordsPrivateGetSavedPasswordListFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("passwordsPrivate.getSavedPasswordList", @@ -178,6 +170,10 @@ class PasswordsPrivateImportPasswordsFunction : public ExtensionFunction { // ExtensionFunction overrides. ResponseAction Run() override; + + private: + void ImportRequestCompleted( + const api::passwords_private::ImportResults& results); }; class PasswordsPrivateExportPasswordsFunction : public ExtensionFunction { @@ -271,49 +267,6 @@ class PasswordsPrivateGetWeakCredentialsFunction : public ExtensionFunction { ResponseAction Run() override; }; -class PasswordsPrivateGetPlaintextInsecurePasswordFunction - : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("passwordsPrivate.getPlaintextInsecurePassword", - PASSWORDSPRIVATE_GETPLAINTEXTINSECUREPASSWORD) - - protected: - ~PasswordsPrivateGetPlaintextInsecurePasswordFunction() override; - - // ExtensionFunction overrides. - ResponseAction Run() override; - - private: - void GotCredential( - absl::optional<api::passwords_private::InsecureCredential> credential); -}; - -class PasswordsPrivateChangeInsecureCredentialFunction - : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("passwordsPrivate.changeInsecureCredential", - PASSWORDSPRIVATE_CHANGEINSECURECREDENTIAL) - - protected: - ~PasswordsPrivateChangeInsecureCredentialFunction() override; - - // ExtensionFunction overrides. - ResponseAction Run() override; -}; - -class PasswordsPrivateRemoveInsecureCredentialFunction - : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removeInsecureCredential", - PASSWORDSPRIVATE_REMOVEINSECURECREDENTIAL) - - protected: - ~PasswordsPrivateRemoveInsecureCredentialFunction() override; - - // ExtensionFunction overrides. - ResponseAction Run() override; -}; - class PasswordsPrivateMuteInsecureCredentialFunction : public ExtensionFunction { public: @@ -353,6 +306,22 @@ class PasswordsPrivateRecordChangePasswordFlowStartedFunction ResponseAction Run() override; }; +class PasswordsPrivateRefreshScriptsIfNecessaryFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("passwordsPrivate.refreshScriptsIfNecessary", + PASSWORDSPRIVATE_REFRESHSCRIPTSIFNECESSARY) + + protected: + ~PasswordsPrivateRefreshScriptsIfNecessaryFunction() override; + + // ExtensionFunction overrides. + ResponseAction Run() override; + + private: + void OnRefreshed(); +}; + class PasswordsPrivateStartPasswordCheckFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("passwordsPrivate.startPasswordCheck", @@ -393,6 +362,22 @@ class PasswordsPrivateGetPasswordCheckStatusFunction ResponseAction Run() override; }; +class PasswordsPrivateStartAutomatedPasswordChangeFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("passwordsPrivate.startAutomatedPasswordChange", + PASSWORDSPRIVATE_STARTAUTOMATEDPASSWORDCHANGE) + + protected: + ~PasswordsPrivateStartAutomatedPasswordChangeFunction() override; + + // ExtensionFunction overrides. + ResponseAction Run() override; + + private: + void OnResultReceived(bool success); +}; + class PasswordsPrivateIsAccountStoreDefaultFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("passwordsPrivate.isAccountStoreDefault", 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 e7eeb49b55c..f917abed9b2 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 @@ -168,25 +168,12 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, } IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - ChangeSavedPasswordWithOneIncorrectIdFromArrayFails) { - EXPECT_TRUE(RunPasswordsSubtest( - "changeSavedPasswordWithOneIncorrectIdFromArrayFails")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, ChangeSavedPasswordWithEmptyPasswordFails) { EXPECT_TRUE(RunPasswordsSubtest("changeSavedPasswordWithEmptyPasswordFails")) << message_; } IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - ChangeSavedPasswordWithEmptyArrayIdFails) { - EXPECT_TRUE(RunPasswordsSubtest("changeSavedPasswordWithEmptyArrayIdFails")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, ChangeSavedPasswordWithNoteSucceeds) { EXPECT_TRUE(RunPasswordsSubtest("ChangeSavedPasswordWithNoteSucceeds")) << message_; @@ -199,23 +186,11 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, } IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - RemoveAndUndoRemoveSavedPasswordsBatch) { - EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemoveSavedPasswordsBatch")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RemoveAndUndoRemovePasswordException) { EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordException")) << message_; } -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - RemoveAndUndoRemovePasswordExceptionsBatch) { - EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordExceptionsBatch")) - << message_; -} - IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestPlaintextPassword) { EXPECT_TRUE(RunPasswordsSubtest("requestPlaintextPassword")) << message_; } @@ -225,6 +200,15 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestPlaintextPasswordFails) { EXPECT_TRUE(RunPasswordsSubtest("requestPlaintextPasswordFails")) << message_; } +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestCredentialDetails) { + EXPECT_TRUE(RunPasswordsSubtest("requestCredentialDetails")) << message_; +} + +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestCredentialDetailsFails) { + ResetPlaintextPassword(); + EXPECT_TRUE(RunPasswordsSubtest("requestCredentialDetailsFails")) << message_; +} + IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, GetSavedPasswordList) { EXPECT_TRUE(RunPasswordsSubtest("getSavedPasswordList")) << message_; } @@ -272,35 +256,6 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, GetWeakCredentials) { EXPECT_TRUE(RunPasswordsSubtest("getWeakCredentials")) << message_; } -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, GetPlaintextInsecurePassword) { - EXPECT_TRUE(RunPasswordsSubtest("getPlaintextInsecurePassword")) << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - GetPlaintextInsecurePasswordFails) { - ResetPlaintextPassword(); - EXPECT_TRUE(RunPasswordsSubtest("getPlaintextInsecurePasswordFails")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - ChangeInsecureCredentialWithEmptyPasswordFails) { - EXPECT_TRUE( - RunPasswordsSubtest("changeInsecureCredentialWithEmptyPasswordFails")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, ChangeInsecureCredentialFails) { - EXPECT_TRUE(RunPasswordsSubtest("changeInsecureCredentialFails")) << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - ChangeInsecureCredentialSucceeds) { - AddCompromisedCredential(0); - EXPECT_TRUE(RunPasswordsSubtest("changeInsecureCredentialSucceeds")) - << message_; -} - IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, OptInForAccountStorage) { SetOptedInForAccountStorage(false); EXPECT_TRUE(RunPasswordsSubtest("optInForAccountStorage")) << message_; @@ -311,24 +266,6 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, OptOutForAccountStorage) { EXPECT_TRUE(RunPasswordsSubtest("optOutForAccountStorage")) << message_; } -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RemoveInsecureCredentialFails) { - EXPECT_TRUE(RunPasswordsSubtest("removeInsecureCredentialFails")) << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - RemoveInsecureCredentialSucceeds) { - AddCompromisedCredential(0); - EXPECT_TRUE(RunPasswordsSubtest("removeInsecureCredentialSucceeds")) - << message_; -} - -IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, - MuteInsecureCredentialSucceeds) { - AddCompromisedCredential(0); - EXPECT_TRUE(RunPasswordsSubtest("muteInsecureCredentialSucceeds")) - << message_; -} - IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, MuteInsecureCredentialFails) { EXPECT_TRUE(RunPasswordsSubtest("muteInsecureCredentialFails")) << message_; } @@ -367,6 +304,10 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, EXPECT_EQ(last_change_flow_url(), ""); } +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RefreshScriptsIfNecessary) { + EXPECT_TRUE(RunPasswordsSubtest("refreshScriptsIfNecessary")) << message_; +} + IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, StartPasswordCheck) { set_start_password_check_state( password_manager::BulkLeakCheckService::State::kRunning); @@ -393,6 +334,15 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, GetPasswordCheckStatus) { EXPECT_TRUE(RunPasswordsSubtest("getPasswordCheckStatus")) << message_; } +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, StartAutomatedPasswordChange) { + EXPECT_TRUE(RunPasswordsSubtest("startAutomatedPasswordChange")); +} + +IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, + StartAutomatedPasswordChangeWithEmptyUrl) { + EXPECT_TRUE(RunPasswordsSubtest("startAutomatedPasswordChangeWithEmptyUrl")); +} + IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, MovePasswordsToAccount) { EXPECT_TRUE(last_moved_passwords().empty()); EXPECT_TRUE(RunPasswordsSubtest("movePasswordsToAccount")) << message_; 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 6fc2e97c4d3..ef166c70330 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 @@ -12,12 +12,11 @@ #include "base/callback.h" #include "base/strings/string_piece_forward.h" -#include "chrome/browser/ui/passwords/settings/password_manager_presenter.h" -#include "chrome/browser/ui/passwords/settings/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/bulk_leak_check_service.h" #include "components/password_manager/core/browser/ui/export_progress_status.h" +#include "components/password_manager/core/browser/ui/import_results.h" #include "components/password_manager/core/browser/ui/insecure_credentials_manager.h" #include "extensions/browser/extension_function.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -33,14 +32,20 @@ namespace extensions { // import/export) and to notify listeners when these values have changed. class PasswordsPrivateDelegate : public KeyedService { public: + using ImportResultsCallback = + base::OnceCallback<void(const api::passwords_private::ImportResults&)>; + using PlaintextPasswordCallback = base::OnceCallback<void(absl::optional<std::u16string>)>; + using RequestCredentialDetailsCallback = base::OnceCallback<void( + absl::optional<api::passwords_private::PasswordUiEntry>)>; + + using RefreshScriptsIfNecessaryCallback = base::OnceClosure; using StartPasswordCheckCallback = base::OnceCallback<void(password_manager::BulkLeakCheckService::State)>; - using PlaintextInsecurePasswordCallback = base::OnceCallback<void( - absl::optional<api::passwords_private::InsecureCredential>)>; + using StartAutomatedPasswordChangeCallback = base::OnceCallback<void(bool)>; ~PasswordsPrivateDelegate() override = default; @@ -86,17 +91,21 @@ class PasswordsPrivateDelegate : public KeyedService { // Changes the username and password corresponding to |ids|. // |ids|: The ids for the password entries being updated. // |params|: The struct which holds the new username, password and note. - virtual bool ChangeSavedPassword( - const std::vector<int>& ids, + // Returns the ids if the change was successful (can be the same ids if the + // username and the password didn't change), nullopt otherwise. + virtual absl::optional<int> ChangeSavedPassword( + int id, const api::passwords_private::ChangeSavedPasswordParams& params) = 0; - // Removes the saved password entries corresponding to the |ids| generated for - // each entry of the password list. Any invalid id will be ignored. - virtual void RemoveSavedPasswords(const std::vector<int>& ids) = 0; + // Removes the saved password entry corresponding to the |id| in the + // specified |from_stores|. Any invalid id will be ignored. + virtual void RemoveSavedPassword( + int id, + api::passwords_private::PasswordStoreSet from_stores) = 0; - // Removes the password exceptions entries corresponding corresponding to - // |ids|. Any invalid id will be ignored. - virtual void RemovePasswordExceptions(const std::vector<int>& ids) = 0; + // Removes the password exception entry corresponding to |id|. Any invalid id + // will be ignored. + virtual void RemovePasswordException(int id) = 0; // Undoes the last removal of a saved password or exception. virtual void UndoRemoveSavedPasswordOrException() = 0; @@ -115,6 +124,20 @@ class PasswordsPrivateDelegate : public KeyedService { PlaintextPasswordCallback callback, content::WebContents* web_contents) = 0; + // Requests the full PasswordUiEntry (with filled password) with the given id. + // Returns the full PasswordUiEntry with |callback|. Returns |absl::nullopt| + // if no matching credential with |id| is found. + // |id| the id created when going over the list of saved passwords. + // |reason| The reason why the full PasswordUiEntry is requested. + // |callback| The callback that gets invoked with the PasswordUiEntry if it + // could be obtained successfully, or absl::nullopt otherwise. + // |web_contents| The web content object used as the UI; will be used to show + // an OS-level authentication dialog if necessary. + virtual void RequestCredentialDetails( + int id, + RequestCredentialDetailsCallback callback, + content::WebContents* web_contents) = 0; + // Moves a list of passwords currently stored on the device to being stored in // the signed-in, non-syncing Google Account. The result of any password is a // no-op if any of these is true: |id| is invalid; |id| corresponds to a @@ -125,7 +148,13 @@ class PasswordsPrivateDelegate : public KeyedService { // Trigger the password import procedure, allowing the user to select a file // containing passwords to import. - virtual void ImportPasswords(content::WebContents* web_contents) = 0; + // |to_store|: destination store (Device or Account) for imported passwords. + // |results_callback|: Used to communicate the status and summary of the + // import process. + virtual void ImportPasswords( + api::passwords_private::PasswordStoreSet to_store, + ImportResultsCallback results_callback, + content::WebContents* web_contents) = 0; // Trigger the password export procedure, allowing the user to save a file // containing their passwords. |callback| will be called with an error @@ -155,49 +184,34 @@ class PasswordsPrivateDelegate : public KeyedService { // Obtains information about compromised credentials. This includes the last // time a check was run, as well as all compromised credentials that are // present in the password store. - virtual std::vector<api::passwords_private::InsecureCredential> + virtual std::vector<api::passwords_private::PasswordUiEntry> GetCompromisedCredentials() = 0; // Obtains information about weak credentials. - virtual std::vector<api::passwords_private::InsecureCredential> + virtual std::vector<api::passwords_private::PasswordUiEntry> GetWeakCredentials() = 0; - // Requests the plaintext password for |credential| due to |reason|. If - // successful, |callback| gets invoked with the same |credential|, whose - // |password| field will be set. - virtual void GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - content::WebContents* web_contents, - PlaintextInsecurePasswordCallback callback) = 0; - - // Attempts to change the stored password of |credential| to |new_password|. - // Returns whether the change succeeded. - virtual bool ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) = 0; - - // Attempts to remove |credential| from the password store. Returns whether - // the remove succeeded. - virtual bool RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) = 0; - // Attempts to mute |credential| from the password store. Returns whether // the mute succeeded. virtual bool MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) = 0; + const api::passwords_private::PasswordUiEntry& credential) = 0; // Attempts to unmute |credential| from the password store. Returns whether // the unmute succeeded. virtual bool UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) = 0; + const api::passwords_private::PasswordUiEntry& credential) = 0; // Records that a change password flow was started for |credential| and // whether |is_manual_flow| applies to the flow. virtual void RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) = 0; + // Refreshes the cache for automatic password change scripts if that is stale + // and runs `callback` once that is complete. + virtual void RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) = 0; + // Requests to start a check for insecure passwords. Invokes |callback| // once a check is running or the request was stopped via StopPasswordCheck(). virtual void StartPasswordCheck(StartPasswordCheckCallback callback) = 0; @@ -208,6 +222,13 @@ class PasswordsPrivateDelegate : public KeyedService { virtual api::passwords_private::PasswordCheckStatus GetPasswordCheckStatus() = 0; + // Starts an automated password change flow for `credential` and returns + // whether the credential was changed successfully by calling `callback` with + // a boolean parameter. + virtual void StartAutomatedPasswordChange( + const api::passwords_private::PasswordUiEntry& credential, + StartAutomatedPasswordChangeCallback callback) = 0; + // Returns a pointer to the current instance of InsecureCredentialsManager. // Needed to get notified when compromised credentials are written out to // disk, since BulkLeakCheckService does not know about that step. diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.cc index 23603d68a95..bb72ab396e3 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.cc @@ -11,7 +11,6 @@ #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/sync_service_factory.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "extensions/browser/extension_system_provider.h" namespace extensions { @@ -34,9 +33,7 @@ PasswordsPrivateDelegateFactory* } PasswordsPrivateDelegateFactory::PasswordsPrivateDelegateFactory() - : BrowserContextKeyedServiceFactory( - "PasswordsPrivateDelegate", - BrowserContextDependencyManager::GetInstance()) { + : ProfileKeyedServiceFactory("PasswordsPrivateDelegate") { DependsOn(BulkLeakCheckServiceFactory::GetInstance()); DependsOn(PasswordStoreFactory::GetInstance()); DependsOn(SyncServiceFactory::GetInstance()); diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h index 428863cd748..9f1bea53fc4 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORDS_PRIVATE_DELEGATE_FACTORY_H_ #include "base/no_destructor.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace context { class BrowserContext; @@ -16,8 +16,7 @@ namespace extensions { class PasswordsPrivateDelegate; // Factory for creating PasswordPrivateDelegates. -class PasswordsPrivateDelegateFactory - : public BrowserContextKeyedServiceFactory { +class PasswordsPrivateDelegateFactory : public ProfileKeyedServiceFactory { public: static PasswordsPrivateDelegate* GetForBrowserContext( content::BrowserContext* browser_context, 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 03245145449..0835cea9506 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 @@ -10,44 +10,51 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/check.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/metrics/user_metrics_action.h" #include "base/notreached.h" -#include "base/numerics/safe_conversions.h" -#include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "chrome/browser/autofill_assistant/password_change/apc_client.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h" #include "chrome/browser/password_manager/account_password_store_factory.h" #include "chrome/browser/password_manager/chrome_password_manager_client.h" #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/identity_manager_factory.h" #include "chrome/browser/sync/sync_service_factory.h" -#include "chrome/browser/ui/passwords/ui_utils.h" +#include "chrome/browser/ui/browser_navigator.h" +#include "chrome/browser/ui/browser_navigator_params.h" #include "chrome/common/extensions/api/passwords_private.h" -#include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" #include "components/keyed_service/core/service_access_type.h" #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h" +#include "components/password_manager/core/browser/move_password_to_account_store_helper.h" #include "components/password_manager/core/browser/password_form.h" -#include "components/password_manager/core/browser/password_list_sorter.h" #include "components/password_manager/core/browser/password_manager_features_util.h" #include "components/password_manager/core/browser/password_manager_util.h" -#include "components/password_manager/core/browser/password_ui_utils.h" -#include "components/password_manager/core/browser/ui/plaintext_reason.h" +#include "components/password_manager/core/browser/password_sync_util.h" #include "components/password_manager/core/common/password_manager_features.h" #include "components/prefs/pref_service.h" #include "components/signin/public/base/signin_metrics.h" +#include "components/sync/driver/sync_service.h" +#include "components/url_formatter/elide_url.h" +#include "content/public/browser/navigation_handle.h" #include "content/public/browser/web_contents.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/l10n/l10n_util.h" #include "url/gurl.h" +#include "url/scheme_host_port.h" #if BUILDFLAG(IS_WIN) #include "chrome/browser/password_manager/password_manager_util_win.h" #endif #if BUILDFLAG(IS_MAC) +#include "chrome/browser/device_reauth/chrome_biometric_authenticator_factory.h" #include "chrome/browser/password_manager/password_manager_util_mac.h" #endif @@ -57,6 +64,8 @@ namespace { +using password_manager::CredentialUIEntry; + // The error message returned to the UI when Chrome refuses to start multiple // exports. const char kExportInProgress[] = "in-progress"; @@ -107,35 +116,94 @@ password_manager::ReauthPurpose GetReauthPurpose( return password_manager::ReauthPurpose::VIEW_PASSWORD; } -password_manager::PlaintextReason ConvertPlaintextReason( +password_manager::metrics_util::AccessPasswordInSettingsEvent +ConvertPlaintextReason( extensions::api::passwords_private::PlaintextReason reason) { switch (reason) { - case extensions::api::passwords_private::PLAINTEXT_REASON_VIEW: - return password_manager::PlaintextReason::kView; case extensions::api::passwords_private::PLAINTEXT_REASON_COPY: - return password_manager::PlaintextReason::kCopy; + return password_manager::metrics_util::ACCESS_PASSWORD_COPIED; + case extensions::api::passwords_private::PLAINTEXT_REASON_VIEW: + return password_manager::metrics_util::ACCESS_PASSWORD_VIEWED; case extensions::api::passwords_private::PLAINTEXT_REASON_EDIT: - return password_manager::PlaintextReason::kEdit; + return password_manager::metrics_util::ACCESS_PASSWORD_EDITED; case extensions::api::passwords_private::PLAINTEXT_REASON_NONE: - break; + NOTREACHED(); + return password_manager::metrics_util::ACCESS_PASSWORD_VIEWED; } +} +base::flat_set<password_manager::PasswordForm::Store> +ConvertToPasswordFormStores( + extensions::api::passwords_private::PasswordStoreSet store) { + switch (store) { + case extensions::api::passwords_private:: + PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT: + return {password_manager::PasswordForm::Store::kProfileStore, + password_manager::PasswordForm::Store::kAccountStore}; + case extensions::api::passwords_private::PASSWORD_STORE_SET_DEVICE: + return {password_manager::PasswordForm::Store::kProfileStore}; + case extensions::api::passwords_private::PASSWORD_STORE_SET_ACCOUNT: + return {password_manager::PasswordForm::Store::kAccountStore}; + default: + break; + } NOTREACHED(); - return password_manager::PlaintextReason::kView; + return {}; } -// Gets all the existing keys in |generator| corresponding to |ids|. If no key -// is found for an id, it is simply ignored. -std::vector<std::string> GetSortKeys( - const extensions::IdGenerator<std::string>& generator, - const std::vector<int> ids) { - std::vector<std::string> sort_keys; - sort_keys.reserve(ids.size()); - for (int id : ids) { - if (const std::string* sort_key = generator.TryGetKey(id)) - sort_keys.emplace_back(*sort_key); +extensions::api::passwords_private::PasswordUiEntry +CreatePasswordUiEntryFromCredentialUiEntry( + int id, + const CredentialUIEntry& credential) { + extensions::api::passwords_private::PasswordUiEntry entry; + entry.urls = extensions::CreateUrlCollectionFromCredential(credential); + entry.username = base::UTF16ToUTF8(credential.username); + // TODO(crbug.com/1345899): Fill the note field after authentication in + // OnRequestCredentialDetailsAuthResult + entry.note = + std::make_unique<std::string>(base::UTF16ToUTF8(credential.note.value)); + entry.id = id; + entry.stored_in = extensions::StoreSetFromCredential(credential); + entry.is_android_credential = + password_manager::IsValidAndroidFacetURI(credential.signon_realm); + if (!credential.federation_origin.opaque()) { + std::u16string formatted_origin = + url_formatter::FormatOriginForSecurityDisplay( + credential.federation_origin, + url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC); + + entry.federation_text = + std::make_unique<std::string>(l10n_util::GetStringFUTF8( + IDS_PASSWORDS_VIA_FEDERATION, formatted_origin)); } - return sort_keys; + return entry; +} + +extensions::api::passwords_private::ImportEntry ConvertImportEntry( + const password_manager::ImportEntry& entry) { + extensions::api::passwords_private::ImportEntry result; + result.status = + static_cast<extensions::api::passwords_private::ImportEntryStatus>( + entry.status); + result.url = entry.url; + result.username = entry.username; + return result; +} + +// Maps password_manager::ImportResults to +// extensions::api::passwords_private::ImportResults. +extensions::api::passwords_private::ImportResults ConvertImportResults( + const password_manager::ImportResults& results) { + extensions::api::passwords_private::ImportResults private_results; + private_results.status = + static_cast<extensions::api::passwords_private::ImportResultsStatus>( + results.status); + private_results.number_imported = results.number_imported; + private_results.file_name = results.file_name; + private_results.failed_imports.reserve(results.failed_imports.size()); + for (const auto& entry : results.failed_imports) + private_results.failed_imports.emplace_back(ConvertImportEntry(entry)); + return private_results; } } // namespace @@ -144,8 +212,6 @@ namespace extensions { PasswordsPrivateDelegateImpl::PasswordsPrivateDelegateImpl(Profile* profile) : profile_(profile), - password_manager_presenter_( - std::make_unique<PasswordManagerPresenter>(this)), saved_passwords_presenter_(PasswordStoreFactory::GetForProfile( profile, ServiceAccessType::EXPLICIT_ACCESS), @@ -153,13 +219,17 @@ PasswordsPrivateDelegateImpl::PasswordsPrivateDelegateImpl(Profile* profile) profile, ServiceAccessType::EXPLICIT_ACCESS)), password_manager_porter_(std::make_unique<PasswordManagerPorter>( + profile, &saved_passwords_presenter_, base::BindRepeating( &PasswordsPrivateDelegateImpl::OnPasswordsExportProgress, base::Unretained(this)))), password_access_authenticator_( base::BindRepeating(&PasswordsPrivateDelegateImpl::OsReauthCall, - base::Unretained(this))), + base::Unretained(this)), + base::BindRepeating( + &PasswordsPrivateDelegateImpl::OsReauthTimeoutCall, + base::Unretained(this))), password_account_storage_settings_watcher_( std::make_unique< password_manager::PasswordAccountStorageSettingsWatcher>( @@ -168,23 +238,18 @@ PasswordsPrivateDelegateImpl::PasswordsPrivateDelegateImpl(Profile* profile) base::BindRepeating(&PasswordsPrivateDelegateImpl:: OnAccountStorageOptInStateChanged, base::Unretained(this)))), - password_check_delegate_(profile, &saved_passwords_presenter_), + password_check_delegate_(profile, + &saved_passwords_presenter_, + &credential_id_generator_), current_entries_initialized_(false), - current_exceptions_initialized_(false), is_initialized_(false), web_contents_(nullptr) { - password_manager_presenter_->Initialize(); - password_manager_presenter_->UpdatePasswordLists(); + saved_passwords_presenter_.AddObserver(this); saved_passwords_presenter_.Init(); } -PasswordsPrivateDelegateImpl::~PasswordsPrivateDelegateImpl() {} - -void PasswordsPrivateDelegateImpl::SendSavedPasswordsList() { - PasswordsPrivateEventRouter* router = - PasswordsPrivateEventRouterFactory::GetForProfile(profile_); - if (router) - router->OnSavedPasswordsListChanged(current_entries_); +PasswordsPrivateDelegateImpl::~PasswordsPrivateDelegateImpl() { + saved_passwords_presenter_.RemoveObserver(this); } void PasswordsPrivateDelegateImpl::GetSavedPasswordsList( @@ -195,16 +260,9 @@ void PasswordsPrivateDelegateImpl::GetSavedPasswordsList( get_saved_passwords_list_callbacks_.push_back(std::move(callback)); } -void PasswordsPrivateDelegateImpl::SendPasswordExceptionsList() { - PasswordsPrivateEventRouter* router = - PasswordsPrivateEventRouterFactory::GetForProfile(profile_); - if (router) - router->OnPasswordExceptionsListChanged(current_exceptions_); -} - void PasswordsPrivateDelegateImpl::GetPasswordExceptionsList( ExceptionEntriesCallback callback) { - if (current_exceptions_initialized_) + if (current_entries_initialized_) std::move(callback).Run(current_exceptions_); else get_password_exception_list_callbacks_.push_back(std::move(callback)); @@ -237,83 +295,94 @@ bool PasswordsPrivateDelegateImpl::AddPassword( const std::u16string& note, bool use_account_store, content::WebContents* web_contents) { - password_manager::PasswordForm form; - form.url = password_manager_util::StripAuthAndParams( + password_manager::PasswordForm::Store store_to_use = + use_account_store ? password_manager::PasswordForm::Store::kAccountStore + : password_manager::PasswordForm::Store::kProfileStore; + CredentialUIEntry credential; + credential.url = password_manager_util::StripAuthAndParams( password_manager_util::ConstructGURLWithScheme(url)); - form.signon_realm = password_manager::GetSignonRealm(form.url); - form.username_value = username; - form.password_value = password; - form.notes.emplace_back(/*value=*/note, /*date_created=*/base::Time::Now()); - form.in_store = use_account_store - ? password_manager::PasswordForm::Store::kAccountStore - : password_manager::PasswordForm::Store::kProfileStore; - form.type = password_manager::PasswordForm::Type::kManuallyAdded; - bool success = saved_passwords_presenter_.AddPassword(form); + credential.signon_realm = password_manager::GetSignonRealm(credential.url); + credential.username = username; + credential.password = password; + credential.note = password_manager::PasswordNote( + /*value=*/note, /*date_created=*/base::Time::Now()); + credential.stored_in = {store_to_use}; + bool success = saved_passwords_presenter_.AddCredential(credential); auto* client = ChromePasswordManagerClient::FromWebContents(web_contents); DCHECK(client); // Update the default store to the last used one. if (success && client->GetPasswordFeatureManager()->IsOptedInForAccountStorage()) { - client->GetPasswordFeatureManager()->SetDefaultPasswordStore(form.in_store); + client->GetPasswordFeatureManager()->SetDefaultPasswordStore(store_to_use); } return success; } -bool PasswordsPrivateDelegateImpl::ChangeSavedPassword( - const std::vector<int>& ids, +absl::optional<int> PasswordsPrivateDelegateImpl::ChangeSavedPassword( + int id, const api::passwords_private::ChangeSavedPasswordParams& params) { - const std::vector<std::string> sort_keys = - GetSortKeys(password_id_generator_, ids); - - DCHECK(!sort_keys.empty()); - if (ids.empty() || sort_keys.size() != ids.size()) - return false; - - std::vector<password_manager::PasswordForm> forms_to_change; - - for (const auto& key : sort_keys) { - auto forms_for_key = password_manager_presenter_->GetPasswordsForKey(key); - if (forms_for_key.empty()) - return false; - for (const auto& form : forms_for_key) - forms_to_change.push_back(*form); - } + const CredentialUIEntry* original_credential = + credential_id_generator_.TryGetKey(id); + if (!original_credential) + return absl::nullopt; - std::u16string username = base::UTF8ToUTF16(params.username); - std::u16string password = base::UTF8ToUTF16(params.password); + CredentialUIEntry updated_credential = *original_credential; + updated_credential.username = base::UTF8ToUTF16(params.username); + updated_credential.password = base::UTF8ToUTF16(params.password); if (params.note) { - return saved_passwords_presenter_.EditSavedPasswords( - forms_to_change, username, password, base::UTF8ToUTF16(*params.note)); + updated_credential.note = password_manager::PasswordNote( + base::UTF8ToUTF16(*params.note), base::Time::Now()); + } + switch (saved_passwords_presenter_.EditSavedCredentials(*original_credential, + updated_credential)) { + case password_manager::SavedPasswordsPresenter::EditResult::kSuccess: + case password_manager::SavedPasswordsPresenter::EditResult::kNothingChanged: + break; + case password_manager::SavedPasswordsPresenter::EditResult::kNotFound: + case password_manager::SavedPasswordsPresenter::EditResult::kAlreadyExisits: + case password_manager::SavedPasswordsPresenter::EditResult::kEmptyPassword: + return absl::nullopt; } - return saved_passwords_presenter_.EditSavedPasswords(forms_to_change, - username, password); -} -void PasswordsPrivateDelegateImpl::RemoveSavedPasswords( - const std::vector<int>& ids) { - ExecuteFunction(base::BindOnce( - &PasswordsPrivateDelegateImpl::RemoveSavedPasswordsInternal, - base::Unretained(this), ids)); + return credential_id_generator_.GenerateId(updated_credential); } -void PasswordsPrivateDelegateImpl::RemoveSavedPasswordsInternal( - const std::vector<int>& ids) { - password_manager_presenter_->RemoveSavedPasswords( - GetSortKeys(password_id_generator_, ids)); +void PasswordsPrivateDelegateImpl::RemoveSavedPassword( + int id, + api::passwords_private::PasswordStoreSet from_stores) { + ExecuteFunction( + base::BindOnce(&PasswordsPrivateDelegateImpl::RemoveEntryInternal, + base::Unretained(this), id, from_stores)); } -void PasswordsPrivateDelegateImpl::RemovePasswordExceptions( - const std::vector<int>& ids) { - ExecuteFunction(base::BindOnce( - &PasswordsPrivateDelegateImpl::RemovePasswordExceptionsInternal, - base::Unretained(this), ids)); +void PasswordsPrivateDelegateImpl::RemoveEntryInternal( + int id, + api::passwords_private::PasswordStoreSet from_stores) { + const CredentialUIEntry* entry = credential_id_generator_.TryGetKey(id); + if (!entry) { + return; + } + + CredentialUIEntry copy = *entry; + copy.stored_in = ConvertToPasswordFormStores(from_stores); + + saved_passwords_presenter_.RemoveCredential(copy); + + if (entry->blocked_by_user) { + base::RecordAction( + base::UserMetricsAction("PasswordManager_RemovePasswordException")); + } else { + base::RecordAction( + base::UserMetricsAction("PasswordManager_RemoveSavedPassword")); + } } -void PasswordsPrivateDelegateImpl::RemovePasswordExceptionsInternal( - const std::vector<int>& ids) { - password_manager_presenter_->RemovePasswordExceptions( - GetSortKeys(exception_id_generator_, ids)); +void PasswordsPrivateDelegateImpl::RemovePasswordException(int id) { + ExecuteFunction(base::BindOnce( + &PasswordsPrivateDelegateImpl::RemoveEntryInternal, + base::Unretained(this), id, + api::passwords_private::PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT)); } void PasswordsPrivateDelegateImpl::UndoRemoveSavedPasswordOrException() { @@ -324,7 +393,7 @@ void PasswordsPrivateDelegateImpl::UndoRemoveSavedPasswordOrException() { void PasswordsPrivateDelegateImpl:: UndoRemoveSavedPasswordOrExceptionInternal() { - password_manager_presenter_->UndoRemoveSavedPasswordOrException(); + saved_passwords_presenter_.UndoLastRemoval(); } void PasswordsPrivateDelegateImpl::RequestPlaintextPassword( @@ -345,6 +414,23 @@ void PasswordsPrivateDelegateImpl::RequestPlaintextPassword( weak_ptr_factory_.GetWeakPtr(), id, reason, std::move(callback))); } +void PasswordsPrivateDelegateImpl::RequestCredentialDetails( + int id, + RequestCredentialDetailsCallback callback, + content::WebContents* web_contents) { + // Save |web_contents| so that it can be used later when OsReauthCall() 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; + password_access_authenticator_.EnsureUserIsAuthenticated( + GetReauthPurpose(api::passwords_private::PLAINTEXT_REASON_VIEW), + base::BindOnce( + &PasswordsPrivateDelegateImpl::OnRequestCredentialDetailsAuthResult, + weak_ptr_factory_.GetWeakPtr(), id, std::move(callback))); +} + void PasswordsPrivateDelegateImpl::OsReauthCall( password_manager::ReauthPurpose purpose, password_manager::PasswordAccessAuthenticator::AuthResultCallback @@ -355,8 +441,31 @@ void PasswordsPrivateDelegateImpl::OsReauthCall( web_contents_->GetTopLevelNativeWindow(), purpose); std::move(callback).Run(result); #elif BUILDFLAG(IS_MAC) - bool result = password_manager_util_mac::AuthenticateUser(purpose); - std::move(callback).Run(result); + if (base::FeatureList::IsEnabled( + password_manager::features::kBiometricAuthenticationInSettings)) { + scoped_refptr<device_reauth::BiometricAuthenticator> + biometric_authenticator = + ChromeBiometricAuthenticatorFactory::GetInstance() + ->GetOrCreateBiometricAuthenticator(); + base::OnceCallback<void()> on_reauth_completed = + base::BindOnce(&PasswordsPrivateDelegateImpl::OnReauthCompleted, + weak_ptr_factory_.GetWeakPtr()); + + biometric_authenticator->AuthenticateWithMessage( + device_reauth::BiometricAuthRequester::kPasswordsInSettings, + password_manager_util_mac::GetMessageForBiometricLoginPrompt(purpose), + std::move(callback).Then(std::move(on_reauth_completed))); + + // If AuthenticateWithMessage is called again(UI isn't blocked so user might + // click multiple times on the button), it invalidates the old request which + // triggers PasswordsPrivateDelegateImpl::OnReauthCompleted which resets + // biometric_authenticator_. Having a local variable solves that problem as + // there's a second scoped_refptr for the authenticator object. + biometric_authenticator_ = std::move(biometric_authenticator); + } else { + bool result = password_manager_util_mac::AuthenticateUser(purpose); + std::move(callback).Run(result); + } #elif BUILDFLAG(IS_CHROMEOS_ASH) bool result = IsOsReauthAllowedAsh(profile_, GetAuthTokenLifetimeForPurpose(purpose)); @@ -368,46 +477,45 @@ void PasswordsPrivateDelegateImpl::OsReauthCall( #endif } -Profile* PasswordsPrivateDelegateImpl::GetProfile() { - return profile_; +void PasswordsPrivateDelegateImpl::OsReauthTimeoutCall() { + PasswordsPrivateEventRouter* router = + PasswordsPrivateEventRouterFactory::GetForProfile(profile_); + if (router) + router->OnPasswordManagerAuthTimeout(); } -void PasswordsPrivateDelegateImpl::SetPasswordList( - const std::vector<std::unique_ptr<password_manager::PasswordForm>>& - password_list) { - // Create a list of PasswordUiEntry objects to send to observers. +void PasswordsPrivateDelegateImpl::SetCredentials( + const std::vector<CredentialUIEntry>& credentials) { + // Create lists of PasswordUiEntry and ExceptionEntry objects to send to + // observers. current_entries_.clear(); + current_exceptions_.clear(); - for (const auto& form : password_list) { - api::passwords_private::PasswordUiEntry entry; - entry.urls = CreateUrlCollectionFromForm(*form); - entry.username = base::UTF16ToUTF8(form->username_value); - const auto& note_itr = base::ranges::find_if( - form->notes, &std::u16string::empty, - &password_manager::PasswordNote::unique_display_name); - entry.password_note = - note_itr == form->notes.end() ? "" : base::UTF16ToUTF8(note_itr->value); - entry.id = password_id_generator_.GenerateId( - password_manager::CreateSortKey(*form)); - entry.frontend_id = password_frontend_id_generator_.GenerateId( - password_manager::CreateSortKey(*form, - password_manager::IgnoreStore(true))); - - if (!form->federation_origin.opaque()) { - entry.federation_text = - std::make_unique<std::string>(l10n_util::GetStringFUTF8( - IDS_PASSWORDS_VIA_FEDERATION, GetDisplayFederation(*form))); + for (const CredentialUIEntry& credential : credentials) { + int id = credential_id_generator_.GenerateId(credential); + if (credential.blocked_by_user) { + api::passwords_private::ExceptionEntry current_exception_entry; + current_exception_entry.urls = + CreateUrlCollectionFromCredential(credential); + current_exception_entry.id = id; + current_exceptions_.push_back(std::move(current_exception_entry)); + } else { + current_entries_.push_back( + CreatePasswordUiEntryFromCredentialUiEntry(id, credential)); } - - entry.from_account_store = form->IsUsingAccountStore(); - - current_entries_.push_back(std::move(entry)); } - SendSavedPasswordsList(); + if (current_entries_initialized_) { + DCHECK(get_saved_passwords_list_callbacks_.empty()); + DCHECK(get_password_exception_list_callbacks_.empty()); + } - DCHECK(!current_entries_initialized_ || - get_saved_passwords_list_callbacks_.empty()); + PasswordsPrivateEventRouter* router = + PasswordsPrivateEventRouterFactory::GetForProfile(profile_); + if (router) { + router->OnSavedPasswordsListChanged(current_entries_); + router->OnPasswordExceptionsListChanged(current_exceptions_); + } current_entries_initialized_ = true; InitializeIfNecessary(); @@ -415,36 +523,6 @@ void PasswordsPrivateDelegateImpl::SetPasswordList( for (auto& callback : get_saved_passwords_list_callbacks_) std::move(callback).Run(current_entries_); get_saved_passwords_list_callbacks_.clear(); -} - -void PasswordsPrivateDelegateImpl::SetPasswordExceptionList( - const std::vector<std::unique_ptr<password_manager::PasswordForm>>& - password_exception_list) { - // Creates a list of exceptions to send to observers. - current_exceptions_.clear(); - - for (const auto& form : password_exception_list) { - api::passwords_private::ExceptionEntry current_exception_entry; - current_exception_entry.urls = CreateUrlCollectionFromForm(*form); - current_exception_entry.id = exception_id_generator_.GenerateId( - password_manager::CreateSortKey(*form)); - current_exception_entry.frontend_id = - exception_frontend_id_generator_.GenerateId( - password_manager::CreateSortKey( - *form, password_manager::IgnoreStore(true))); - - current_exception_entry.from_account_store = form->IsUsingAccountStore(); - current_exceptions_.push_back(std::move(current_exception_entry)); - } - - SendPasswordExceptionsList(); - - DCHECK(!current_entries_initialized_ || - get_saved_passwords_list_callbacks_.empty()); - - current_exceptions_initialized_ = true; - InitializeIfNecessary(); - for (auto& callback : get_password_exception_list_callbacks_) std::move(callback).Run(current_exceptions_); get_password_exception_list_callbacks_.clear(); @@ -455,18 +533,46 @@ void PasswordsPrivateDelegateImpl::MovePasswordsToAccount( content::WebContents* web_contents) { auto* client = ChromePasswordManagerClient::FromWebContents(web_contents); DCHECK(client); - std::vector<std::string> sort_keys; + + if (!client->GetPasswordFeatureManager()->IsOptedInForAccountStorage() || + SyncServiceFactory::GetForProfile(profile_)->IsSyncFeatureEnabled()) { + return; + } + + std::vector<password_manager::PasswordForm> forms_to_move; for (int id : ids) { - if (const std::string* sort_key = password_id_generator_.TryGetKey(id)) - sort_keys.push_back(*sort_key); + const CredentialUIEntry* entry = credential_id_generator_.TryGetKey(id); + if (!entry) { + continue; + } + + std::vector<password_manager::PasswordForm> corresponding_forms = + saved_passwords_presenter_.GetCorrespondingPasswordForms(*entry); + if (corresponding_forms.empty()) { + continue; + } + + // password_manager::MovePasswordsToAccountStore() takes care of moving the + // entire equivalence class, so passing the first element is fine. + forms_to_move.push_back(std::move(corresponding_forms[0])); } - password_manager_presenter_->MovePasswordsToAccountStore(sort_keys, client); + + password_manager::MovePasswordsToAccountStore( + forms_to_move, client, + password_manager::metrics_util::MoveToAccountStoreTrigger:: + kExplicitlyTriggeredInSettings); } void PasswordsPrivateDelegateImpl::ImportPasswords( + api::passwords_private::PasswordStoreSet to_store, + ImportResultsCallback results_callback, content::WebContents* web_contents) { - password_manager_porter_->set_web_contents(web_contents); - password_manager_porter_->Load(); + DCHECK_NE(api::passwords_private::PasswordStoreSet:: + PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT, + to_store); + password_manager_porter_->Import( + web_contents, *ConvertToPasswordFormStores(to_store).begin(), + base::BindOnce(&ConvertImportResults).Then(std::move(results_callback))); } void PasswordsPrivateDelegateImpl::ExportPasswords( @@ -486,7 +592,7 @@ void PasswordsPrivateDelegateImpl::ExportPasswords( } void PasswordsPrivateDelegateImpl::CancelExportPasswords() { - password_manager_porter_->CancelStore(); + password_manager_porter_->CancelExport(); } api::passwords_private::ExportProgressStatus @@ -518,61 +624,38 @@ void PasswordsPrivateDelegateImpl::SetAccountStorageOptIn( signin_metrics::ReauthAccessPoint::kPasswordSettings, base::DoNothing()); } -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> PasswordsPrivateDelegateImpl::GetCompromisedCredentials() { return password_check_delegate_.GetCompromisedCredentials(); } -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> PasswordsPrivateDelegateImpl::GetWeakCredentials() { return password_check_delegate_.GetWeakCredentials(); } -void PasswordsPrivateDelegateImpl::GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - content::WebContents* web_contents, - PlaintextInsecurePasswordCallback callback) { - // TODO(crbug.com/495290): Pass the native window directly to the - // reauth-handling code. - web_contents_ = web_contents; - password_access_authenticator_.EnsureUserIsAuthenticated( - GetReauthPurpose(reason), - base::BindOnce(&PasswordsPrivateDelegateImpl:: - OnGetPlaintextInsecurePasswordAuthResult, - weak_ptr_factory_.GetWeakPtr(), std::move(credential), - reason, std::move(callback))); -} - -bool PasswordsPrivateDelegateImpl::ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) { - return password_check_delegate_.ChangeInsecureCredential(credential, - new_password); -} - -bool PasswordsPrivateDelegateImpl::RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { - return password_check_delegate_.RemoveInsecureCredential(credential); -} - bool PasswordsPrivateDelegateImpl::MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { return password_check_delegate_.MuteInsecureCredential(credential); } bool PasswordsPrivateDelegateImpl::UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { return password_check_delegate_.UnmuteInsecureCredential(credential); } void PasswordsPrivateDelegateImpl::RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) { password_check_delegate_.RecordChangePasswordFlowStarted(credential, is_manual_flow); } +void PasswordsPrivateDelegateImpl::RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) { + password_check_delegate_.RefreshScriptsIfNecessary(std::move(callback)); +} + void PasswordsPrivateDelegateImpl::StartPasswordCheck( StartPasswordCheckCallback callback) { password_check_delegate_.StartPasswordCheck(std::move(callback)); @@ -587,6 +670,38 @@ PasswordsPrivateDelegateImpl::GetPasswordCheckStatus() { return password_check_delegate_.GetPasswordCheckStatus(); } +void PasswordsPrivateDelegateImpl::StartAutomatedPasswordChange( + const api::passwords_private::PasswordUiEntry& credential, + StartAutomatedPasswordChangeCallback callback) { + if (!credential.change_password_url) { + std::move(callback).Run(false); + return; + } + + GURL url = + url::SchemeHostPort(GURL(*credential.change_password_url)).GetURL(); + if (!url.is_valid()) { + std::move(callback).Run(false); + return; + } + + NavigateParams params(profile_, url, + ui::PageTransition::PAGE_TRANSITION_LINK); + params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; + base::WeakPtr<content::NavigationHandle> navigation_handle = + Navigate(¶ms); + + if (!navigation_handle) { + std::move(callback).Run(false); + return; + } + + ApcClient* apc_client = ApcClient::GetOrCreateForWebContents( + navigation_handle.get()->GetWebContents()); + apc_client->Start(url, credential.username, + /*skip_login=*/false, std::move(callback)); +} + password_manager::InsecureCredentialsManager* PasswordsPrivateDelegateImpl::GetInsecureCredentialsManager() { return password_check_delegate_.GetInsecureCredentialsManager(); @@ -612,64 +727,63 @@ void PasswordsPrivateDelegateImpl::OnRequestPlaintextPasswordAuthResult( return; } - // Request the password. When it is retrieved, ShowPassword() will be called. - const std::string* sort_key = password_id_generator_.TryGetKey(id); - if (!sort_key) { + const CredentialUIEntry* entry = credential_id_generator_.TryGetKey(id); + if (!entry) { std::move(callback).Run(absl::nullopt); return; } if (reason == api::passwords_private::PLAINTEXT_REASON_COPY) { + ui::ScopedClipboardWriter clipboard_writer(ui::ClipboardBuffer::kCopyPaste); + clipboard_writer.WriteText(entry->password); + clipboard_writer.MarkAsConfidential(); // In case of copy we don't need to give password back to UI. callback // will receive either empty string in case of success or null otherwise. // Copying occurs here so javascript doesn't need plaintext password. - callback = base::BindOnce( - [](PlaintextPasswordCallback callback, - absl::optional<std::u16string> password) { - if (!password) { - std::move(callback).Run(absl::nullopt); - return; - } - ui::ScopedClipboardWriter clipboard_writer( - ui::ClipboardBuffer::kCopyPaste); - clipboard_writer.WriteText(*password); - clipboard_writer.MarkAsConfidential(); - std::move(callback).Run(std::u16string()); - }, - std::move(callback)); - } - - password_manager_presenter_->RequestPlaintextPassword( - *sort_key, ConvertPlaintextReason(reason), std::move(callback)); + std::move(callback).Run(std::u16string()); + } else { + std::move(callback).Run(entry->password); + } + EmitHistogramsForCredentialAccess(*entry, reason); } -void PasswordsPrivateDelegateImpl::OnExportPasswordsAuthResult( - base::OnceCallback<void(const std::string&)> accepted_callback, - content::WebContents* web_contents, +void PasswordsPrivateDelegateImpl::OnRequestCredentialDetailsAuthResult( + int id, + RequestCredentialDetailsCallback callback, bool authenticated) { if (!authenticated) { - std::move(accepted_callback).Run(kReauthenticationFailed); + std::move(callback).Run(absl::nullopt); return; } - password_manager_porter_->set_web_contents(web_contents); - bool accepted = password_manager_porter_->Store(); - std::move(accepted_callback) - .Run(accepted ? std::string() : kExportInProgress); + const CredentialUIEntry* credential = credential_id_generator_.TryGetKey(id); + if (!credential) { + std::move(callback).Run(absl::nullopt); + return; + } + + api::passwords_private::PasswordUiEntry password_ui_entry = + CreatePasswordUiEntryFromCredentialUiEntry(id, *credential); + password_ui_entry.password = + std::make_unique<std::string>(base::UTF16ToUTF8(credential->password)); + std::move(callback).Run(std::move(password_ui_entry)); + + EmitHistogramsForCredentialAccess( + *credential, api::passwords_private::PLAINTEXT_REASON_VIEW); } -void PasswordsPrivateDelegateImpl::OnGetPlaintextInsecurePasswordAuthResult( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - PlaintextInsecurePasswordCallback callback, +void PasswordsPrivateDelegateImpl::OnExportPasswordsAuthResult( + base::OnceCallback<void(const std::string&)> accepted_callback, + content::WebContents* web_contents, bool authenticated) { if (!authenticated) { - std::move(callback).Run(absl::nullopt); + std::move(accepted_callback).Run(kReauthenticationFailed); return; } - std::move(callback).Run(password_check_delegate_.GetPlaintextInsecurePassword( - std::move(credential))); + bool accepted = password_manager_porter_->Export(web_contents); + std::move(accepted_callback) + .Run(accepted ? std::string() : kExportInProgress); } void PasswordsPrivateDelegateImpl::OnAccountStorageOptInStateChanged() { @@ -683,12 +797,11 @@ void PasswordsPrivateDelegateImpl::OnAccountStorageOptInStateChanged() { void PasswordsPrivateDelegateImpl::Shutdown() { password_account_storage_settings_watcher_.reset(); password_manager_porter_.reset(); - password_manager_presenter_.reset(); + biometric_authenticator_.reset(); } -IdGenerator<std::string>& -PasswordsPrivateDelegateImpl::GetPasswordIdGeneratorForTesting() { - return password_id_generator_; +void PasswordsPrivateDelegateImpl::OnReauthCompleted() { + biometric_authenticator_.reset(); } void PasswordsPrivateDelegateImpl::ExecuteFunction(base::OnceClosure callback) { @@ -700,9 +813,13 @@ void PasswordsPrivateDelegateImpl::ExecuteFunction(base::OnceClosure callback) { pre_initialization_callbacks_.emplace_back(std::move(callback)); } +void PasswordsPrivateDelegateImpl::OnSavedPasswordsChanged( + password_manager::SavedPasswordsPresenter::SavedPasswordsView passwords) { + SetCredentials(saved_passwords_presenter_.GetSavedCredentials()); +} + void PasswordsPrivateDelegateImpl::InitializeIfNecessary() { - if (is_initialized_ || !current_entries_initialized_ || - !current_exceptions_initialized_) + if (is_initialized_ || !current_entries_initialized_) return; is_initialized_ = true; @@ -712,4 +829,24 @@ void PasswordsPrivateDelegateImpl::InitializeIfNecessary() { pre_initialization_callbacks_.clear(); } +void PasswordsPrivateDelegateImpl::EmitHistogramsForCredentialAccess( + const CredentialUIEntry& entry, + api::passwords_private::PlaintextReason reason) { + syncer::SyncService* sync_service = nullptr; + if (SyncServiceFactory::HasSyncService(profile_)) { + sync_service = SyncServiceFactory::GetForProfile(profile_); + } + if (password_manager::sync_util::IsSyncAccountCredential( + entry.url, entry.username, sync_service, + IdentityManagerFactory::GetForProfile(profile_))) { + base::RecordAction( + base::UserMetricsAction("PasswordManager_SyncCredentialShown")); + } + + UMA_HISTOGRAM_ENUMERATION( + "PasswordManager.AccessPasswordInSettings", + ConvertPlaintextReason(reason), + password_manager::metrics_util::ACCESS_PASSWORD_COUNT); +} + } // namespace extensions 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 733f5849f81..8c77c897001 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 @@ -9,6 +9,7 @@ #include <memory> #include <string> +#include <utility> #include <vector> #include "base/callback.h" @@ -19,13 +20,13 @@ #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h" #include "chrome/browser/ui/passwords/settings/password_manager_porter.h" -#include "chrome/browser/ui/passwords/settings/password_manager_presenter.h" -#include "chrome/browser/ui/passwords/settings/password_ui_view.h" #include "chrome/common/extensions/api/passwords_private.h" +#include "components/device_reauth/biometric_authenticator.h" #include "components/keyed_service/core/keyed_service.h" #include "components/password_manager/core/browser/password_access_authenticator.h" #include "components/password_manager/core/browser/password_account_storage_settings_watcher.h" #include "components/password_manager/core/browser/reauth_purpose.h" +#include "components/password_manager/core/browser/ui/credential_ui_entry.h" #include "components/password_manager/core/browser/ui/export_progress_status.h" #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" #include "extensions/browser/extension_function.h" @@ -40,8 +41,9 @@ class WebContents; namespace extensions { // Concrete PasswordsPrivateDelegate implementation. -class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, - public PasswordUIView { +class PasswordsPrivateDelegateImpl + : public PasswordsPrivateDelegate, + public password_manager::SavedPasswordsPresenter::Observer { public: explicit PasswordsPrivateDelegateImpl(Profile* profile); @@ -63,19 +65,26 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, const std::u16string& note, bool use_account_store, content::WebContents* web_contents) override; - bool ChangeSavedPassword( - const std::vector<int>& ids, + absl::optional<int> ChangeSavedPassword( + int id, const api::passwords_private::ChangeSavedPasswordParams& params) override; - void RemoveSavedPasswords(const std::vector<int>& ids) override; - void RemovePasswordExceptions(const std::vector<int>& ids) override; + void RemoveSavedPassword( + int id, + api::passwords_private::PasswordStoreSet from_stores) override; + void RemovePasswordException(int id) override; void UndoRemoveSavedPasswordOrException() override; void RequestPlaintextPassword(int id, api::passwords_private::PlaintextReason reason, PlaintextPasswordCallback callback, content::WebContents* web_contents) override; + void RequestCredentialDetails(int id, + RequestCredentialDetailsCallback callback, + content::WebContents* web_contents) override; void MovePasswordsToAccount(const std::vector<int>& ids, content::WebContents* web_contents) override; - void ImportPasswords(content::WebContents* web_contents) override; + void ImportPasswords(api::passwords_private::PasswordStoreSet to_store, + ImportResultsCallback results_callback, + content::WebContents* web_contents) override; void ExportPasswords( base::OnceCallback<void(const std::string&)> accepted_callback, content::WebContents* web_contents) override; @@ -86,48 +95,37 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, // TODO(crbug.com/1102294): Mimic the signature in PasswordFeatureManager. void SetAccountStorageOptIn(bool opt_in, content::WebContents* web_contents) override; - std::vector<api::passwords_private::InsecureCredential> + std::vector<api::passwords_private::PasswordUiEntry> GetCompromisedCredentials() override; - std::vector<api::passwords_private::InsecureCredential> GetWeakCredentials() + std::vector<api::passwords_private::PasswordUiEntry> GetWeakCredentials() override; - void GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - content::WebContents* web_contents, - PlaintextInsecurePasswordCallback callback) override; - bool ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) override; - bool RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; bool MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; + const api::passwords_private::PasswordUiEntry& credential) override; bool UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; + const api::passwords_private::PasswordUiEntry& credential) override; void RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) override; + void RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) override; void StartPasswordCheck(StartPasswordCheckCallback callback) override; void StopPasswordCheck() override; api::passwords_private::PasswordCheckStatus GetPasswordCheckStatus() override; + void StartAutomatedPasswordChange( + const api::passwords_private::PasswordUiEntry& credential, + StartAutomatedPasswordChangeCallback callback) override; password_manager::InsecureCredentialsManager* GetInsecureCredentialsManager() override; - // PasswordUIView implementation. - Profile* GetProfile() override; - void SetPasswordList( - const std::vector<std::unique_ptr<password_manager::PasswordForm>>& - password_list) override; - void SetPasswordExceptionList( - const std::vector<std::unique_ptr<password_manager::PasswordForm>>& - password_exception_list) override; - // KeyedService overrides: void Shutdown() override; - IdGenerator<std::string>& GetPasswordIdGeneratorForTesting(); - #if defined(UNIT_TEST) + int GetIdForCredential( + const password_manager::CredentialUIEntry& credential) { + return credential_id_generator_.GenerateId(credential); + } + // Use this in tests to mock the OS-level reauthentication. void set_os_reauth_call( password_manager::PasswordAccessAuthenticator::ReauthCallback @@ -138,6 +136,11 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, #endif // defined(UNIT_TEST) private: + // password_manager::SavedPasswordsPresenter::Observer implementation. + void OnSavedPasswordsChanged( + password_manager::SavedPasswordsPresenter::SavedPasswordsView passwords) + override; + // Called after the lists are fetched. Once both lists have been set, the // class is considered initialized and any queued functions (which could // not be executed immediately due to uninitialized data) are invoked. @@ -147,11 +150,12 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, // has been initialized or by deferring it until initialization has completed. void ExecuteFunction(base::OnceClosure callback); - void SendSavedPasswordsList(); - void SendPasswordExceptionsList(); + void SetCredentials( + const std::vector<password_manager::CredentialUIEntry>& credentials); - void RemoveSavedPasswordsInternal(const std::vector<int>& ids); - void RemovePasswordExceptionsInternal(const std::vector<int>& ids); + void RemoveEntryInternal( + int id, + api::passwords_private::PasswordStoreSet from_stores); void UndoRemoveSavedPasswordOrExceptionInternal(); // Callback for when the password list has been written to the destination. @@ -165,19 +169,18 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, PlaintextPasswordCallback callback, bool authenticated); + // Callback for RequestCredentialDetails() after authentication check. + void OnRequestCredentialDetailsAuthResult( + int id, + RequestCredentialDetailsCallback callback, + bool authenticated); + // Callback for ExportPasswords() after authentication check. void OnExportPasswordsAuthResult( base::OnceCallback<void(const std::string&)> accepted_callback, content::WebContents* web_contents, bool authenticated); - // Callback for GetPlaintextInsecurePassword() after authentication check. - void OnGetPlaintextInsecurePasswordAuthResult( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - PlaintextInsecurePasswordCallback callback, - bool authenticated); - void OnAccountStorageOptInStateChanged(); // Decides whether an authentication check is successful. Passes the result @@ -189,12 +192,20 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, password_manager::PasswordAccessAuthenticator::AuthResultCallback callback); + // Records user action and emits histogram values for retrieving |entry|. + void EmitHistogramsForCredentialAccess( + const password_manager::CredentialUIEntry& entry, + api::passwords_private::PlaintextReason reason); + + // Callback for biometric authentication after authentication check. + void OnReauthCompleted(); + + // Invokes PasswordsPrivateEventRouter::OnPasswordManagerAuthTimeout(). + void OsReauthTimeoutCall(); + // Not owned by this class. raw_ptr<Profile> profile_; - // Used to communicate with the password store. - std::unique_ptr<PasswordManagerPresenter> password_manager_presenter_; - // Used to add/edit passwords and to create |password_check_delegate_|. password_manager::SavedPasswordsPresenter saved_passwords_presenter_; @@ -214,23 +225,20 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, UiEntries current_entries_; ExceptionEntries current_exceptions_; - // Generators that map between sort keys used by |password_manager_presenter_| - // and ids used by the JavaScript front end. - IdGenerator<std::string> password_id_generator_; - IdGenerator<std::string> password_frontend_id_generator_; - IdGenerator<std::string> exception_id_generator_; - IdGenerator<std::string> exception_frontend_id_generator_; + // An id generator for saved passwords and blocked websites. + IdGenerator<password_manager::CredentialUIEntry, + int, + password_manager::CredentialUIEntry::Less> + credential_id_generator_; - // Whether SetPasswordList and SetPasswordExceptionList have been called, and - // whether this class has been initialized, meaning both have been called. + // Whether SetCredentials has been called, and whether this class has been + // initialized. bool current_entries_initialized_; - bool current_exceptions_initialized_; bool is_initialized_; // Vector of callbacks which are queued up before the password store has been - // initialized. Once both SetPasswordList() and SetPasswordExceptionList() - // have been called, this class is considered initialized and can these - // callbacks are invoked. + // initialized. Once SetCredentials() has been called, this class is + // considered initialized and can these callbacks are invoked. std::vector<base::OnceClosure> pre_initialization_callbacks_; std::vector<UiEntriesCallback> get_saved_passwords_list_callbacks_; std::vector<ExceptionEntriesCallback> get_password_exception_list_callbacks_; @@ -239,6 +247,9 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate, // NativeWindow for the window where the API was called. raw_ptr<content::WebContents> web_contents_; + // Biometric authenticator used to authenticate user on Mac in settings. + scoped_refptr<device_reauth::BiometricAuthenticator> biometric_authenticator_; + base::WeakPtrFactory<PasswordsPrivateDelegateImpl> weak_ptr_factory_{this}; }; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_browsertest.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_browsertest.cc new file mode 100644 index 00000000000..ad6469bccb6 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_browsertest.cc @@ -0,0 +1,79 @@ +// Copyright 2022 The Chromium Authors. All 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/passwords_private/passwords_private_delegate_impl.h" + +#include <memory> +#include <string> + +#include "base/test/mock_callback.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/autofill_assistant/password_change/apc_client.h" +#include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/ui_features.h" +#include "chrome/common/extensions/api/passwords_private.h" +#include "chrome/test/base/chrome_test_utils.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" +#include "url/gurl.h" + +namespace extensions { + +namespace { + +constexpr char kUsername[] = "Bob"; +constexpr char kUrl[] = "https://www.example.com"; + +class PasswordsPrivateDelegateImplBrowserTest : public InProcessBrowserTest { + public: + PasswordsPrivateDelegateImplBrowserTest() { + // Enable the unified side panel, as this is a prerequisite for the + // Automated Password Change flow to be startable. + feature_list.InitAndEnableFeature(features::kUnifiedSidePanel); + } + ~PasswordsPrivateDelegateImplBrowserTest() override = default; + + content::WebContents* web_contents() { + return chrome_test_utils::GetActiveWebContents(this); + } + + private: + base::test::ScopedFeatureList feature_list; +}; + +IN_PROC_BROWSER_TEST_F(PasswordsPrivateDelegateImplBrowserTest, + StartAutomatedPasswordChange) { + PasswordsPrivateDelegateImpl delegate(browser()->profile()); + + const GURL url(kUrl); + api::passwords_private::PasswordUiEntry credential; + credential.username = kUsername; + credential.change_password_url = std::make_unique<std::string>(kUrl); + base::MockCallback< + PasswordsPrivateDelegate::StartAutomatedPasswordChangeCallback> + apc_callback; + + content::TestNavigationObserver navigation_observer(url); + navigation_observer.StartWatchingNewWebContents(); + + delegate.StartAutomatedPasswordChange(credential, apc_callback.Get()); + navigation_observer.Wait(); + EXPECT_EQ(web_contents()->GetLastCommittedURL(), url); + + // The `ApcClient` is running. + ApcClient* apc_client = ApcClient::GetOrCreateForWebContents(web_contents()); + ASSERT_TRUE(apc_client); + EXPECT_TRUE(apc_client->IsRunning()); + + EXPECT_CALL(apc_callback, Run(false)).Times(1); + apc_client->Stop(); +} + +} // namespace + +} // namespace extensions 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 94807625cbf..7e45c869191 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 @@ -25,6 +25,7 @@ #include "chrome/browser/password_manager/account_password_store_factory.h" #include "chrome/browser/password_manager/chrome_password_manager_client.h" #include "chrome/browser/password_manager/password_manager_test_util.h" +#include "chrome/browser/sync/sync_service_factory.h" #include "chrome/common/extensions/api/passwords_private.h" #include "chrome/test/base/testing_profile.h" #include "components/password_manager/content/browser/password_manager_log_router_factory.h" @@ -37,6 +38,7 @@ #include "components/password_manager/core/browser/reauth_purpose.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/signin/public/base/signin_metrics.h" +#include "components/sync/driver/test_sync_service.h" #include "content/public/browser/browser_context.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_renderer_host.h" @@ -48,13 +50,13 @@ using MockReauthCallback = base::MockCallback< password_manager::PasswordAccessAuthenticator::ReauthCallback>; -using PasswordFormList = - std::vector<std::unique_ptr<password_manager::PasswordForm>>; using password_manager::ReauthPurpose; using password_manager::TestPasswordStore; using ::testing::_; using ::testing::Eq; +using ::testing::IsNull; using ::testing::Ne; +using ::testing::Pointee; using ::testing::Return; using ::testing::SizeIs; using ::testing::StrictMock; @@ -66,6 +68,8 @@ constexpr char kHistogramName[] = "PasswordManager.AccessPasswordInSettings"; using MockPlaintextPasswordCallback = base::MockCallback<PasswordsPrivateDelegate::PlaintextPasswordCallback>; +using MockRequestCredentialDetailsCallback = base::MockCallback< + PasswordsPrivateDelegate::RequestCredentialDetailsCallback>; class MockPasswordManagerClient : public ChromePasswordManagerClient { public: @@ -114,6 +118,25 @@ MockPasswordManagerClient::CreateForWebContentsAndGet( return mock_client; } +void SetUpSyncInTransportMode(Profile* profile) { + auto* sync_service = static_cast<syncer::TestSyncService*>( + SyncServiceFactory::GetInstance()->SetTestingFactoryAndUse( + profile, + base::BindRepeating( + [](content::BrowserContext*) -> std::unique_ptr<KeyedService> { + return std::make_unique<syncer::TestSyncService>(); + }))); + CoreAccountInfo account; + account.email = "foo@gmail.com"; + account.gaia = "foo"; + account.account_id = CoreAccountId::FromGaiaId(account.gaia); + sync_service->SetAccountInfo(account); + sync_service->SetDisableReasons({}); + sync_service->SetTransportState(syncer::SyncService::TransportState::ACTIVE); + sync_service->SetHasSyncConsent(false); + ASSERT_FALSE(sync_service->IsSyncFeatureEnabled()); +} + class PasswordEventObserver : public extensions::TestEventRouter::EventObserver { public: @@ -152,7 +175,7 @@ void PasswordEventObserver::OnBroadcastEvent(const extensions::Event& event) { if (event.event_name != event_name_) { return; } - event_args_ = event.event_args->Clone(); + event_args_ = base::Value(event.event_args.Clone()); } std::unique_ptr<KeyedService> BuildPasswordsPrivateEventRouter( @@ -161,21 +184,25 @@ std::unique_ptr<KeyedService> BuildPasswordsPrivateEventRouter( PasswordsPrivateEventRouter::Create(context)); } -password_manager::PasswordForm CreateSampleForm() { +password_manager::PasswordForm CreateSampleForm( + password_manager::PasswordForm::Store store = + password_manager::PasswordForm::Store::kProfileStore) { password_manager::PasswordForm form; form.signon_realm = "http://abc1.com"; form.url = GURL("http://abc1.com"); form.username_value = u"test@gmail.com"; form.password_value = u"test"; + form.in_store = store; return form; } MATCHER_P(PasswordUiEntryDataEquals, expected, "") { return testing::Value(expected.get().urls.link, arg.urls.link) && testing::Value(expected.get().username, arg.username) && - testing::Value(expected.get().password_note, arg.password_note) && - testing::Value(expected.get().from_account_store, - arg.from_account_store); + testing::Value(*expected.get().note, *arg.note) && + testing::Value(expected.get().stored_in, arg.stored_in) && + testing::Value(expected.get().is_android_credential, + arg.is_android_credential); } } // namespace @@ -192,7 +219,7 @@ class PasswordsPrivateDelegateImplTest : public testing::Test { ~PasswordsPrivateDelegateImplTest() override; // Sets up a testing password store and fills it with |forms|. - void SetUpPasswordStore(std::vector<password_manager::PasswordForm> forms); + void SetUpPasswordStores(std::vector<password_manager::PasswordForm> forms); // Sets up a testing EventRouter with a production // PasswordsPrivateEventRouter. @@ -204,7 +231,7 @@ class PasswordsPrivateDelegateImplTest : public testing::Test { content::BrowserTaskEnvironment task_environment_; TestingProfile profile_; raw_ptr<extensions::TestEventRouter> event_router_ = nullptr; - scoped_refptr<TestPasswordStore> store_ = + scoped_refptr<TestPasswordStore> profile_store_ = CreateAndUseTestPasswordStore(&profile_); scoped_refptr<TestPasswordStore> account_store_ = CreateAndUseTestAccountPasswordStore(&profile_); @@ -217,16 +244,22 @@ class PasswordsPrivateDelegateImplTest : public testing::Test { PasswordsPrivateDelegateImplTest::PasswordsPrivateDelegateImplTest() { SetUpRouters(); + SetUpSyncInTransportMode(&profile_); } PasswordsPrivateDelegateImplTest::~PasswordsPrivateDelegateImplTest() { ui::Clipboard::DestroyClipboardForCurrentThread(); } -void PasswordsPrivateDelegateImplTest::SetUpPasswordStore( +void PasswordsPrivateDelegateImplTest::SetUpPasswordStores( std::vector<password_manager::PasswordForm> forms) { for (const password_manager::PasswordForm& form : forms) { - store_->AddLogin(form); + if (form.IsUsingAccountStore()) + account_store_->AddLogin(form); + else if (form.IsUsingProfileStore()) + profile_store_->AddLogin(form); + else + NOTREACHED() << "Store not set"; } // Spin the loop to allow PasswordStore tasks being processed. base::RunLoop().RunUntilIdle(); @@ -248,44 +281,32 @@ TEST_F(PasswordsPrivateDelegateImplTest, GetSavedPasswordsList) { EXPECT_CALL(callback, Run).Times(0); delegate.GetSavedPasswordsList(callback.Get()); - PasswordFormList list; - list.push_back(std::make_unique<password_manager::PasswordForm>()); - EXPECT_CALL(callback, Run); - delegate.SetPasswordList(list); + SetUpPasswordStores({}); EXPECT_CALL(callback, Run); delegate.GetSavedPasswordsList(callback.Get()); } TEST_F(PasswordsPrivateDelegateImplTest, - PasswordsDuplicatedInStoresHaveSameFrontendId) { + PasswordsDuplicatedInStoresAreRepresentedAsSingleEntity) { PasswordsPrivateDelegateImpl delegate(&profile_); - auto account_password = std::make_unique<password_manager::PasswordForm>(); - account_password->in_store = - password_manager::PasswordForm::Store::kAccountStore; - auto profile_password = std::make_unique<password_manager::PasswordForm>(); - profile_password->in_store = - password_manager::PasswordForm::Store::kProfileStore; - - PasswordFormList list; - list.push_back(std::move(account_password)); - list.push_back(std::move(profile_password)); + password_manager::PasswordForm account_password = + CreateSampleForm(password_manager::PasswordForm::Store::kAccountStore); + password_manager::PasswordForm profile_password = + CreateSampleForm(password_manager::PasswordForm::Store::kProfileStore); - delegate.SetPasswordList(list); + SetUpPasswordStores({account_password, profile_password}); base::MockCallback<PasswordsPrivateDelegate::UiEntriesCallback> callback; - int first_frontend_id, second_frontend_id; - EXPECT_CALL(callback, Run(SizeIs(2))) + EXPECT_CALL(callback, Run(SizeIs(1))) .WillOnce([&](const PasswordsPrivateDelegate::UiEntries& passwords) { - first_frontend_id = passwords[0].frontend_id; - second_frontend_id = passwords[1].frontend_id; + EXPECT_EQ(api::passwords_private::PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT, + passwords[0].stored_in); }); delegate.GetSavedPasswordsList(callback.Get()); - - EXPECT_EQ(first_frontend_id, second_frontend_id); } TEST_F(PasswordsPrivateDelegateImplTest, GetPasswordExceptionsList) { @@ -296,48 +317,34 @@ TEST_F(PasswordsPrivateDelegateImplTest, GetPasswordExceptionsList) { EXPECT_CALL(callback, Run).Times(0); delegate.GetPasswordExceptionsList(callback.Get()); - PasswordFormList list; - list.push_back(std::make_unique<password_manager::PasswordForm>()); - EXPECT_CALL(callback, Run); - delegate.SetPasswordExceptionList(list); + SetUpPasswordStores({}); EXPECT_CALL(callback, Run); delegate.GetPasswordExceptionsList(callback.Get()); } TEST_F(PasswordsPrivateDelegateImplTest, - ExceptionsDuplicatedInStoresHaveSameFrontendId) { + ExceptionsDuplicatedInStoresAreRepresentedAsSingleEntity) { PasswordsPrivateDelegateImpl delegate(&profile_); - - auto account_exception = std::make_unique<password_manager::PasswordForm>(); - account_exception->blocked_by_user = true; - account_exception->in_store = + password_manager::PasswordForm account_exception; + account_exception.blocked_by_user = true; + account_exception.url = GURL("https://test.com"); + account_exception.in_store = password_manager::PasswordForm::Store::kAccountStore; - auto profile_exception = std::make_unique<password_manager::PasswordForm>(); - profile_exception->blocked_by_user = true; - profile_exception->in_store = + password_manager::PasswordForm profile_exception; + profile_exception.url = GURL("https://test.com"); + profile_exception.blocked_by_user = true; + profile_exception.in_store = password_manager::PasswordForm::Store::kProfileStore; - PasswordFormList list; - list.push_back(std::move(account_exception)); - list.push_back(std::move(profile_exception)); - - delegate.SetPasswordExceptionList(list); + SetUpPasswordStores({account_exception, profile_exception}); base::MockCallback<PasswordsPrivateDelegate::ExceptionEntriesCallback> callback; - int first_frontend_id, second_frontend_id; - EXPECT_CALL(callback, Run(SizeIs(2))) - .WillOnce( - [&](const PasswordsPrivateDelegate::ExceptionEntries& exceptions) { - first_frontend_id = exceptions[0].frontend_id; - second_frontend_id = exceptions[1].frontend_id; - }); + EXPECT_CALL(callback, Run(SizeIs(1))); delegate.GetPasswordExceptionsList(callback.Get()); - - EXPECT_EQ(first_frontend_id, second_frontend_id); } TEST_F(PasswordsPrivateDelegateImplTest, AddPassword) { @@ -376,13 +383,14 @@ TEST_F(PasswordsPrivateDelegateImplTest, AddPassword) { api::passwords_private::PasswordUiEntry expected_entry1; expected_entry1.urls.link = "https://example1.com/"; expected_entry1.username = "username1"; - expected_entry1.password_note = ""; - expected_entry1.from_account_store = true; + expected_entry1.note = std::make_unique<std::string>(); + expected_entry1.stored_in = + api::passwords_private::PASSWORD_STORE_SET_ACCOUNT; api::passwords_private::PasswordUiEntry expected_entry2; expected_entry2.urls.link = "http://example2.com/login"; expected_entry2.username = ""; - expected_entry2.password_note = "note"; - expected_entry2.from_account_store = false; + expected_entry2.note = std::make_unique<std::string>("note"); + expected_entry2.stored_in = api::passwords_private::PASSWORD_STORE_SET_DEVICE; EXPECT_CALL(callback, Run(testing::UnorderedElementsAre( PasswordUiEntryDataEquals(testing::ByRef(expected_entry1)), @@ -428,7 +436,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, AddPasswordUpdatesDefaultStore) { TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPassword) { password_manager::PasswordForm sample_form = CreateSampleForm(); - SetUpPasswordStore({sample_form}); + SetUpPasswordStores({sample_form}); PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -444,13 +452,20 @@ TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPassword) { base::UTF8ToUTF16(passwords[0].username)); }); delegate.GetSavedPasswordsList(callback.Get()); - int sample_form_id = delegate.GetPasswordIdGeneratorForTesting().GenerateId( - password_manager::CreateSortKey(sample_form)); + int sample_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(sample_form)); api::passwords_private::ChangeSavedPasswordParams params; params.password = "new_pass"; params.username = "new_user"; - EXPECT_TRUE(delegate.ChangeSavedPassword({sample_form_id}, params)); + + sample_form.username_value = u"new_user"; + sample_form.password_value = u"new_pass"; + int new_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(sample_form)); + + auto result = delegate.ChangeSavedPassword(sample_form_id, params); + EXPECT_EQ(result, new_form_id); // Spin the loop to allow PasswordStore tasks posted when changing the // password to be completed. @@ -460,7 +475,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPassword) { EXPECT_CALL(callback, Run(SizeIs(1))) .WillOnce([](const PasswordsPrivateDelegate::UiEntries& passwords) { EXPECT_EQ("new_user", passwords[0].username); - EXPECT_EQ("", passwords[0].password_note); + EXPECT_THAT(passwords[0].note, Pointee(Eq(""))); }); delegate.GetSavedPasswordsList(callback.Get()); } @@ -472,7 +487,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPasswordWithNote) { /*date_created=*/base::Time::Now(), /*hide_by_default=*/true); sample_form.notes.emplace_back(u"note with empty display name", /*date_created=*/base::Time::Now()); - SetUpPasswordStore({sample_form}); + SetUpPasswordStores({sample_form}); PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -486,18 +501,25 @@ TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPasswordWithNote) { .WillOnce([&](const PasswordsPrivateDelegate::UiEntries& passwords) { EXPECT_EQ(sample_form.username_value, base::UTF8ToUTF16(passwords[0].username)); - EXPECT_EQ(sample_form.notes[1].value, - base::UTF8ToUTF16(passwords[0].password_note)); + EXPECT_THAT(passwords[0].note, + Pointee(Eq(base::UTF16ToUTF8(sample_form.notes[1].value)))); }); delegate.GetSavedPasswordsList(callback.Get()); - int sample_form_id = delegate.GetPasswordIdGeneratorForTesting().GenerateId( - password_manager::CreateSortKey(sample_form)); + int sample_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(sample_form)); api::passwords_private::ChangeSavedPasswordParams params; params.password = "new_pass"; params.username = "new_user"; params.note = std::make_unique<std::string>("new note"); - EXPECT_TRUE(delegate.ChangeSavedPassword({sample_form_id}, params)); + + sample_form.password_value = u"new_pass"; + sample_form.username_value = u"new_user"; + int new_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(sample_form)); + + auto result = delegate.ChangeSavedPassword(sample_form_id, params); + EXPECT_EQ(result, new_form_id); // Spin the loop to allow PasswordStore tasks posted when changing the // password to be completed. @@ -507,16 +529,81 @@ TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPasswordWithNote) { EXPECT_CALL(callback, Run(SizeIs(1))) .WillOnce([](const PasswordsPrivateDelegate::UiEntries& passwords) { EXPECT_EQ("new_user", passwords[0].username); - EXPECT_EQ("new note", passwords[0].password_note); + EXPECT_THAT(passwords[0].note, Pointee(Eq("new note"))); }); delegate.GetSavedPasswordsList(callback.Get()); } +TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPasswordInBothStores) { + password_manager::PasswordForm profile_form = CreateSampleForm(); + password_manager::PasswordForm account_form = profile_form; + account_form.in_store = password_manager::PasswordForm::Store::kAccountStore; + SetUpPasswordStores({profile_form, account_form}); + + PasswordsPrivateDelegateImpl delegate(&profile_); + // Spin the loop to allow PasswordStore tasks posted on the creation of + // |delegate| to be completed. + base::RunLoop().RunUntilIdle(); + + int profile_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(profile_form)); + int account_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(account_form)); + + ASSERT_EQ(profile_form_id, account_form_id); + + api::passwords_private::ChangeSavedPasswordParams params; + params.password = "new_pass"; + params.username = "new_user"; + + profile_form.username_value = u"new_user"; + profile_form.password_value = u"new_pass"; + int new_profile_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(profile_form)); + account_form.username_value = u"new_user"; + account_form.password_value = u"new_pass"; + int new_account_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(account_form)); + + ASSERT_EQ(new_profile_form_id, new_account_form_id); + + EXPECT_EQ(new_profile_form_id, + delegate.ChangeSavedPassword(profile_form_id, params)); +} + +TEST_F(PasswordsPrivateDelegateImplTest, ChangeSavedPasswordInAccountStore) { + password_manager::PasswordForm profile_form = CreateSampleForm(); + profile_form.password_value = u"different_pass"; + password_manager::PasswordForm account_form = CreateSampleForm(); + account_form.in_store = password_manager::PasswordForm::Store::kAccountStore; + SetUpPasswordStores({profile_form, account_form}); + + PasswordsPrivateDelegateImpl delegate(&profile_); + // Spin the loop to allow PasswordStore tasks posted on the creation of + // |delegate| to be completed. + base::RunLoop().RunUntilIdle(); + + int account_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(account_form)); + + api::passwords_private::ChangeSavedPasswordParams params; + params.password = "new_pass"; + params.username = "new_user"; + + account_form.username_value = u"new_user"; + account_form.password_value = u"new_pass"; + int new_account_form_id = delegate.GetIdForCredential( + password_manager::CredentialUIEntry(account_form)); + + auto result = delegate.ChangeSavedPassword(account_form_id, params); + EXPECT_THAT(result, new_account_form_id); +} + // Checking callback result of RequestPlaintextPassword with reason Copy. // By implementation for Copy, callback will receive empty string. TEST_F(PasswordsPrivateDelegateImplTest, TestCopyPasswordCallbackResult) { password_manager::PasswordForm form = CreateSampleForm(); - SetUpPasswordStore({form}); + SetUpPasswordStores({form}); PasswordsPrivateDelegateImpl delegate(&profile_); base::RunLoop().RunUntilIdle(); @@ -589,7 +676,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, } TEST_F(PasswordsPrivateDelegateImplTest, TestCopyPasswordCallbackResultFail) { - SetUpPasswordStore({CreateSampleForm()}); + SetUpPasswordStores({CreateSampleForm()}); PasswordsPrivateDelegateImpl delegate(&profile_); base::RunLoop().RunUntilIdle(); @@ -609,7 +696,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestCopyPasswordCallbackResultFail) { delegate.RequestPlaintextPassword( 0, api::passwords_private::PLAINTEXT_REASON_COPY, password_callback.Get(), nullptr); - // Clipboard should not be modifiend in case Reauth failed + // Clipboard should not be modified in case Reauth failed std::u16string result; test_clipboard_->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &result); @@ -621,7 +708,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestCopyPasswordCallbackResultFail) { } TEST_F(PasswordsPrivateDelegateImplTest, TestPassedReauthOnView) { - SetUpPasswordStore({CreateSampleForm()}); + SetUpPasswordStores({CreateSampleForm()}); PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -647,8 +734,40 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestPassedReauthOnView) { 1); } +TEST_F(PasswordsPrivateDelegateImplTest, + TestPassedReauthOnRequestCredentialDetails) { + SetUpPasswordStores({CreateSampleForm()}); + + PasswordsPrivateDelegateImpl delegate(&profile_); + // Spin the loop to allow PasswordStore tasks posted on the creation of + // |delegate| to be completed. + base::RunLoop().RunUntilIdle(); + + MockReauthCallback callback; + delegate.set_os_reauth_call(callback.Get()); + + EXPECT_CALL(callback, Run(ReauthPurpose::VIEW_PASSWORD, _)) + .WillOnce(testing::WithArg<1>( + [&](password_manager::PasswordAccessAuthenticator::AuthResultCallback + callback) { std::move(callback).Run(true); })); + + MockRequestCredentialDetailsCallback password_callback; + EXPECT_CALL(password_callback, Run) + .WillOnce( + [&](absl::optional<api::passwords_private::PasswordUiEntry> entry) { + EXPECT_THAT(entry->password, Pointee(Eq("test"))); + EXPECT_THAT(entry->username, Eq("test@gmail.com")); + }); + + delegate.RequestCredentialDetails(0, password_callback.Get(), nullptr); + + histogram_tester().ExpectUniqueSample( + kHistogramName, password_manager::metrics_util::ACCESS_PASSWORD_VIEWED, + 1); +} + TEST_F(PasswordsPrivateDelegateImplTest, TestFailedReauthOnView) { - SetUpPasswordStore({CreateSampleForm()}); + SetUpPasswordStores({CreateSampleForm()}); PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -673,10 +792,9 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestFailedReauthOnView) { histogram_tester().ExpectTotalCount(kHistogramName, 0); } -TEST_F(PasswordsPrivateDelegateImplTest, TestReauthOnExport) { - SetUpPasswordStore({CreateSampleForm()}); - StrictMock<base::MockCallback<base::OnceCallback<void(const std::string&)>>> - mock_accepted; +TEST_F(PasswordsPrivateDelegateImplTest, + TestFailedReauthOnRequestCredentialDetails) { + SetUpPasswordStores({CreateSampleForm()}); PasswordsPrivateDelegateImpl delegate(&profile_); // Spin the loop to allow PasswordStore tasks posted on the creation of @@ -686,24 +804,21 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestReauthOnExport) { MockReauthCallback callback; delegate.set_os_reauth_call(callback.Get()); - EXPECT_CALL(mock_accepted, Run(std::string())).Times(2); - - EXPECT_CALL(callback, Run(ReauthPurpose::EXPORT, _)) + EXPECT_CALL(callback, Run(ReauthPurpose::VIEW_PASSWORD, _)) .WillOnce(testing::WithArg<1>( [&](password_manager::PasswordAccessAuthenticator::AuthResultCallback - callback) { std::move(callback).Run(true); })); - delegate.ExportPasswords(mock_accepted.Get(), nullptr); + callback) { std::move(callback).Run(false); })); - // Export should ignore previous reauthentication results. - EXPECT_CALL(callback, Run(ReauthPurpose::EXPORT, _)) - .WillOnce(testing::WithArg<1>( - [&](password_manager::PasswordAccessAuthenticator::AuthResultCallback - callback) { std::move(callback).Run(true); })); - delegate.ExportPasswords(mock_accepted.Get(), nullptr); + MockRequestCredentialDetailsCallback password_callback; + EXPECT_CALL(password_callback, Run(Eq(absl::nullopt))); + delegate.RequestCredentialDetails(0, password_callback.Get(), nullptr); + + // Since Reauth had failed password was not viewed and metric wasn't recorded + histogram_tester().ExpectTotalCount(kHistogramName, 0); } TEST_F(PasswordsPrivateDelegateImplTest, TestReauthFailedOnExport) { - SetUpPasswordStore({CreateSampleForm()}); + SetUpPasswordStores({CreateSampleForm()}); StrictMock<base::MockCallback<base::OnceCallback<void(const std::string&)>>> mock_accepted; @@ -724,73 +839,6 @@ TEST_F(PasswordsPrivateDelegateImplTest, TestReauthFailedOnExport) { delegate.ExportPasswords(mock_accepted.Get(), nullptr); } -// Verifies that PasswordsPrivateDelegateImpl::GetPlaintextInsecurePassword -// fails if the re-auth fails. -TEST_F(PasswordsPrivateDelegateImplTest, - TestReauthOnGetPlaintextInsecurePasswordFails) { - PasswordsPrivateDelegateImpl delegate(&profile_); - - MockReauthCallback reauth_callback; - delegate.set_os_reauth_call(reauth_callback.Get()); - - base::MockCallback< - PasswordsPrivateDelegate::PlaintextInsecurePasswordCallback> - credential_callback; - - EXPECT_CALL(reauth_callback, Run(ReauthPurpose::VIEW_PASSWORD, _)) - .WillOnce(testing::WithArg<1>( - [&](password_manager::PasswordAccessAuthenticator::AuthResultCallback - callback) { std::move(callback).Run(false); })); - - EXPECT_CALL(credential_callback, Run(Eq(absl::nullopt))); - - delegate.GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential(), - api::passwords_private::PLAINTEXT_REASON_VIEW, nullptr, - credential_callback.Get()); -} - -// Verifies that PasswordsPrivateDelegateImpl::GetPlaintextInsecurePassword -// succeeds if the re-auth succeeds and there is a matching compromised -// credential in the store. -TEST_F(PasswordsPrivateDelegateImplTest, TestReauthOnGetPlaintextCompPassword) { - PasswordsPrivateDelegateImpl delegate(&profile_); - - password_manager::PasswordForm form = CreateSampleForm(); - form.password_issues = { - {password_manager::InsecureType::kLeaked, - password_manager::InsecurityMetadata(base::Time::FromTimeT(1), - password_manager::IsMuted(false))}}; - store_->AddLogin(form); - base::RunLoop().RunUntilIdle(); - - api::passwords_private::InsecureCredential credential = - std::move(delegate.GetCompromisedCredentials().at(0)); - - MockReauthCallback reauth_callback; - delegate.set_os_reauth_call(reauth_callback.Get()); - - base::MockCallback< - PasswordsPrivateDelegate::PlaintextInsecurePasswordCallback> - credential_callback; - - absl::optional<api::passwords_private::InsecureCredential> opt_credential; - EXPECT_CALL(reauth_callback, Run(ReauthPurpose::VIEW_PASSWORD, _)) - .WillOnce(testing::WithArg<1>( - [&](password_manager::PasswordAccessAuthenticator::AuthResultCallback - callback) { std::move(callback).Run(true); })); - EXPECT_CALL(credential_callback, Run).WillOnce(MoveArg(&opt_credential)); - - delegate.GetPlaintextInsecurePassword( - std::move(credential), api::passwords_private::PLAINTEXT_REASON_VIEW, - nullptr, credential_callback.Get()); - - ASSERT_TRUE(opt_credential.has_value()); - EXPECT_EQ(form.signon_realm, opt_credential->signon_realm); - EXPECT_EQ(form.username_value, base::UTF8ToUTF16(opt_credential->username)); - EXPECT_EQ(form.password_value, base::UTF8ToUTF16(*opt_credential->password)); -} - TEST_F(PasswordsPrivateDelegateImplTest, GetUrlCollectionValueWithSchemeWhenIpAddress) { PasswordsPrivateDelegateImpl delegate(&profile_); @@ -798,7 +846,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, delegate.GetUrlCollection("127.0.0.1"); EXPECT_TRUE(urls.has_value()); EXPECT_EQ("127.0.0.1", urls.value().shown); - EXPECT_EQ("http://127.0.0.1/", urls.value().origin); + EXPECT_EQ("http://127.0.0.1/", urls.value().signon_realm); EXPECT_EQ("http://127.0.0.1/", urls.value().link); } @@ -809,7 +857,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, delegate.GetUrlCollection("example.com/login"); EXPECT_TRUE(urls.has_value()); EXPECT_EQ("example.com", urls.value().shown); - EXPECT_EQ("https://example.com/", urls.value().origin); + EXPECT_EQ("https://example.com/", urls.value().signon_realm); EXPECT_EQ("https://example.com/login", urls.value().link); } @@ -821,7 +869,7 @@ TEST_F(PasswordsPrivateDelegateImplTest, "http://username:password@example.com/login?param=value#ref"); EXPECT_TRUE(urls.has_value()); EXPECT_EQ("example.com", urls.value().shown); - EXPECT_EQ("http://example.com/", urls.value().origin); + EXPECT_EQ("http://example.com/", urls.value().signon_realm); EXPECT_EQ("http://example.com/login", urls.value().link); } @@ -861,4 +909,172 @@ TEST_F(PasswordsPrivateDelegateImplTest, IsAccountStoreDefault) { EXPECT_FALSE(delegate.IsAccountStoreDefault(web_contents.get())); } +TEST_F(PasswordsPrivateDelegateImplTest, TestMovePasswordsToAccountStore) { + // This enables uses of TestWebContents. + content::RenderViewHostTestEnabler test_render_host_factories; + std::unique_ptr<content::WebContents> web_contents = + content::WebContentsTester::CreateTestWebContents(&profile_, nullptr); + auto* client = + MockPasswordManagerClient::CreateForWebContentsAndGet(web_contents.get()); + ON_CALL(*(client->GetPasswordFeatureManager()), IsOptedInForAccountStorage) + .WillByDefault(Return(true)); + + PasswordsPrivateDelegateImpl delegate(&profile_); + + password_manager::PasswordForm form1 = + CreateSampleForm(password_manager::PasswordForm::Store::kProfileStore); + password_manager::PasswordForm form2 = form1; + form2.username_value = u"different_username"; + + SetUpPasswordStores({form1, form2}); + + int first_id = + delegate.GetIdForCredential(password_manager::CredentialUIEntry(form1)); + int second_id = + delegate.GetIdForCredential(password_manager::CredentialUIEntry(form2)); + + delegate.MovePasswordsToAccount({first_id, second_id}, web_contents.get()); + base::RunLoop().RunUntilIdle(); + + histogram_tester().ExpectUniqueSample( + "PasswordManager.AccountStorage.MoveToAccountStoreFlowAccepted", + password_manager::metrics_util::MoveToAccountStoreTrigger:: + kExplicitlyTriggeredInSettings, + 2); +} + +TEST_F(PasswordsPrivateDelegateImplTest, AndroidCredential) { + PasswordsPrivateDelegateImpl delegate(&profile_); + + password_manager::PasswordForm android_form; + android_form.signon_realm = "android://hash@example.com"; + android_form.username_value = u"test@gmail.com"; + android_form.in_store = password_manager::PasswordForm::Store::kProfileStore; + SetUpPasswordStores({android_form}); + + base::MockCallback<PasswordsPrivateDelegate::UiEntriesCallback> callback; + + api::passwords_private::PasswordUiEntry expected_entry; + expected_entry.urls.link = + "https://play.google.com/store/apps/details?id=example.com"; + expected_entry.username = "test@gmail.com"; + expected_entry.note = std::make_unique<std::string>(); + expected_entry.is_android_credential = true; + expected_entry.stored_in = api::passwords_private::PASSWORD_STORE_SET_DEVICE; + EXPECT_CALL(callback, Run(testing::ElementsAre(PasswordUiEntryDataEquals( + testing::ByRef(expected_entry))))); + delegate.GetSavedPasswordsList(callback.Get()); +} + +TEST_F(PasswordsPrivateDelegateImplTest, VerifyCastingOfImportEntryStatus) { + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_NONE) == + static_cast<int>(password_manager::ImportEntry::Status::NONE), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_UNKNOWN_ERROR) == + static_cast<int>( + password_manager::ImportEntry::Status::UNKNOWN_ERROR), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_MISSING_PASSWORD) == + static_cast<int>( + password_manager::ImportEntry::Status::MISSING_PASSWORD), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_MISSING_URL) == + static_cast<int>(password_manager::ImportEntry::Status::MISSING_URL), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_INVALID_URL) == + static_cast<int>(password_manager::ImportEntry::Status::INVALID_URL), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_NON_ASCII_URL) == + static_cast<int>( + password_manager::ImportEntry::Status::NON_ASCII_URL), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_LONG_URL) == + static_cast<int>(password_manager::ImportEntry::Status::LONG_URL), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_LONG_PASSWORD) == + static_cast<int>( + password_manager::ImportEntry::Status::LONG_PASSWORD), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_LONG_USERNAME) == + static_cast<int>( + password_manager::ImportEntry::Status::LONG_USERNAME), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_CONFLICT_PROFILE) == + static_cast<int>( + password_manager::ImportEntry::Status::CONFLICT_PROFILE), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportEntryStatus:: + IMPORT_ENTRY_STATUS_CONFLICT_ACCOUNT) == + static_cast<int>( + password_manager::ImportEntry::Status::CONFLICT_ACCOUNT), + ""); +} + +TEST_F(PasswordsPrivateDelegateImplTest, VerifyCastingOfImportResultsStatus) { + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_NONE) == + static_cast<int>(password_manager::ImportResults::Status::NONE), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_UNKNOWN_ERROR) == + static_cast<int>( + password_manager::ImportResults::Status::UNKNOWN_ERROR), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_SUCCESS) == + static_cast<int>(password_manager::ImportResults::Status::SUCCESS), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_IO_ERROR) == + static_cast<int>(password_manager::ImportResults::Status::IO_ERROR), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_BAD_FORMAT) == + static_cast<int>(password_manager::ImportResults::Status::BAD_FORMAT), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_DISMISSED) == + static_cast<int>(password_manager::ImportResults::Status::DISMISSED), + ""); + static_assert(static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_MAX_FILE_SIZE) == + static_cast<int>( + password_manager::ImportResults::Status::MAX_FILE_SIZE), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_IMPORT_ALREADY_ACTIVE) == + static_cast<int>( + password_manager::ImportResults::Status::IMPORT_ALREADY_ACTIVE), + ""); + static_assert( + static_cast<int>(api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_NUM_PASSWORDS_EXCEEDED) == + static_cast<int>( + password_manager::ImportResults::Status::NUM_PASSWORDS_EXCEEDED), + ""); +} + } // 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 e1e6ff3ea2c..0474aa262fc 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 @@ -45,8 +45,7 @@ void PasswordsPrivateEventRouter::SendSavedPasswordListToListeners() { auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_SAVED_PASSWORDS_LIST_CHANGED, api::passwords_private::OnSavedPasswordsListChanged::kEventName, - base::Value(cached_saved_password_parameters_.value()) - .TakeListDeprecated()); + std::move(cached_saved_password_parameters_).value()); event_router_->BroadcastEvent(std::move(extension_event)); } @@ -66,8 +65,7 @@ void PasswordsPrivateEventRouter::SendPasswordExceptionListToListeners() { auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_PASSWORD_EXCEPTIONS_LIST_CHANGED, api::passwords_private::OnPasswordExceptionsListChanged::kEventName, - base::Value(cached_password_exception_parameters_.value()) - .TakeListDeprecated()); + std::move(cached_password_exception_parameters_).value()); event_router_->BroadcastEvent(std::move(extension_event)); } @@ -78,8 +76,8 @@ void PasswordsPrivateEventRouter::OnPasswordsExportProgress( params.status = status; params.folder_name = std::make_unique<std::string>(std::move(folder_name)); - std::vector<base::Value> event_value; - event_value.push_back(base::Value::FromUniquePtrValue(params.ToValue())); + base::Value::List event_value; + event_value.Append(base::Value::FromUniquePtrValue(params.ToValue())); auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_PASSWORDS_FILE_EXPORT_PROGRESS, @@ -99,7 +97,7 @@ void PasswordsPrivateEventRouter::OnAccountStorageOptInStateChanged( } void PasswordsPrivateEventRouter::OnCompromisedCredentialsChanged( - std::vector<api::passwords_private::InsecureCredential> + std::vector<api::passwords_private::PasswordUiEntry> compromised_credentials) { auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_COMPROMISED_CREDENTIALS_INFO_CHANGED, @@ -110,7 +108,7 @@ void PasswordsPrivateEventRouter::OnCompromisedCredentialsChanged( } void PasswordsPrivateEventRouter::OnWeakCredentialsChanged( - std::vector<api::passwords_private::InsecureCredential> weak_credentials) { + std::vector<api::passwords_private::PasswordUiEntry> weak_credentials) { auto extension_event = std::make_unique<Event>( events::PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED, api::passwords_private::OnWeakCredentialsChanged::kEventName, @@ -128,6 +126,13 @@ void PasswordsPrivateEventRouter::OnPasswordCheckStatusChanged( event_router_->BroadcastEvent(std::move(extension_event)); } +void PasswordsPrivateEventRouter::OnPasswordManagerAuthTimeout() { + event_router_->BroadcastEvent(std::make_unique<Event>( + events::PASSWORDS_PRIVATE_ON_PASSWORD_MANAGER_AUTH_TIMEOUT, + api::passwords_private::OnPasswordManagerAuthTimeout::kEventName, + api::passwords_private::OnPasswordManagerAuthTimeout::Create())); +} + PasswordsPrivateEventRouter* PasswordsPrivateEventRouter::Create( content::BrowserContext* context) { return new PasswordsPrivateEventRouter(context); 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 ccfff3d319a..9225745675d 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 @@ -66,18 +66,21 @@ class PasswordsPrivateEventRouter : public KeyedService { // Notifies listeners about a change to the information about compromised // credentials. void OnCompromisedCredentialsChanged( - std::vector<api::passwords_private::InsecureCredential> + std::vector<api::passwords_private::PasswordUiEntry> compromised_credentials); // Notifies listeners about a change to the information about weak // credentials. void OnWeakCredentialsChanged( - std::vector<api::passwords_private::InsecureCredential> weak_credentials); + std::vector<api::passwords_private::PasswordUiEntry> weak_credentials); // Notifies listeners about a change to the status of the password check. void OnPasswordCheckStatusChanged( const api::passwords_private::PasswordCheckStatus& status); + // Notifies listeners about the timeout for password manager access. + void OnPasswordManagerAuthTimeout(); + protected: explicit PasswordsPrivateEventRouter(content::BrowserContext* context); @@ -91,9 +94,8 @@ class PasswordsPrivateEventRouter : public KeyedService { // Cached parameters which are saved so that when new listeners are added, the // most up-to-date lists can be sent to them immediately. - absl::optional<std::vector<base::Value>> cached_saved_password_parameters_; - absl::optional<std::vector<base::Value>> - cached_password_exception_parameters_; + absl::optional<base::Value::List> cached_saved_password_parameters_; + absl::optional<base::Value::List> cached_password_exception_parameters_; }; } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.cc index 80dd51be6e4..7b5541d6573 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.cc @@ -6,7 +6,6 @@ #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/extension_system_provider.h" #include "extensions/browser/extensions_browser_client.h" @@ -28,9 +27,9 @@ PasswordsPrivateEventRouterFactory::GetInstance() { } PasswordsPrivateEventRouterFactory::PasswordsPrivateEventRouterFactory() - : BrowserContextKeyedServiceFactory( + : ProfileKeyedServiceFactory( "PasswordsPrivateEventRouter", - BrowserContextDependencyManager::GetInstance()) { + ProfileSelections::BuildRedirectedInIncognito()) { DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); DependsOn(PasswordsPrivateDelegateFactory::GetInstance()); } @@ -44,12 +43,6 @@ KeyedService* PasswordsPrivateEventRouterFactory::BuildServiceInstanceFor( return PasswordsPrivateEventRouter::Create(context); } -content::BrowserContext* -PasswordsPrivateEventRouterFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - return ExtensionsBrowserClient::Get()->GetOriginalContext(context); -} - bool PasswordsPrivateEventRouterFactory:: ServiceIsCreatedWithBrowserContext() const { return true; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h index 8a28211631e..f1aac153d43 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_API_PASSWORDS_PRIVATE_PASSWORDS_PRIVATE_EVENT_ROUTER_FACTORY_H_ #include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace extensions { @@ -15,8 +15,7 @@ class PasswordsPrivateEventRouter; // This is a factory class used by the BrowserContextDependencyManager // to instantiate the passwordsPrivate event router per profile (since the // extension event router is per profile). -class PasswordsPrivateEventRouterFactory - : public BrowserContextKeyedServiceFactory { +class PasswordsPrivateEventRouterFactory : public ProfileKeyedServiceFactory { public: PasswordsPrivateEventRouterFactory( const PasswordsPrivateEventRouterFactory&) = delete; @@ -33,8 +32,6 @@ class PasswordsPrivateEventRouterFactory protected: // BrowserContextKeyedServiceFactory overrides: - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; bool ServiceIsNULLWhileTesting() const override; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.cc index ca14da0a05d..b611ea8e7e8 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.cc @@ -9,18 +9,24 @@ #include "components/password_manager/core/browser/password_form.h" #include "components/password_manager/core/browser/password_manager_util.h" #include "components/password_manager/core/browser/password_ui_utils.h" +#include "components/password_manager/core/browser/ui/credential_ui_entry.h" #include "url/gurl.h" namespace extensions { -api::passwords_private::UrlCollection CreateUrlCollectionFromForm( - const password_manager::PasswordForm& form) { +namespace { + +using password_manager::CredentialUIEntry; +using Store = password_manager::PasswordForm::Store; + +} // namespace + +api::passwords_private::UrlCollection CreateUrlCollectionFromCredential( + const CredentialUIEntry& credential) { api::passwords_private::UrlCollection urls; - GURL link_url; - std::tie(urls.shown, link_url) = - password_manager::GetShownOriginAndLinkUrl(form); - urls.origin = form.signon_realm; - urls.link = link_url.spec(); + urls.shown = GetShownOrigin(credential); + urls.link = GetShownUrl(credential).spec(); + urls.signon_realm = credential.signon_realm; return urls; } @@ -28,9 +34,26 @@ api::passwords_private::UrlCollection CreateUrlCollectionFromGURL( const GURL& url) { api::passwords_private::UrlCollection urls; urls.shown = password_manager::GetShownOrigin(url::Origin::Create(url)); - urls.origin = password_manager_util::GetSignonRealm(url); + urls.signon_realm = password_manager_util::GetSignonRealm(url); urls.link = url.spec(); return urls; } +extensions::api::passwords_private::PasswordStoreSet StoreSetFromCredential( + const CredentialUIEntry& credential) { + if (credential.stored_in.contains(Store::kAccountStore) && + credential.stored_in.contains(Store::kProfileStore)) { + return extensions::api::passwords_private:: + PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT; + } + if (credential.stored_in.contains(Store::kAccountStore)) { + return extensions::api::passwords_private::PASSWORD_STORE_SET_ACCOUNT; + } + if (credential.stored_in.contains(Store::kProfileStore)) { + return extensions::api::passwords_private::PASSWORD_STORE_SET_DEVICE; + } + NOTREACHED(); + return extensions::api::passwords_private::PASSWORD_STORE_SET_DEVICE; +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.h b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.h index 2086ccd5fe7..b7f9d78b0a8 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils.h @@ -7,22 +7,25 @@ #include <functional> #include <map> +#include <string> #include "base/containers/flat_map.h" #include "chrome/common/extensions/api/passwords_private.h" #include "url/gurl.h" +class Profile; + namespace password_manager { -struct PasswordForm; +struct CredentialUIEntry; } // namespace password_manager namespace extensions { -// Obtains a collection of URLs from the passed in |form|. This includes an -// origin URL used for internal logic, a human friendly string shown to the user -// as well as a URL that is linked to. -api::passwords_private::UrlCollection CreateUrlCollectionFromForm( - const password_manager::PasswordForm& form); +// Obtains a collection of URLs from the passed in |credential|. This includes +// an origin URL used for internal logic, a human friendly string shown to the +// user as well as a URL that is linked to. +api::passwords_private::UrlCollection CreateUrlCollectionFromCredential( + const password_manager::CredentialUIEntry& credential); // Obtains a collection of URLs from the passed in |url|. This includes an // origin URL used for internal logic, a human friendly string shown to the user @@ -30,14 +33,18 @@ api::passwords_private::UrlCollection CreateUrlCollectionFromForm( api::passwords_private::UrlCollection CreateUrlCollectionFromGURL( const GURL& url); +// Returns PasswordStoreSet for |credential|. +extensions::api::passwords_private::PasswordStoreSet StoreSetFromCredential( + const password_manager::CredentialUIEntry& credential); + // This class is an id generator for an arbitrary key type. It is used by both // PasswordManagerPresenter and PasswordCheckDelegate to create ids send to the // UI. It is similar to base::IDMap, but has the following important // differences: // - IdGenerator owns a copy of the key data, so that clients don't need to // worry about dangling pointers. -// - Repeated calls to GenerateId with the same |key| are no-ops, and return the -// same ids. +// - Repeated calls to GenerateId with the same |key| return the same ids and +// replace the |key|. template <typename KeyT, typename IdT = int32_t, typename KeyCompare = std::less<>> @@ -50,6 +57,7 @@ class IdGenerator { // a == b. IdT GenerateId(const KeyT& key) { auto result = key_cache_.emplace(key, next_id_); + IdT id_for_key = result.first->second; if (result.second) { // In case we haven't seen |key| before, add a pointer to the inserted key // and the corresponding id to the |id_cache_|. This insertion should @@ -58,9 +66,15 @@ class IdGenerator { &result.first->first); DCHECK_EQ(&result.first->first, iter->second); ++next_id_; + } else { + // Refresh the |key| in the caches, as the |result.first->first| may + // compare the same due to |KeyCompare|. + key_cache_.erase(result.first); + auto new_result = key_cache_.emplace(key, id_for_key); + id_cache_[id_for_key] = &new_result.first->first; } - return result.first->second; + return id_for_key; } // This method tries to return the key corresponding to |id|. In case |id| was diff --git a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils_unittest.cc b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils_unittest.cc index 44a0a6b286f..312d47a72d6 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils_unittest.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/passwords_private_utils_unittest.cc @@ -5,20 +5,33 @@ #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h" #include "components/password_manager/core/browser/password_form.h" +#include "components/password_manager/core/browser/ui/credential_ui_entry.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace extensions { +namespace { + +using password_manager::CredentialUIEntry; + +struct StringFirstLetterCmp { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return lhs.empty() ? !rhs.empty() : lhs[0] < rhs[0]; + } +}; + +} // namespace + TEST(CreateUrlCollectionFromFormTest, UrlsFromHtmlForm) { password_manager::PasswordForm html_form; html_form.url = GURL("http://example.com/LoginAuth"); html_form.signon_realm = html_form.url.DeprecatedGetOriginAsURL().spec(); api::passwords_private::UrlCollection html_urls = - CreateUrlCollectionFromForm(html_form); - EXPECT_EQ(html_urls.origin, "http://example.com/"); + CreateUrlCollectionFromCredential(CredentialUIEntry(html_form)); + EXPECT_EQ(html_urls.signon_realm, "http://example.com/"); EXPECT_EQ(html_urls.shown, "example.com"); EXPECT_EQ(html_urls.link, "http://example.com/LoginAuth"); } @@ -31,8 +44,8 @@ TEST(CreateUrlCollectionFromFormTest, UrlsFromFederatedForm) { url::Origin::Create(GURL("https://google.com/")); api::passwords_private::UrlCollection federated_urls = - CreateUrlCollectionFromForm(federated_form); - EXPECT_EQ(federated_urls.origin, "federation://example.com/google.com"); + CreateUrlCollectionFromCredential(CredentialUIEntry(federated_form)); + EXPECT_EQ(federated_urls.signon_realm, "federation://example.com/google.com"); EXPECT_EQ(federated_urls.shown, "example.com"); EXPECT_EQ(federated_urls.link, "https://example.com/"); } @@ -43,8 +56,8 @@ TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithoutDisplayName) { android_form.app_display_name.clear(); api::passwords_private::UrlCollection android_urls = - CreateUrlCollectionFromForm(android_form); - EXPECT_EQ("android://example@com.example.android", android_urls.origin); + CreateUrlCollectionFromCredential(CredentialUIEntry(android_form)); + EXPECT_EQ("android://example@com.example.android", android_urls.signon_realm); EXPECT_EQ("android.example.com", android_urls.shown); EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android", android_urls.link); @@ -56,8 +69,8 @@ TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithAppName) { android_form.app_display_name = "Example Android App"; api::passwords_private::UrlCollection android_urls = - CreateUrlCollectionFromForm(android_form); - EXPECT_EQ(android_urls.origin, "android://hash@com.example.android"); + CreateUrlCollectionFromCredential(CredentialUIEntry(android_form)); + EXPECT_EQ(android_urls.signon_realm, "android://hash@com.example.android"); EXPECT_EQ("Example Android App", android_urls.shown); EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android", android_urls.link); @@ -68,7 +81,7 @@ TEST(CreateUrlCollectionFromGURLTest, UrlsFromGURL) { api::passwords_private::UrlCollection urls = CreateUrlCollectionFromGURL(url); EXPECT_EQ(urls.shown, "example.com"); - EXPECT_EQ(urls.origin, "https://example.com/"); + EXPECT_EQ(urls.signon_realm, "https://example.com/"); EXPECT_EQ(urls.link, "https://example.com/login"); } @@ -94,6 +107,20 @@ TEST(IdGeneratorTest, GenerateIds) { EXPECT_THAT(id_generator.TryGetKey(bar_id), Pointee(Eq("bar"))); EXPECT_THAT(id_generator.TryGetKey(baz_id), Pointee(Eq("baz"))); + + // Check that due to clashing |KeyCompare|, new |key|s invalidate existing + // |key|. + IdGenerator<std::string, int32_t, StringFirstLetterCmp> + clashing_id_generator_; + int crab_id = clashing_id_generator_.GenerateId("crab"); + + EXPECT_THAT(clashing_id_generator_.TryGetKey(crab_id), Pointee(Eq("crab"))); + + int chromium_id = clashing_id_generator_.GenerateId("chromium"); + + EXPECT_EQ(crab_id, chromium_id); + EXPECT_THAT(clashing_id_generator_.TryGetKey(chromium_id), + Pointee(Eq("chromium"))); } } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc b/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc index 5ec8dfb19c5..3c2e66d7244 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc +++ b/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc @@ -13,6 +13,7 @@ #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/l10n/time_format.h" +#include "url/gurl.h" namespace extensions { @@ -25,21 +26,20 @@ constexpr size_t kNumMocks = 3; api::passwords_private::PasswordUiEntry CreateEntry(int id) { api::passwords_private::PasswordUiEntry entry; entry.urls.shown = "test" + base::NumberToString(id) + ".com"; - entry.urls.origin = "http://" + entry.urls.shown + "/login"; - entry.urls.link = entry.urls.origin; + entry.urls.signon_realm = "http://" + entry.urls.shown + "/login"; + entry.urls.link = entry.urls.signon_realm; entry.username = "testName" + base::NumberToString(id); entry.id = id; - entry.frontend_id = id; + entry.stored_in = api::passwords_private::PASSWORD_STORE_SET_DEVICE; return entry; } api::passwords_private::ExceptionEntry CreateException(int id) { api::passwords_private::ExceptionEntry exception; exception.urls.shown = "exception" + base::NumberToString(id) + ".com"; - exception.urls.origin = "http://" + exception.urls.shown + "/login"; - exception.urls.link = exception.urls.origin; + exception.urls.signon_realm = "http://" + exception.urls.shown + "/login"; + exception.urls.link = exception.urls.signon_realm; exception.id = id; - exception.frontend_id = id; return exception; } } // namespace @@ -88,66 +88,56 @@ bool TestPasswordsPrivateDelegate::AddPassword( return !url.empty() && !password.empty(); } -bool TestPasswordsPrivateDelegate::ChangeSavedPassword( - const std::vector<int>& ids, +absl::optional<int> TestPasswordsPrivateDelegate::ChangeSavedPassword( + const int id, const api::passwords_private::ChangeSavedPasswordParams& params) { - for (int id : ids) { - if (static_cast<size_t>(id) >= current_entries_.size()) { - return false; - } + if (static_cast<size_t>(id) >= current_entries_.size()) { + return absl::nullopt; } - return !params.password.empty() && !ids.empty(); + + if (params.password.empty()) + return absl::nullopt; + + return id; } -void TestPasswordsPrivateDelegate::RemoveSavedPasswords( - const std::vector<int>& ids) { +void TestPasswordsPrivateDelegate::RemoveSavedPassword( + int id, + api::passwords_private::PasswordStoreSet from_stores) { if (current_entries_.empty()) return; - // Since this is just mock data, remove the first |ids.size()| elements - // regardless of the data contained. - auto first_remaining = (ids.size() <= current_entries_.size()) - ? current_entries_.begin() + ids.size() - : current_entries_.end(); - last_deleted_entries_batch_.assign( - std::make_move_iterator(current_entries_.begin()), - std::make_move_iterator(first_remaining)); - current_entries_.erase(current_entries_.begin(), first_remaining); + // Since this is just mock data, remove the first element regardless of the + // data contained. One case where this logic is especially false is when the + // password is stored in both stores and |store| only specifies one of them + // (in that case the number of entries shouldn't change). + last_deleted_entry_ = std::move(current_entries_[0]); + current_entries_.erase(current_entries_.begin()); SendSavedPasswordsList(); } -void TestPasswordsPrivateDelegate::RemovePasswordExceptions( - const std::vector<int>& ids) { +void TestPasswordsPrivateDelegate::RemovePasswordException(int id) { if (current_exceptions_.empty()) return; - // Since this is just mock data, remove the first |ids.size()| elements - // regardless of the data contained. - auto first_remaining = (ids.size() <= current_exceptions_.size()) - ? current_exceptions_.begin() + ids.size() - : current_exceptions_.end(); - last_deleted_exceptions_batch_.assign( - std::make_move_iterator(current_exceptions_.begin()), - std::make_move_iterator(first_remaining)); - current_exceptions_.erase(current_exceptions_.begin(), first_remaining); + // Since this is just mock data, remove the first element regardless of the + // data contained. + last_deleted_exception_ = std::move(current_exceptions_[0]); + current_exceptions_.erase(current_exceptions_.begin()); SendPasswordExceptionsList(); } // Simplified version of undo logic, only use for testing. void TestPasswordsPrivateDelegate::UndoRemoveSavedPasswordOrException() { - if (!last_deleted_entries_batch_.empty()) { - current_entries_.insert( - current_entries_.begin(), - std::make_move_iterator(last_deleted_entries_batch_.begin()), - std::make_move_iterator(last_deleted_entries_batch_.end())); - last_deleted_entries_batch_.clear(); + if (last_deleted_entry_.has_value()) { + current_entries_.insert(current_entries_.begin(), + std::move(last_deleted_entry_.value())); + last_deleted_entry_ = absl::nullopt; SendSavedPasswordsList(); - } else if (!last_deleted_exceptions_batch_.empty()) { - current_exceptions_.insert( - current_exceptions_.begin(), - std::make_move_iterator(last_deleted_exceptions_batch_.begin()), - std::make_move_iterator(last_deleted_exceptions_batch_.end())); - last_deleted_exceptions_batch_.clear(); + } else if (last_deleted_exception_.has_value()) { + current_exceptions_.insert(current_exceptions_.begin(), + std::move(last_deleted_exception_.value())); + last_deleted_exception_ = absl::nullopt; SendPasswordExceptionsList(); } } @@ -161,6 +151,20 @@ void TestPasswordsPrivateDelegate::RequestPlaintextPassword( std::move(callback).Run(plaintext_password_); } +void TestPasswordsPrivateDelegate::RequestCredentialDetails( + int id, + RequestCredentialDetailsCallback callback, + content::WebContents* web_contents) { + api::passwords_private::PasswordUiEntry entry = CreateEntry(42); + if (plaintext_password_.has_value()) { + entry.password = std::make_unique<std::string>( + base::UTF16ToUTF8(plaintext_password_.value())); + std::move(callback).Run(std::move(entry)); + } else { + std::move(callback).Run(std::move(absl::nullopt)); + } +} + void TestPasswordsPrivateDelegate::MovePasswordsToAccount( const std::vector<int>& ids, content::WebContents* web_contents) { @@ -168,10 +172,16 @@ void TestPasswordsPrivateDelegate::MovePasswordsToAccount( } void TestPasswordsPrivateDelegate::ImportPasswords( + api::passwords_private::PasswordStoreSet to_store, + ImportResultsCallback results_callback, content::WebContents* web_contents) { - // The testing of password importing itself should be handled via - // |PasswordManagerPorter|. import_passwords_triggered_ = true; + + import_results_.status = api::passwords_private::ImportResultsStatus:: + IMPORT_RESULTS_STATUS_SUCCESS; + import_results_.file_name = "test.csv"; + import_results_.number_imported = 42; + std::move(results_callback).Run(import_results_); } void TestPasswordsPrivateDelegate::ExportPasswords( @@ -205,12 +215,13 @@ void TestPasswordsPrivateDelegate::SetAccountStorageOptIn( is_opted_in_for_account_storage_ = opt_in; } -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> TestPasswordsPrivateDelegate::GetCompromisedCredentials() { - api::passwords_private::InsecureCredential credential; + api::passwords_private::PasswordUiEntry credential; credential.username = "alice"; - credential.formatted_origin = "example.com"; - credential.detailed_origin = "https://example.com"; + credential.urls.shown = "example.com"; + credential.urls.link = "https://example.com"; + credential.urls.signon_realm = "https://example.com"; credential.is_android_credential = false; credential.change_password_url = std::make_unique<std::string>("https://example.com/change-password"); @@ -223,80 +234,53 @@ TestPasswordsPrivateDelegate::GetCompromisedCredentials() { TimeFormat::FORMAT_ELAPSED, TimeFormat::LENGTH_LONG, base::Days(3))); credential.compromised_info->compromise_type = api::passwords_private::COMPROMISE_TYPE_LEAKED; - std::vector<api::passwords_private::InsecureCredential> credentials; + credential.stored_in = api::passwords_private::PASSWORD_STORE_SET_DEVICE; + std::vector<api::passwords_private::PasswordUiEntry> credentials; credentials.push_back(std::move(credential)); return credentials; } -std::vector<api::passwords_private::InsecureCredential> +std::vector<api::passwords_private::PasswordUiEntry> TestPasswordsPrivateDelegate::GetWeakCredentials() { - api::passwords_private::InsecureCredential credential; + api::passwords_private::PasswordUiEntry credential; credential.username = "bob"; - credential.formatted_origin = "example.com"; - credential.detailed_origin = "https://example.com"; + credential.urls.shown = "example.com"; + credential.urls.link = "https://example.com"; credential.is_android_credential = false; credential.change_password_url = std::make_unique<std::string>("https://example.com/change-password"); - std::vector<api::passwords_private::InsecureCredential> credentials; + credential.stored_in = api::passwords_private::PASSWORD_STORE_SET_DEVICE; + std::vector<api::passwords_private::PasswordUiEntry> credentials; credentials.push_back(std::move(credential)); return credentials; } -void TestPasswordsPrivateDelegate::GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - content::WebContents* web_contents, - PlaintextInsecurePasswordCallback callback) { - // Return a mocked password value. - if (!plaintext_password_) { - std::move(callback).Run(absl::nullopt); - return; - } - - credential.password = - std::make_unique<std::string>(base::UTF16ToUTF8(*plaintext_password_)); - std::move(callback).Run(std::move(credential)); -} - -// Fake implementation of ChangeInsecureCredential. This succeeds if the -// delegate knows of a insecure credential with the same id. -bool TestPasswordsPrivateDelegate::ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) { - return IsCredentialPresentInInsecureCredentialsList(credential); -} - -// Fake implementation of RemoveInsecureCredential. This succeeds if the -// delegate knows of a insecure credential with the same id. -bool TestPasswordsPrivateDelegate::RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { - return base::EraseIf(insecure_credentials_, - [&credential](const auto& insecure_credential) { - return insecure_credential.id == credential.id; - }) != 0; -} - // Fake implementation of MuteInsecureCredential. This succeeds if the // delegate knows of a insecure credential with the same id. bool TestPasswordsPrivateDelegate::MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { return IsCredentialPresentInInsecureCredentialsList(credential); } // Fake implementation of UnmuteInsecureCredential. This succeeds if the // delegate knows of a insecure credential with the same id. bool TestPasswordsPrivateDelegate::UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { return IsCredentialPresentInInsecureCredentialsList(credential); } void TestPasswordsPrivateDelegate::RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) { last_change_flow_url_ = credential.change_password_url ? *credential.change_password_url : ""; } +void TestPasswordsPrivateDelegate::RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) { + std::move(callback).Run(); +} + void TestPasswordsPrivateDelegate::StartPasswordCheck( StartPasswordCheckCallback callback) { start_password_check_triggered_ = true; @@ -307,6 +291,13 @@ void TestPasswordsPrivateDelegate::StopPasswordCheck() { stop_password_check_triggered_ = true; } +void TestPasswordsPrivateDelegate::StartAutomatedPasswordChange( + const api::passwords_private::PasswordUiEntry& credential, + StartAutomatedPasswordChangeCallback callback) { + std::move(callback).Run(credential.change_password_url && + GURL(*credential.change_password_url).is_valid()); +} + api::passwords_private::PasswordCheckStatus TestPasswordsPrivateDelegate::GetPasswordCheckStatus() { api::passwords_private::PasswordCheckStatus status; @@ -338,7 +329,7 @@ void TestPasswordsPrivateDelegate::SetIsAccountStoreDefault(bool is_default) { } void TestPasswordsPrivateDelegate::AddCompromisedCredential(int id) { - api::passwords_private::InsecureCredential cred; + api::passwords_private::PasswordUiEntry cred; cred.id = id; insecure_credentials_.push_back(std::move(cred)); } @@ -358,7 +349,7 @@ void TestPasswordsPrivateDelegate::SendPasswordExceptionsList() { } bool TestPasswordsPrivateDelegate::IsCredentialPresentInInsecureCredentialsList( - const api::passwords_private::InsecureCredential& credential) { + const api::passwords_private::PasswordUiEntry& credential) { return std::any_of(insecure_credentials_.begin(), insecure_credentials_.end(), [&credential](const auto& insecure_credential) { return insecure_credential.id == credential.id; diff --git a/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h b/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h index 4f1b84ae8dc..cd657c8b62c 100644 --- a/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h +++ b/chromium/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h @@ -24,14 +24,15 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate { // PasswordsPrivateDelegate implementation. void GetSavedPasswordsList(UiEntriesCallback callback) override; void GetPasswordExceptionsList(ExceptionEntriesCallback callback) override; - // Fake implementation of GetUrlCollection. This returns value if |url| is + // Fake implementation of `GetUrlCollection`. This returns a value if `url` is // not empty. absl::optional<api::passwords_private::UrlCollection> GetUrlCollection( const std::string& url) override; - // Fake implementation. This returns value set by SetIsAccountStoreDefault. + // Fake implementation. This returns the value set by + // `SetIsAccountStoreDefault`. bool IsAccountStoreDefault(content::WebContents* web_contents) override; - // Fake implementation of AddPassword. This returns true if |url| and - // |password| aren't empty. + // Fake implementation of AddPassword. This returns true if `url` and + // `password` aren't empty. bool AddPassword(const std::string& url, const std::u16string& username, const std::u16string& password, @@ -39,22 +40,28 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate { bool use_account_store, content::WebContents* web_contents) override; // Fake implementation of ChangeSavedPassword. This succeeds if the current - // list of entries has each of the ids, vector of ids isn't empty and if the - // new password isn't empty. - bool ChangeSavedPassword( - const std::vector<int>& ids, + // list of entries has the id and if the new password isn't empty. + absl::optional<int> ChangeSavedPassword( + const int id, const api::passwords_private::ChangeSavedPasswordParams& params) override; - void RemoveSavedPasswords(const std::vector<int>& id) override; - void RemovePasswordExceptions(const std::vector<int>& ids) override; + void RemoveSavedPassword( + int id, + api::passwords_private::PasswordStoreSet from_store) override; + void RemovePasswordException(int id) override; // Simplified version of undo logic, only use for testing. void UndoRemoveSavedPasswordOrException() override; void RequestPlaintextPassword(int id, api::passwords_private::PlaintextReason reason, PlaintextPasswordCallback callback, content::WebContents* web_contents) override; + void RequestCredentialDetails(int id, + RequestCredentialDetailsCallback callback, + content::WebContents* web_contents) override; void MovePasswordsToAccount(const std::vector<int>& ids, content::WebContents* web_contents) override; - void ImportPasswords(content::WebContents* web_contents) override; + void ImportPasswords(api::passwords_private::PasswordStoreSet to_store, + ImportResultsCallback results_callback, + content::WebContents* web_contents) override; void ExportPasswords(base::OnceCallback<void(const std::string&)> callback, content::WebContents* web_contents) override; void CancelExportPasswords() override; @@ -63,40 +70,33 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate { bool IsOptedInForAccountStorage() override; void SetAccountStorageOptIn(bool opt_in, content::WebContents* web_contents) override; - std::vector<api::passwords_private::InsecureCredential> + std::vector<api::passwords_private::PasswordUiEntry> GetCompromisedCredentials() override; - std::vector<api::passwords_private::InsecureCredential> GetWeakCredentials() + std::vector<api::passwords_private::PasswordUiEntry> GetWeakCredentials() override; - void GetPlaintextInsecurePassword( - api::passwords_private::InsecureCredential credential, - api::passwords_private::PlaintextReason reason, - content::WebContents* web_contents, - PlaintextInsecurePasswordCallback callback) override; - // Fake implementation of ChangeInsecureCredential. This succeeds if the - // delegate knows of a insecure credential with the same id. - bool ChangeInsecureCredential( - const api::passwords_private::InsecureCredential& credential, - base::StringPiece new_password) override; - // Fake implementation of RemoveInsecureCredential. This succeeds if the - // delegate knows of a insecure credential with the same id. - bool RemoveInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; - // Fake implementation of MuteInsecureCredential. This succeeds if the + // Fake implementation of `MuteInsecureCredential`. This succeeds if the // delegate knows of a insecure credential with the same id. bool MuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; - // Fake implementation of UnmuteInsecureCredential. This succeeds if the + const api::passwords_private::PasswordUiEntry& credential) override; + // Fake implementation of `UnmuteInsecureCredential`. This succeeds if the // delegate knows of a insecure credential with the same id. bool UnmuteInsecureCredential( - const api::passwords_private::InsecureCredential& credential) override; - // Fake implementation of RecordChangePasswordFlowStarted. Sets the url - // returned by |last_change_flow_url()|. + const api::passwords_private::PasswordUiEntry& credential) override; + // Fake implementation of `RecordChangePasswordFlowStarted`. Sets the url + // returned by `last_change_flow_url()`. void RecordChangePasswordFlowStarted( - const api::passwords_private::InsecureCredential& credential, + const api::passwords_private::PasswordUiEntry& credential, bool is_manual_flow) override; + // Fake implementation of `RefreshScriptsIfNecessary` that directly calls + // `callback`. + void RefreshScriptsIfNecessary( + RefreshScriptsIfNecessaryCallback callback) override; void StartPasswordCheck(StartPasswordCheckCallback callback) override; void StopPasswordCheck() override; api::passwords_private::PasswordCheckStatus GetPasswordCheckStatus() override; + void StartAutomatedPasswordChange( + const api::passwords_private::PasswordUiEntry& credential, + StartAutomatedPasswordChangeCallback callback) override; password_manager::InsecureCredentialsManager* GetInsecureCredentialsManager() override; @@ -133,7 +133,7 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate { void SendSavedPasswordsList(); void SendPasswordExceptionsList(); bool IsCredentialPresentInInsecureCredentialsList( - const api::passwords_private::InsecureCredential& credential); + const api::passwords_private::PasswordUiEntry& credential); // The current list of entries/exceptions. Cached here so that when new // observers are added, this delegate can send the current lists without // having to request them from |password_manager_presenter_| again. @@ -141,17 +141,18 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate { std::vector<api::passwords_private::ExceptionEntry> current_exceptions_; // Simplified version of an undo manager that only allows undoing and redoing - // the very last deletion. When the batches are *empty*, this means there is + // the very last deletion. When the entries are nullopt, this means there is // no previous deletion to undo. - std::vector<api::passwords_private::PasswordUiEntry> - last_deleted_entries_batch_; - std::vector<api::passwords_private::ExceptionEntry> - last_deleted_exceptions_batch_; + absl::optional<api::passwords_private::PasswordUiEntry> last_deleted_entry_; + absl::optional<api::passwords_private::ExceptionEntry> + last_deleted_exception_; absl::optional<std::u16string> plaintext_password_ = u"plaintext"; + api::passwords_private::ImportResults import_results_; + // List of insecure credentials. - std::vector<api::passwords_private::InsecureCredential> insecure_credentials_; + std::vector<api::passwords_private::PasswordUiEntry> insecure_credentials_; raw_ptr<Profile> profile_ = nullptr; bool is_opted_in_for_account_storage_ = false; diff --git a/chromium/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chromium/chrome/browser/extensions/api/permissions/permissions_apitest.cc index cc5cdc6f999..c6d0a373a82 100644 --- a/chromium/chrome/browser/extensions/api/permissions/permissions_apitest.cc +++ b/chromium/chrome/browser/extensions/api/permissions/permissions_apitest.cc @@ -100,8 +100,9 @@ IN_PROC_BROWSER_TEST_P(PermissionsApiTestWithContextType, << message_; } -// TODO(crbug/1065399): Flaky on ChromeOS and Linux non-dbg builds. -#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(NDEBUG) +// TODO(crbug/1065399): Flaky on ChromeOS, Linux, and Mac non-dbg builds. +#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)) && \ + defined(NDEBUG) #define MAYBE_FaviconPermission DISABLED_FaviconPermission #else #define MAYBE_FaviconPermission FaviconPermission diff --git a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc index 2fe215dc36f..5d11cff5064 100644 --- a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc +++ b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc @@ -243,17 +243,20 @@ class PlatformKeysTest : public PlatformKeysTestBase { } void SetupTestCACerts() { - net::TestRootCerts* root_certs = net::TestRootCerts::GetInstance(); // "root.pem" is the issuer of the "l1_leaf.pem" and (transitively) // "l1_leaf.pem" certs which are loaded on the JS side. Generated by // create_test_certs.sh . - root_certs->AddFromFile(extension_path().AppendASCII("root.pem")); + scoped_refptr<net::X509Certificate> root = + net::ImportCertFromFile(extension_path().AppendASCII("root.pem")); + ASSERT_TRUE(root); + scoped_test_root_.Reset({root}); } const bool key_permission_policy_; const UserClientCertSlot user_client_cert_slot_; crypto::ScopedTestNSSDB user_private_slot_db_; const ContextType context_type_; + net::ScopedTestRoot scoped_test_root_; }; class TestSelectDelegate diff --git a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.cc b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.cc index ef2c1885407..6e73b965f26 100644 --- a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.cc +++ b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.cc @@ -14,7 +14,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/common/chrome_paths.h" #include "chrome/test/base/ui_test_utils.h" -#include "chromeos/dbus/session_manager/fake_session_manager_client.h" +#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" #include "components/policy/core/browser/browser_policy_connector.h" #include "components/policy/core/common/cloud/test/policy_builder.h" #include "components/policy/policy_constants.h" @@ -100,11 +100,11 @@ void PlatformKeysTestBase::SetUpCommandLine(base::CommandLine* command_line) { void PlatformKeysTestBase::SetUpInProcessBrowserTestFixture() { extensions::MixinBasedExtensionApiTest::SetUpInProcessBrowserTestFixture(); - chromeos::SessionManagerClient::InitializeFakeInMemory(); + ash::SessionManagerClient::InitializeFakeInMemory(); policy::AffiliationTestHelper affiliation_helper = policy::AffiliationTestHelper::CreateForCloud( - chromeos::FakeSessionManagerClient::Get()); + ash::FakeSessionManagerClient::Get()); if (enrollment_status() == EnrollmentStatus::ENROLLED) { std::set<std::string> device_affiliation_ids; diff --git a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.h b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.h index eeac4c42433..594b882474a 100644 --- a/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.h +++ b/chromium/chrome/browser/extensions/api/platform_keys/platform_keys_test_base.h @@ -7,10 +7,10 @@ #include <memory> -#include "ash/components/tpm/stub_install_attributes.h" #include "chrome/browser/ash/login/test/cryptohome_mixin.h" #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h" #include "chrome/browser/extensions/mixin_based_extension_apitest.h" +#include "chromeos/ash/components/install_attributes/stub_install_attributes.h" #include "components/account_id/account_id.h" #include "components/policy/core/common/mock_configuration_policy_provider.h" #include "google_apis/gaia/fake_gaia.h" diff --git a/chromium/chrome/browser/extensions/api/preference/preference_api.cc b/chromium/chrome/browser/extensions/api/preference/preference_api.cc index 30a4ccdc3d0..81b405f26ac 100644 --- a/chromium/chrome/browser/extensions/api/preference/preference_api.cc +++ b/chromium/chrome/browser/extensions/api/preference/preference_api.cc @@ -58,7 +58,7 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) #include "chrome/browser/chromeos/extensions/controlled_pref_mapping.h" -#include "chromeos/startup/browser_init_params.h" +#include "chromeos/startup/browser_params_proxy.h" #endif using extensions::mojom::APIPermissionID; @@ -467,9 +467,9 @@ PreferenceEventRouter::PreferenceEventRouter(Profile* profile) constexpr char kExtensionControlledPrefObserversCapability[] = "crbug/1334985"; bool ash_supports_crosapi_observers = - chromeos::BrowserInitParams::Get()->ash_capabilities.has_value() && + chromeos::BrowserParamsProxy::Get()->AshCapabilities().has_value() && base::Contains( - chromeos::BrowserInitParams::Get()->ash_capabilities.value(), + chromeos::BrowserParamsProxy::Get()->AshCapabilities().value(), kExtensionControlledPrefObserversCapability); #endif diff --git a/chromium/chrome/browser/extensions/api/preference/preference_api_lacros_browsertest.cc b/chromium/chrome/browser/extensions/api/preference/preference_api_lacros_browsertest.cc index 9742fb20d39..fa0f7f36609 100644 --- a/chromium/chrome/browser/extensions/api/preference/preference_api_lacros_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/preference/preference_api_lacros_browsertest.cc @@ -16,7 +16,7 @@ #include "chromeos/crosapi/mojom/prefs.mojom.h" #include "chromeos/lacros/lacros_service.h" #include "chromeos/lacros/lacros_test_helper.h" -#include "chromeos/startup/browser_init_params.h" +#include "chromeos/startup/browser_params_proxy.h" #include "components/keep_alive_registry/keep_alive_types.h" #include "components/keep_alive_registry/scoped_keep_alive.h" #include "components/prefs/pref_service.h" @@ -105,9 +105,9 @@ class ExtensionPreferenceApiLacrosBrowserTest // writing to the ash standalone browser prefstore. constexpr char kExtensionControlledPrefObserversCapability[] = "crbug/1334964"; - return chromeos::BrowserInitParams::Get()->ash_capabilities.has_value() && + return chromeos::BrowserParamsProxy::Get()->AshCapabilities().has_value() && base::Contains( - chromeos::BrowserInitParams::Get()->ash_capabilities.value(), + chromeos::BrowserParamsProxy::Get()->AshCapabilities().value(), kExtensionControlledPrefObserversCapability); } diff --git a/chromium/chrome/browser/extensions/api/preference/preference_helpers.cc b/chromium/chrome/browser/extensions/api/preference/preference_helpers.cc index 2e311fc1d2c..de1be982bfc 100644 --- a/chromium/chrome/browser/extensions/api/preference/preference_helpers.cc +++ b/chromium/chrome/browser/extensions/api/preference/preference_helpers.cc @@ -151,10 +151,10 @@ void DispatchEventToExtensionsImpl(Profile* profile, } } - base::Value args_copy = args->Clone(); - auto event = std::make_unique<Event>( - histogram_value, event_name, - std::move(args_copy).TakeListDeprecated(), restrict_to_profile); + base::Value::List args_copy = args->GetList().Clone(); + auto event = + std::make_unique<Event>(histogram_value, event_name, + std::move(args_copy), restrict_to_profile); router->DispatchEventToExtension(extension->id(), std::move(event)); } } diff --git a/chromium/chrome/browser/extensions/api/printing/print_job_submitter.cc b/chromium/chrome/browser/extensions/api/printing/print_job_submitter.cc index 3a05db933bf..01133ce9d4a 100644 --- a/chromium/chrome/browser/extensions/api/printing/print_job_submitter.cc +++ b/chromium/chrome/browser/extensions/api/printing/print_job_submitter.cc @@ -77,11 +77,11 @@ bool IsUserConfirmationRequired(content::BrowserContext* browser_context, const std::string& extension_id) { if (g_skip_confirmation_dialog_for_testing) return false; - const base::Value* list = + const base::Value::List& list = Profile::FromBrowserContext(browser_context) ->GetPrefs() - ->GetList(prefs::kPrintingAPIExtensionsAllowlist); - return !base::Contains(list->GetList(), base::Value(extension_id)); + ->GetValueList(prefs::kPrintingAPIExtensionsAllowlist); + return !base::Contains(list, base::Value(extension_id)); } } // namespace diff --git a/chromium/chrome/browser/extensions/api/printing/print_job_submitter.h b/chromium/chrome/browser/extensions/api/printing/print_job_submitter.h index 0e65c1d7fdd..dd8174e3cf9 100644 --- a/chromium/chrome/browser/extensions/api/printing/print_job_submitter.h +++ b/chromium/chrome/browser/extensions/api/printing/print_job_submitter.h @@ -10,6 +10,7 @@ #include "base/auto_reset.h" #include "base/callback.h" +#include "base/memory/raw_ptr.h" #include "base/memory/read_only_shared_memory_region.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" @@ -117,14 +118,14 @@ class PrintJobSubmitter : public printing::PrintJob::Observer { void FireErrorCallback(const std::string& error); gfx::NativeWindow native_window_; - content::BrowserContext* const browser_context_; + const raw_ptr<content::BrowserContext> browser_context_; // Tracks whether |native_window_| got destroyed. std::unique_ptr<NativeWindowTracker> native_window_tracker_; // These objects are owned by PrintingAPIHandler. - PrintJobController* const print_job_controller_; - mojo::Remote<printing::mojom::PdfFlattener>* const pdf_flattener_; + const raw_ptr<PrintJobController> print_job_controller_; + const raw_ptr<mojo::Remote<printing::mojom::PdfFlattener>> pdf_flattener_; // TODO(crbug.com/996785): Consider tracking extension being unloaded instead // of storing scoped_refptr. @@ -139,7 +140,7 @@ class PrintJobSubmitter : public printing::PrintJob::Observer { #if BUILDFLAG(IS_CHROMEOS_LACROS) const int local_printer_version_; #endif - crosapi::mojom::LocalPrinter* const local_printer_; + const raw_ptr<crosapi::mojom::LocalPrinter> local_printer_; SubmitJobCallback callback_; base::WeakPtrFactory<PrintJobSubmitter> weak_ptr_factory_{this}; }; diff --git a/chromium/chrome/browser/extensions/api/printing/printing_api_handler.h b/chromium/chrome/browser/extensions/api/printing/printing_api_handler.h index 1d23385941a..614f51b92dd 100644 --- a/chromium/chrome/browser/extensions/api/printing/printing_api_handler.h +++ b/chromium/chrome/browser/extensions/api/printing/printing_api_handler.h @@ -10,6 +10,7 @@ #include <vector> #include "base/callback.h" +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "build/chromeos_buildflags.h" #include "chrome/common/extensions/api/printing.h" @@ -156,9 +157,9 @@ class PrintingAPIHandler : public BrowserContextKeyedAPI, static const bool kServiceIsNULLWhileTesting = true; static const char* service_name() { return "PrintingAPIHandler"; } - content::BrowserContext* const browser_context_; - EventRouter* const event_router_; - ExtensionRegistry* const extension_registry_; + const raw_ptr<content::BrowserContext> browser_context_; + const raw_ptr<EventRouter> event_router_; + const raw_ptr<ExtensionRegistry> extension_registry_; std::unique_ptr<PrintJobController> print_job_controller_; std::unique_ptr<chromeos::CupsWrapper> cups_wrapper_; @@ -169,7 +170,7 @@ class PrintingAPIHandler : public BrowserContextKeyedAPI, // This is needed to cancel print jobs. base::flat_map<std::string, PrintJobInfo> print_jobs_; - crosapi::mojom::LocalPrinter* local_printer_; + raw_ptr<crosapi::mojom::LocalPrinter> local_printer_; #if BUILDFLAG(IS_CHROMEOS_LACROS) int local_printer_version_ = 0; #endif diff --git a/chromium/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc b/chromium/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc index ba3fe06c8cd..451da3c7cef 100644 --- a/chromium/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc +++ b/chromium/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc @@ -10,6 +10,7 @@ #include <vector> #include "base/containers/span.h" +#include "base/memory/raw_ptr.h" #include "base/run_loop.h" #include "base/test/values_test_util.h" #include "base/values.h" @@ -82,7 +83,7 @@ class PrintingEventObserver : public TestEventRouter::EventObserver { const Event& event) override { if (event.event_name == event_name_) { extension_id_ = extension_id; - event_args_ = event.event_args->Clone(); + event_args_ = base::Value(event.event_args.Clone()); } } @@ -92,7 +93,7 @@ class PrintingEventObserver : public TestEventRouter::EventObserver { private: // Event router this class should observe. - TestEventRouter* const event_router_; + const raw_ptr<TestEventRouter> event_router_; // The name of the observed event. const std::string event_name_; @@ -190,8 +191,8 @@ std::unique_ptr<api::printing::SubmitJob::Params> ConstructSubmitJobParams( request.job.content_type = content_type; request.document_blob_uuid = std::move(document_blob_uuid); - std::vector<base::Value> args; - args.emplace_back(base::Value::FromUniquePtrValue(request.ToValue())); + base::Value::List args; + args.Append(base::Value::FromUniquePtrValue(request.ToValue())); return api::printing::SubmitJob::Params::Create(args); } @@ -430,10 +431,10 @@ class PrintingAPIHandlerUnittest : public testing::Test { protected: content::BrowserTaskEnvironment task_environment_; - TestingProfile* testing_profile_; - TestEventRouter* event_router_ = nullptr; - FakePrintJobController* print_job_controller_; - chromeos::TestCupsWrapper* cups_wrapper_; + raw_ptr<TestingProfile> testing_profile_; + raw_ptr<TestEventRouter> event_router_ = nullptr; + raw_ptr<FakePrintJobController> print_job_controller_; + raw_ptr<chromeos::TestCupsWrapper> cups_wrapper_; std::unique_ptr<PrintingAPIHandler> printing_api_handler_; scoped_refptr<const Extension> extension_; absl::optional<api::printing::SubmitJobStatus> submit_job_status_; diff --git a/chromium/chrome/browser/extensions/api/processes/processes_api.cc b/chromium/chrome/browser/extensions/api/processes/processes_api.cc index 3dcc62771b6..2fbbbb63181 100644 --- a/chromium/chrome/browser/extensions/api/processes/processes_api.cc +++ b/chromium/chrome/browser/extensions/api/processes/processes_api.cc @@ -338,10 +338,9 @@ void ProcessesEventRouter::OnTaskUnresponsive(task_manager::TaskId id) { api::processes::OnUnresponsive::Create(process)); } -void ProcessesEventRouter::DispatchEvent( - events::HistogramValue histogram_value, - const std::string& event_name, - std::vector<base::Value> event_args) const { +void ProcessesEventRouter::DispatchEvent(events::HistogramValue histogram_value, + const std::string& event_name, + base::Value::List event_args) const { EventRouter* event_router = EventRouter::Get(browser_context_); if (event_router) { std::unique_ptr<Event> event( diff --git a/chromium/chrome/browser/extensions/api/processes/processes_api.h b/chromium/chrome/browser/extensions/api/processes/processes_api.h index 32096ed78a7..a1052d82f6b 100644 --- a/chromium/chrome/browser/extensions/api/processes/processes_api.h +++ b/chromium/chrome/browser/extensions/api/processes/processes_api.h @@ -49,7 +49,7 @@ class ProcessesEventRouter : public task_manager::TaskManagerObserver { void DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> event_args) const; + base::Value::List event_args) const; // Determines whether there is a registered listener for the specified event. // It helps to avoid collecting data if no one is interested in it. diff --git a/chromium/chrome/browser/extensions/api/proxy/proxy_apitest.cc b/chromium/chrome/browser/extensions/api/proxy/proxy_apitest.cc index 419d67415a3..599925fdc10 100644 --- a/chromium/chrome/browser/extensions/api/proxy/proxy_apitest.cc +++ b/chromium/chrome/browser/extensions/api/proxy/proxy_apitest.cc @@ -46,8 +46,10 @@ class ProxySettingsApiTest : public ExtensionApiTest { ASSERT_TRUE(pref != NULL); EXPECT_TRUE(pref->IsExtensionControlled()); + // TODO(https://crbug.com/1348219) This should call + // `PrefService::GetValueDict`. ProxyConfigDictionary dict( - pref_service->GetDictionary(proxy_config::prefs::kProxy)->Clone()); + pref_service->GetValue(proxy_config::prefs::kProxy).Clone()); ProxyPrefs::ProxyMode mode; ASSERT_TRUE(dict.GetMode(&mode)); diff --git a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc index 09eeb775849..6a5d1e4f94b 100644 --- a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc +++ b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc @@ -9,7 +9,7 @@ #include <utility> #include "ash/components/login/auth/extended_authenticator.h" -#include "ash/components/login/auth/user_context.h" +#include "ash/components/login/auth/public/user_context.h" #include "ash/constants/ash_pref_names.h" #include "base/bind.h" #include "base/containers/contains.h" diff --git a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc index 267607de5f3..b35c33f2ff1 100644 --- a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc @@ -8,7 +8,6 @@ #include <memory> -#include "ash/components/cryptohome/system_salt_getter.h" #include "ash/components/login/auth/fake_extended_authenticator.h" #include "ash/constants/ash_features.h" #include "ash/constants/ash_pref_names.h" @@ -43,8 +42,9 @@ #include "chrome/common/chrome_features.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_profile_manager.h" -#include "chromeos/dbus/userdataauth/fake_cryptohome_misc_client.h" -#include "chromeos/dbus/userdataauth/fake_userdataauth_client.h" +#include "chromeos/ash/components/cryptohome/system_salt_getter.h" +#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h" +#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h" #include "components/prefs/pref_service.h" #include "components/prefs/testing_pref_service.h" #include "components/sync_preferences/testing_pref_service_syncable.h" @@ -179,7 +179,7 @@ class QuickUnlockPrivateUnitTest ash::UserDataAuthClient::InitializeFake(); if (std::get<0>(param) == TestType::kCryptohome) { auto* fake_userdataauth_client_testapi = - chromeos::FakeUserDataAuthClient::TestApi::Get(); + ash::FakeUserDataAuthClient::TestApi::Get(); fake_userdataauth_client_testapi->set_supports_low_entropy_credentials( true); fake_userdataauth_client_testapi->set_enable_auth_check(true); @@ -682,7 +682,7 @@ TEST_P(QuickUnlockPrivateUnitTest, GetAuthTokenValid) { EXPECT_EQ(token_info->token, quick_unlock_storage->GetAuthToken()->Identifier()); EXPECT_EQ(token_info->lifetime_seconds, - ash::quick_unlock::AuthToken::kTokenExpirationSeconds); + ash::quick_unlock::AuthToken::kTokenExpiration.InSeconds()); } // Verifies that GetAuthTokenValid fails when an invalid password is provided. diff --git a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc index 23cbbdd757f..9f9dece59e0 100644 --- a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc +++ b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc @@ -7,7 +7,7 @@ #include <utility> #include "ash/components/login/auth/extended_authenticator.h" -#include "ash/components/login/auth/user_context.h" +#include "ash/components/login/auth/public/user_context.h" #include "base/bind.h" #include "chrome/browser/ash/login/quick_unlock/auth_token.h" #include "chrome/browser/ash/login/quick_unlock/fingerprint_storage.h" @@ -76,7 +76,7 @@ void QuickUnlockPrivateGetAuthTokenHelper::OnAuthSuccess( ash::quick_unlock::QuickUnlockFactory::GetForProfile(profile_); quick_unlock_storage->MarkStrongAuth(); token_info->token = quick_unlock_storage->CreateAuthToken(user_context); - token_info->lifetime_seconds = AuthToken::kTokenExpirationSeconds; + token_info->lifetime_seconds = AuthToken::kTokenExpiration.InSeconds(); // The user has successfully authenticated, so we should reset pin/fingerprint // attempt counts. diff --git a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h index a43dbfa7d40..eff1def469c 100644 --- a/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h +++ b/chromium/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h @@ -9,6 +9,7 @@ #include "ash/components/login/auth/auth_status_consumer.h" #include "base/callback.h" +#include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "content/public/browser/browser_thread.h" @@ -79,7 +80,7 @@ class QuickUnlockPrivateGetAuthTokenHelper void OnAuthFailure(const ash::AuthFailure& error) override; void OnAuthSuccess(const ash::UserContext& user_context) override; - Profile* profile_; + raw_ptr<Profile> profile_; ResultCallback callback_; }; diff --git a/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h b/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h index a1d9757b869..3f9bd5a9e8e 100644 --- a/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h +++ b/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h @@ -77,7 +77,9 @@ class ChromeRuntimeAPIDelegate : public extensions::RuntimeAPIDelegate, void CallUpdateCallbacks(const std::string& extension_id, const UpdateCheckResult& result); - raw_ptr<content::BrowserContext> browser_context_; + // TODO(crbug.com/1298696): unit_tests breaks with MTECheckedPtr + // enabled. Triage. + raw_ptr<content::BrowserContext, DegradeToNoOpWhenMTE> browser_context_; content::NotificationRegistrar registrar_; diff --git a/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc b/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc index a46c51f9652..65b38a1954f 100644 --- a/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc +++ b/chromium/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc @@ -104,16 +104,19 @@ class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { updates_[id] = std::move(args); } - void StartUpdateCheck( - ExtensionDownloader* downloader, - ExtensionDownloaderDelegate* delegate, - std::unique_ptr<ManifestFetchData> fetch_data) override { + void StartUpdateCheck(ExtensionDownloader* downloader, + ExtensionDownloaderDelegate* delegate, + std::vector<ExtensionDownloaderTask> tasks) override { + std::set<int> request_ids; + for (const ExtensionDownloaderTask& task : tasks) + request_ids.insert(task.request_id); // Instead of immediately firing callbacks to the delegate in matching // cases below, we instead post a task since the delegate typically isn't // expecting a synchronous reply (the real code has to go do at least one // network request before getting a response, so this is is a reasonable // expectation by delegates). - for (const std::string& id : fetch_data->GetExtensionIds()) { + for (const ExtensionDownloaderTask& task : tasks) { + const ExtensionId& id = task.id; auto no_update = no_updates_.find(id); if (no_update != no_updates_.end()) { no_updates_.erase(no_update); @@ -123,8 +126,7 @@ class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { &ExtensionDownloaderDelegate::OnExtensionDownloadFailed, base::Unretained(delegate), id, ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE, - ExtensionDownloaderDelegate::PingResult(), - fetch_data->request_ids(), + ExtensionDownloaderDelegate::PingResult(), request_ids, ExtensionDownloaderDelegate::FailureData())); continue; } @@ -140,8 +142,7 @@ class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { &ExtensionDownloaderDelegate::OnExtensionDownloadFinished, base::Unretained(delegate), crx_info, false /* file_ownership_passed */, GURL(), - ExtensionDownloaderDelegate::PingResult(), - fetch_data->request_ids(), + ExtensionDownloaderDelegate::PingResult(), request_ids, ExtensionDownloaderDelegate::InstallCallback())); continue; } diff --git a/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc b/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc index c2ef475804d..b18741fd0e7 100644 --- a/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc +++ b/chromium/chrome/browser/extensions/api/runtime/runtime_apitest.cc @@ -498,7 +498,11 @@ IN_PROC_BROWSER_TEST_P(BackgroundPageOnlyRuntimeApiTest, } { - content::DOMMessageQueue message_queue; + ExtensionHost* host = ProcessManager::Get(browser()->profile()) + ->GetBackgroundHostForExtension(extension->id()); + ASSERT_TRUE(host); + content::DOMMessageQueue message_queue(host->host_contents()); + static constexpr char kScript[] = R"( const foundWindows = chrome.extension.getViews({type: 'tab'}); domAutomationController.send(foundWindows.length); diff --git a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc index 8ee19900fb5..b63799126eb 100644 --- a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc +++ b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc @@ -21,6 +21,8 @@ #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/enterprise/connectors/common.h" #include "chrome/browser/enterprise/connectors/connectors_service.h" +#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h" +#include "chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h" #include "chrome/browser/policy/chrome_browser_policy_connector.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_attributes_entry.h" @@ -56,23 +58,12 @@ #include "components/user_manager/user.h" #include "components/user_manager/user_manager.h" #else -#include "chrome/browser/policy/chrome_browser_policy_connector.h" #include "components/enterprise/browser/controller/browser_dm_token_storage.h" #include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h" #endif namespace { -#if BUILDFLAG(IS_CHROMEOS_ASH) -const char kActiveDirectoryPolicyClientDescription[] = "an Active Directory"; -const char kPolicyClientDescription[] = "any"; -const char kUserPolicyClientDescription[] = "a user"; -#else -const char kChromeBrowserCloudManagementClientDescription[] = - "a machine-level user"; -#endif -const char kProfilePolicyClientDescription[] = "a profile-level user"; - const char16_t kMaskedUsername[] = u"*****"; void AddAnalysisConnectorVerdictToEvent( @@ -105,11 +96,6 @@ std::string MalwareRuleToThreatType(const std::string& rule_name) { } } -bool IsClientValid(const std::string& dm_token, - policy::CloudPolicyClient* client) { - return client && client->dm_token() == dm_token; -} - std::string DangerTypeToThreatType(download::DownloadDangerType danger_type) { switch (danger_type) { case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: @@ -224,7 +210,9 @@ const char const char SafeBrowsingPrivateEventRouter::kKeyUserJustification[] = "userJustification"; -// All new event names should be added to the kAllEvents array below! +// All new event names should be added to the array +// `enterprise_connectors::ReportingServiceSettings::kAllReportingEvents` in +// `chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h` const char SafeBrowsingPrivateEventRouter::kKeyPasswordReuseEvent[] = "passwordReuseEvent"; const char SafeBrowsingPrivateEventRouter::kKeyPasswordChangedEvent[] = @@ -240,17 +228,6 @@ const char SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent[] = const char SafeBrowsingPrivateEventRouter::kKeyLoginEvent[] = "loginEvent"; const char SafeBrowsingPrivateEventRouter::kKeyPasswordBreachEvent[] = "passwordBreachEvent"; -// All new event names should be added to this array! -const char* SafeBrowsingPrivateEventRouter::kAllEvents[8] = { - SafeBrowsingPrivateEventRouter::kKeyPasswordReuseEvent, - SafeBrowsingPrivateEventRouter::kKeyPasswordChangedEvent, - SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent, - SafeBrowsingPrivateEventRouter::kKeyInterstitialEvent, - SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent, - SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent, - SafeBrowsingPrivateEventRouter::kKeyLoginEvent, - SafeBrowsingPrivateEventRouter::kKeyPasswordBreachEvent, -}; const char SafeBrowsingPrivateEventRouter::kKeyUnscannedReason[] = "unscannedReason"; @@ -268,14 +245,12 @@ SafeBrowsingPrivateEventRouter::SafeBrowsingPrivateEventRouter( event_router_ = EventRouter::Get(context_); identity_manager_ = IdentityManagerFactory::GetForProfile( Profile::FromBrowserContext(context_)); + reporting_client_ = + enterprise_connectors::RealtimeReportingClientFactory::GetForProfile( + context); } -SafeBrowsingPrivateEventRouter::~SafeBrowsingPrivateEventRouter() { - if (browser_client_) - browser_client_->RemoveObserver(this); - if (profile_client_) - profile_client_->RemoveObserver(this); -} +SafeBrowsingPrivateEventRouter::~SafeBrowsingPrivateEventRouter() = default; // static std::string SafeBrowsingPrivateEventRouter::GetBaseName( @@ -301,8 +276,8 @@ void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordReuseDetected( // |event_router_| can be null in tests. if (event_router_) { - std::vector<base::Value> event_value; - event_value.push_back(base::Value::FromUniquePtrValue(params.ToValue())); + base::Value::List event_value; + event_value.Append(base::Value::FromUniquePtrValue(params.ToValue())); auto extension_event = std::make_unique<Event>( events:: @@ -314,7 +289,7 @@ void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordReuseDetected( } absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyPasswordReuseEvent) == 0) { return; @@ -330,16 +305,16 @@ void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordReuseDetected( warning_shown ? safe_browsing::EventResult::WARNED : safe_browsing::EventResult::ALLOWED)); - ReportRealtimeEvent(kKeyPasswordReuseEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyPasswordReuseEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordChanged( const std::string& user_name) { // |event_router_| can be null in tests. if (event_router_) { - std::vector<base::Value> event_value; - event_value.push_back(base::Value(user_name)); + base::Value::List event_value; + event_value.Append(user_name); auto extension_event = std::make_unique<Event>( events::SAFE_BROWSING_PRIVATE_ON_POLICY_SPECIFIED_PASSWORD_CHANGED, api::safe_browsing_private::OnPolicySpecifiedPasswordChanged:: @@ -349,7 +324,7 @@ void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordChanged( } absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyPasswordChangedEvent) == 0) { return; @@ -359,8 +334,8 @@ void SafeBrowsingPrivateEventRouter::OnPolicySpecifiedPasswordChanged( event.Set(kKeyUserName, user_name); event.Set(kKeyProfileUserName, GetProfileUserName()); - ReportRealtimeEvent(kKeyPasswordChangedEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyPasswordChangedEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnDangerousDownloadOpened( @@ -379,8 +354,8 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadOpened( // |event_router_| can be null in tests. if (event_router_) { - std::vector<base::Value> event_value; - event_value.push_back(base::Value::FromUniquePtrValue(params.ToValue())); + base::Value::List event_value; + event_value.Append(base::Value::FromUniquePtrValue(params.ToValue())); auto extension_event = std::make_unique<Event>( events::SAFE_BROWSING_PRIVATE_ON_DANGEROUS_DOWNLOAD_OPENED, @@ -390,7 +365,7 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadOpened( } absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyDangerousDownloadEvent) == 0) { return; @@ -417,8 +392,9 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadOpened( event.Set(kKeyScanId, scan_id); } - ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent(kKeyDangerousDownloadEvent, + std::move(settings.value()), + std::move(event)); } void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialShown( @@ -436,8 +412,8 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialShown( // |event_router_| can be null in tests. if (event_router_) { - std::vector<base::Value> event_value; - event_value.push_back(base::Value::FromUniquePtrValue(params.ToValue())); + base::Value::List event_value; + event_value.Append(base::Value::FromUniquePtrValue(params.ToValue())); auto extension_event = std::make_unique<Event>( events::SAFE_BROWSING_PRIVATE_ON_SECURITY_INTERSTITIAL_SHOWN, @@ -447,7 +423,7 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialShown( } absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyInterstitialEvent) == 0) { return; @@ -466,8 +442,8 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialShown( event.Set(kKeyClickedThrough, false); event.Set(kKeyEventResult, safe_browsing::EventResultToString(event_result)); - ReportRealtimeEvent(kKeyInterstitialEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyInterstitialEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialProceeded( @@ -485,8 +461,8 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialProceeded( // |event_router_| can be null in tests. if (event_router_) { - std::vector<base::Value> event_value; - event_value.push_back(base::Value::FromUniquePtrValue(params.ToValue())); + base::Value::List event_value; + event_value.Append(base::Value::FromUniquePtrValue(params.ToValue())); auto extension_event = std::make_unique<Event>( events::SAFE_BROWSING_PRIVATE_ON_SECURITY_INTERSTITIAL_PROCEEDED, @@ -496,7 +472,7 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialProceeded( } absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyInterstitialEvent) == 0) { return; @@ -511,8 +487,8 @@ void SafeBrowsingPrivateEventRouter::OnSecurityInterstitialProceeded( event.Set(kKeyEventResult, safe_browsing::EventResultToString( safe_browsing::EventResult::BYPASSED)); - ReportRealtimeEvent(kKeyInterstitialEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyInterstitialEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorResult( @@ -553,7 +529,7 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDeepScanningResult( const std::string& evidence_locker_filepath, const std::string& scan_id) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyDangerousDownloadEvent) == 0) { return; @@ -590,8 +566,9 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDeepScanningResult( event.Set(kKeyScanId, scan_id); } - ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent(kKeyDangerousDownloadEvent, + std::move(settings.value()), + std::move(event)); } void SafeBrowsingPrivateEventRouter::OnSensitiveDataEvent( @@ -605,7 +582,7 @@ void SafeBrowsingPrivateEventRouter::OnSensitiveDataEvent( const int64_t content_size, safe_browsing::EventResult event_result) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeySensitiveDataEvent) == 0) { return; @@ -633,8 +610,8 @@ void SafeBrowsingPrivateEventRouter::OnSensitiveDataEvent( AddAnalysisConnectorVerdictToEvent(result, event); - ReportRealtimeEvent(kKeySensitiveDataEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeySensitiveDataEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorWarningBypassed( @@ -649,7 +626,7 @@ void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorWarningBypassed( const int64_t content_size, absl::optional<std::u16string> user_justification) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeySensitiveDataEvent) == 0) { return; @@ -680,8 +657,8 @@ void SafeBrowsingPrivateEventRouter::OnAnalysisConnectorWarningBypassed( AddAnalysisConnectorVerdictToEvent(result, event); - ReportRealtimeEvent(kKeySensitiveDataEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeySensitiveDataEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnUnscannedFileEvent( @@ -695,7 +672,7 @@ void SafeBrowsingPrivateEventRouter::OnUnscannedFileEvent( const int64_t content_size, safe_browsing::EventResult event_result) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyUnscannedFileEvent) == 0) { return; @@ -718,8 +695,8 @@ void SafeBrowsingPrivateEventRouter::OnUnscannedFileEvent( event.Set(kKeyClickedThrough, event_result == safe_browsing::EventResult::BYPASSED); - ReportRealtimeEvent(kKeyUnscannedFileEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyUnscannedFileEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnDangerousDownloadEvent( @@ -746,7 +723,7 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadEvent( const int64_t content_size, safe_browsing::EventResult event_result) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyDangerousDownloadEvent) == 0) { return; @@ -774,8 +751,9 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadEvent( event.Set(kKeyScanId, scan_id); } - ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent(kKeyDangerousDownloadEvent, + std::move(settings.value()), + std::move(event)); } void SafeBrowsingPrivateEventRouter::OnDangerousDownloadWarningBypassed( @@ -800,7 +778,7 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadWarningBypassed( const std::string& scan_id, const int64_t content_size) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value() || settings->enabled_event_names.count(kKeyDangerousDownloadEvent) == 0) { return; @@ -828,8 +806,9 @@ void SafeBrowsingPrivateEventRouter::OnDangerousDownloadWarningBypassed( event.Set(kKeyScanId, scan_id); } - ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent(kKeyDangerousDownloadEvent, + std::move(settings.value()), + std::move(event)); } void SafeBrowsingPrivateEventRouter::OnLoginEvent( @@ -838,7 +817,7 @@ void SafeBrowsingPrivateEventRouter::OnLoginEvent( const url::Origin& federated_origin, const std::u16string& username) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value()) { return; } @@ -858,15 +837,15 @@ void SafeBrowsingPrivateEventRouter::OnLoginEvent( event.Set(kKeyProfileUserName, GetProfileUserName()); event.Set(kKeyLoginUserName, MaskUsername(username)); - ReportRealtimeEvent(kKeyLoginEvent, std::move(settings.value()), - std::move(event)); + reporting_client_->ReportRealtimeEvent( + kKeyLoginEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::OnPasswordBreach( const std::string& trigger, const std::vector<std::pair<GURL, std::u16string>>& identities) { absl::optional<enterprise_connectors::ReportingSettings> settings = - GetReportingSettings(); + reporting_client_->GetReportingSettings(); if (!settings.has_value()) { return; } @@ -887,7 +866,7 @@ void SafeBrowsingPrivateEventRouter::OnPasswordBreach( base::Value::Dict identity; identity.Set(kKeyPasswordBreachIdentitiesUrl, i.first.spec()); - identity.Set(kKeyPasswordBreachIdentitiesUsername, i.second); + identity.Set(kKeyPasswordBreachIdentitiesUsername, MaskUsername(i.second)); identities_list.Append(std::move(identity)); } @@ -900,40 +879,8 @@ void SafeBrowsingPrivateEventRouter::OnPasswordBreach( event.Set(kKeyPasswordBreachIdentities, std::move(identities_list)); event.Set(kKeyProfileUserName, GetProfileUserName()); - ReportRealtimeEvent(kKeyPasswordBreachEvent, std::move(settings.value()), - std::move(event)); -} - -// static -bool SafeBrowsingPrivateEventRouter::ShouldInitRealtimeReportingClient() { - if (!base::FeatureList::IsEnabled(kRealtimeReportingFeature) && - !base::FeatureList::IsEnabled( - enterprise_connectors::kEnterpriseConnectorsEnabled)) { - DVLOG(2) << "Safe browsing real-time reporting is not enabled."; - return false; - } - - return true; -} - -void SafeBrowsingPrivateEventRouter::SetBrowserCloudPolicyClientForTesting( - policy::CloudPolicyClient* client) { - if (client == nullptr && browser_client_) - browser_client_->RemoveObserver(this); - - browser_client_ = client; - if (browser_client_) - browser_client_->AddObserver(this); -} - -void SafeBrowsingPrivateEventRouter::SetProfileCloudPolicyClientForTesting( - policy::CloudPolicyClient* client) { - if (client == nullptr && profile_client_) - profile_client_->RemoveObserver(this); - - profile_client_ = client; - if (profile_client_) - profile_client_->AddObserver(this); + reporting_client_->ReportRealtimeEvent( + kKeyPasswordBreachEvent, std::move(settings.value()), std::move(event)); } void SafeBrowsingPrivateEventRouter::SetIdentityManagerForTesting( @@ -941,291 +888,8 @@ void SafeBrowsingPrivateEventRouter::SetIdentityManagerForTesting( identity_manager_ = identity_manager; } -void SafeBrowsingPrivateEventRouter::InitRealtimeReportingClient( - const enterprise_connectors::ReportingSettings& settings) { - // If the corresponding client is already initialized, do nothing. - if ((settings.per_profile && - IsClientValid(settings.dm_token, profile_client_)) || - (!settings.per_profile && - IsClientValid(settings.dm_token, browser_client_))) { - DVLOG(2) << "Safe browsing real-time event reporting already initialized."; - return; - } - - if (!ShouldInitRealtimeReportingClient()) - return; - - // |identity_manager_| may be null in tests. If there is no identity - // manager don't enable the real-time reporting API since the router won't - // be able to fill in all the info needed for the reports. - if (!identity_manager_) { - DVLOG(2) << "Safe browsing real-time event requires an identity manager."; - return; - } - - policy::CloudPolicyClient* client = nullptr; - std::string policy_client_desc; -#if BUILDFLAG(IS_CHROMEOS_ASH) - std::pair<std::string, policy::CloudPolicyClient*> desc_and_client = - InitBrowserReportingClient(settings.dm_token); -#else - std::pair<std::string, policy::CloudPolicyClient*> desc_and_client = - settings.per_profile ? InitProfileReportingClient(settings.dm_token) - : InitBrowserReportingClient(settings.dm_token); -#endif - if (!desc_and_client.second) - return; - policy_client_desc = std::move(desc_and_client.first); - client = std::move(desc_and_client.second); - - OnCloudPolicyClientAvailable(policy_client_desc, client); -} - -std::pair<std::string, policy::CloudPolicyClient*> -SafeBrowsingPrivateEventRouter::InitBrowserReportingClient( - const std::string& dm_token) { - // |device_management_service| may be null in tests. If there is no device - // management service don't enable the real-time reporting API since the - // router won't be able to create the reporting server client below. - policy::DeviceManagementService* device_management_service = - g_browser_process->browser_policy_connector() - ->device_management_service(); - std::string policy_client_desc; -#if BUILDFLAG(IS_CHROMEOS_ASH) - policy_client_desc = kPolicyClientDescription; -#else - policy_client_desc = kChromeBrowserCloudManagementClientDescription; -#endif - if (!device_management_service) { - DVLOG(2) << "Safe browsing real-time event requires a device management " - "service."; - return {policy_client_desc, nullptr}; - } - - policy::CloudPolicyClient* client = nullptr; - -#if BUILDFLAG(IS_CHROMEOS_ASH) - const user_manager::User* user = GetChromeOSUser(); - if (user) { - Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(user); - // If primary user profile is not finalized, use the current profile. - if (!profile) - profile = Profile::FromBrowserContext(context_); - DCHECK(profile); - if (user->IsActiveDirectoryUser()) { - // TODO(crbug.com/1012048): Handle AD, likely through crbug.com/1012170. - policy_client_desc = kActiveDirectoryPolicyClientDescription; - } else { - policy_client_desc = kUserPolicyClientDescription; - policy::UserCloudPolicyManagerAsh* policy_manager = - profile->GetUserCloudPolicyManagerAsh(); - if (policy_manager) - client = policy_manager->core()->client(); - } - } else { - LOG(ERROR) << "Could not determine who the user is."; - } -#else - std::string client_id = - policy::BrowserDMTokenStorage::Get()->RetrieveClientId(); -#if BUILDFLAG(IS_CHROMEOS_LACROS) - Profile* main_profile = enterprise_connectors::GetMainProfileLacros(); - if (main_profile) { - // Prefer the user client id if available. - client_id = reporting::GetUserClientId(main_profile).value_or(client_id); - } -#endif - - // Make sure DeviceManagementService has been initialized. - device_management_service->ScheduleInitialization(0); - - browser_private_client_ = std::make_unique<policy::CloudPolicyClient>( - device_management_service, g_browser_process->shared_url_loader_factory(), - policy::CloudPolicyClient::DeviceDMTokenCallback()); - client = browser_private_client_.get(); - - // TODO(crbug.com/1069049): when we decide to add the extra URL parameters to - // the uploaded reports, do the following: - // client->add_connector_url_params(base::FeatureList::IsEnabled( - // enterprise_connectors::kEnterpriseConnectorsEnabled)); - if (!client->is_registered()) { - client->SetupRegistration( - dm_token, client_id, - /*user_affiliation_ids=*/std::vector<std::string>()); - } -#endif - - return {policy_client_desc, client}; -} - -#if !BUILDFLAG(IS_CHROMEOS_ASH) -std::pair<std::string, policy::CloudPolicyClient*> -SafeBrowsingPrivateEventRouter::InitProfileReportingClient( - const std::string& dm_token) { - policy::UserCloudPolicyManager* policy_manager = - Profile::FromBrowserContext(context_)->GetUserCloudPolicyManager(); - if (!policy_manager || !policy_manager->core() || - !policy_manager->core()->client()) { - return {kProfilePolicyClientDescription, nullptr}; - } - - profile_private_client_ = std::make_unique<policy::CloudPolicyClient>( - policy_manager->core()->client()->service(), - g_browser_process->shared_url_loader_factory(), - policy::CloudPolicyClient::DeviceDMTokenCallback()); - policy::CloudPolicyClient* client = profile_private_client_.get(); - - // TODO(crbug.com/1069049): when we decide to add the extra URL parameters to - // the uploaded reports, do the following: - // client->add_connector_url_params(base::FeatureList::IsEnabled( - // enterprise_connectors::kEnterpriseConnectorsEnabled)); - - client->SetupRegistration(dm_token, - policy_manager->core()->client()->client_id(), - /*user_affiliation_ids*/ {}); - - return {kProfilePolicyClientDescription, client}; -} -#endif // !BUILDFLAG(IS_CHROMEOS_ASH) - -void SafeBrowsingPrivateEventRouter::OnCloudPolicyClientAvailable( - const std::string& policy_client_desc, - policy::CloudPolicyClient* client) { - if (policy_client_desc == kProfilePolicyClientDescription) - profile_client_ = client; - else - browser_client_ = client; - - if (client == nullptr) { - LOG(ERROR) << "Could not obtain " << policy_client_desc - << " for safe browsing real-time event reporting."; - return; - } - - client->AddObserver(this); - - VLOG(1) << "Ready for safe browsing real-time event reporting."; -} - -absl::optional<enterprise_connectors::ReportingSettings> -SafeBrowsingPrivateEventRouter::GetReportingSettings() { - return enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext( - context_) - ->GetReportingSettings( - enterprise_connectors::ReportingConnector::SECURITY_EVENT); -} - -void SafeBrowsingPrivateEventRouter::ReportRealtimeEvent( - const std::string& name, - const enterprise_connectors::ReportingSettings& settings, - base::Value::Dict event) { - if (rejected_dm_token_timers_.contains(settings.dm_token)) { - return; - } - -#ifndef NDEBUG - // Make sure that the event is included in the kAllEvents array. - bool found = false; - for (const char* known_event_name : - extensions::SafeBrowsingPrivateEventRouter::kAllEvents) { - if (name == known_event_name) { - found = true; - break; - } - } - DCHECK(found); -#endif - - // Make sure real-time reporting is initialized. - InitRealtimeReportingClient(settings); - if ((settings.per_profile && !profile_client_) || - (!settings.per_profile && !browser_client_)) { - return; - } - - // Format the current time (UTC) in RFC3339 format. - base::Time::Exploded now_exploded; - base::Time::Now().UTCExplode(&now_exploded); - std::string now_str = base::StringPrintf( - "%d-%02d-%02dT%02d:%02d:%02d.%03dZ", now_exploded.year, - now_exploded.month, now_exploded.day_of_month, now_exploded.hour, - now_exploded.minute, now_exploded.second, now_exploded.millisecond); - - policy::CloudPolicyClient* client = - settings.per_profile ? profile_client_.get() : browser_client_.get(); - base::Value::Dict wrapper; - wrapper.Set("time", now_str); - wrapper.Set(name, std::move(event)); - - auto upload_callback = base::BindOnce( - [](base::Value::Dict wrapper, bool per_profile, std::string dm_token, - bool uploaded) { - // Show the report on chrome://safe-browsing, if appropriate. - wrapper.Set("uploaded_successfully", uploaded); - wrapper.Set(per_profile ? "profile_dm_token" : "browser_dm_token", - std::move(dm_token)); - safe_browsing::WebUIInfoSingleton::GetInstance()->AddToReportingEvents( - std::move(wrapper)); - }, - wrapper.Clone(), settings.per_profile, client->dm_token()); - - base::Value::List event_list; - event_list.Append(std::move(wrapper)); - - Profile* profile = Profile::FromBrowserContext(context_); - - client->UploadSecurityEventReport( - context_, - enterprise_connectors::IncludeDeviceInfo(profile, settings.per_profile), - policy::RealtimeReportingJobConfiguration::BuildReport( - std::move(event_list), - reporting::GetContext(Profile::FromBrowserContext(context_))), - std::move(upload_callback)); -} - std::string SafeBrowsingPrivateEventRouter::GetProfileUserName() const { return safe_browsing::GetProfileEmail(identity_manager_); } -#if BUILDFLAG(IS_CHROMEOS_ASH) -// static -const user_manager::User* SafeBrowsingPrivateEventRouter::GetChromeOSUser() { - return user_manager::UserManager::IsInitialized() - ? user_manager::UserManager::Get()->GetPrimaryUser() - : nullptr; -} - -#endif - -void SafeBrowsingPrivateEventRouter::RemoveDmTokenFromRejectedSet( - const std::string& dm_token) { - rejected_dm_token_timers_.erase(dm_token); -} - -void SafeBrowsingPrivateEventRouter::OnClientError( - policy::CloudPolicyClient* client) { - base::Value::Dict error_value; - error_value.Set("error", - "An event got an error status and hasn't been reported"); - error_value.Set("status", client->status()); - safe_browsing::WebUIInfoSingleton::GetInstance()->AddToReportingEvents( - error_value); - - // This is the status set when the server returned 403, which is what the - // reporting server returns when the customer is not allowed to report events. - if (client->status() == policy::DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED) { - // This could happen if a second event was fired before the first one - // returned an error. - if (!rejected_dm_token_timers_.contains(client->dm_token())) { - rejected_dm_token_timers_[client->dm_token()] = - std::make_unique<base::OneShotTimer>(); - rejected_dm_token_timers_[client->dm_token()]->Start( - FROM_HERE, base::Hours(24), - base::BindOnce( - &SafeBrowsingPrivateEventRouter::RemoveDmTokenFromRejectedSet, - weak_ptr_factory_.GetWeakPtr(), client->dm_token())); - } - } -} - } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h index 129bf100328..75311fa0759 100644 --- a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h +++ b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h @@ -15,6 +15,7 @@ #include "base/values.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/enterprise/connectors/common.h" +#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h" #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h" #include "components/download/public/common/download_danger_type.h" #include "components/keyed_service/core/keyed_service.h" @@ -35,30 +36,16 @@ class IdentityManager; class GURL; -namespace policy { -class DeviceManagementService; -} - namespace safe_browsing { enum class DeepScanAccessPoint; } -#if BUILDFLAG(IS_CHROMEOS_ASH) - -namespace user_manager { -class User; -} - -#endif - namespace extensions { // An event router that observes Safe Browsing events and notifies listeners. // The router also uploads events to the chrome reporting server side API if // the kRealtimeReportingFeature feature is enabled. -class SafeBrowsingPrivateEventRouter - : public KeyedService, - public policy::CloudPolicyClient::Observer { +class SafeBrowsingPrivateEventRouter : public KeyedService { public: // Feature that controls whether real-time reports are sent. static const base::Feature kRealtimeReportingFeature; @@ -102,7 +89,6 @@ class SafeBrowsingPrivateEventRouter static const char kKeyUnscannedFileEvent[]; static const char kKeyLoginEvent[]; static const char kKeyPasswordBreachEvent[]; - static const char* kAllEvents[8]; static const char kKeyUnscannedReason[]; @@ -245,80 +231,9 @@ class SafeBrowsingPrivateEventRouter const std::string& trigger, const std::vector<std::pair<GURL, std::u16string>>& identities); - // Returns true if enterprise real-time reporting should be initialized, - // checking both the feature flag. This function is public so that it can - // called in tests. - static bool ShouldInitRealtimeReportingClient(); - - void SetBrowserCloudPolicyClientForTesting(policy::CloudPolicyClient* client); - void SetProfileCloudPolicyClientForTesting(policy::CloudPolicyClient* client); - void SetIdentityManagerForTesting(signin::IdentityManager* identity_manager); - // policy::CloudPolicyClient::Observer: - void OnClientError(policy::CloudPolicyClient* client) override; - void OnPolicyFetched(policy::CloudPolicyClient* client) override {} - void OnRegistrationStateChanged(policy::CloudPolicyClient* client) override {} - - protected: - // Report safe browsing event through real-time reporting channel, if enabled. - // Declared as virtual for tests. Declared as protected to be called directly - // by tests. - virtual void ReportRealtimeEvent( - const std::string&, - const enterprise_connectors::ReportingSettings& settings, - base::Value::Dict event); - private: - // Initialize a real-time report client if needed. This client is used only - // if real-time reporting is enabled, the machine is properly reigistered - // with CBCM and the appropriate policies are enabled. - void InitRealtimeReportingClient( - const enterprise_connectors::ReportingSettings& settings); - - // Sub-methods called by InitRealtimeReportingClient to make appropriate - // verifications and initialize the corresponding client. Returns a policy - // client description and a client, which can be nullptr if it can't be - // initialized. - std::pair<std::string, policy::CloudPolicyClient*> InitBrowserReportingClient( - const std::string& dm_token); - -#if !BUILDFLAG(IS_CHROMEOS_ASH) - std::pair<std::string, policy::CloudPolicyClient*> InitProfileReportingClient( - const std::string& dm_token); -#endif - - // Determines if the real-time reporting feature is enabled. - // Obtain settings to apply to a reporting event from ConnectorsService. - // absl::nullopt represents that reporting should not be done. - absl::optional<enterprise_connectors::ReportingSettings> - GetReportingSettings(); - - // Called whenever the real-time reporting policy changes. - void RealtimeReportingPrefChanged(const std::string& pref); - - // Create a privately owned cloud policy client for events routing. - void CreatePrivateCloudPolicyClient( - const std::string& policy_client_desc, - policy::DeviceManagementService* device_management_service, - const std::string& client_id, - const std::string& dm_token); - - // Handle the availability of a cloud policy client. - void OnCloudPolicyClientAvailable(const std::string& policy_client_desc, - policy::CloudPolicyClient* client); - -#if BUILDFLAG(IS_CHROMEOS_ASH) - - // Return the Chrome OS user who is subject to reporting, or nullptr if - // the user cannot be deterined. - static const user_manager::User* GetChromeOSUser(); - -#endif - - // Determines if real-time reporting is available based on platform and user. - static bool IsRealtimeReportingAvailable(); - // Removes any path information and returns just the basename. static std::string GetBaseName(const std::string& filename); @@ -353,17 +268,11 @@ class SafeBrowsingPrivateEventRouter const int64_t content_size, safe_browsing::EventResult event_result); - void RemoveDmTokenFromRejectedSet(const std::string& dm_token); - raw_ptr<content::BrowserContext> context_; raw_ptr<signin::IdentityManager> identity_manager_ = nullptr; raw_ptr<EventRouter> event_router_ = nullptr; - - // The cloud policy clients used to upload browser events and profile events - // to the cloud. These clients are never used to fetch policies. These - // pointers are not owned by the class. - raw_ptr<policy::CloudPolicyClient> browser_client_ = nullptr; - raw_ptr<policy::CloudPolicyClient> profile_client_ = nullptr; + raw_ptr<enterprise_connectors::RealtimeReportingClient> reporting_client_ = + nullptr; // The private clients are used on platforms where we cannot just get a // client and we create our own (used through the above client pointers). diff --git a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.cc b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.cc index 50b4be9464f..17f527b585c 100644 --- a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.cc +++ b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.cc @@ -5,7 +5,9 @@ #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h" #include "chrome/browser/enterprise/connectors/connectors_service.h" +#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h" #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h" +#include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/signin/identity_manager_factory.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" @@ -35,6 +37,8 @@ SafeBrowsingPrivateEventRouterFactory::SafeBrowsingPrivateEventRouterFactory() DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); DependsOn(IdentityManagerFactory::GetInstance()); DependsOn(enterprise_connectors::ConnectorsServiceFactory::GetInstance()); + DependsOn( + enterprise_connectors::RealtimeReportingClientFactory::GetInstance()); } SafeBrowsingPrivateEventRouterFactory:: @@ -48,7 +52,11 @@ KeyedService* SafeBrowsingPrivateEventRouterFactory::BuildServiceInstanceFor( content::BrowserContext* SafeBrowsingPrivateEventRouterFactory::GetBrowserContextToUse( content::BrowserContext* context) const { - return ExtensionsBrowserClient::Get()->GetOriginalContext(context); + Profile* profile = Profile::FromBrowserContext(context); + if (!profile || profile->IsSystemProfile()) { + return nullptr; + } + return chrome::GetBrowserContextOwnInstanceInIncognito(context); } bool SafeBrowsingPrivateEventRouterFactory::ServiceIsCreatedWithBrowserContext() diff --git a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc index 4e78881b143..58992a4b6db 100644 --- a/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc +++ b/chromium/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc @@ -22,10 +22,13 @@ #include "chrome/browser/enterprise/connectors/common.h" #include "chrome/browser/enterprise/connectors/connectors_prefs.h" #include "chrome/browser/enterprise/connectors/connectors_service.h" +#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h" +#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h" #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h" #include "chrome/browser/policy/dm_token_utils.h" #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h" #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h" +#include "chrome/browser/safe_browsing/test_extension_event_observer.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/api/safe_browsing_private.h" #include "chrome/test/base/testing_browser_process.h" @@ -45,11 +48,11 @@ #include "testing/gtest/include/gtest/gtest.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "ash/components/tpm/stub_install_attributes.h" #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" #include "chrome/browser/ash/login/users/scoped_test_user_manager.h" #include "chrome/browser/ash/profiles/profile_helper.h" #include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h" +#include "chromeos/ash/components/install_attributes/stub_install_attributes.h" #include "components/account_id/account_id.h" #include "components/user_manager/scoped_user_manager.h" #include "components/user_manager/user.h" @@ -96,7 +99,7 @@ class SafeBrowsingEventObserver : public TestEventRouter::EventObserver { // extensions::TestEventRouter::EventObserver: void OnBroadcastEvent(const extensions::Event& event) override { if (event.event_name == event_name_) { - event_args_ = event.event_args->Clone(); + event_args_ = base::Value(event.event_args.Clone()); } } @@ -108,12 +111,6 @@ class SafeBrowsingEventObserver : public TestEventRouter::EventObserver { base::Value event_args_; }; -std::unique_ptr<KeyedService> BuildSafeBrowsingPrivateEventRouter( - content::BrowserContext* context) { - return std::unique_ptr<KeyedService>( - new SafeBrowsingPrivateEventRouter(context)); -} - class SafeBrowsingPrivateEventRouterTestBase : public testing::Test { public: SafeBrowsingPrivateEventRouterTestBase() @@ -250,7 +247,8 @@ class SafeBrowsingPrivateEventRouterTestBase : public testing::Test { // Set a mock cloud policy client in the router. client_ = std::make_unique<policy::MockCloudPolicyClient>(); client_->SetDMToken("fake-token"); - SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_) + enterprise_connectors::RealtimeReportingClientFactory::GetForProfile( + profile_) ->SetBrowserCloudPolicyClientForTesting(client_.get()); } @@ -270,7 +268,13 @@ class SafeBrowsingPrivateEventRouterTestBase : public testing::Test { std::map<std::string, std::vector<std::string>>()) { event_router_ = extensions::CreateAndUseTestEventRouter(profile_); SafeBrowsingPrivateEventRouterFactory::GetInstance()->SetTestingFactory( - profile_, base::BindRepeating(&BuildSafeBrowsingPrivateEventRouter)); + profile_, base::BindRepeating( + &safe_browsing::BuildSafeBrowsingPrivateEventRouter)); + + enterprise_connectors::RealtimeReportingClientFactory::GetInstance() + ->SetTestingFactory( + profile_, + base::BindRepeating(&safe_browsing::BuildRealtimeReportingClient)); SetReportingPolicy(realtime_reporting_enable, authorized, enabled_event_names, enabled_opt_in_events); @@ -869,8 +873,8 @@ TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnPasswordBreach) { validator.ExpectPasswordBreachEvent( "SAFETY_CHECK", { - {"https://first.example.com/", u"first_user_name"}, - {"https://second.example.com/", u"second_user_name"}, + {"https://first.example.com/", u"*****"}, + {"https://second.example.com/", u"*****@gmail.com"}, }, profile_->GetProfileUserName()); @@ -878,7 +882,7 @@ TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnPasswordBreach) { "SAFETY_CHECK", { {GURL("https://first.example.com"), u"first_user_name"}, - {GURL("https://second.example.com"), u"second_user_name"}, + {GURL("https://second.example.com"), u"second_user_name@gmail.com"}, }); } @@ -930,7 +934,7 @@ TEST_F(SafeBrowsingPrivateEventRouterTest, validator.ExpectPasswordBreachEvent( "SAFETY_CHECK", { - {"https://secondexample.com/", u"second_user_name"}, + {"https://secondexample.com/", u"*****"}, }, profile_->GetProfileUserName()); @@ -1371,13 +1375,6 @@ class SafeBrowsingIsRealtimeReportingEnabledTest #endif }; -TEST_P(SafeBrowsingIsRealtimeReportingEnabledTest, - ShouldInitRealtimeReportingClient) { - EXPECT_EQ( - should_init(), - SafeBrowsingPrivateEventRouter::ShouldInitRealtimeReportingClient()); -} - TEST_P(SafeBrowsingIsRealtimeReportingEnabledTest, CheckRealtimeReport) { // In production, the router won't actually be authorized unless it was // initialized. The second argument to SetUpRouters() takes this into diff --git a/chromium/chrome/browser/extensions/api/scripting/scripting_api.cc b/chromium/chrome/browser/extensions/api/scripting/scripting_api.cc index 08ba9873de3..23d0a104053 100644 --- a/chromium/chrome/browser/extensions/api/scripting/scripting_api.cc +++ b/chromium/chrome/browser/extensions/api/scripting/scripting_api.cc @@ -682,10 +682,12 @@ bool ScriptingExecuteScriptFunction::Execute( : mojom::RunLocation::kDocumentIdle; script_executor->ExecuteScript( mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()), - mojom::CodeInjection::NewJs( - mojom::JSInjection::New(std::move(sources), execution_world, - /*wants_result=*/true, user_gesture(), - /*wait_for_promise=*/true)), + mojom::CodeInjection::NewJs(mojom::JSInjection::New( + std::move(sources), execution_world, + blink::mojom::WantResultOption::kWantResult, + user_gesture() ? blink::mojom::UserActivationOption::kActivate + : blink::mojom::UserActivationOption::kDoNotActivate, + blink::mojom::PromiseResultOption::kAwait)), frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK, run_location, ScriptExecutor::DEFAULT_PROCESS, /* webview_src */ GURL(), diff --git a/chromium/chrome/browser/extensions/api/scripting/scripting_apitest.cc b/chromium/chrome/browser/extensions/api/scripting/scripting_apitest.cc index de64164f94f..3fb3235f3f8 100644 --- a/chromium/chrome/browser/extensions/api/scripting/scripting_apitest.cc +++ b/chromium/chrome/browser/extensions/api/scripting/scripting_apitest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "build/build_config.h" #include "chrome/browser/extensions/api/scripting/scripting_api.h" #include "base/test/bind.h" @@ -16,6 +17,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/prerender_test_util.h" #include "content/public/test/test_navigation_observer.h" #include "extensions/browser/background_script_executor.h" #include "extensions/browser/disable_reason.h" @@ -508,4 +510,23 @@ IN_PROC_BROWSER_TEST_F(PersistentScriptingAPITest, EXPECT_TRUE(result_catcher_.GetNextResult()) << result_catcher_.message(); } +class ScriptingAPIPrerenderingTest : public ScriptingAPITest { + protected: + ScriptingAPIPrerenderingTest() = default; + ~ScriptingAPIPrerenderingTest() override = default; + + private: + content::test::ScopedPrerenderFeatureList scoped_feature_list_; +}; + +// TODO(crbug.com/1351648): disabled on Mac due to flakiness. +#if BUILDFLAG(IS_MAC) +#define MAYBE_Basic DISABLED_Basic +#else // BUILDFLAG(IS_MAC) +#define MAYBE_Basic Basic +#endif // BUILDFLAG(IS_MAC) +IN_PROC_BROWSER_TEST_F(ScriptingAPIPrerenderingTest, MAYBE_Basic) { + ASSERT_TRUE(RunExtensionTest("scripting/prerendering")) << message_; +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc b/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc index 2279fd5040d..cbf6bc88458 100644 --- a/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc +++ b/chromium/chrome/browser/extensions/api/sessions/sessions_api.cc @@ -641,7 +641,7 @@ void SessionsEventRouter::TabRestoreServiceChanged( sessions::TabRestoreService* service) { EventRouter::Get(profile_)->BroadcastEvent(std::make_unique<Event>( events::SESSIONS_ON_CHANGED, api::sessions::OnChanged::kEventName, - std::vector<base::Value>())); + base::Value::List())); } void SessionsEventRouter::TabRestoreServiceDestroyed( diff --git a/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc b/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc index c2ee2f9b252..1b210e522b9 100644 --- a/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc +++ b/chromium/chrome/browser/extensions/api/sessions/sessions_apitest.cc @@ -24,6 +24,7 @@ #include "chrome/browser/extensions/extension_function_test_utils.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/sync/session_sync_service_factory.h" +#include "chrome/browser/ui/tabs/tab_enums.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/in_process_browser_test.h" @@ -39,7 +40,7 @@ #include "components/sync/protocol/model_type_state.pb.h" #include "components/sync/protocol/session_specifics.pb.h" #include "components/sync/protocol/sync_enums.pb.h" -#include "components/sync/test/engine/mock_model_type_worker.h" +#include "components/sync/test/mock_model_type_worker.h" #include "components/sync_sessions/session_store.h" #include "components/sync_sessions/session_sync_service.h" #include "content/public/test/browser_test.h" @@ -77,7 +78,7 @@ void BuildWindowSpecifics(int window_id, sync_pb::SessionWindow* window = header->add_window(); window->set_window_id(window_id); window->set_selected_tab_index(0); - window->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED); + window->set_browser_type(sync_pb::SyncEnums_BrowserType_TYPE_TABBED); for (auto iter = tab_list.cbegin(); iter != tab_list.cend(); ++iter) { window->add_tab(*iter); } @@ -103,36 +104,29 @@ void BuildTabSpecifics(const std::string& tag, navigation->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED); } -testing::AssertionResult CheckSessionModels(const base::ListValue& devices, +testing::AssertionResult CheckSessionModels(const base::Value::List& devices, size_t num_sessions) { - EXPECT_EQ(5u, devices.GetListDeprecated().size()); - for (size_t i = 0; i < devices.GetListDeprecated().size(); ++i) { - const base::Value& device_value = devices.GetListDeprecated()[i]; + EXPECT_EQ(5u, devices.size()); + for (size_t i = 0; i < devices.size(); ++i) { + const base::Value& device_value = devices[i]; EXPECT_TRUE(device_value.is_dict()); const base::Value::Dict device = utils::ToDictionary(device_value); EXPECT_EQ(kSessionTags[i], api_test_utils::GetString(device, "info")); EXPECT_EQ(kSessionTags[i], api_test_utils::GetString(device, "deviceName")); - const std::unique_ptr<base::ListValue> sessions = + const base::Value::List sessions = api_test_utils::GetList(device, "sessions"); - EXPECT_EQ(num_sessions, sessions->GetListDeprecated().size()); + EXPECT_EQ(num_sessions, sessions.size()); // Because this test is hurried, really there are only ever 0 or 1 // sessions, and if 1, that will be a Window. Grab it. if (num_sessions == 0) continue; - const base::Value::Dict session = - utils::ToDictionary(sessions->GetListDeprecated()[0]); + const base::Value::Dict session = utils::ToDictionary(sessions[0]); const base::Value::Dict window = api_test_utils::GetDict(session, "window"); // Only the tabs are interesting. - const std::unique_ptr<base::ListValue> tabs = - api_test_utils::GetList(window, "tabs"); - if (!tabs) { - return testing::AssertionFailure() - << "window dictionary does not contain a tabs list entry"; - } - EXPECT_EQ(std::size(kTabIDs), tabs->GetListDeprecated().size()); - for (size_t j = 0; j < tabs->GetListDeprecated().size(); ++j) { - const base::Value::Dict tab = - utils::ToDictionary(tabs->GetListDeprecated()[j]); + const base::Value::List tabs = api_test_utils::GetList(window, "tabs"); + EXPECT_EQ(std::size(kTabIDs), tabs.size()); + for (size_t j = 0; j < tabs.size(); ++j) { + const base::Value::Dict tab = utils::ToDictionary(tabs[j]); EXPECT_FALSE(tab.contains("id")); // sessions API does not give tab IDs EXPECT_EQ(static_cast<int>(j), api_test_utils::GetInteger(tab, "index")); EXPECT_EQ(0, api_test_utils::GetInteger(tab, "windowId")); @@ -277,33 +271,29 @@ void ExtensionSessionsTest::CreateSessionModels() { IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevices) { CreateSessionModels(); - std::unique_ptr<base::ListValue> result( + base::Value::List result( utils::ToList(utils::RunFunctionAndReturnSingleResult( CreateFunction<SessionsGetDevicesFunction>(true).get(), "[{\"maxResults\": 0}]", browser()))); - ASSERT_TRUE(result); - EXPECT_TRUE(CheckSessionModels(*result, 0u)); + EXPECT_TRUE(CheckSessionModels(result, 0u)); } IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevicesMaxResults) { CreateSessionModels(); - std::unique_ptr<base::ListValue> result( + base::Value::List result( utils::ToList(utils::RunFunctionAndReturnSingleResult( CreateFunction<SessionsGetDevicesFunction>(true).get(), "[]", browser()))); - ASSERT_TRUE(result); - EXPECT_TRUE(CheckSessionModels(*result, 1u)); + EXPECT_TRUE(CheckSessionModels(result, 1u)); } IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetDevicesListEmpty) { - std::unique_ptr<base::ListValue> result( + base::Value::List devices( utils::ToList(utils::RunFunctionAndReturnSingleResult( CreateFunction<SessionsGetDevicesFunction>(true).get(), "[]", browser()))); - ASSERT_TRUE(result); - base::ListValue* devices = result.get(); - EXPECT_EQ(0u, devices->GetListDeprecated().size()); + EXPECT_TRUE(devices.empty()); } IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, RestoreForeignSessionWindow) { @@ -314,18 +304,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, RestoreForeignSessionWindow) { CreateFunction<SessionsRestoreFunction>(true).get(), "[\"tag3.3\"]", browser(), api_test_utils::INCLUDE_INCOGNITO)); - std::unique_ptr<base::ListValue> result( + base::Value::List windows( utils::ToList(utils::RunFunctionAndReturnSingleResult( CreateFunction<WindowsGetAllFunction>(true).get(), "[]", browser()))); - ASSERT_TRUE(result); - base::ListValue* windows = result.get(); - EXPECT_EQ(2u, windows->GetListDeprecated().size()); + EXPECT_EQ(2u, windows.size()); const base::Value::Dict restored_window = api_test_utils::GetDict(restored_window_session, "window"); base::Value::Dict window; int restored_id = api_test_utils::GetInteger(restored_window, "id"); - for (base::Value& window_value : windows->GetListDeprecated()) { + for (base::Value& window_value : windows) { window = utils::ToDictionary(std::move(window_value)); if (api_test_utils::GetInteger(window, "id") == restored_id) break; @@ -375,13 +363,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, RestoreNonEditableTabstrip) { } IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetRecentlyClosedIncognito) { - std::unique_ptr<base::ListValue> result( + base::Value::List sessions( utils::ToList(utils::RunFunctionAndReturnSingleResult( CreateFunction<SessionsGetRecentlyClosedFunction>(true).get(), "[]", CreateIncognitoBrowser()))); - ASSERT_TRUE(result); - base::ListValue* sessions = result.get(); - EXPECT_EQ(0u, sessions->GetListDeprecated().size()); + EXPECT_TRUE(sessions.empty()); } IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetRecentlyClosedMaxResults) { @@ -396,7 +382,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionSessionsTest, GetRecentlyClosedMaxResults) { content::WebContentsDestroyedWatcher destroyed_watcher( browser()->tab_strip_model()->GetWebContentsAt(tab_index)); browser()->tab_strip_model()->CloseWebContentsAt( - tab_index, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB); + tab_index, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB); destroyed_watcher.Wait(); } diff --git a/chromium/chrome/browser/extensions/api/settings_private/OWNERS b/chromium/chrome/browser/extensions/api/settings_private/OWNERS index 2dbba8f0aab..443a569fed9 100644 --- a/chromium/chrome/browser/extensions/api/settings_private/OWNERS +++ b/chromium/chrome/browser/extensions/api/settings_private/OWNERS @@ -1,3 +1 @@ -michaelpg@chromium.org - file://chrome/browser/resources/settings/OWNERS diff --git a/chromium/chrome/browser/extensions/api/settings_private/generated_prefs.cc b/chromium/chrome/browser/extensions/api/settings_private/generated_prefs.cc index ba6944df9ab..f9704d6f06b 100644 --- a/chromium/chrome/browser/extensions/api/settings_private/generated_prefs.cc +++ b/chromium/chrome/browser/extensions/api/settings_private/generated_prefs.cc @@ -12,7 +12,6 @@ #include "chrome/browser/extensions/api/settings_private/generated_pref.h" #include "chrome/browser/extensions/api/settings_private/prefs_util_enums.h" #include "chrome/browser/password_manager/generated_password_leak_detection_pref.h" -#include "chrome/browser/privacy_sandbox/generated_floc_pref.h" #include "chrome/browser/safe_browsing/generated_safe_browsing_pref.h" #include "chrome/common/extensions/api/settings_private.h" #include "components/content_settings/core/common/pref_names.h" @@ -104,7 +103,6 @@ void GeneratedPrefs::CreatePrefs() { std::make_unique<safe_browsing::GeneratedSafeBrowsingPref>(profile_); prefs_[content_settings::kGeneratedNotificationPref] = std::make_unique<content_settings::GeneratedNotificationPref>(profile_); - prefs_[kGeneratedFlocPref] = std::make_unique<GeneratedFlocPref>(profile_); } } // namespace settings_private 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 9d578203162..4e2acc72898 100644 --- a/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chromium/chrome/browser/extensions/api/settings_private/prefs_util.cc @@ -21,7 +21,6 @@ #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h" #include "chrome/browser/password_manager/generated_password_leak_detection_pref.h" #include "chrome/browser/prefs/session_startup_pref.h" -#include "chrome/browser/privacy_sandbox/generated_floc_pref.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/safe_browsing/generated_safe_browsing_pref.h" #include "chrome/common/chrome_features.h" @@ -76,8 +75,8 @@ #include "chrome/browser/ash/system/timezone_util.h" #include "chrome/browser/extensions/api/settings_private/chromeos_resolve_time_zone_by_geolocation_method_short.h" #include "chrome/browser/extensions/api/settings_private/chromeos_resolve_time_zone_by_geolocation_on_off.h" +#include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.h" #include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h" -#include "chromeos/services/assistant/public/cpp/assistant_prefs.h" #include "components/account_manager_core/pref_names.h" #include "ui/chromeos/events/pref_names.h" #endif @@ -183,6 +182,8 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_allowlist)[bookmarks::prefs::kShowBookmarkBar] = settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_allowlist)[::prefs::kSidePanelHorizontalAlignment] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch // of lacros-chrome is complete. @@ -296,8 +297,6 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_allowlist)[::prefs::kPrivacySandboxPageViewed] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[::kGeneratedFlocPref] = - settings_api::PrefType::PREF_TYPE_BOOLEAN; // Security page (*s_allowlist)[::kGeneratedPasswordLeakDetectionPref] = @@ -554,7 +553,7 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_allowlist)[crostini::prefs::kCrostiniSharedUsbDevices] = settings_api::PrefType::PREF_TYPE_LIST; - (*s_allowlist)[crostini::prefs::kCrostiniContainers] = + (*s_allowlist)[guest_os::prefs::kGuestOsContainers] = settings_api::PrefType::PREF_TYPE_LIST; (*s_allowlist)[crostini::prefs::kCrostiniPortForwarding] = settings_api::PrefType::PREF_TYPE_LIST; @@ -588,24 +587,23 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_NUMBER; // Google Assistant. - (*s_allowlist)[chromeos::assistant::prefs::kAssistantConsentStatus] = + (*s_allowlist)[ash::assistant::prefs::kAssistantConsentStatus] = settings_api::PrefType::PREF_TYPE_NUMBER; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantDisabledByPolicy] = + (*s_allowlist)[ash::assistant::prefs::kAssistantDisabledByPolicy] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantEnabled] = + (*s_allowlist)[ash::assistant::prefs::kAssistantEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantContextEnabled] = + (*s_allowlist)[ash::assistant::prefs::kAssistantContextEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantHotwordAlwaysOn] = + (*s_allowlist)[ash::assistant::prefs::kAssistantHotwordAlwaysOn] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantHotwordEnabled] = + (*s_allowlist)[ash::assistant::prefs::kAssistantHotwordEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist) - [chromeos::assistant::prefs::kAssistantVoiceMatchEnabledDuringOobe] = - settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantLaunchWithMicOpen] = + (*s_allowlist)[ash::assistant::prefs::kAssistantVoiceMatchEnabledDuringOobe] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - (*s_allowlist)[chromeos::assistant::prefs::kAssistantNotificationEnabled] = + (*s_allowlist)[ash::assistant::prefs::kAssistantLaunchWithMicOpen] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_allowlist)[ash::assistant::prefs::kAssistantNotificationEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; // Quick Answers. @@ -798,7 +796,8 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_allowlist)[ash::prefs::kUserCameraAllowed] = settings_api::PrefType::PREF_TYPE_BOOLEAN; - + (*s_allowlist)[ash::prefs::kUserMicrophoneAllowed] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; #else // System settings. (*s_allowlist)[::prefs::kBackgroundModeEnabled] = @@ -826,8 +825,6 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) - (*s_allowlist)[::prefs::kSettingsShowOSBanner] = - settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_allowlist)[::prefs::kPrintingAPIExtensionsAllowlist] = settings_api::PrefType::PREF_TYPE_LIST; #endif @@ -1258,7 +1255,7 @@ bool PrefsUtil::IsPrefPrimaryUserControlled(const std::string& pref_name) { bool PrefsUtil::IsHotwordDisabledForChildUser(const std::string& pref_name) { const std::string& hotwordEnabledPref = - chromeos::assistant::prefs::kAssistantHotwordEnabled; + ash::assistant::prefs::kAssistantHotwordEnabled; if (!profile_->IsChild() || pref_name != hotwordEnabledPref) return false; diff --git a/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_api_ash.cc b/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_api_ash.cc index 7db25c1220d..e5c52402a0f 100644 --- a/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_api_ash.cc +++ b/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_api_ash.cc @@ -31,7 +31,8 @@ SharedStoragePrivateGetFunction::~SharedStoragePrivateGetFunction() = default; ExtensionFunction::ResponseAction SharedStoragePrivateGetFunction::Run() { PrefService* prefs = Profile::FromBrowserContext(browser_context())->GetPrefs(); - return RespondNow(OneArgument(prefs->Get(prefs::kSharedStorage)->Clone())); + return RespondNow( + OneArgument(prefs->GetValue(prefs::kSharedStorage).Clone())); } SharedStoragePrivateSetFunction::SharedStoragePrivateSetFunction() = default; diff --git a/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_apitest.cc b/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_apitest.cc index b92e8cee82e..4a3ea98a08e 100644 --- a/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/shared_storage/shared_storage_private_apitest.cc @@ -9,7 +9,7 @@ #if BUILDFLAG(IS_CHROMEOS_LACROS) #include "base/containers/contains.h" -#include "chromeos/startup/browser_init_params.h" +#include "chromeos/startup/browser_params_proxy.h" #endif // BUILDFLAG(IS_CHROMEOS_LACROS) namespace extensions { @@ -18,7 +18,7 @@ using SharedStoragePrivateApiTest = ExtensionApiTest; IN_PROC_BROWSER_TEST_F(SharedStoragePrivateApiTest, Test) { #if BUILDFLAG(IS_CHROMEOS_LACROS) - auto capabilities = chromeos::BrowserInitParams::Get()->ash_capabilities; + auto capabilities = chromeos::BrowserParamsProxy::Get()->AshCapabilities(); if (!capabilities || !base::Contains(*capabilities, "b/231890240")) { LOG(WARNING) << "Unsupported ash version for shared storage."; return; diff --git a/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.cc b/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.cc new file mode 100644 index 00000000000..055f84c15f0 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.cc @@ -0,0 +1,59 @@ +// Copyright 2022 The Chromium Authors. All 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/side_panel/side_panel_api.h" + +#include <memory> + +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "chrome/browser/extensions/api/side_panel/side_panel_service.h" +#include "chrome/common/extensions/api/side_panel.h" +#include "extensions/common/extension_features.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace extensions { +namespace { + +bool IsSidePanelApiAvailable() { + return base::FeatureList::IsEnabled( + extensions_features::kExtensionSidePanelIntegration); +} + +} // namespace + +SidePanelApiFunction::SidePanelApiFunction() = default; +SidePanelApiFunction::~SidePanelApiFunction() = default; +SidePanelService* SidePanelApiFunction::GetService() { + return extensions::SidePanelService::Get(browser_context()); +} + +ExtensionFunction::ResponseAction SidePanelApiFunction::Run() { + if (!IsSidePanelApiAvailable()) + return RespondNow(Error("API Unavailable")); + return RunFunction(); +} + +ExtensionFunction::ResponseAction SidePanelGetOptionsFunction::RunFunction() { + std::unique_ptr<api::side_panel::GetOptions::Params> params( + api::side_panel::GetOptions::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); + auto tab_id = params->options.tab_id + ? absl::optional<int>(*(params->options.tab_id)) + : absl::nullopt; + const api::side_panel::PanelOptions& options = + GetService()->GetOptions(*extension(), tab_id); + return RespondNow(OneArgument(std::move(*options.ToValue()))); +} + +ExtensionFunction::ResponseAction SidePanelSetOptionsFunction::RunFunction() { + std::unique_ptr<api::side_panel::SetOptions::Params> params( + api::side_panel::SetOptions::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); + // TODO(crbug.com/1328645): Validate the relative extension path exists. + GetService()->SetOptions(*extension(), std::move(params->options)); + return RespondNow(NoArguments()); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.h b/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.h new file mode 100644 index 00000000000..604ac1520ac --- /dev/null +++ b/chromium/chrome/browser/extensions/api/side_panel/side_panel_api.h @@ -0,0 +1,53 @@ +// Copyright 2022 The Chromium Authors. All 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_SIDE_PANEL_SIDE_PANEL_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SIDE_PANEL_SIDE_PANEL_API_H_ + +#include "extensions/browser/extension_function.h" +#include "extensions/browser/extension_function_histogram_value.h" + +namespace extensions { + +class SidePanelService; + +class SidePanelApiFunction : public ExtensionFunction { + protected: + SidePanelApiFunction(); + ~SidePanelApiFunction() override; + ResponseAction Run() override; + + virtual ResponseAction RunFunction() = 0; + SidePanelService* GetService(); +}; + +class SidePanelGetOptionsFunction : public SidePanelApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sidePanel.getOptions", SIDEPANEL_GETOPTIONS) + SidePanelGetOptionsFunction() = default; + SidePanelGetOptionsFunction(const SidePanelGetOptionsFunction&) = delete; + SidePanelGetOptionsFunction& operator=(const SidePanelGetOptionsFunction&) = + delete; + + private: + ~SidePanelGetOptionsFunction() override = default; + ResponseAction RunFunction() override; +}; + +class SidePanelSetOptionsFunction : public SidePanelApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sidePanel.setOptions", SIDEPANEL_SETOPTIONS) + SidePanelSetOptionsFunction() = default; + SidePanelSetOptionsFunction(const SidePanelSetOptionsFunction&) = delete; + SidePanelSetOptionsFunction& operator=(const SidePanelSetOptionsFunction&) = + delete; + + private: + ~SidePanelSetOptionsFunction() override = default; + ResponseAction RunFunction() override; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SIDE_PANEL_SIDE_PANEL_API_H_ diff --git a/chromium/chrome/browser/extensions/api/side_panel/side_panel_apitest.cc b/chromium/chrome/browser/extensions/api/side_panel/side_panel_apitest.cc new file mode 100644 index 00000000000..8331fbc5545 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/side_panel/side_panel_apitest.cc @@ -0,0 +1,41 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "components/version_info/channel.h" +#include "content/public/test/browser_test.h" +#include "extensions/common/extension_features.h" + +namespace extensions { + +class SidePanelApiTest : public ExtensionApiTest { + public: + SidePanelApiTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionSidePanelIntegration); + } + + private: + base::test::ScopedFeatureList feature_list_; + ScopedCurrentChannel current_channel_{version_info::Channel::CANARY}; +}; + +// Verify normal chrome.sidePanel functionality. +IN_PROC_BROWSER_TEST_F(SidePanelApiTest, Extension) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(RunExtensionTest("side_panel/extension")) << message_; +} + +// Verify chrome.sidePanel behavior without permissions. +IN_PROC_BROWSER_TEST_F(SidePanelApiTest, PermissionMissing) { + ASSERT_TRUE(RunExtensionTest("side_panel/permission_missing")) << message_; +} + +// Verify chrome.sidePanel.get behavior without side_panel manifest key. +IN_PROC_BROWSER_TEST_F(SidePanelApiTest, MissingManifestKey) { + ASSERT_TRUE(RunExtensionTest("side_panel/missing_manifest_key")) << message_; +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.cc b/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.cc new file mode 100644 index 00000000000..d97c121a736 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.cc @@ -0,0 +1,112 @@ +// Copyright 2022 The Chromium Authors. All 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/side_panel/side_panel_service.h" + +#include <cstddef> +#include <memory> + +#include "base/no_destructor.h" +#include "chrome/common/extensions/api/side_panel.h" +#include "chrome/common/extensions/api/side_panel/side_panel_info.h" +#include "components/sessions/core/session_id.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace extensions { + +namespace { + +api::side_panel::PanelOptions GetPanelOptionsFromManifest( + const Extension& extension) { + auto path = SidePanelInfo::GetDefaultPath(&extension); + api::side_panel::PanelOptions options; + if (!path.empty()) { + options.path = std::make_unique<std::string>(std::string(path)); + options.enabled = std::make_unique<bool>(true); + } + return options; +} + +// TODO(crbug.com/1332599): Add a Clone() method for generated types. +api::side_panel::PanelOptions CloneOptions( + const api::side_panel::PanelOptions& options) { + auto clone = api::side_panel::PanelOptions::FromValue( + base::Value(std::move(options.ToValue()->GetDict()))); + return clone ? std::move(*clone) : api::side_panel::PanelOptions(); +} + +} // namespace + +SidePanelService::~SidePanelService() = default; + +SidePanelService::SidePanelService(content::BrowserContext* context) + : browser_context_(context) {} + +api::side_panel::PanelOptions SidePanelService::GetOptions( + const Extension& extension, + absl::optional<TabId> id) { + auto extension_panel_options = panels_.find(extension.id()); + + // Get default path from manifest if nothing was stored in this service for + // the calling extension. + if (extension_panel_options == panels_.end()) { + return GetPanelOptionsFromManifest(extension); + } + + TabId default_tab_id = SessionID::InvalidValue().id(); + TabId tab_id = id.has_value() ? id.value() : default_tab_id; + TabPanelOptions& tab_panel_options = extension_panel_options->second; + + // The specific `tab_id` may have already been saved. + if (tab_id != default_tab_id) { + auto specific_tab_options = tab_panel_options.find(tab_id); + if (specific_tab_options != tab_panel_options.end()) + return CloneOptions(specific_tab_options->second); + } + + // Fall back to the default tab if no tab ID was specified or entries for the + // specific tab weren't found. + auto default_options = tab_panel_options.find(default_tab_id); + if (default_options != tab_panel_options.end()) { + auto options = CloneOptions(default_options->second); + return options; + } + + // Fall back to the manifest-specified options as a last resort. + return GetPanelOptionsFromManifest(extension); +} + +// Upsert to merge `panels_[extension_id][tab_id]` with `set_options`. +void SidePanelService::SetOptions(const Extension& extension, + api::side_panel::PanelOptions options) { + TabId tab_id = SessionID::InvalidValue().id(); + if (options.tab_id) + tab_id = *options.tab_id; + TabPanelOptions& extension_panel_options = panels_[extension.id()]; + auto it = extension_panel_options.find(tab_id); + if (it == extension_panel_options.end()) { + extension_panel_options[tab_id] = std::move(options); + } else { + auto& existing_options = it->second; + if (options.path) + existing_options.path = std::move(options.path); + if (options.enabled) + existing_options.enabled = std::move(options.enabled); + } +} + +// static +BrowserContextKeyedAPIFactory<SidePanelService>* +SidePanelService::GetFactoryInstance() { + static base::NoDestructor<BrowserContextKeyedAPIFactory<SidePanelService>> + instance; + return instance.get(); +} + +// static +SidePanelService* SidePanelService::Get(content::BrowserContext* context) { + return BrowserContextKeyedAPIFactory<SidePanelService>::Get(context); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.h b/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.h new file mode 100644 index 00000000000..bb9175f805b --- /dev/null +++ b/chromium/chrome/browser/extensions/api/side_panel/side_panel_service.h @@ -0,0 +1,63 @@ +// Copyright 2022 The Chromium Authors. All 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_SIDE_PANEL_SIDE_PANEL_SERVICE_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SIDE_PANEL_SIDE_PANEL_SERVICE_H_ + +#include "base/containers/flat_map.h" +#include "chrome/common/extensions/api/side_panel.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/common/extension_id.h" + +namespace extensions { + +// The single responsibility of this service is to be the source of truth for +// side panel options. Extensions can interact with this service using the API +// and side panel UI updates can rely on the response of GetOptions(tab_id). +class SidePanelService : public BrowserContextKeyedAPI { + public: + explicit SidePanelService(content::BrowserContext* context); + + SidePanelService(const SidePanelService&) = delete; + SidePanelService& operator=(const SidePanelService&) = delete; + + ~SidePanelService() override; + + // Convenience method to get the SidePanelService for a profile. + static SidePanelService* Get(content::BrowserContext* context); + + // BrowserContextKeyedAPI implementation. + static BrowserContextKeyedAPIFactory<SidePanelService>* GetFactoryInstance(); + + // Get options for tab_id. Options are loaded in order first from service + // storage, manifest, or an empty object will be returned, if they're unset. + using TabId = int; + api::side_panel::PanelOptions GetOptions(const Extension& extension, + absl::optional<TabId> tab_id); + + // Set options for tab_id if specified. Otherwise set default options. + void SetOptions(const Extension& extension, + api::side_panel::PanelOptions set_options); + + private: + // TODO(crbug.com/1328645): Remove options for matching ExtensionId on + // uninstallation. + friend class BrowserContextKeyedAPIFactory<SidePanelService>; + + content::BrowserContext* const browser_context_; + + // BrowserContextKeyedAPI implementation. + static const char* service_name() { return "SidePanelService"; } + static const bool kServiceRedirectedInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + + using TabPanelOptions = base::flat_map<TabId, api::side_panel::PanelOptions>; + using ExtensionPanelOptions = base::flat_map<ExtensionId, TabPanelOptions>; + + ExtensionPanelOptions panels_; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SIDE_PANEL_SIDE_PANEL_SERVICE_H_ diff --git a/chromium/chrome/browser/extensions/api/storage/policy_value_store.cc b/chromium/chrome/browser/extensions/api/storage/policy_value_store.cc index 504f383a01f..8958996012d 100644 --- a/chromium/chrome/browser/extensions/api/storage/policy_value_store.cc +++ b/chromium/chrome/browser/extensions/api/storage/policy_value_store.cc @@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/storage/policy_value_store.h" +#include <algorithm> #include <utility> #include "base/logging.h" @@ -41,10 +42,10 @@ void PolicyValueStore::SetCurrentPolicy(const policy::PolicyMap& policy) { DCHECK(IsOnBackendSequence()); // Convert |policy| to a dictionary value. Only include mandatory policies // for now. - base::DictionaryValue current_policy; + base::Value::Dict current_policy; for (const auto& it : policy) { if (it.second.level == policy::POLICY_LEVEL_MANDATORY) { - current_policy.SetKey(it.first, it.second.value_unsafe()->Clone()); + current_policy.Set(it.first, it.second.value_unsafe()->Clone()); } } @@ -52,7 +53,7 @@ void PolicyValueStore::SetCurrentPolicy(const policy::PolicyMap& policy) { // TODO(joaodasilva): it'd be better to have a less expensive way of // determining which keys are currently stored, or of determining which keys // must be removed. - base::DictionaryValue previous_policy; + base::Value::Dict previous_policy; ValueStore::ReadResult read_result = delegate_->Get(); if (!read_result.status().ok()) { @@ -61,15 +62,15 @@ void PolicyValueStore::SetCurrentPolicy(const policy::PolicyMap& policy) { // Leave |previous_policy| empty, so that events are generated for every // policy in |current_policy|. } else { - read_result.settings().Swap(&previous_policy); + std::swap(read_result.settings(), previous_policy); } // Now get two lists of changes: changes after setting the current policies, // and changes after removing old policies that aren't in |current_policy| // anymore. std::vector<std::string> removed_keys; - for (auto kv : previous_policy.DictItems()) { - if (!current_policy.FindKey(kv.first)) + for (auto kv : previous_policy) { + if (!current_policy.Find(kv.first)) removed_keys.push_back(kv.first); } @@ -148,7 +149,7 @@ ValueStore::WriteResult PolicyValueStore::Set(WriteOptions options, ValueStore::WriteResult PolicyValueStore::Set( WriteOptions options, - const base::DictionaryValue& settings) { + const base::Value::Dict& settings) { return WriteResult(ReadOnlyError()); } diff --git a/chromium/chrome/browser/extensions/api/storage/policy_value_store.h b/chromium/chrome/browser/extensions/api/storage/policy_value_store.h index e202a7c6123..bf9ee43cc68 100644 --- a/chromium/chrome/browser/extensions/api/storage/policy_value_store.h +++ b/chromium/chrome/browser/extensions/api/storage/policy_value_store.h @@ -55,7 +55,7 @@ class PolicyValueStore : public value_store::ValueStore { const std::string& key, const base::Value& value) override; WriteResult Set(WriteOptions options, - const base::DictionaryValue& values) override; + const base::Value::Dict& values) override; WriteResult Remove(const std::string& key) override; WriteResult Remove(const std::vector<std::string>& keys) override; WriteResult Clear() override; diff --git a/chromium/chrome/browser/extensions/api/storage/policy_value_store_unittest.cc b/chromium/chrome/browser/extensions/api/storage/policy_value_store_unittest.cc index 7c2b390bd1d..90a86e253da 100644 --- a/chromium/chrome/browser/extensions/api/storage/policy_value_store_unittest.cc +++ b/chromium/chrome/browser/extensions/api/storage/policy_value_store_unittest.cc @@ -90,7 +90,7 @@ class MutablePolicyValueStore : public PolicyValueStore { } WriteResult Set(WriteOptions options, - const base::DictionaryValue& values) override { + const base::Value::Dict& values) override { return delegate()->Set(options, values); } @@ -166,10 +166,11 @@ TEST_F(PolicyValueStoreTest, DontProvideRecommendedPolicies) { ValueStore::ReadResult result = store_->Get(); ASSERT_TRUE(result.status().ok()); - EXPECT_EQ(1u, result.settings().DictSize()); - base::Value* value = NULL; - EXPECT_FALSE(result.settings().Get("may", &value)); - EXPECT_TRUE(result.settings().Get("must", &value)); + EXPECT_EQ(1u, result.settings().size()); + base::Value* value = result.settings().Find("may"); + EXPECT_FALSE(value); + value = result.settings().Find("must"); + ASSERT_TRUE(value); EXPECT_EQ(expected, *value); } @@ -179,8 +180,8 @@ TEST_F(PolicyValueStoreTest, ReadOnly) { base::Value string_value("value"); EXPECT_FALSE(store_->Set(options, "key", string_value).status().ok()); - base::DictionaryValue dict; - dict.SetStringKey("key", "value"); + base::Value::Dict dict; + dict.Set("key", "value"); EXPECT_FALSE(store_->Set(options, dict).status().ok()); EXPECT_FALSE(store_->Remove("key").status().ok()); diff --git a/chromium/chrome/browser/extensions/api/storage/settings_apitest.cc b/chromium/chrome/browser/extensions/api/storage/settings_apitest.cc index fa3996c16ff..25e0302ab3a 100644 --- a/chromium/chrome/browser/extensions/api/storage/settings_apitest.cc +++ b/chromium/chrome/browser/extensions/api/storage/settings_apitest.cc @@ -29,9 +29,9 @@ #include "components/sync/model/sync_change_processor.h" #include "components/sync/model/sync_error_factory.h" #include "components/sync/model/syncable_service.h" -#include "components/sync/test/model/fake_sync_change_processor.h" -#include "components/sync/test/model/sync_change_processor_wrapper_for_test.h" -#include "components/sync/test/model/sync_error_factory_mock.h" +#include "components/sync/test/fake_sync_change_processor.h" +#include "components/sync/test/sync_change_processor_wrapper_for_test.h" +#include "components/sync/test/sync_error_factory_mock.h" #include "components/version_info/channel.h" #include "content/public/test/browser_test.h" #include "extensions/browser/api/storage/backend_task_runner.h" diff --git a/chromium/chrome/browser/extensions/api/storage/settings_sync_unittest.cc b/chromium/chrome/browser/extensions/api/storage/settings_sync_unittest.cc index eee647483f8..ff22cc8d41b 100644 --- a/chromium/chrome/browser/extensions/api/storage/settings_sync_unittest.cc +++ b/chromium/chrome/browser/extensions/api/storage/settings_sync_unittest.cc @@ -22,8 +22,8 @@ #include "chrome/test/base/testing_profile.h" #include "components/sync/model/sync_change_processor.h" #include "components/sync/model/sync_error_factory.h" -#include "components/sync/test/model/sync_change_processor_wrapper_for_test.h" -#include "components/sync/test/model/sync_error_factory_mock.h" +#include "components/sync/test/sync_change_processor_wrapper_for_test.h" +#include "components/sync/test/sync_error_factory_mock.h" #include "components/value_store/test_value_store_factory.h" #include "components/value_store/testing_value_store.h" #include "content/public/test/browser_task_environment.h" @@ -98,7 +98,8 @@ testing::AssertionResult SettingsEq(const char* _1, << "Expected: " << expected << ", actual has error: " << actual.status().message; } - return ValuesEq(_1, _2, &expected, &actual.settings()); + base::Value settings(actual.PassSettings()); + return ValuesEq(_1, _2, &expected, &settings); } // SyncChangeProcessor which just records the changes made, accessed after diff --git a/chromium/chrome/browser/extensions/api/storage/sync_storage_backend.cc b/chromium/chrome/browser/extensions/api/storage/sync_storage_backend.cc index 961c263e028..4b2ce7b403a 100644 --- a/chromium/chrome/browser/extensions/api/storage/sync_storage_backend.cc +++ b/chromium/chrome/browser/extensions/api/storage/sync_storage_backend.cc @@ -22,10 +22,10 @@ namespace extensions { namespace { void AddAllSyncData(const std::string& extension_id, - const base::DictionaryValue& src, + const base::Value::Dict& src, syncer::ModelType type, syncer::SyncDataList* dst) { - for (auto it : src.DictItems()) { + for (auto it : src) { dst->push_back(settings_sync_util::CreateData(extension_id, it.first, it.second, type)); } diff --git a/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.cc b/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.cc index f43309ea257..73f873eda70 100644 --- a/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.cc +++ b/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.cc @@ -97,7 +97,8 @@ ValueStore::WriteResult SyncableSettingsStorage::Set( } ValueStore::WriteResult SyncableSettingsStorage::Set( - WriteOptions options, const base::DictionaryValue& values) { + WriteOptions options, + const base::Value::Dict& values) { DCHECK(IsOnBackendSequence()); WriteResult result = HandleResult(delegate_->Set(options, values)); if (!result.status().ok()) @@ -172,8 +173,7 @@ absl::optional<syncer::ModelError> SyncableSettingsStorage::StartSyncing( maybe_settings.status().message.c_str())); } - std::unique_ptr<base::DictionaryValue> current_settings = - maybe_settings.PassSettings(); + base::Value::Dict current_settings = maybe_settings.PassSettings(); return sync_state->DictEmpty() ? SendLocalSettingsToSync(std::move(current_settings)) : OverwriteLocalSettingsWithSync(std::move(sync_state), @@ -182,15 +182,15 @@ absl::optional<syncer::ModelError> SyncableSettingsStorage::StartSyncing( absl::optional<syncer::ModelError> SyncableSettingsStorage::SendLocalSettingsToSync( - std::unique_ptr<base::DictionaryValue> local_state) { + base::Value::Dict local_state) { DCHECK(IsOnBackendSequence()); - if (local_state->DictEmpty()) + if (local_state.empty()) return absl::nullopt; // Transform the current settings into a list of sync changes. value_store::ValueStoreChangeList changes; - for (auto pair : local_state->DictItems()) { + for (auto pair : local_state) { changes.push_back(value_store::ValueStoreChange(pair.first, absl::nullopt, std::move(pair.second))); } @@ -205,13 +205,13 @@ SyncableSettingsStorage::SendLocalSettingsToSync( absl::optional<syncer::ModelError> SyncableSettingsStorage::OverwriteLocalSettingsWithSync( std::unique_ptr<base::DictionaryValue> sync_state, - std::unique_ptr<base::DictionaryValue> local_state) { + base::Value::Dict local_state) { DCHECK(IsOnBackendSequence()); // This is implemented by building up a list of sync changes then sending // those to ProcessSyncChanges. This generates events like onStorageChanged. std::unique_ptr<SettingSyncDataList> changes(new SettingSyncDataList()); - for (auto it : local_state->DictItems()) { + for (auto it : local_state) { absl::optional<base::Value> sync_value = sync_state->ExtractKey(it.first); if (sync_value.has_value()) { if (*sync_value == it.second) { @@ -277,7 +277,7 @@ absl::optional<syncer::ModelError> SyncableSettingsStorage::ProcessSyncChanges( sync_processor_->type())); continue; } - current_value = maybe_settings.settings().ExtractKey(key); + current_value = maybe_settings.settings().Extract(key); } syncer::SyncError error; diff --git a/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.h b/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.h index 15d9abcc821..4f7c328caeb 100644 --- a/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.h +++ b/chromium/chrome/browser/extensions/api/storage/syncable_settings_storage.h @@ -54,7 +54,7 @@ class SyncableSettingsStorage : public value_store::ValueStore { const std::string& key, const base::Value& value) override; WriteResult Set(WriteOptions options, - const base::DictionaryValue& values) override; + const base::Value::Dict& values) override; WriteResult Remove(const std::string& key) override; WriteResult Remove(const std::vector<std::string>& keys) override; WriteResult Clear() override; @@ -94,13 +94,13 @@ class SyncableSettingsStorage : public value_store::ValueStore { // in sync yet. // Returns any error when trying to sync, or absl::nullopt on success. absl::optional<syncer::ModelError> SendLocalSettingsToSync( - std::unique_ptr<base::DictionaryValue> local_state); + base::Value::Dict local_state); // Overwrites local state with sync state. // Returns any error when trying to sync, or absl::nullopt on success. absl::optional<syncer::ModelError> OverwriteLocalSettingsWithSync( std::unique_ptr<base::DictionaryValue> sync_state, - std::unique_ptr<base::DictionaryValue> local_state); + base::Value::Dict local_state); // Called when an Add/Update/Remove comes from sync. syncer::SyncError OnSyncAdd(const std::string& key, diff --git a/chromium/chrome/browser/extensions/api/streams_private/streams_private_api.cc b/chromium/chrome/browser/extensions/api/streams_private/streams_private_api.cc index 2f5f7a76451..b2095ffc3fc 100644 --- a/chromium/chrome/browser/extensions/api/streams_private/streams_private_api.cc +++ b/chromium/chrome/browser/extensions/api/streams_private/streams_private_api.cc @@ -7,7 +7,7 @@ #include <utility> #include "chrome/browser/extensions/extension_tab_util.h" -#include "chrome/browser/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h" +#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h" #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h" #include "components/sessions/core/session_id.h" #include "content/public/browser/browser_thread.h" diff --git a/chromium/chrome/browser/extensions/api/system_display/system_display_extension_apitest.cc b/chromium/chrome/browser/extensions/api/system_display/system_display_extension_apitest.cc index 837d0bd43df..0315a196572 100644 --- a/chromium/chrome/browser/extensions/api/system_display/system_display_extension_apitest.cc +++ b/chromium/chrome/browser/extensions/api/system_display/system_display_extension_apitest.cc @@ -62,7 +62,7 @@ IN_PROC_BROWSER_TEST_P(SystemDisplayExtensionApiTest, GetDisplayInfo) { #endif // BUILDFLAG(IS_WIN) -#if !BUILDFLAG(IS_CHROMEOS_ASH) +#if !(BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)) using SystemDisplayExtensionApiFunctionTest = SystemDisplayExtensionApiTest; diff --git a/chromium/chrome/browser/extensions/api/system_private/system_private_api.cc b/chromium/chrome/browser/extensions/api/system_private/system_private_api.cc index 6c5df053249..7b12c529482 100644 --- a/chromium/chrome/browser/extensions/api/system_private/system_private_api.cc +++ b/chromium/chrome/browser/extensions/api/system_private/system_private_api.cc @@ -19,8 +19,7 @@ #include "google_apis/google_api_keys.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chromeos/dbus/dbus_thread_manager.h" // nogncheck -#include "chromeos/dbus/update_engine/update_engine_client.h" +#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h" #else #include "chrome/browser/upgrade_detector/upgrade_detector.h" #endif @@ -71,9 +70,8 @@ ExtensionFunction::ResponseAction SystemPrivateGetUpdateStatusFunction::Run() { #if BUILDFLAG(IS_CHROMEOS_ASH) // With UpdateEngineClient, we can provide more detailed information about // system updates on ChromeOS. - const update_engine::StatusResult status = chromeos::DBusThreadManager::Get() - ->GetUpdateEngineClient() - ->GetLastStatus(); + const update_engine::StatusResult status = + ash::UpdateEngineClient::Get()->GetLastStatus(); // |download_progress| is set to 1 after download finishes // (i.e. verify, finalize and need-reboot phase) to indicate the progress // even though |status.download_progress| is 0 in these phases. @@ -110,6 +108,8 @@ ExtensionFunction::ResponseAction SystemPrivateGetUpdateStatusFunction::Run() { case update_engine::Operation::REPORTING_ERROR_EVENT: case update_engine::Operation::ATTEMPTING_ROLLBACK: case update_engine::Operation::NEED_PERMISSION_TO_UPDATE: + case update_engine::Operation::CLEANUP_PREVIOUS_UPDATE: + case update_engine::Operation::UPDATED_BUT_DEFERRED: state = kNotAvailableState; break; default: diff --git a/chromium/chrome/browser/extensions/api/system_private/system_private_apitest.cc b/chromium/chrome/browser/extensions/api/system_private/system_private_apitest.cc index ce8416d6e26..d20c2ab1b08 100644 --- a/chromium/chrome/browser/extensions/api/system_private/system_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/system_private/system_private_apitest.cc @@ -12,10 +12,8 @@ #include "content/public/test/browser_test.h" #if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chromeos/dbus/dbus_thread_manager.h" // nogncheck -#include "chromeos/dbus/update_engine/fake_update_engine_client.h" - -using chromeos::UpdateEngineClient; +#include "chromeos/ash/components/dbus/update_engine/fake_update_engine_client.h" +#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h" #endif namespace extensions { @@ -33,16 +31,15 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, GetIncognitoModeAvailability) { class GetUpdateStatusApiTest : public ExtensionApiTest { public: - GetUpdateStatusApiTest() : fake_update_engine_client_(NULL) {} + GetUpdateStatusApiTest() = default; GetUpdateStatusApiTest(const GetUpdateStatusApiTest&) = delete; GetUpdateStatusApiTest& operator=(const GetUpdateStatusApiTest&) = delete; void SetUpInProcessBrowserTestFixture() override { ExtensionApiTest::SetUpInProcessBrowserTestFixture(); - fake_update_engine_client_ = new chromeos::FakeUpdateEngineClient; - chromeos::DBusThreadManager::GetSetterForTesting()->SetUpdateEngineClient( - std::unique_ptr<UpdateEngineClient>(fake_update_engine_client_)); + fake_update_engine_client_ = + ash::UpdateEngineClient::InitializeFakeForTest(); } void TearDownInProcessBrowserTestFixture() override { @@ -50,7 +47,7 @@ class GetUpdateStatusApiTest : public ExtensionApiTest { } protected: - chromeos::FakeUpdateEngineClient* fake_update_engine_client_; + ash::FakeUpdateEngineClient* fake_update_engine_client_ = nullptr; }; IN_PROC_BROWSER_TEST_F(GetUpdateStatusApiTest, Progress) { diff --git a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc index 65926f8d389..ce6e33f6d4b 100644 --- a/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc +++ b/chromium/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc @@ -292,14 +292,13 @@ void TabCaptureRegistry::DispatchStatusChangeEvent( if (!router) return; - std::unique_ptr<base::ListValue> args(new base::ListValue()); + base::Value::List args; tab_capture::CaptureInfo info; request->GetCaptureInfo(&info); - args->GetList().Append(base::Value::FromUniquePtrValue(info.ToValue())); + args.Append(base::Value::FromUniquePtrValue(info.ToValue())); auto event = std::make_unique<Event>(events::TAB_CAPTURE_ON_STATUS_CHANGED, tab_capture::OnStatusChanged::kEventName, - std::move(*args).TakeListDeprecated(), - browser_context_); + std::move(args), browser_context_); router->DispatchEventToExtension(request->extension_id(), std::move(event)); } diff --git a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_api.cc b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_api.cc index ba2780236a4..0fac333c0db 100644 --- a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_api.cc +++ b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_api.cc @@ -316,7 +316,7 @@ bool TabGroupsMoveFunction::MoveGroup(int group_id, // Attach tabs in consecutive indices, to insert them in the same order. target_tab_strip->InsertWebContentsAt(new_index + i, std::move(web_contents), - TabStripModel::ADD_NONE, *group); + AddTabTypes::ADD_NONE, *group); } return true; diff --git a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.cc b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.cc index efc4e7be62e..3d4942b9acf 100644 --- a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.cc +++ b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.cc @@ -94,7 +94,7 @@ void TabGroupsEventRouter::DispatchGroupUpdated(tab_groups::TabGroupId group) { void TabGroupsEventRouter::DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> args) { + base::Value::List args) { // |event_router_| can be null in tests. if (!event_router_) return; diff --git a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.h b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.h index 45956ad4763..f946ab60cf8 100644 --- a/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.h +++ b/chromium/chrome/browser/extensions/api/tab_groups/tab_groups_event_router.h @@ -48,7 +48,7 @@ class TabGroupsEventRouter : public TabStripModelObserver, void DispatchEvent(events::HistogramValue histogram_value, const std::string& event_name, - std::vector<base::Value> args); + base::Value::List args); const raw_ptr<Profile> profile_; const raw_ptr<EventRouter> event_router_ = nullptr; diff --git a/chromium/chrome/browser/extensions/api/tabs/execute_script_apitest.cc b/chromium/chrome/browser/extensions/api/tabs/execute_script_apitest.cc index e4bc05e4d1f..8c1d9c421ce 100644 --- a/chromium/chrome/browser/extensions/api/tabs/execute_script_apitest.cc +++ b/chromium/chrome/browser/extensions/api/tabs/execute_script_apitest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/cfi_buildflags.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "build/build_config.h" @@ -196,8 +197,9 @@ class BackForwardCacheDisabledDestructiveScriptTest base::test::ScopedFeatureList scoped_feature_list_; }; -// Flaky on ASAN and -dbg. crbug.com/1293865 -#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG) +// Flaky on ASAN and -dbg, and Linux CFI bots. crbug.com/1293865 +#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG) || \ + (BUILDFLAG(CFI_ICALL_CHECK) && BUILDFLAG(IS_LINUX)) #define MAYBE_SynchronousRemoval DISABLED_SynchronousRemoval #else #define MAYBE_SynchronousRemoval SynchronousRemoval @@ -208,8 +210,9 @@ IN_PROC_BROWSER_TEST_P(BackForwardCacheDisabledDestructiveScriptTest, ASSERT_TRUE(RunSubtest("synchronous")) << message_; } -// Flaky on ASAN and -dbg. crbug.com/1293865 -#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG) +// Flaky on ASAN and -dbg and Linux CFI. crbug.com/1293865 +#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG) || \ + (BUILDFLAG(CFI_ICALL_CHECK) && BUILDFLAG(IS_LINUX)) #define MAYBE_MicrotaskRemoval DISABLED_MicrotaskRemoval #else #define MAYBE_MicrotaskRemoval MicrotaskRemoval diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc index 0f9c3953b34..df2d3177c4f 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api.cc @@ -58,9 +58,11 @@ #include "chrome/browser/ui/browser_navigator_params.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/recently_audible_helper.h" +#include "chrome/browser/ui/tabs/tab_enums.h" #include "chrome/browser/ui/tabs/tab_group.h" #include "chrome/browser/ui/tabs/tab_group_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" #include "chrome/browser/ui/tabs/tab_utils.h" #include "chrome/browser/ui/window_sizer/window_sizer.h" #include "chrome/browser/web_applications/web_app_helpers.h" @@ -365,7 +367,7 @@ int MoveTabToWindow(ExtensionFunction* function, target_index = target_tab_strip->count(); return target_tab_strip->InsertWebContentsAt( - target_index, std::move(web_contents), TabStripModel::ADD_NONE); + target_index, std::move(web_contents), AddTabTypes::ADD_NONE); } // This function sets the state of the browser window to a "locked" @@ -792,7 +794,7 @@ ExtensionFunction::ResponseAction WindowsCreateFunction::Run() { if (!target_tab_strip) return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); target_tab_strip->InsertWebContentsAt( - urls.size(), std::move(detached_tab), TabStripModel::ADD_NONE); + urls.size(), std::move(detached_tab), AddTabTypes::ADD_NONE); } } // Create a new tab if the created window is still empty. Don't create a new @@ -800,7 +802,10 @@ ExtensionFunction::ResponseAction WindowsCreateFunction::Run() { if (!contents && urls.empty() && window_type == Browser::TYPE_NORMAL) { chrome::NewTab(new_window); } - chrome::SelectNumberedTab(new_window, 0, {TabStripModel::GestureType::kNone}); + chrome::SelectNumberedTab( + new_window, 0, + TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kNone)); if (focused) { new_window->window()->Show(); @@ -1389,7 +1394,7 @@ ExtensionFunction::ResponseAction TabsHighlightFunction::Run() { if (!tabstrip) return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); ui::ListSelectionModel selection; - int active_index = -1; + absl::optional<size_t> active_index; if (params->highlight_info.tabs.as_integers) { std::vector<int>& tab_indices = *params->highlight_info.tabs.as_integers; @@ -1426,7 +1431,7 @@ ExtensionFunction::ResponseAction TabsHighlightFunction::Run() { bool TabsHighlightFunction::HighlightTab(TabStripModel* tabstrip, ui::ListSelectionModel* selection, - int* active_index, + absl::optional<size_t>* active_index, int index, std::string* error) { // Make sure the index is in range. @@ -1437,8 +1442,8 @@ bool TabsHighlightFunction::HighlightTab(TabStripModel* tabstrip, } // By default, we make the first tab in the list active. - if (*active_index == -1) - *active_index = index; + if (!active_index->has_value()) + *active_index = static_cast<size_t>(index); selection->AddIndexToSelection(index); return true; diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_api.h b/chromium/chrome/browser/extensions/api/tabs/tabs_api.h index 03d1938fa7f..81302bfd4cb 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api.h +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api.h @@ -126,7 +126,7 @@ class TabsHighlightFunction : public ExtensionFunction { ResponseAction Run() override; bool HighlightTab(TabStripModel* tabstrip, ui::ListSelectionModel* selection, - int* active_index, + absl::optional<size_t>* active_index, int index, std::string* error); DECLARE_EXTENSION_FUNCTION("tabs.highlight", TABS_HIGHLIGHT) 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 7154cba7b24..c5c83950cac 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc @@ -1117,8 +1117,9 @@ TEST_F(TabsApiUnitTest, TabsGoForwardAndBackWithoutTabId) { ASSERT_EQ(2, tab_strip_model->count()); // Activate first tab. - tab_strip_model->ActivateTabAt(tab1_index, - {TabStripModel::GestureType::kOther}); + tab_strip_model->ActivateTabAt( + tab1_index, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); // Go back without tab_id. But first tab should be navigated since it's // activated. @@ -1149,8 +1150,9 @@ TEST_F(TabsApiUnitTest, TabsGoForwardAndBackWithoutTabId) { controller.GetLastCommittedEntry()->GetTransitionType()); // Activate second tab. - tab_strip_model->ActivateTabAt(tab2_index, - {TabStripModel::GestureType::kOther}); + tab_strip_model->ActivateTabAt( + tab2_index, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); auto goback_function2 = base::MakeRefCounted<TabsGoBackFunction>(); goback_function2->set_extension(extension_with_tabs_permission.get()); diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_apitest.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_apitest.cc index 2d28188efde..7dc16b8b529 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_apitest.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_apitest.cc @@ -22,6 +22,7 @@ #include "content/public/common/content_features.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/prerender_test_util.h" #include "extensions/test/result_catcher.h" #include "extensions/test/test_extension_dir.h" #include "net/dns/mock_host_resolver.h" @@ -465,6 +466,27 @@ IN_PROC_BROWSER_TEST_P(IncognitoExtensionApiTabTest, Tabs) { INSTANTIATE_TEST_SUITE_P(All, IncognitoExtensionApiTabTest, testing::Bool()); +class ExtensionApiTabPrerenderingTest : public ExtensionApiTabTest { + public: + ExtensionApiTabPrerenderingTest() + : prerender_helper_(base::BindRepeating( + &ExtensionApiTabPrerenderingTest::GetWebContents, + base::Unretained(this))) {} + ~ExtensionApiTabPrerenderingTest() override = default; + + content::WebContents* GetWebContents() { + return browser()->tab_strip_model()->GetWebContentsAt(0); + } + + private: + content::test::PrerenderTestHelper prerender_helper_; +}; + +// TODO(crbug.com/1352966): Flaky on multiple platforms. +IN_PROC_BROWSER_TEST_F(ExtensionApiTabPrerenderingTest, DISABLED_Prerendering) { + ASSERT_TRUE(RunExtensionTest("tabs/prerendering")) << message_; +} + // Adding a new test? Awesome. But API tests are the old hotness. The new // hotness is extension_function_test_utils. See tabs_test.cc for an example. // We are trying to phase out many uses of API tests as they tend to be flaky. 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 18c3467893d..040d5445904 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.cc @@ -68,7 +68,7 @@ bool WillDispatchTabUpdatedEvent( *event_args_out = std::make_unique<base::Value::List>(); (*event_args_out)->Append(ExtensionTabUtil::GetTabId(contents)); - (*event_args_out)->Append(base::Value(std::move(changed_properties))); + (*event_args_out)->Append(std::move(changed_properties)); (*event_args_out)->Append(std::move(tab_value)); return true; } @@ -239,10 +239,8 @@ void TabsEventRouter::OnTabStripModelChanged( if (tab_strip_model->empty()) return; - if (selection.active_tab_changed()) { - DispatchActiveTabChanged(selection.old_contents, selection.new_contents, - selection.new_model.active()); - } + if (selection.active_tab_changed()) + DispatchActiveTabChanged(selection.old_contents, selection.new_contents); if (selection.selection_changed()) { DispatchTabSelectionChanged(tab_strip_model, selection.old_model); @@ -297,8 +295,7 @@ void TabsEventRouter::OnZoomChanged( Profile::FromBrowserContext(data.web_contents->GetBrowserContext()); DispatchEvent(profile, events::TABS_ON_ZOOM_CHANGE, api::tabs::OnZoomChange::kEventName, - std::make_unique<base::ListValue>( - api::tabs::OnZoomChange::Create(zoom_change_info)), + api::tabs::OnZoomChange::Create(zoom_change_info), EventRouter::USER_GESTURE_UNKNOWN); } @@ -352,14 +349,14 @@ void TabsEventRouter::DispatchTabInsertedAt(TabStripModel* tab_strip_model, } int tab_id = ExtensionTabUtil::GetTabId(contents); - std::unique_ptr<base::ListValue> args(new base::ListValue); - args->Append(tab_id); + base::Value::List args; + args.Append(tab_id); base::Value::Dict object_args; object_args.Set(tabs_constants::kNewWindowIdKey, Value(ExtensionTabUtil::GetWindowIdOfTab(contents))); object_args.Set(tabs_constants::kNewPositionKey, Value(index)); - args->Append(base::Value(std::move(object_args))); + args.Append(std::move(object_args)); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); DispatchEvent(profile, events::TABS_ON_ATTACHED, @@ -372,15 +369,15 @@ void TabsEventRouter::DispatchTabClosingAt(TabStripModel* tab_strip_model, int index) { int tab_id = ExtensionTabUtil::GetTabId(contents); - std::unique_ptr<base::ListValue> args(new base::ListValue); - args->Append(tab_id); + base::Value::List args; + args.Append(tab_id); base::Value::Dict object_args; object_args.Set(tabs_constants::kWindowIdKey, ExtensionTabUtil::GetWindowIdOfTab(contents)); object_args.Set(tabs_constants::kWindowClosing, tab_strip_model->closing_all()); - args->Append(base::Value(std::move(object_args))); + args.Append(std::move(object_args)); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); DispatchEvent(profile, events::TABS_ON_REMOVED, @@ -398,14 +395,14 @@ void TabsEventRouter::DispatchTabDetachedAt(WebContents* contents, return; } - std::unique_ptr<base::ListValue> args(new base::ListValue); - args->Append(ExtensionTabUtil::GetTabId(contents)); + base::Value::List args; + args.Append(ExtensionTabUtil::GetTabId(contents)); base::Value::Dict object_args; object_args.Set(tabs_constants::kOldWindowIdKey, ExtensionTabUtil::GetWindowIdOfTab(contents)); object_args.Set(tabs_constants::kOldPositionKey, index); - args->Append(base::Value(std::move(object_args))); + args.Append(std::move(object_args)); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); DispatchEvent(profile, events::TABS_ON_DETACHED, @@ -414,35 +411,32 @@ void TabsEventRouter::DispatchTabDetachedAt(WebContents* contents, } void TabsEventRouter::DispatchActiveTabChanged(WebContents* old_contents, - WebContents* new_contents, - int index) { - auto args = std::make_unique<base::ListValue>(); + WebContents* new_contents) { + base::Value::List args; int tab_id = ExtensionTabUtil::GetTabId(new_contents); - args->Append(tab_id); + args.Append(tab_id); base::Value::Dict object_args; object_args.Set(tabs_constants::kWindowIdKey, ExtensionTabUtil::GetWindowIdOfTab(new_contents)); - args->Append(base::Value(object_args.Clone())); + args.Append(object_args.Clone()); // The onActivated event replaced onActiveChanged and onSelectionChanged. The // deprecated events take two arguments: tabId, {windowId}. Profile* profile = Profile::FromBrowserContext(new_contents->GetBrowserContext()); - DispatchEvent( - profile, events::TABS_ON_SELECTION_CHANGED, - api::tabs::OnSelectionChanged::kEventName, - base::ListValue::From(base::Value::ToUniquePtrValue(args->Clone())), - EventRouter::USER_GESTURE_UNKNOWN); + DispatchEvent(profile, events::TABS_ON_SELECTION_CHANGED, + api::tabs::OnSelectionChanged::kEventName, args.Clone(), + EventRouter::USER_GESTURE_UNKNOWN); DispatchEvent(profile, events::TABS_ON_ACTIVE_CHANGED, api::tabs::OnActiveChanged::kEventName, std::move(args), EventRouter::USER_GESTURE_UNKNOWN); // The onActivated event takes one argument: {windowId, tabId}. - auto on_activated_args = std::make_unique<base::ListValue>(); + base::Value::List on_activated_args; object_args.Set(tabs_constants::kTabIdKey, tab_id); - on_activated_args->Append(base::Value(std::move(object_args))); + on_activated_args.Append(std::move(object_args)); DispatchEvent( profile, events::TABS_ON_ACTIVATED, api::tabs::OnActivated::kEventName, std::move(on_activated_args), EventRouter::USER_GESTURE_UNKNOWN); @@ -463,7 +457,7 @@ void TabsEventRouter::DispatchTabSelectionChanged( all_tabs.Append(tab_id); } - std::unique_ptr<base::ListValue> args(new base::ListValue); + base::Value::List args; base::Value::Dict select_info; select_info.Set( @@ -471,15 +465,13 @@ void TabsEventRouter::DispatchTabSelectionChanged( ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model)); select_info.Set(tabs_constants::kTabIdsKey, std::move(all_tabs)); - args->Append(base::Value(std::move(select_info))); + args.Append(std::move(select_info)); // The onHighlighted event replaced onHighlightChanged. Profile* profile = tab_strip_model->profile(); - DispatchEvent( - profile, events::TABS_ON_HIGHLIGHT_CHANGED, - api::tabs::OnHighlightChanged::kEventName, - base::ListValue::From(base::Value::ToUniquePtrValue(args->Clone())), - EventRouter::USER_GESTURE_UNKNOWN); + DispatchEvent(profile, events::TABS_ON_HIGHLIGHT_CHANGED, + api::tabs::OnHighlightChanged::kEventName, args.Clone(), + EventRouter::USER_GESTURE_UNKNOWN); DispatchEvent(profile, events::TABS_ON_HIGHLIGHTED, api::tabs::OnHighlighted::kEventName, std::move(args), EventRouter::USER_GESTURE_UNKNOWN); @@ -488,15 +480,15 @@ void TabsEventRouter::DispatchTabSelectionChanged( void TabsEventRouter::DispatchTabMoved(WebContents* contents, int from_index, int to_index) { - std::unique_ptr<base::ListValue> args(new base::ListValue); - args->Append(ExtensionTabUtil::GetTabId(contents)); + base::Value::List args; + args.Append(ExtensionTabUtil::GetTabId(contents)); base::Value::Dict object_args; object_args.Set(tabs_constants::kWindowIdKey, ExtensionTabUtil::GetWindowIdOfTab(contents)); object_args.Set(tabs_constants::kFromIndexKey, from_index); object_args.Set(tabs_constants::kToIndexKey, to_index); - args->Append(base::Value(std::move(object_args))); + args.Append(std::move(object_args)); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); DispatchEvent(profile, events::TABS_ON_MOVED, api::tabs::OnMoved::kEventName, @@ -510,9 +502,9 @@ void TabsEventRouter::DispatchTabReplacedAt(WebContents* old_contents, // WebContents being swapped. const int new_tab_id = ExtensionTabUtil::GetTabId(new_contents); const int old_tab_id = ExtensionTabUtil::GetTabId(old_contents); - std::unique_ptr<base::ListValue> args(new base::ListValue); - args->Append(new_tab_id); - args->Append(old_tab_id); + base::Value::List args; + args.Append(new_tab_id); + args.Append(old_tab_id); DispatchEvent(Profile::FromBrowserContext(new_contents->GetBrowserContext()), events::TABS_ON_REPLACED, api::tabs::OnReplaced::kEventName, @@ -530,7 +522,7 @@ void TabsEventRouter::TabCreatedAt(WebContents* contents, Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); auto event = std::make_unique<Event>(events::TABS_ON_CREATED, api::tabs::OnCreated::kEventName, - std::vector<base::Value>(), profile); + base::Value::List(), profile); event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED; event->will_dispatch_callback = base::BindRepeating(&WillDispatchTabCreatedEvent, contents, active); @@ -572,15 +564,14 @@ void TabsEventRouter::DispatchEvent( Profile* profile, events::HistogramValue histogram_value, const std::string& event_name, - std::unique_ptr<base::ListValue> args, + base::Value::List args, EventRouter::UserGestureState user_gesture) { EventRouter* event_router = EventRouter::Get(profile); if (!profile_->IsSameOrParent(profile) || !event_router) return; - auto event = - std::make_unique<Event>(histogram_value, event_name, - std::move(*args).TakeListDeprecated(), profile); + auto event = std::make_unique<Event>(histogram_value, event_name, + std::move(args), profile); event->user_gesture = user_gesture; event_router->BroadcastEvent(std::move(event)); } @@ -597,7 +588,7 @@ void TabsEventRouter::DispatchTabUpdatedEvent( events::TABS_ON_UPDATED, api::tabs::OnUpdated::kEventName, // The event arguments depend on the extension's permission. They are set // in WillDispatchTabUpdatedEvent(). - std::vector<base::Value>(), profile); + base::Value::List(), profile); event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED; event->will_dispatch_callback = base::BindRepeating(&WillDispatchTabUpdatedEvent, contents, diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.h b/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.h index e65c8ee1c2a..6247466e7fa 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.h +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_event_router.h @@ -102,8 +102,7 @@ class TabsEventRouter : public TabStripModelObserver, int index, bool was_active); void DispatchActiveTabChanged(content::WebContents* old_contents, - content::WebContents* new_contents, - int index); + content::WebContents* new_contents); void DispatchTabSelectionChanged(TabStripModel* tab_strip_model, const ui::ListSelectionModel& old_model); void DispatchTabMoved(content::WebContents* contents, @@ -132,7 +131,7 @@ class TabsEventRouter : public TabStripModelObserver, void DispatchEvent(Profile* profile, events::HistogramValue histogram_value, const std::string& event_name, - std::unique_ptr<base::ListValue> args, + base::Value::List args, EventRouter::UserGestureState user_gesture); // Packages |changed_property_names| as a tab updated event for the tab diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_interactive_test.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_interactive_test.cc index 4e434a237a8..0793f8a36ee 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_interactive_test.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_interactive_test.cc @@ -112,14 +112,13 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, MAYBE_QueryLastFocusedWindowTabs) { scoped_refptr<const extensions::Extension> extension( extensions::ExtensionBuilder("Test").Build()); function->set_extension(extension.get()); - std::unique_ptr<base::ListValue> result( + base::Value::List result_tabs( utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"lastFocusedWindow\":true}]", browser()))); - base::ListValue* result_tabs = result.get(); // We should have one initial tab and one added tab. - EXPECT_EQ(2u, result_tabs->GetListDeprecated().size()); - for (const base::Value& result_tab : result_tabs->GetListDeprecated()) { + EXPECT_EQ(2u, result_tabs.size()); + for (const base::Value& result_tab : result_tabs) { EXPECT_EQ(focused_window_id, api_test_utils::GetInteger(utils::ToDictionary(result_tab), keys::kWindowIdKey)); @@ -128,13 +127,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, MAYBE_QueryLastFocusedWindowTabs) { // Get tabs NOT in the 'last focused' window called from the focused browser. function = new extensions::TabsQueryFunction(); function->set_extension(extension.get()); - result = utils::ToList(utils::RunFunctionAndReturnSingleResult( + result_tabs = utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"lastFocusedWindow\":false}]", browser())); - result_tabs = result.get(); // We should get one tab for each extra window and one for the initial window. - EXPECT_EQ(kExtraWindows + 1, result_tabs->GetListDeprecated().size()); - for (const base::Value& result_tab : result_tabs->GetListDeprecated()) { + EXPECT_EQ(kExtraWindows + 1, result_tabs.size()); + for (const base::Value& result_tab : result_tabs) { EXPECT_NE(focused_window_id, api_test_utils::GetInteger(utils::ToDictionary(result_tab), keys::kWindowIdKey)); diff --git a/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc b/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc index 6e4d24ed62d..d1dde378ccb 100644 --- a/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc +++ b/chromium/chrome/browser/extensions/api/tabs/tabs_test.cc @@ -176,12 +176,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetWindow) { EXPECT_EQ(window_id, GetWindowId(result)); // "populate" was enabled so tabs should be populated. - std::unique_ptr<base::ListValue> tabs = - api_test_utils::GetList(result, keys::kTabsKey); - ASSERT_TRUE(tabs); - ASSERT_FALSE(tabs->GetListDeprecated().empty()); - absl::optional<int> tab0_id = - tabs->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + base::Value::List tabs = api_test_utils::GetList(result, keys::kTabsKey); + ASSERT_FALSE(tabs.empty()); + absl::optional<int> tab0_id = tabs[0].GetDict().FindInt(keys::kIdKey); ASSERT_TRUE(tab0_id.has_value()); EXPECT_GE(*tab0_id, 0); @@ -263,12 +260,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetCurrentWindow) { // to RunFunctionAndReturnSingleResult. EXPECT_EQ(window_id, GetWindowId(result)); // "populate" was enabled so tabs should be populated. - std::unique_ptr<base::ListValue> tabs = - api_test_utils::GetList(result, keys::kTabsKey); - ASSERT_TRUE(tabs); - ASSERT_FALSE(tabs->GetListDeprecated().empty()); - absl::optional<int> tab0_id = - tabs->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + base::Value::List tabs = api_test_utils::GetList(result, keys::kTabsKey); + ASSERT_FALSE(tabs.empty()); + absl::optional<int> tab0_id = tabs[0].GetDict().FindInt(keys::kIdKey); ASSERT_TRUE(tab0_id.has_value()); // The tab id should not be -1 as this is a browser window. EXPECT_GE(*tab0_id, 0); @@ -297,12 +291,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { scoped_refptr<WindowsGetAllFunction> function = new WindowsGetAllFunction(); scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); function->set_extension(extension.get()); - std::unique_ptr<base::ListValue> result = utils::ToList( + base::Value::List windows = utils::ToList( utils::RunFunctionAndReturnSingleResult(function.get(), "[]", browser())); - base::ListValue* windows = result.get(); - EXPECT_EQ(window_ids.size(), windows->GetListDeprecated().size()); - for (const base::Value& result_window : windows->GetListDeprecated()) { + EXPECT_EQ(window_ids.size(), windows.size()); + for (const base::Value& result_window : windows) { result_ids.insert(GetWindowId(utils::ToDictionary(result_window))); // "populate" was not passed in so tabs are not populated. @@ -315,12 +308,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { result_ids.clear(); function = new WindowsGetAllFunction(); function->set_extension(extension.get()); - result = utils::ToList(utils::RunFunctionAndReturnSingleResult( + windows = utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"populate\": true}]", browser())); - windows = result.get(); - EXPECT_EQ(window_ids.size(), windows->GetListDeprecated().size()); - for (const base::Value& result_window : windows->GetListDeprecated()) { + EXPECT_EQ(window_ids.size(), windows.size()); + for (const base::Value& result_window : windows) { result_ids.insert(GetWindowId(utils::ToDictionary(result_window))); // "populate" was enabled so tabs should be populated. @@ -361,16 +353,15 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { scoped_refptr<WindowsGetAllFunction> function = new WindowsGetAllFunction(); scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); function->set_extension(extension.get()); - std::unique_ptr<base::ListValue> result( + base::Value::List windows( utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"windowTypes\": [\"app\", \"devtools\", \"normal\", \"panel\", " "\"popup\"]}]", browser()))); - base::ListValue* windows = result.get(); - EXPECT_EQ(window_ids.size(), windows->GetListDeprecated().size()); - for (const base::Value& result_window : windows->GetListDeprecated()) { + EXPECT_EQ(window_ids.size(), windows.size()); + for (const base::Value& result_window : windows) { result_ids.insert(GetWindowId(utils::ToDictionary(result_window))); // "populate" was not passed in so tabs are not populated. @@ -383,15 +374,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { result_ids.clear(); function = new WindowsGetAllFunction(); function->set_extension(extension.get()); - result = utils::ToList(utils::RunFunctionAndReturnSingleResult( + windows = utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"populate\": true, \"windowTypes\": [\"app\", \"devtools\", " "\"normal\", \"panel\", \"popup\"]}]", browser())); - windows = result.get(); - EXPECT_EQ(window_ids.size(), windows->GetListDeprecated().size()); - for (const base::Value& result_window : windows->GetListDeprecated()) { + EXPECT_EQ(window_ids.size(), windows.size()); + for (const base::Value& result_window : windows) { result_ids.insert(GetWindowId(utils::ToDictionary(result_window))); // "populate" was enabled so tabs should be populated. @@ -602,27 +592,25 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryCurrentWindowTabs) { // Get tabs in the 'current' window called from non-focused browser. scoped_refptr<TabsQueryFunction> function = new TabsQueryFunction(); function->set_extension(ExtensionBuilder("Test").Build().get()); - std::unique_ptr<base::ListValue> result( + base::Value::List result_tabs( utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"currentWindow\":true}]", browser()))); - base::ListValue* result_tabs = result.get(); // We should have one initial tab and one added tab. - EXPECT_EQ(2u, result_tabs->GetListDeprecated().size()); - for (const base::Value& result_tab : result_tabs->GetListDeprecated()) { + EXPECT_EQ(2u, result_tabs.size()); + for (const base::Value& result_tab : result_tabs) { EXPECT_EQ(window_id, GetTabWindowId(utils::ToDictionary(result_tab))); } // Get tabs NOT in the 'current' window called from non-focused browser. function = new TabsQueryFunction(); function->set_extension(ExtensionBuilder("Test").Build().get()); - result = utils::ToList(utils::RunFunctionAndReturnSingleResult( + result_tabs = utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{\"currentWindow\":false}]", browser())); - result_tabs = result.get(); // We should have one tab for each extra window. - EXPECT_EQ(kExtraWindows, result_tabs->GetListDeprecated().size()); - for (const base::Value& result_tab : result_tabs->GetListDeprecated()) { + EXPECT_EQ(kExtraWindows, result_tabs.size()); + for (const base::Value& result_tab : result_tabs) { EXPECT_NE(window_id, GetTabWindowId(utils::ToDictionary(result_tab))); } } @@ -643,15 +631,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryAllTabsWithDevTools) { // Get tabs in the 'current' window called from non-focused browser. scoped_refptr<TabsQueryFunction> function = new TabsQueryFunction(); function->set_extension(ExtensionBuilder("Test").Build().get()); - std::unique_ptr<base::ListValue> result( + base::Value::List result_tabs( utils::ToList(utils::RunFunctionAndReturnSingleResult( function.get(), "[{}]", browser()))); std::set<int> result_ids; - base::ListValue* result_tabs = result.get(); // We should have one tab per browser except for DevTools. - EXPECT_EQ(kNumWindows, result_tabs->GetListDeprecated().size()); - for (const base::Value& result_tab : result_tabs->GetListDeprecated()) { + EXPECT_EQ(kNumWindows, result_tabs.size()); + for (const base::Value& result_tab : result_tabs) { result_ids.insert(GetTabWindowId(utils::ToDictionary(result_tab))); } EXPECT_EQ(window_ids, result_ids); @@ -674,11 +661,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryTabGroups) { constexpr char kFormatQueryArgs[] = R"([{"groupId":%d}])"; const std::string args = base::StringPrintf( kFormatQueryArgs, tab_groups_util::GetGroupId(group_id)); - std::unique_ptr<base::ListValue> result( + base::Value::List result( utils::ToList(utils::RunFunctionAndReturnSingleResult(function.get(), args, browser()))); - EXPECT_EQ(2u, result->GetListDeprecated().size()); + EXPECT_EQ(2u, result.size()); } IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DontCreateTabInClosingPopupWindow) { @@ -1264,18 +1251,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Get non-discarded tabs. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": false}]")); + base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); // The two created plus the default tab. - EXPECT_EQ(3u, result->GetListDeprecated().size()); + EXPECT_EQ(3u, result.size()); } // Get discarded tabs. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": true}]")); - EXPECT_EQ(0u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); + EXPECT_EQ(0u, result.size()); } TabStripModel* tab_strip_model = browser()->tab_strip_model(); @@ -1297,22 +1282,19 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Get non-discarded tabs after discarding one tab. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": false}]")); - EXPECT_EQ(2u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); + EXPECT_EQ(2u, result.size()); } // Get discarded tabs after discarding one tab. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": true}]")); - EXPECT_EQ(1u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); + EXPECT_EQ(1u, result.size()); // Make sure the returned tab is the correct one. int tab_id_a = ExtensionTabUtil::GetTabId(web_contents_a); - absl::optional<int> id = - result->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + absl::optional<int> id = result[0].FindIntKey(keys::kIdKey); ASSERT_TRUE(id); EXPECT_EQ(tab_id_a, *id); @@ -1323,16 +1305,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Get non-discarded tabs after discarding two created tabs. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": false}]")); - ASSERT_EQ(1u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); + ASSERT_EQ(1u, result.size()); // Make sure the returned tab is the correct one. int tab_id_c = ExtensionTabUtil::GetTabId(tab_strip_model->GetWebContentsAt(0)); - absl::optional<int> id = - result->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + absl::optional<int> id = result[0].FindIntKey(keys::kIdKey); ASSERT_TRUE(id); EXPECT_EQ(tab_id_c, *id); @@ -1340,9 +1320,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Get discarded tabs after discarding two created tabs. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": true}]")); - EXPECT_EQ(2u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); + EXPECT_EQ(2u, result.size()); } // Activates the first created tab. @@ -1350,16 +1329,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { // Get non-discarded tabs after activating a discarded tab. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": false}]")); - EXPECT_EQ(2u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); + EXPECT_EQ(2u, result.size()); } // Get discarded tabs after activating a discarded tab. { - std::unique_ptr<base::ListValue> result( - RunQueryFunction("[{\"discarded\": true}]")); - EXPECT_EQ(1u, result->GetListDeprecated().size()); + base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); + EXPECT_EQ(1u, result.size()); } } @@ -1507,21 +1484,19 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { // Queries and results used. const char* kAutoDiscardableQueryInfo = "[{\"autoDiscardable\": true}]"; const char* kNonAutoDiscardableQueryInfo = "[{\"autoDiscardable\": false}]"; - std::unique_ptr<base::ListValue> query_result; - base::Value::Dict update_result; // Get auto-discardable tabs. Returns all since tabs are auto-discardable // by default. - query_result = RunQueryFunction(kAutoDiscardableQueryInfo); - EXPECT_EQ(3u, query_result->GetListDeprecated().size()); + base::Value::List query_result = RunQueryFunction(kAutoDiscardableQueryInfo); + EXPECT_EQ(3u, query_result.size()); // Get non auto-discardable tabs. query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); - EXPECT_EQ(0u, query_result->GetListDeprecated().size()); + EXPECT_EQ(0u, query_result.size()); // Update the auto-discardable state of web contents A. int tab_id_a = ExtensionTabUtil::GetTabId(web_contents_a); - update_result = RunUpdateFunction( + base::Value::Dict update_result = RunUpdateFunction( base::StringPrintf("[%u, {\"autoDiscardable\": false}]", tab_id_a)); EXPECT_EQ(tab_id_a, api_test_utils::GetInteger(update_result, "id")); EXPECT_FALSE(api_test_utils::GetBoolean(update_result, "autoDiscardable")); @@ -1533,15 +1508,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { // Get auto-discardable tabs after changing the status of web contents A. query_result = RunQueryFunction(kAutoDiscardableQueryInfo); - EXPECT_EQ(2u, query_result->GetListDeprecated().size()); + EXPECT_EQ(2u, query_result.size()); // Get non auto-discardable tabs after changing the status of web contents A. query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); - ASSERT_EQ(1u, query_result->GetListDeprecated().size()); + ASSERT_EQ(1u, query_result.size()); // Make sure the returned tab is the correct one. - absl::optional<int> tab_id = - query_result->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + absl::optional<int> tab_id = query_result[0].FindIntKey(keys::kIdKey); ASSERT_TRUE(tab_id); EXPECT_EQ(tab_id_a, *tab_id); @@ -1554,18 +1528,17 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { // Get auto-discardable tabs after changing the status of both created tabs. query_result = RunQueryFunction(kAutoDiscardableQueryInfo); - EXPECT_EQ(1u, query_result->GetListDeprecated().size()); + EXPECT_EQ(1u, query_result.size()); // Make sure the returned tab is the correct one. - absl::optional<int> id_value = - query_result->GetListDeprecated()[0].FindIntKey(keys::kIdKey); + absl::optional<int> id_value = query_result[0].FindIntKey(keys::kIdKey); ASSERT_TRUE(id_value); EXPECT_EQ(ExtensionTabUtil::GetTabId(tab_strip_model->GetWebContentsAt(0)), *id_value); // Get auto-discardable tabs after changing the status of both created tabs. query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); - EXPECT_EQ(2u, query_result->GetListDeprecated().size()); + EXPECT_EQ(2u, query_result.size()); // Resets the first tab back to auto-discardable. update_result = RunUpdateFunction( @@ -1575,11 +1548,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { // Get auto-discardable tabs after resetting the status of web contents A. query_result = RunQueryFunction(kAutoDiscardableQueryInfo); - EXPECT_EQ(2u, query_result->GetListDeprecated().size()); + EXPECT_EQ(2u, query_result.size()); // Get non auto-discardable tabs after resetting the status of web contents A. query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); - EXPECT_EQ(1u, query_result->GetListDeprecated().size()); + EXPECT_EQ(1u, query_result.size()); } // Tester class for the tabs.zoom* api functions. @@ -2055,11 +2028,13 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TemporaryAddressSpoof) { ASSERT_TRUE(navigation_manager.WaitForRequestStart()); browser()->tab_strip_model()->ActivateTabAt( - 0, {TabStripModel::GestureType::kOther}); + 0, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); EXPECT_EQ(first_web_contents, browser()->tab_strip_model()->GetActiveWebContents()); browser()->tab_strip_model()->ActivateTabAt( - 1, {TabStripModel::GestureType::kOther}); + 1, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); EXPECT_EQ(second_web_contents, browser()->tab_strip_model()->GetActiveWebContents()); 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 32e03171c0c..4d0befa34c4 100644 --- a/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc +++ b/chromium/chrome/browser/extensions/api/tabs/windows_event_router.cc @@ -304,7 +304,7 @@ void WindowsEventRouter::OnActiveWindowChanged( std::unique_ptr<Event> event = std::make_unique<Event>( events::WINDOWS_ON_FOCUS_CHANGED, windows::OnFocusChanged::kEventName, - std::vector<base::Value>()); + base::Value::List()); event->will_dispatch_callback = base::BindRepeating(&WillDispatchWindowFocusedEvent, window_controller); EventRouter::Get(profile_)->BroadcastEvent(std::move(event)); @@ -315,7 +315,7 @@ void WindowsEventRouter::DispatchEvent(events::HistogramValue histogram_value, WindowController* window_controller, std::unique_ptr<base::ListValue> args) { auto event = std::make_unique<Event>(histogram_value, event_name, - std::move(*args).TakeListDeprecated(), + std::move(args->GetList()), window_controller->profile()); event->will_dispatch_callback = base::BindRepeating(&WillDispatchWindowEvent, window_controller); diff --git a/chromium/chrome/browser/extensions/api/terminal/DIR_METADATA b/chromium/chrome/browser/extensions/api/terminal/DIR_METADATA index b3cd94034ff..06e627fbb01 100644 --- a/chromium/chrome/browser/extensions/api/terminal/DIR_METADATA +++ b/chromium/chrome/browser/extensions/api/terminal/DIR_METADATA @@ -1,3 +1 @@ -monorail { - component: "UI>Shell>Containers" -} +mixins: "//chrome/browser/ash/guest_os/COMMON_METADATA" diff --git a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.cc b/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.cc deleted file mode 100644 index 347dd47f287..00000000000 --- a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.cc +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/extensions/api/terminal/crostini_startup_status.h" - -#include <algorithm> -#include <memory> -#include <vector> - -#include "base/containers/flat_map.h" -#include "base/location.h" -#include "base/no_destructor.h" -#include "base/strings/strcat.h" -#include "base/strings/stringprintf.h" -#include "base/system/sys_info.h" -#include "base/time/time.h" -#include "chrome/browser/ash/crostini/crostini_util.h" -#include "chrome/grit/generated_resources.h" -#include "chromeos/dbus/util/version_loader.h" -#include "components/version_info/version_info.h" -#include "content/public/browser/browser_task_traits.h" -#include "content/public/browser/browser_thread.h" -#include "ui/base/l10n/l10n_util.h" - -using crostini::mojom::InstallerState; - -namespace extensions { - -namespace { - -const char kCursorHide[] = "\x1b[?25l"; -const char kCursorShow[] = "\x1b[?25h"; -const char kColor0Normal[] = "\x1b[0m"; // Default. -const char kColor1RedBright[] = "\x1b[1;31m"; -const char kColor2GreenBright[] = "\x1b[1;32m"; -const char kColor3Yellow[] = "\x1b[33m"; -const char kColor5Purple[] = "\x1b[35m"; -const char kEraseInLine[] = "\x1b[K"; -const char kSpinner[] = "|/-\\"; -const int kMaxStage = static_cast<int>(InstallerState::kMaxValue); - -std::string MoveForward(int i) { - return base::StringPrintf("\x1b[%dC", i); -} - -} // namespace - -CrostiniStartupStatus::CrostiniStartupStatus( - base::RepeatingCallback<void(const std::string&)> print, - bool verbose) - : print_(std::move(print)), verbose_(verbose) { -} - -CrostiniStartupStatus::~CrostiniStartupStatus() = default; - -void CrostiniStartupStatus::OnCrostiniRestarted( - crostini::CrostiniResult result) { - if (result != crostini::CrostiniResult::SUCCESS) { - PrintAfterStage( - kColor1RedBright, - base::StringPrintf("\rError starting penguin container: %d (%s)\r\n", - result, CrostiniResultString(result))); - crostini::RecordAppLaunchResultHistogram( - crostini::CrostiniAppLaunchAppType::kTerminal, result); - } else { - if (verbose_) { - // We change the stage_string but don't increment the stage number. This - // is deliberate, per UX they don't want more pieces in the stage progress - // bar. - const std::string& stage_string = l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_CONNECT_CONTAINER); - PrintStage(kColor3Yellow, stage_string); - } - } -} - -void CrostiniStartupStatus::OnCrostiniConnected( - crostini::CrostiniResult result) { - crostini::RecordAppLaunchResultHistogram( - crostini::CrostiniAppLaunchAppType::kTerminal, result); - if (result != crostini::CrostiniResult::SUCCESS) { - PrintAfterStage( - kColor1RedBright, - base::StringPrintf( - "\rError connecting shell to penguin container: %d (%s)\r\n", - result, CrostiniResultString(result))); - } else { - if (verbose_) { - stage_index_ = kMaxStage + 1; // done. - PrintStage(kColor2GreenBright, - base::StrCat({l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_READY), - "\r\n"})); - } - } - Print( - base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); -} - -void CrostiniStartupStatus::ShowProgressAtInterval() { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - show_progress_timer_ = std::make_unique<base::RepeatingTimer>(); - show_progress_timer_->Start(FROM_HERE, base::Milliseconds(300), - base::BindRepeating( - [](CrostiniStartupStatus* self) { - self->spinner_index_++; - self->PrintProgress(); - }, - this)); -} - -void CrostiniStartupStatus::OnStageStarted(InstallerState stage) { - stage_index_ = static_cast<int>(stage) + 1; - static base::NoDestructor<base::flat_map<InstallerState, std::string>> - kStartStrings({ - {InstallerState::kStart, - l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_START)}, - {InstallerState::kInstallImageLoader, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_INSTALL_IMAGE_LOADER)}, - {InstallerState::kCreateDiskImage, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_CREATE_DISK_IMAGE)}, - {InstallerState::kStartTerminaVm, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_START_TERMINA_VM)}, - {InstallerState::kStartLxd, - l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_START_LXD)}, - {InstallerState::kCreateContainer, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_CREATE_CONTAINER)}, - {InstallerState::kSetupContainer, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_SETUP_CONTAINER)}, - {InstallerState::kStartContainer, - l10n_util::GetStringUTF8( - IDS_CROSTINI_TERMINAL_STATUS_START_CONTAINER)}, - }); - const std::string& stage_string = - verbose_ ? (*kStartStrings)[stage] : std::string(); - PrintStage(kColor3Yellow, stage_string); -} - -void CrostiniStartupStatus::OnContainerDownloading(int32_t download_percent) { - if (download_percent % 8 == 0) { - PrintAfterStage(kColor3Yellow, "."); - } -} - -void CrostiniStartupStatus::Print(const std::string& output) { - print_.Run(output); -} - -void CrostiniStartupStatus::InitializeProgress() { - if (progress_initialized_) { - return; - } - progress_initialized_ = true; - Print(base::StringPrintf("%s%s[%s] ", kCursorHide, kColor5Purple, - std::string(kMaxStage, ' ').c_str())); -} - -void CrostiniStartupStatus::PrintProgress() { - InitializeProgress(); - Print(base::StringPrintf("\r%s%s%c", MoveForward(stage_index_).c_str(), - kColor5Purple, kSpinner[spinner_index_ & 0x3])); -} - -void CrostiniStartupStatus::PrintStage(const char* color, - const std::string& output) { - DCHECK_GE(stage_index_, 1); - InitializeProgress(); - std::string progress(stage_index_ - 1, '='); - Print(base::StringPrintf("\r%s[%s%s%s%s%s ", kColor5Purple, progress.c_str(), - MoveForward(3 + (kMaxStage - stage_index_)).c_str(), - kEraseInLine, color, output.c_str())); - end_of_line_index_ = 4 + kMaxStage + output.size(); -} - -void CrostiniStartupStatus::PrintAfterStage(const char* color, - const std::string& output) { - InitializeProgress(); - Print(base::StringPrintf("\r%s%s%s", MoveForward(end_of_line_index_).c_str(), - color, output.c_str())); - end_of_line_index_ += output.size(); -} - -} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.h b/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.h deleted file mode 100644 index f2a0508b26e..00000000000 --- a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_EXTENSIONS_API_TERMINAL_CROSTINI_STARTUP_STATUS_H_ -#define CHROME_BROWSER_EXTENSIONS_API_TERMINAL_CROSTINI_STARTUP_STATUS_H_ - -#include <string> - -#include "base/bind.h" -#include "base/gtest_prod_util.h" -#include "base/memory/weak_ptr.h" -#include "base/timer/timer.h" -#include "chrome/browser/ash/crostini/crostini_manager.h" -#include "chrome/browser/ash/crostini/crostini_simple_types.h" -#include "chrome/browser/ash/crostini/crostini_types.mojom.h" - -namespace extensions { - -// Displays startup status to the crostini terminal. -class CrostiniStartupStatus - : public crostini::CrostiniManager::RestartObserver { - public: - CrostiniStartupStatus(base::RepeatingCallback<void(const std::string&)> print, - bool verbose); - ~CrostiniStartupStatus() override; - - // Updates the progress spinner every 300ms. - void ShowProgressAtInterval(); - - // Called when startup is complete. - void OnCrostiniRestarted(crostini::CrostiniResult result); - void OnCrostiniConnected(crostini::CrostiniResult result); - - private: - FRIEND_TEST_ALL_PREFIXES(CrostiniStartupStatusTest, TestNotVerbose); - FRIEND_TEST_ALL_PREFIXES(CrostiniStartupStatusTest, TestVerbose); - - // crostini::CrostiniManager::RestartObserver - void OnStageStarted(crostini::mojom::InstallerState stage) override; - void OnContainerDownloading(int32_t download_percent) override; - - void Print(const std::string& output); - void InitializeProgress(); - void PrintProgress(); - void PrintStage(const char* color, const std::string& output); - void PrintAfterStage(const char* color, const std::string& output); - - base::RepeatingCallback<void(const std::string& output)> print_; - const bool verbose_; - bool progress_initialized_ = false; - int spinner_index_ = 0; - int stage_index_ = 1; - int end_of_line_index_ = 0; - std::unique_ptr<base::RepeatingTimer> show_progress_timer_; - - base::WeakPtrFactory<CrostiniStartupStatus> weak_factory_{this}; -}; - -} // namespace extensions - -#endif // CHROME_BROWSER_EXTENSIONS_API_TERMINAL_CROSTINI_STARTUP_STATUS_H_ diff --git a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status_unittest.cc b/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status_unittest.cc deleted file mode 100644 index 0ac09539c88..00000000000 --- a/chromium/chrome/browser/extensions/api/terminal/crostini_startup_status_unittest.cc +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/extensions/api/terminal/crostini_startup_status.h" - -#include <memory> -#include <vector> - -#include "base/bind.h" -#include "base/metrics/histogram_base.h" -#include "base/test/metrics/histogram_tester.h" -#include "chrome/browser/ash/crostini/crostini_simple_types.h" -#include "testing/gtest/include/gtest/gtest.h" - -using crostini::mojom::InstallerState; - -namespace extensions { - -class CrostiniStartupStatusTest : public testing::Test { - protected: - void Print(const std::string& output) { - output_.emplace_back(std::move(output)); - } - - void Done() { done_ = true; } - - std::unique_ptr<CrostiniStartupStatus> NewStartupStatus(bool verbose) { - return std::make_unique<CrostiniStartupStatus>( - base::BindRepeating(&CrostiniStartupStatusTest::Print, - base::Unretained(this)), - verbose); - } - - std::vector<std::string> output_; - bool done_ = false; - base::HistogramTester histogram_tester_{}; -}; - -TEST_F(CrostiniStartupStatusTest, TestNotVerbose) { - auto startup_status = NewStartupStatus(false); - startup_status->OnStageStarted(InstallerState::kStart); - startup_status->OnStageStarted(InstallerState::kInstallImageLoader); - startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS); - startup_status->OnCrostiniConnected(crostini::CrostiniResult::SUCCESS); - - ASSERT_EQ(output_.size(), 4u); - // Hide cursor, init progress. - EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); - - // CR, purple, forward 12, yellow, empty-stage. - EXPECT_EQ(output_[1], "\r\x1b[35m[\x1b[12C\x1b[K\x1b[33m "); - - // CR, purple, progress, forward 11, erase, yellow, empty-stage. - EXPECT_EQ(output_[2], "\r\x1b[35m[=\x1b[11C\x1b[K\x1b[33m "); - - // CR, delete line, default color, show cursor. - EXPECT_EQ(output_[3], "\r\x1b[K\x1b[0m\x1b[?25h"); - - histogram_tester_.ExpectBucketCount("Crostini.AppLaunchResult", - crostini::CrostiniResult::SUCCESS, 1); - histogram_tester_.ExpectBucketCount("Crostini.AppLaunchResult.Terminal", - crostini::CrostiniResult::SUCCESS, 1); -} - -TEST_F(CrostiniStartupStatusTest, TestVerbose) { - auto startup_status = NewStartupStatus(true); - startup_status->OnStageStarted(InstallerState::kStart); - startup_status->OnStageStarted(InstallerState::kInstallImageLoader); - startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS); - startup_status->OnCrostiniConnected(crostini::CrostiniResult::SUCCESS); - - ASSERT_EQ(output_.size(), 6u); - // Hide cursor, init progress. - EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); - - // CR, purple, forward 12, yellow, stage. - EXPECT_EQ(output_[1], "\r\x1b[35m[\x1b[12C\x1b[K\x1b[33mInitializing "); - - // CR, purple, progress, forward 11, erase, yellow, stage. - EXPECT_EQ(output_[2], - "\r\x1b[35m[=\x1b[11C\x1b[K\x1b[33mChecking the virtual machine "); - - // CR, purple, progress, forward 11, erase, yellow, container connect - // pseudo-stage. - EXPECT_EQ(output_[3], - "\r\x1B[35m[=\x1B[11C\x1B[K\x1B[33mConnecting to the container "); - - // CR, purple, progress, forward 2, erase, green, done, symbol, CRLF. - EXPECT_EQ(output_[4], - "\r\x1b[35m[==========\x1b[2C\x1b[K\x1b[1;32mReady\r\n "); - - // CR, delete line, default color, show cursor; - EXPECT_EQ(output_[5], "\r\x1b[K\x1b[0m\x1b[?25h"); - - histogram_tester_.ExpectBucketCount("Crostini.AppLaunchResult", - crostini::CrostiniResult::SUCCESS, 1); - histogram_tester_.ExpectBucketCount("Crostini.AppLaunchResult.Terminal", - crostini::CrostiniResult::SUCCESS, 1); -} - -TEST_F(CrostiniStartupStatusTest, - TestNoOutOfBoundsAccessWhenRestartBeforeStageStart) { - // Repro case for crbug/1214039. - auto startup_status = NewStartupStatus(true); - startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS); - - ASSERT_EQ(output_.size(), 2u); -} - -} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/terminal/startup_status.cc b/chromium/chrome/browser/extensions/api/terminal/startup_status.cc new file mode 100644 index 00000000000..c4db031b248 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/terminal/startup_status.cc @@ -0,0 +1,148 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/terminal/startup_status.h" +#include <unistd.h> + +#include <algorithm> +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "base/location.h" +#include "base/strings/strcat.h" +#include "base/strings/stringprintf.h" +#include "base/time/time.h" +#include "chrome/grit/generated_resources.h" +#include "content/public/browser/browser_thread.h" +#include "ui/base/l10n/l10n_util.h" + +namespace extensions { + +namespace { + +const char kCursorHide[] = "\x1b[?25l"; +const char kCursorShow[] = "\x1b[?25h"; +const char kColor0Normal[] = "\x1b[0m"; // Default. +const char kColor1RedBright[] = "\x1b[1;31m"; +const char kColor2GreenBright[] = "\x1b[1;32m"; +const char kColor3Yellow[] = "\x1b[33m"; +const char kColor5Purple[] = "\x1b[35m"; +const char kEraseInLine[] = "\x1b[K"; +const char kSpinnerCharacters[] = "|/-\\"; + +std::string MoveForward(int i) { + return base::StringPrintf("\x1b[%dC", i); +} + +} // namespace + +StartupStatusPrinter::StartupStatusPrinter( + base::RepeatingCallback<void(const std::string& output)> print, + bool verbose) + : print_(std::move(print)), verbose_(verbose) {} + +StartupStatusPrinter::~StartupStatusPrinter() = default; + +// Starts showing the progress indicator. +void StartupStatusPrinter::StartShowingSpinner() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + show_progress_timer_ = std::make_unique<base::RepeatingTimer>(); + show_progress_timer_->Start( + FROM_HERE, base::Milliseconds(300), + base::BindRepeating(&StartupStatusPrinter::PrintProgress, + // We own the timer, so this'll never get called after + // we're destroyed. + base::Unretained(this))); +} + +void StartupStatusPrinter::PrintStageWithColor(int stage_index, + const char* color, + const std::string& stage_name) { + DCHECK_GE(stage_index_, 0); + DCHECK_LE(stage_index_, max_stage_); + InitializeProgress(); + stage_index_ = stage_index; + auto output = verbose_ ? stage_name : ""; + std::string progress(stage_index_, '='); + std::string padding(max_stage_ - stage_index_, ' '); + Print(base::StringPrintf("\r%s[%s%s] %s%s%s ", kColor5Purple, + progress.c_str(), padding.c_str(), kEraseInLine, + color, output.c_str())); + end_of_line_index_ = 4 + max_stage_ + output.size(); +} + +void StartupStatusPrinter::PrintStage(int stage_index, + const std::string& stage_name) { + PrintStageWithColor(stage_index, kColor3Yellow, stage_name); +} + +void StartupStatusPrinter::PrintError(const std::string& output) { + InitializeProgress(); + Print(base::StringPrintf("\r%s%s%s", MoveForward(end_of_line_index_).c_str(), + kColor1RedBright, output.c_str())); + end_of_line_index_ += output.size(); + Print( + base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); +} + +void StartupStatusPrinter::PrintSucceeded() { + InitializeProgress(); + if (verbose_) { + auto output = base::StrCat( + {l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_READY), "\r\n"}); + PrintStageWithColor(max_stage_, kColor2GreenBright, output); + } + Print( + base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); +} + +void StartupStatusPrinter::Print(const std::string& output) { + print_.Run(output); +} + +void StartupStatusPrinter::InitializeProgress() { + if (progress_initialized_) { + return; + } + progress_initialized_ = true; + Print(base::StringPrintf("%s%s[%s] ", kCursorHide, kColor5Purple, + std::string(max_stage_, ' ').c_str())); +} + +void StartupStatusPrinter::PrintProgress() { + InitializeProgress(); + spinner_index_++; + Print(base::StringPrintf("\r%s%s%c", MoveForward(stage_index_).c_str(), + kColor5Purple, + kSpinnerCharacters[spinner_index_ & 0x3])); +} + +StartupStatus::StartupStatus(std::unique_ptr<StartupStatusPrinter> printer, + int max_stage) + : printer_(std::move(printer)), max_stage_(max_stage) { + printer_->set_max_stage(max_stage); +} + +StartupStatus::~StartupStatus() = default; + +void StartupStatus::OnConnectingToVsh() { + const std::string& stage_string = + l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_CONNECT_CONTAINER); + printer()->PrintStage(max_stage_, stage_string); +} +void StartupStatus::StartShowingSpinner() { + printer()->StartShowingSpinner(); +} + +void StartupStatus::OnFinished(bool success, + const std::string& failure_reason) { + if (success) { + printer()->PrintSucceeded(); + } else { + printer()->PrintError(failure_reason); + } +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/terminal/startup_status.h b/chromium/chrome/browser/extensions/api/terminal/startup_status.h new file mode 100644 index 00000000000..52135498637 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/terminal/startup_status.h @@ -0,0 +1,83 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_TERMINAL_STARTUP_STATUS_H_ +#define CHROME_BROWSER_EXTENSIONS_API_TERMINAL_STARTUP_STATUS_H_ + +#include <memory> +#include <string> + +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" + +namespace extensions { + +class StartupStatusPrinter { + public: + explicit StartupStatusPrinter( + base::RepeatingCallback<void(const std::string& output)> print, + bool verbose); + ~StartupStatusPrinter(); + + // Starts showing the progress indicator. + void StartShowingSpinner(); + + // Updates the output for a new stage named `stage_name` and number + // `stage_index`. If `succeeded` is true, indicates that the last stage has + // completed successfully and we're now in the end-state. + void PrintStage(int stage_index, const std::string& stage_name); + + // Displays an error message to the user. + void PrintError(const std::string& output); + + // Displays a successful connection message to the user. + void PrintSucceeded(); + + // Sets the max stage number. + void set_max_stage(int max_stage) { + DCHECK(!progress_initialized_); + max_stage_ = max_stage; + } + + base::WeakPtr<StartupStatusPrinter> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + void Print(const std::string& output); + void InitializeProgress(); + void PrintProgress(); + void PrintStageWithColor(int stage_index, + const char* color, + const std::string& stage_name); + + base::RepeatingCallback<void(const std::string& output)> print_; + const bool verbose_; + bool progress_initialized_ = false; + int spinner_index_ = 0; + int stage_index_ = 1; + int end_of_line_index_ = 0; + int max_stage_ = -1; + std::unique_ptr<base::RepeatingTimer> show_progress_timer_; + base::WeakPtrFactory<StartupStatusPrinter> weak_factory_{this}; +}; + +class StartupStatus { + public: + explicit StartupStatus(std::unique_ptr<StartupStatusPrinter> printer, + int max_stage); + virtual ~StartupStatus(); + void OnConnectingToVsh(); + void OnFinished(bool success, const std::string& failure_reason); + void StartShowingSpinner(); + StartupStatusPrinter* printer() { return printer_.get(); } + + private: + std::unique_ptr<StartupStatusPrinter> printer_; + const int max_stage_; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_TERMINAL_STARTUP_STATUS_H_ diff --git a/chromium/chrome/browser/extensions/api/terminal/startup_status_unittest.cc b/chromium/chrome/browser/extensions/api/terminal/startup_status_unittest.cc new file mode 100644 index 00000000000..621b828d7de --- /dev/null +++ b/chromium/chrome/browser/extensions/api/terminal/startup_status_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/terminal/startup_status.h" + +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +class StartupStatusTest : public testing::Test { + protected: + void Print(const std::string& output) { + output_.emplace_back(std::move(output)); + } + + std::unique_ptr<StartupStatusPrinter> NewStatusPrinter(bool verbose) { + return std::make_unique<StartupStatusPrinter>( + base::BindRepeating(&StartupStatusTest::Print, base::Unretained(this)), + verbose); + } + + std::vector<std::string> output_; +}; + +TEST_F(StartupStatusTest, TestNotVerbose) { + auto status_printer = NewStatusPrinter(false); + status_printer->set_max_stage(10); + status_printer->PrintStage(0, "Starting Stage"); + status_printer->PrintStage(2, "Second Stage"); + status_printer->PrintStage(10, "Last Stage"); + status_printer->PrintSucceeded(); + + ASSERT_EQ(output_.size(), 5); + + // Hide cursor, init progress. + EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); + + // CR, purple, progress, erase-right, yellow, empty-stage. + EXPECT_EQ(output_[1], "\r\x1b[35m[ ] \x1b[K\x1b[33m "); + + // CR, purple, progress, erase-right, yellow, empty-stage. + EXPECT_EQ(output_[2], "\r\x1b[35m[== ] \x1b[K\x1b[33m "); + + // CR, purple, progress, erase-right, yellow, empty-stage. + EXPECT_EQ(output_[3], "\r\x1b[35m[==========] \x1b[K\x1b[33m "); + + // CR, erase-right, default color, show cursor. + EXPECT_EQ(output_[4], "\r\x1b[K\x1b[0m\x1b[?25h"); +} + +TEST_F(StartupStatusTest, TestVerbose) { + auto status_printer = NewStatusPrinter(true); + status_printer->set_max_stage(10); + status_printer->PrintStage(0, "Starting Stage"); + status_printer->PrintStage(2, "Second Stage"); + status_printer->PrintStage(10, "Last Stage"); + status_printer->PrintSucceeded(); + + ASSERT_EQ(output_.size(), 6); + + // Hide cursor, init progress. + EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); + + // CR, purple, progress, erase-right, yellow, stage. + EXPECT_EQ(output_[1], "\r\x1b[35m[ ] \x1b[K\x1b[33mStarting Stage "); + + // CR, purple, progress, erase-right, yellow, stage. + EXPECT_EQ(output_[2], "\r\x1b[35m[== ] \x1b[K\x1b[33mSecond Stage "); + + // CR, purple, progress, erase-right, yellow, stage. + EXPECT_EQ(output_[3], "\r\x1b[35m[==========] \x1b[K\x1b[33mLast Stage "); + + // CR, purple, progress, erase-right, yellow, stage. + EXPECT_EQ(output_[4], "\r\x1b[35m[==========] \x1b[K\x1b[1;32mReady\r\n "); + + // CR, erase-right, default color, show cursor. + EXPECT_EQ(output_[5], "\r\x1b[K\x1b[0m\x1b[?25h"); +} + +TEST_F(StartupStatusTest, TestError) { + auto status_printer = NewStatusPrinter(false); + status_printer->set_max_stage(10); + status_printer->PrintStage(1, "First Stage"); + status_printer->PrintError("Error message"); // Prints two things. + + ASSERT_EQ(output_.size(), 4); + + // Hide cursor, init progress. + EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); + // CR, purple, progress, erase-right, yellow, stage. + EXPECT_EQ(output_[1], "\r\x1b[35m[= ] \x1b[K\x1b[33m "); + + // CR, Move forward 14 characters, red, error message. + EXPECT_EQ(output_[2], "\r\x1b[14C\x1b[1;31mError message"); + + // CR, erase-right, default color, show cursor. + EXPECT_EQ(output_[3], "\r\x1b[K\x1b[0m\x1b[?25h"); +} + +} // namespace extensions 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 9a769941b6b..46d4aa2bedc 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.cc @@ -28,13 +28,19 @@ #include "base/system/sys_info.h" #include "base/task/task_runner_util.h" #include "base/values.h" -#include "chrome/browser/ash/crostini/crostini_features.h" -#include "chrome/browser/ash/crostini/crostini_manager.h" #include "chrome/browser/ash/crostini/crostini_pref_names.h" -#include "chrome/browser/ash/crostini/crostini_terminal.h" #include "chrome/browser/ash/crostini/crostini_util.h" +#include "chrome/browser/ash/guest_os/guest_id.h" +#include "chrome/browser/ash/guest_os/guest_os_pref_names.h" +#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h" +#include "chrome/browser/ash/guest_os/guest_os_terminal.h" +#include "chrome/browser/ash/guest_os/public/guest_os_service.h" +#include "chrome/browser/ash/guest_os/public/guest_os_terminal_provider.h" +#include "chrome/browser/ash/guest_os/public/guest_os_terminal_provider_registry.h" +#include "chrome/browser/ash/guest_os/public/types.h" +#include "chrome/browser/ash/guest_os/virtual_machines/virtual_machines_util.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/extensions/api/terminal/crostini_startup_status.h" +#include "chrome/browser/extensions/api/terminal/startup_status.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/policy/system_features_disable_list_policy_handler.h" @@ -45,6 +51,7 @@ #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/api/terminal_private.h" +#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h" #include "chromeos/process_proxy/process_proxy_registry.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_context.h" @@ -73,8 +80,6 @@ namespace OpenWindow = extensions::api::terminal_private::OpenWindow; namespace GetPrefs = extensions::api::terminal_private::GetPrefs; namespace SetPrefs = extensions::api::terminal_private::SetPrefs; -using crostini::mojom::InstallerState; - namespace { const char kCroshName[] = "crosh"; @@ -90,16 +95,17 @@ const char kSwitchVmName[] = "vm_name"; const char kSwitchTargetContainer[] = "target_container"; const char kSwitchStartupId[] = "startup_id"; const char kSwitchCurrentWorkingDir[] = "cwd"; +const char kSwitchContainerFeatures[] = "container_features"; const char kCwdTerminalIdPrefix[] = "terminal_id:"; // Prefs that we read and observe. static const base::NoDestructor<std::vector<std::string>> kPrefsReadAllowList{{ ash::prefs::kAccessibilitySpokenFeedbackEnabled, - crostini::prefs::kCrostiniContainers, crostini::prefs::kCrostiniEnabled, - crostini::prefs::kCrostiniTerminalSettings, + guest_os::prefs::kGuestOsTerminalSettings, crostini::prefs::kTerminalSshAllowedByPolicy, + guest_os::prefs::kGuestOsContainers, }}; void CloseTerminal(const std::string& terminal_id, @@ -178,6 +184,19 @@ std::string GetSwitch(const base::CommandLine& src, return result; } +std::string GetContainerFeaturesArg() { + std::string result; + // There are only a few available features so concatenating strings is + // sufficient. + for (vm_tools::cicerone::ContainerFeature feature : + crostini::GetContainerFeatures()) { + if (!result.empty()) + result += ","; + result += base::NumberToString(static_cast<int>(feature)); + } + return result; +} + void NotifyProcessOutput(content::BrowserContext* browser_context, const std::string& terminal_id, const std::string& output_type, @@ -189,10 +208,10 @@ void NotifyProcessOutput(content::BrowserContext* browser_context, return; } - std::vector<base::Value> args; - args.push_back(base::Value(terminal_id)); - args.push_back(base::Value(output_type)); - args.push_back(base::Value(base::make_span( + base::Value::List args; + args.Append(terminal_id); + args.Append(output_type); + args.Append(base::Value(base::make_span( reinterpret_cast<const uint8_t*>(&output[0]), output.size()))); extensions::EventRouter* event_router = @@ -210,10 +229,10 @@ void PrefChanged(Profile* profile, const std::string& pref_name) { if (!event_router) { return; } - std::vector<base::Value> args; + base::Value::List args; base::Value prefs(base::Value::Type::DICTIONARY); - prefs.SetKey(pref_name, profile->GetPrefs()->Get(pref_name)->Clone()); - args.push_back(std::move(prefs)); + prefs.SetKey(pref_name, profile->GetPrefs()->GetValue(pref_name).Clone()); + args.Append(std::move(prefs)); auto event = std::make_unique<extensions::Event>( extensions::events::TERMINAL_PRIVATE_ON_PREF_CHANGED, terminal_private::OnPrefChanged::kEventName, std::move(args)); @@ -296,11 +315,10 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( base::CommandLine(base::FilePath(kStubbedCroshCommand))); } } else if (process_name == kVmShellName) { - // Ensure crostini is allowed before starting terminal. - Profile* profile = Profile::FromBrowserContext(browser_context()); - if (!crostini::CrostiniFeatures::Get()->IsAllowedNow(profile)) + // Ensure vms are allowed before starting terminal. + if (!virtual_machines::AreVirtualMachinesAllowedByPolicy()) { return RespondNow(Error("vmshell not allowed")); - + } // command=vmshell: ensure --owner_id, --vm_name, --target_container, --cwd // are set, and the specified vm/container is running. base::CommandLine cmdline((base::FilePath(kVmShellCommand))); @@ -314,29 +332,57 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( std::string vm_name = GetSwitch(params_args, &cmdline, kSwitchVmName, crostini::kCrostiniDefaultVmName); std::string container_name = - GetSwitch(params_args, &cmdline, kSwitchTargetContainer, - crostini::kCrostiniDefaultContainerName); + GetSwitch(params_args, &cmdline, kSwitchTargetContainer, ""); GetSwitch(params_args, &cmdline, kSwitchCurrentWorkingDir, ""); std::string startup_id = params_args.GetSwitchValueASCII(kSwitchStartupId); - container_id_ = - std::make_unique<crostini::ContainerId>(vm_name, container_name); - VLOG(1) << "Starting " << *container_id_ + guest_id_ = std::make_unique<guest_os::GuestId>(guest_os::VmType::UNKNOWN, + vm_name, container_name); + + // Unlike the other switches, this is computed here directly rather than + // taken from |args|. + std::string container_features = GetContainerFeaturesArg(); + if (!container_features.empty()) + cmdline.AppendSwitchASCII(kSwitchContainerFeatures, container_features); + + VLOG(1) << "Starting " << *guest_id_ << ", cmdline=" << cmdline.GetCommandLineString(); - auto* mgr = crostini::CrostiniManager::GetForProfile(profile); - bool verbose = !mgr->GetContainerInfo(*container_id_).has_value(); - startup_status_ = std::make_unique<CrostiniStartupStatus>( + Profile* profile = Profile::FromBrowserContext(browser_context()); + auto* service = guest_os::GuestOsService::GetForProfile(profile); + guest_os::GuestOsTerminalProvider* provider = nullptr; + if (service) { + provider = service->TerminalProviderRegistry()->Get(*guest_id_); + } + auto* tracker = guest_os::GuestOsSessionTracker::GetForProfile(profile); + bool verbose = !(tracker && tracker->GetInfo(*guest_id_).has_value()); + auto status_printer = std::make_unique<StartupStatusPrinter>( base::BindRepeating(&NotifyProcessOutput, browser_context(), startup_id, api::terminal_private::ToString( api::terminal_private::OUTPUT_TYPE_STDOUT)), verbose); - startup_status_->ShowProgressAtInterval(); - mgr->RestartCrostini( - *container_id_, - base::BindOnce( - &TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted, - this, user_id_hash, std::move(cmdline)), - startup_status_.get()); + if (provider) { + startup_status_ = + provider->CreateStartupStatus(std::move(status_printer)); + startup_status_->StartShowingSpinner(); + provider->EnsureRunning( + startup_status_.get(), + base::BindOnce( + &TerminalPrivateOpenTerminalProcessFunction::OnGuestRunning, this, + user_id_hash, std::move(cmdline))); + } else { + // Don't recognise the guest. Options include: + // * It doesn't exist but the terminal app thinks it does e.g. out-of-sync + // prefs, race between uninstalling and launching terminal. + // * We're installing Bruschetta, and it's using the terminal to finish + // installing. We don't show Bruschetta until it's installed, but it's + // running and users need to connect to it. + // Given this, try and connect directly to it, if it succeeds, great, if + // it fails, then report failure to the user. + startup_status_ = + std::make_unique<StartupStatus>(std::move(status_printer), 2); + startup_status_->StartShowingSpinner(); + OpenVmshellProcess(user_id_hash, std::move(cmdline)); + } } else { // command=[unrecognized]. return RespondNow(Error("Invalid process name: " + process_name)); @@ -344,25 +390,24 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( return RespondLater(); } -void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted( +void TerminalPrivateOpenTerminalProcessFunction::OnGuestRunning( const std::string& user_id_hash, base::CommandLine cmdline, - crostini::CrostiniResult result) { - startup_status_->OnCrostiniRestarted(result); - if (result == crostini::CrostiniResult::SUCCESS) { + bool success, + std::string failure_reason) { + if (success) { OpenVmshellProcess(user_id_hash, std::move(cmdline)); } else { - const std::string msg = - base::StringPrintf("Error starting crostini for terminal: %d (%s)", - result, CrostiniResultString(result)); - LOG(ERROR) << msg; - Respond(Error(msg)); + startup_status_->OnFinished(success, failure_reason); + LOG(ERROR) << failure_reason; + Respond(Error(failure_reason)); } } void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess( const std::string& user_id_hash, base::CommandLine cmdline) { + startup_status_->OnConnectingToVsh(); const std::string cwd = cmdline.GetSwitchValueASCII(kSwitchCurrentWorkingDir); if (!base::StartsWith(cwd, kCwdTerminalIdPrefix)) { @@ -376,28 +421,31 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess( cwd.substr(sizeof(kCwdTerminalIdPrefix) - 1)); // Lookup container shell pid from cicierone to use for cwd. - crostini::CrostiniManager::GetForProfile( - Profile::FromBrowserContext(browser_context())) - ->GetVshSession( - *container_id_, host_pid, - base::BindOnce( - &TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession, - this, user_id_hash, std::move(cmdline), /*terminal_id=*/cwd)); + vm_tools::cicerone::GetVshSessionRequest request; + request.set_vm_name(guest_id_->vm_name); + request.set_container_name(guest_id_->container_name); + request.set_owner_id(crostini::CryptohomeIdForProfile( + Profile::FromBrowserContext(browser_context()))); + request.set_host_vsh_pid(host_pid); + ash::CiceroneClient::Get()->GetVshSession( + request, + base::BindOnce( + &TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession, this, + user_id_hash, std::move(cmdline), /*terminal_id=*/cwd)); } void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession( const std::string& user_id_hash, base::CommandLine cmdline, const std::string& terminal_id, - bool success, - const std::string& failure_reason, - int32_t container_shell_pid) { - if (!success) { - LOG(WARNING) << "Failed to get vsh session for " << terminal_id << ". " - << failure_reason; + absl::optional<vm_tools::cicerone::GetVshSessionResponse> response) { + if (!response || !response->success()) { + LOG(WARNING) << "Failed to get vsh session for " << terminal_id << ": " + << (response ? response->failure_reason() : "empty response"); } else { - cmdline.AppendSwitchASCII(kSwitchCurrentWorkingDir, - base::NumberToString(container_shell_pid)); + cmdline.AppendSwitchASCII( + kSwitchCurrentWorkingDir, + base::NumberToString(response->container_shell_pid())); } OpenProcess(user_id_hash, std::move(cmdline)); } @@ -438,9 +486,8 @@ void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread( bool success, const std::string& terminal_id) { if (startup_status_) { - startup_status_->OnCrostiniConnected( - success ? crostini::CrostiniResult::SUCCESS - : crostini::CrostiniResult::VSH_CONNECT_FAILED); + startup_status_->OnFinished( + success, success ? "" : "Error connecting shell to guest"); } auto* contents = GetSenderWebContents(); if (!contents) { @@ -508,9 +555,12 @@ ExtensionFunction::ResponseAction TerminalPrivateSendInputFunction::Run() { void TerminalPrivateSendInputFunction::SendInputOnRegistryTaskRunner( const std::string& terminal_id, const std::string& text) { - bool success = - chromeos::ProcessProxyRegistry::Get()->SendInput(terminal_id, text); + chromeos::ProcessProxyRegistry::Get()->SendInput( + terminal_id, text, + base::BindOnce(&TerminalPrivateSendInputFunction::OnSendInput, this)); +} +void TerminalPrivateSendInputFunction::OnSendInput(bool success) { content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&TerminalPrivateSendInputFunction::RespondOnUIThread, this, @@ -629,7 +679,7 @@ ExtensionFunction::ResponseAction TerminalPrivateOpenWindowFunction::Run() { OpenWindow::Params::Create(args())); EXTENSION_FUNCTION_VALIDATE(params.get()); - const std::string* url = &crostini::GetTerminalHomeUrl(); + const std::string* url = &guest_os::GetTerminalHomeUrl(); bool as_tab = false; auto& data = params->data; @@ -650,7 +700,7 @@ ExtensionFunction::ResponseAction TerminalPrivateOpenWindowFunction::Run() { LOG(ERROR) << "cannot find the browser"; } } else { - crostini::LaunchTerminalWithUrl( + guest_os::LaunchTerminalWithUrl( Profile::FromBrowserContext(browser_context()), display::kInvalidDisplayId, GURL(*url)); } @@ -663,7 +713,7 @@ TerminalPrivateOpenOptionsPageFunction:: ExtensionFunction::ResponseAction TerminalPrivateOpenOptionsPageFunction::Run() { - crostini::LaunchTerminalSettings( + guest_os::LaunchTerminalSettings( Profile::FromBrowserContext(browser_context())); return RespondNow(NoArguments()); } @@ -684,6 +734,9 @@ TerminalPrivateGetOSInfoFunction::~TerminalPrivateGetOSInfoFunction() = default; ExtensionFunction::ResponseAction TerminalPrivateGetOSInfoFunction::Run() { base::DictionaryValue info; + info.SetBoolKey("alternative_emulator", + base::FeatureList::IsEnabled( + chromeos::features::kTerminalAlternativeEmulator)); info.SetBoolKey( "multi_profile", base::FeatureList::IsEnabled(chromeos::features::kTerminalMultiProfile)); @@ -708,11 +761,11 @@ ExtensionFunction::ResponseAction TerminalPrivateGetPrefsFunction::Run() { LOG(WARNING) << "Ignoring non-allowed GetPrefs path=" << path; continue; } - if (path == crostini::prefs::kCrostiniTerminalSettings) { - crostini::RecordTerminalSettingsChangesUMAs( + if (path == guest_os::prefs::kGuestOsTerminalSettings) { + guest_os::RecordTerminalSettingsChangesUMAs( Profile::FromBrowserContext(browser_context())); } - result.SetKey(path, service->Get(path)->Clone()); + result.SetKey(path, service->GetValue(path).Clone()); } return RespondNow(OneArgument(std::move(result))); } @@ -728,7 +781,7 @@ ExtensionFunction::ResponseAction TerminalPrivateSetPrefsFunction::Run() { static const base::NoDestructor< base::flat_map<std::string, base::Value::Type>> - kAllowList{{{crostini::prefs::kCrostiniTerminalSettings, + kAllowList{{{guest_os::prefs::kGuestOsTerminalSettings, base::Value::Type::DICTIONARY}}}; for (base::DictionaryValue::Iterator it(params->prefs.additional_properties); 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 fe9a26253d8..d23d3626b84 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_api.h @@ -9,21 +9,20 @@ #include <string> #include <vector> -#include "chrome/browser/ash/crostini/crostini_simple_types.h" -#include "chrome/browser/profiles/profile.h" -#include "components/value_store/value_store.h" +#include "chromeos/ash/components/dbus/cicerone/cicerone_service.pb.h" #include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/extension_function.h" +#include "third_party/abseil-cpp/absl/types/optional.h" class PrefChangeRegistrar; -namespace crostini { -struct ContainerId; -} // namespace crostini +namespace guest_os { +struct GuestId; +} // namespace guest_os namespace extensions { -class CrostiniStartupStatus; +class StartupStatus; class TerminalPrivateAPI : public BrowserContextKeyedAPI { public: @@ -66,20 +65,19 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction { std::unique_ptr<std::vector<std::string>> args); private: - void OnCrostiniRestarted( - const std::string& user_id_hash, - base::CommandLine cmdline, - crostini::CrostiniResult result); + void OnGuestRunning(const std::string& user_id_hash, + base::CommandLine cmdline, + bool success, + std::string failure_reason); void OpenVmshellProcess(const std::string& user_id_hash, base::CommandLine cmdline); - void OnGetVshSession(const std::string& user_id_hash, - base::CommandLine cmdline, - const std::string& terminal_id, - bool success, - const std::string& failure_reason, - int32_t container_shell_pid); + void OnGetVshSession( + const std::string& user_id_hash, + base::CommandLine cmdline, + const std::string& terminal_id, + absl::optional<vm_tools::cicerone::GetVshSessionResponse>); void OpenProcess(const std::string& user_id_hash, base::CommandLine cmdline); @@ -95,8 +93,8 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction { base::CommandLine cmdline, const std::string& user_id_hash); void RespondOnUIThread(bool success, const std::string& terminal_id); - std::unique_ptr<CrostiniStartupStatus> startup_status_; - std::unique_ptr<crostini::ContainerId> container_id_; + std::unique_ptr<StartupStatus> startup_status_; + std::unique_ptr<guest_os::GuestId> guest_id_; }; // Opens new vmshell process. Returns the new terminal id. @@ -127,6 +125,7 @@ class TerminalPrivateSendInputFunction : public ExtensionFunction { private: void SendInputOnRegistryTaskRunner(const std::string& terminal_id, const std::string& input); + void OnSendInput(bool success); void RespondOnUIThread(bool success); }; diff --git a/chromium/chrome/browser/extensions/api/terminal/terminal_private_apitest.cc b/chromium/chrome/browser/extensions/api/terminal/terminal_private_apitest.cc index af66bd0a738..7a50f0a4a55 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_apitest.cc @@ -70,8 +70,8 @@ class CatProcess { chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( FROM_HERE, base::BindLambdaForTesting([&]() { - chromeos::ProcessProxyRegistry::Get()->SendInput(this->process_id_, - data); + chromeos::ProcessProxyRegistry::Get()->SendInput( + this->process_id_, data, base::DoNothing()); })); run_loop.Run(); diff --git a/chromium/chrome/browser/extensions/api/terminal/terminal_private_browsertest.cc b/chromium/chrome/browser/extensions/api/terminal/terminal_private_browsertest.cc index 979c64d1450..d643c3b7309 100644 --- a/chromium/chrome/browser/extensions/api/terminal/terminal_private_browsertest.cc +++ b/chromium/chrome/browser/extensions/api/terminal/terminal_private_browsertest.cc @@ -4,8 +4,11 @@ #include <memory> +#include "ash/components/settings/cros_settings_names.h" #include "chrome/browser/ash/crostini/crostini_browser_test_util.h" #include "chrome/browser/ash/crostini/fake_crostini_features.h" +#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h" +#include "chrome/browser/ash/settings/stub_cros_settings_provider.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/policy/system_features_disable_list_policy_handler.h" #include "chrome/browser/ui/browser.h" @@ -39,6 +42,7 @@ class TerminalPrivateBrowserTest : public CrostiniBrowserTestBase { /*world_id=*/1); EXPECT_EQ(eval_result.value.GetString(), expected); } + ash::ScopedTestingCrosSettings cros_settings_; }; IN_PROC_BROWSER_TEST_F(TerminalPrivateBrowserTest, OpenTerminalProcessChecks) { @@ -51,12 +55,13 @@ IN_PROC_BROWSER_TEST_F(TerminalPrivateBrowserTest, OpenTerminalProcessChecks) { resolve(lastError ? lastError.message : "success"); })}))"; - // 'vmshell not allowed' when crostini is not allowed. - fake_crostini_features_.set_could_be_allowed(true); - fake_crostini_features_.set_is_allowed_now(false); + // 'vmshell not allowed' when VMs are not allowed. + cros_settings_.device_settings()->SetBoolean(ash::kVirtualMachinesAllowed, + false); ExpectJsResult(script, "vmshell not allowed"); - fake_crostini_features_.set_is_allowed_now(true); + cros_settings_.device_settings()->SetBoolean(ash::kVirtualMachinesAllowed, + true); ExpectJsResult(script, "success"); // openTerminalProcess not defined. 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 e7409cc8c60..d2944efa889 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 @@ -14,6 +14,7 @@ #include "ash/public/cpp/keyboard/keyboard_switches.h" #include "ash/public/cpp/keyboard/keyboard_types.h" #include "base/bind.h" +#include "base/check.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/metrics/field_trial_params.h" @@ -527,15 +528,15 @@ void ChromeVirtualKeyboardDelegate::OnHasInputDevices( DCHECK_CURRENTLY_ON(content::BrowserThread::UI); auto* keyboard_client = ChromeKeyboardControllerClient::Get(); - std::unique_ptr<base::DictionaryValue> results(new base::DictionaryValue()); - results->SetStringKey("layout", GetKeyboardLayout()); + base::Value::Dict results; + results.Set("layout", GetKeyboardLayout()); // TODO(bshe): Consolidate a11y, hotrod and normal mode into a mode enum. See // crbug.com/529474. - results->SetBoolKey("a11ymode", - keyboard_client->IsEnableFlagSet( - keyboard::KeyboardEnableFlag::kAccessibilityEnabled)); - results->SetBoolKey("hotrodmode", g_hotrod_keyboard_enabled); + results.Set("a11ymode", + keyboard_client->IsEnableFlagSet( + keyboard::KeyboardEnableFlag::kAccessibilityEnabled)); + results.Set("hotrodmode", g_hotrod_keyboard_enabled); base::Value features(base::Value::Type::LIST); keyboard::KeyboardConfig config = keyboard_client->GetKeyboardConfig(); @@ -577,8 +578,8 @@ void ChromeVirtualKeyboardDelegate::OnHasInputDevices( "borderedkey", base::FeatureList::IsEnabled( chromeos::features::kVirtualKeyboardBorderedKey))); features.Append(GenerateFeatureFlag( - "multitouch", - base::FeatureList::IsEnabled(features::kVirtualKeyboardMultitouch))); + "multitouch", base::FeatureList::IsEnabled( + chromeos::features::kVirtualKeyboardMultitouch))); features.Append(GenerateFeatureFlag( "roundCorners", base::FeatureList::IsEnabled( chromeos::features::kVirtualKeyboardRoundCorners))); @@ -597,32 +598,38 @@ void ChromeVirtualKeyboardDelegate::OnHasInputDevices( GenerateFeatureFlag("autocorrectparamstuning", base::FeatureList::IsEnabled( chromeos::features::kAutocorrectParamsTuning))); + features.Append( + GenerateFeatureFlag("handwritinglibrarydlc", + base::FeatureList::IsEnabled( + chromeos::features::kHandwritingLibraryDlc))); - results->SetKey("features", std::move(features)); + results.Set("features", std::move(features)); std::move(on_settings_callback).Run(std::move(results)); } void ChromeVirtualKeyboardDelegate::DispatchConfigChangeEvent( - std::unique_ptr<base::DictionaryValue> settings) { + absl::optional<base::Value::Dict> settings) { + DCHECK(settings); + EventRouter* router = GetRouterForEventName( browser_context_, keyboard_api::OnKeyboardConfigChanged::kEventName); if (!router) return; - auto event_args = std::make_unique<base::ListValue>(); - event_args->Append(base::Value::FromUniquePtrValue(std::move(settings))); + base::Value::List event_args; + event_args.Append(base::Value(std::move(*settings))); auto event = std::make_unique<extensions::Event>( extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_KEYBOARD_CONFIG_CHANGED, - keyboard_api::OnKeyboardConfigChanged::kEventName, - std::move(*event_args).TakeListDeprecated(), browser_context_); + keyboard_api::OnKeyboardConfigChanged::kEventName, std::move(event_args), + browser_context_); router->BroadcastEvent(std::move(event)); } -api::virtual_keyboard::FeatureRestrictions -ChromeVirtualKeyboardDelegate::RestrictFeatures( - const api::virtual_keyboard::RestrictFeatures::Params& params) { +void ChromeVirtualKeyboardDelegate::RestrictFeatures( + const api::virtual_keyboard::RestrictFeatures::Params& params, + OnRestrictFeaturesCallback callback) { const api::virtual_keyboard::FeatureRestrictions& restrictions = params.restrictions; api::virtual_keyboard::FeatureRestrictions update; @@ -669,7 +676,7 @@ ChromeVirtualKeyboardDelegate::RestrictFeatures( // Keeping this unnecessary reload for now, just to avoid behaviour changes. ChromeKeyboardControllerClient::Get()->RebuildKeyboardIfEnabled(); } - return update; + std::move(callback).Run(std::move(update)); } } // namespace extensions 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 ef16c2c0204..12cd15fe3d2 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 @@ -9,9 +9,11 @@ #include "ash/public/cpp/clipboard_history_controller.h" #include "base/memory/weak_ptr.h" +#include "base/values.h" #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 "third_party/abseil-cpp/absl/types/optional.h" namespace media { class AudioSystem; @@ -66,8 +68,9 @@ class ChromeVirtualKeyboardDelegate OnGetClipboardHistoryCallback get_history_callback) override; bool PasteClipboardItem(const std::string& clipboard_item_id) override; bool DeleteClipboardItem(const std::string& clipboard_item_id) override; - api::virtual_keyboard::FeatureRestrictions RestrictFeatures( - const api::virtual_keyboard::RestrictFeatures::Params& params) override; + void RestrictFeatures( + const api::virtual_keyboard::RestrictFeatures::Params& params, + OnRestrictFeaturesCallback callback) override; private: // ash::ClipboardHistoryController::Observer: @@ -79,8 +82,7 @@ class ChromeVirtualKeyboardDelegate void OnHasInputDevices(OnKeyboardSettingsCallback on_settings_callback, bool has_audio_input_devices); - void DispatchConfigChangeEvent( - std::unique_ptr<base::DictionaryValue> settings); + void DispatchConfigChangeEvent(absl::optional<base::Value::Dict> settings); content::BrowserContext* browser_context_; std::unique_ptr<media::AudioSystem> audio_system_; diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.cc b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.cc new file mode 100644 index 00000000000..67c5cd383b8 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.cc @@ -0,0 +1,230 @@ +// Copyright 2022 The Chromium Authors. All 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/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h" + +#include "base/notreached.h" +#include "chromeos/crosapi/mojom/virtual_keyboard.mojom.h" +#include "chromeos/lacros/lacros_service.h" + +namespace extensions { +namespace { +void UpdateRestriction( + crosapi::mojom::VirtualKeyboardFeature feature, + bool enabled, + crosapi::mojom::VirtualKeyboardRestrictions* restrictions) { + if (enabled) { + restrictions->enabled_features.push_back(feature); + } else { + restrictions->disabled_features.push_back(feature); + } +} + +void PopulateFeatureRestrictions( + const std::vector<crosapi::mojom::VirtualKeyboardFeature>& features, + bool enabled, + api::virtual_keyboard::FeatureRestrictions* update) { + for (auto feature : features) { + switch (feature) { + case crosapi::mojom::VirtualKeyboardFeature::AUTOCOMPLETE: + update->auto_complete_enabled = std::make_unique<bool>(enabled); + break; + case crosapi::mojom::VirtualKeyboardFeature::AUTOCORRECT: + update->auto_correct_enabled = std::make_unique<bool>(enabled); + break; + case crosapi::mojom::VirtualKeyboardFeature::HANDWRITING: + update->handwriting_enabled = std::make_unique<bool>(enabled); + break; + case crosapi::mojom::VirtualKeyboardFeature::SPELL_CHECK: + update->spell_check_enabled = std::make_unique<bool>(enabled); + break; + case crosapi::mojom::VirtualKeyboardFeature::VOICE_INPUT: + update->voice_input_enabled = std::make_unique<bool>(enabled); + break; + case crosapi::mojom::VirtualKeyboardFeature::NONE: + NOTREACHED(); + break; + } + } +} +} // namespace + +LacrosVirtualKeyboardDelegate::LacrosVirtualKeyboardDelegate() = default; + +LacrosVirtualKeyboardDelegate::~LacrosVirtualKeyboardDelegate() = default; + +void LacrosVirtualKeyboardDelegate::GetKeyboardConfig( + OnKeyboardSettingsCallback on_settings_callback) { + NOTIMPLEMENTED_LOG_ONCE(); +} + +void LacrosVirtualKeyboardDelegate::OnKeyboardConfigChanged() { + NOTIMPLEMENTED_LOG_ONCE(); +} + +bool LacrosVirtualKeyboardDelegate::HideKeyboard() { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::InsertText(const std::u16string& text) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::OnKeyboardLoaded() { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +void LacrosVirtualKeyboardDelegate::SetHotrodKeyboard(bool enable) { + NOTIMPLEMENTED_LOG_ONCE(); +} + +bool LacrosVirtualKeyboardDelegate::LockKeyboard(bool state) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SendKeyEvent(const std::string& type, + int char_value, + int key_code, + const std::string& key_name, + int modifiers) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::ShowLanguageSettings() { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::ShowSuggestionSettings() { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::IsSettingsEnabled() { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetVirtualKeyboardMode( + int mode_enum, + gfx::Rect target_bounds, + OnSetModeCallback on_set_mode_callback) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetDraggableArea( + const api::virtual_keyboard_private::Bounds& rect) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetRequestedKeyboardState(int state_enum) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetOccludedBounds( + const std::vector<gfx::Rect>& bounds) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetHitTestBounds( + const std::vector<gfx::Rect>& bounds) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetAreaToRemainOnScreen( + const gfx::Rect& bounds) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::SetWindowBoundsInScreen( + const gfx::Rect& bounds_in_screen) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +void LacrosVirtualKeyboardDelegate::GetClipboardHistory( + const std::set<std::string>& item_ids_filter, + OnGetClipboardHistoryCallback get_history_callback) { + NOTIMPLEMENTED_LOG_ONCE(); +} + +bool LacrosVirtualKeyboardDelegate::PasteClipboardItem( + const std::string& clipboard_item_id) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +bool LacrosVirtualKeyboardDelegate::DeleteClipboardItem( + const std::string& clipboard_item_id) { + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + +void LacrosVirtualKeyboardDelegate::ParseRestrictFeaturesResult( + OnRestrictFeaturesCallback callback, + crosapi::mojom::VirtualKeyboardRestrictionsPtr update_mojo) { + api::virtual_keyboard::FeatureRestrictions update; + PopulateFeatureRestrictions(update_mojo->enabled_features, true, &update); + PopulateFeatureRestrictions(update_mojo->disabled_features, false, &update); + std::move(callback).Run(std::move(update)); +} + +void LacrosVirtualKeyboardDelegate::RestrictFeatures( + const api::virtual_keyboard::RestrictFeatures::Params& params, + OnRestrictFeaturesCallback callback) { + const api::virtual_keyboard::FeatureRestrictions& restrictions = + params.restrictions; + + auto* lacros_service = chromeos::LacrosService::Get(); + if (!lacros_service->IsAvailable<crosapi::mojom::VirtualKeyboard>()) { + std::move(callback).Run(api::virtual_keyboard::FeatureRestrictions()); + return; + } + + auto restrictions_mojo = crosapi::mojom::VirtualKeyboardRestrictions::New(); + if (restrictions.auto_complete_enabled) { + UpdateRestriction(crosapi::mojom::VirtualKeyboardFeature::AUTOCOMPLETE, + *restrictions.auto_complete_enabled, + restrictions_mojo.get()); + } + if (restrictions.auto_correct_enabled) { + UpdateRestriction(crosapi::mojom::VirtualKeyboardFeature::AUTOCORRECT, + *restrictions.auto_correct_enabled, + restrictions_mojo.get()); + } + if (restrictions.handwriting_enabled) { + UpdateRestriction(crosapi::mojom::VirtualKeyboardFeature::HANDWRITING, + *restrictions.handwriting_enabled, + restrictions_mojo.get()); + } + if (restrictions.spell_check_enabled) { + UpdateRestriction(crosapi::mojom::VirtualKeyboardFeature::SPELL_CHECK, + *restrictions.spell_check_enabled, + restrictions_mojo.get()); + } + if (restrictions.voice_input_enabled) { + UpdateRestriction(crosapi::mojom::VirtualKeyboardFeature::VOICE_INPUT, + *restrictions.voice_input_enabled, + restrictions_mojo.get()); + } + + lacros_service->GetRemote<crosapi::mojom::VirtualKeyboard>() + ->RestrictFeatures( + std::move(restrictions_mojo), + base::BindOnce( + &LacrosVirtualKeyboardDelegate::ParseRestrictFeaturesResult, + weak_factory_.GetWeakPtr(), std::move(callback))); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h new file mode 100644 index 00000000000..9e55511c897 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h @@ -0,0 +1,76 @@ +// Copyright 2022 The Chromium Authors. All 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_VIRTUAL_KEYBOARD_PRIVATE_LACROS_VIRTUAL_KEYBOARD_DELEGATE_H_ +#define CHROME_BROWSER_EXTENSIONS_API_VIRTUAL_KEYBOARD_PRIVATE_LACROS_VIRTUAL_KEYBOARD_DELEGATE_H_ + +#include <string> + +#include "base/memory/weak_ptr.h" +#include "chromeos/crosapi/mojom/virtual_keyboard.mojom.h" +#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" +#include "extensions/common/api/virtual_keyboard.h" + +namespace extensions { + +// The virtual keyboard api delegate for lacros browser, it handles virtual +// keyboar api request from lacros browser extensions. Currently it only +// supports RestrictFeatures requests, all other apis are unimplemented. +class LacrosVirtualKeyboardDelegate : public VirtualKeyboardDelegate { + public: + LacrosVirtualKeyboardDelegate(); + + LacrosVirtualKeyboardDelegate(const LacrosVirtualKeyboardDelegate&) = delete; + LacrosVirtualKeyboardDelegate& operator=( + const LacrosVirtualKeyboardDelegate&) = delete; + + ~LacrosVirtualKeyboardDelegate() override; + + private: + // VirtualKeyboardDelegate impl: + void GetKeyboardConfig( + OnKeyboardSettingsCallback on_settings_callback) override; + void OnKeyboardConfigChanged() override; + bool HideKeyboard() override; + bool InsertText(const std::u16string& text) override; + bool OnKeyboardLoaded() override; + void SetHotrodKeyboard(bool enable) override; + bool LockKeyboard(bool state) override; + bool SendKeyEvent(const std::string& type, + int char_value, + int key_code, + const std::string& key_name, + int modifiers) override; + bool ShowLanguageSettings() override; + bool ShowSuggestionSettings() override; + bool IsSettingsEnabled() override; + bool SetVirtualKeyboardMode(int mode_enum, + gfx::Rect target_bounds, + OnSetModeCallback on_set_mode_callback) override; + bool SetDraggableArea( + const api::virtual_keyboard_private::Bounds& rect) override; + bool SetRequestedKeyboardState(int state_enum) override; + bool SetOccludedBounds(const std::vector<gfx::Rect>& bounds) override; + bool SetHitTestBounds(const std::vector<gfx::Rect>& bounds) override; + bool SetAreaToRemainOnScreen(const gfx::Rect& bounds) override; + bool SetWindowBoundsInScreen(const gfx::Rect& bounds_in_screen) override; + void GetClipboardHistory( + const std::set<std::string>& item_ids_filter, + OnGetClipboardHistoryCallback get_history_callback) override; + bool PasteClipboardItem(const std::string& clipboard_item_id) override; + bool DeleteClipboardItem(const std::string& clipboard_item_id) override; + void RestrictFeatures( + const api::virtual_keyboard::RestrictFeatures::Params& params, + OnRestrictFeaturesCallback callback) override; + + void ParseRestrictFeaturesResult( + OnRestrictFeaturesCallback callback, + crosapi::mojom::VirtualKeyboardRestrictionsPtr update); + + base::WeakPtrFactory<LacrosVirtualKeyboardDelegate> weak_factory_{this}; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_VIRTUAL_KEYBOARD_PRIVATE_LACROS_VIRTUAL_KEYBOARD_DELEGATE_H_ diff --git a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc index 0ad00574eb3..304c58205ee 100644 --- a/chromium/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc +++ b/chromium/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc @@ -77,7 +77,13 @@ class VirtualKeyboardPrivateApiTest : public extensions::ExtensionApiTest { } }; -IN_PROC_BROWSER_TEST_F(VirtualKeyboardPrivateApiTest, Multipaste) { +// TODO(crbug.com/1352320): Flaky on release bots. +#if defined(NDEBUG) +#define MAYBE_Multipaste DISABLED_Multipaste +#else +#define MAYBE_Multipaste Multipaste +#endif +IN_PROC_BROWSER_TEST_F(VirtualKeyboardPrivateApiTest, MAYBE_Multipaste) { // Copy to the clipboard an item of each display format type. CopyHtmlItem(); CopyTextItem(); 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 1c2051ad1b0..91c2cfad9ca 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 @@ -15,8 +15,8 @@ #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/common/extensions/api/vpn_provider.h" +#include "chromeos/ash/components/network/shill_property_handler.h" #include "chromeos/crosapi/mojom/vpn_service.mojom-test-utils.h" -#include "chromeos/network/shill_property_handler.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/pepper_vpn_provider_resource_host_proxy.h" #include "content/public/browser/vpn_service_proxy.h" @@ -33,10 +33,10 @@ #include "chrome/browser/ash/crosapi/crosapi_manager.h" #include "chrome/browser/ash/crosapi/vpn_service_ash.h" #include "chrome/browser/ash/profiles/profile_helper.h" -#include "chromeos/dbus/shill/fake_shill_third_party_vpn_driver_client.h" -#include "chromeos/dbus/shill/shill_manager_client.h" -#include "chromeos/dbus/shill/shill_profile_client.h" -#include "chromeos/network/network_profile_handler.h" +#include "chromeos/ash/components/dbus/shill/fake_shill_third_party_vpn_driver_client.h" +#include "chromeos/ash/components/dbus/shill/shill_manager_client.h" +#include "chromeos/ash/components/dbus/shill/shill_profile_client.h" +#include "chromeos/ash/components/network/network_profile_handler.h" #include "third_party/cros_system_api/dbus/service_constants.h" #endif @@ -89,7 +89,7 @@ void DoNothingSuccessCallback(const std::string& service_path, // Records the number of calls and their parameters. Always replies successfully // to calls. class TestShillThirdPartyVpnDriverClient - : public FakeShillThirdPartyVpnDriverClient { + : public ash::FakeShillThirdPartyVpnDriverClient { public: void SetParameters(const std::string& object_path_value, const base::Value& parameters, @@ -309,7 +309,7 @@ class VpnProviderApiTestAsh : public VpnProviderApiTestBase { bool HasService(const std::string& service_path) const { std::string profile_path; base::Value properties = - ShillProfileClient::Get()->GetTestInterface()->GetService( + ash::ShillProfileClient::Get()->GetTestInterface()->GetService( service_path, &profile_path); return properties.is_dict(); } @@ -325,15 +325,15 @@ class VpnProviderApiTestAsh : public VpnProviderApiTestBase { } void ClearNetworkProfiles() { - ShillProfileClient::Get()->GetTestInterface()->ClearProfiles(); + ash::ShillProfileClient::Get()->GetTestInterface()->ClearProfiles(); // ShillProfileClient doesn't notify NetworkProfileHandler that profiles got // cleared, therefore we have to call ShillManagerClient explicitly. - ShillManagerClient::Get()->GetTestInterface()->ClearProfiles(); + ash::ShillManagerClient::Get()->GetTestInterface()->ClearProfiles(); } protected: void AddNetworkProfileForUser() { - ShillProfileClient::Get()->GetTestInterface()->AddProfile( + ash::ShillProfileClient::Get()->GetTestInterface()->AddProfile( kNetworkProfilePath, ash::ProfileHelper::GetUserIdHashFromProfile(profile())); content::RunAllPendingInMessageLoop(); diff --git a/chromium/chrome/browser/extensions/api/web_accessible_resources_apitest.cc b/chromium/chrome/browser/extensions/api/web_accessible_resources_apitest.cc new file mode 100644 index 00000000000..e66010d437c --- /dev/null +++ b/chromium/chrome/browser/extensions/api/web_accessible_resources_apitest.cc @@ -0,0 +1,355 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/version_info/channel.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "extensions/browser/background_script_executor.h" +#include "extensions/common/extension_features.h" +#include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" +#include "net/base/filename_util.h" +#include "net/dns/mock_host_resolver.h" + +namespace extensions { +namespace { + +class WebAccessibleResourcesApiTest : public ExtensionApiTest { + public: + void SetUpOnMainThread() override { + ExtensionApiTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + ASSERT_TRUE(StartEmbeddedTestServer()); + } +}; + +// Fetch web accessible resources directly from a file:// page. +IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesApiTest, + FileSchemeInitiators_MainWorld) { + // Load extension. + TestExtensionDir extension_dir; + const char* kManifestStub = R"({ + "name": "Test", + "version": "0.1", + "manifest_version": 3, + "web_accessible_resources": [ + { + "resources": [ "ok_0.html" ], + "matches": [ "file://*/*" ] + }, + { + "resources": [ "ok_1.html" ], + "matches": [ "<all_urls>" ] + }, + { + "resources": [ "no_0.html" ], + "matches": [ "http://*.example.com/*" ] + }, + { + "resources": [ "no_1.html" ], + "matches": [ "*://*/*" ] + } + ] + })"; + extension_dir.WriteManifest(kManifestStub); + extension_dir.WriteFile(FILE_PATH_LITERAL("ok_0.html"), "ok_0.html"); + extension_dir.WriteFile(FILE_PATH_LITERAL("ok_1.html"), "ok_1.html"); + extension_dir.WriteFile(FILE_PATH_LITERAL("no_0.html"), "no_0.html"); + extension_dir.WriteFile(FILE_PATH_LITERAL("no_1.html"), "no_1.html"); + const Extension* extension = + LoadExtension(extension_dir.UnpackedPath(), {.allow_file_access = true}); + + // Navigate to extension's index.html via file:// and test. + base::FilePath test_page; + ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_page)); + test_page = test_page.AppendASCII("simple.html"); + GURL gurl = net::FilePathToFileURL(test_page); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); + auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); + static constexpr char kScriptTemplate[] = R"( + // Verify that web accessible resource can be fetched. + async function run(expectOk, filename) { + return new Promise(async resolve => { + const url = `chrome-extension://%s/${filename}`; + + // Fetch and verify the contents of fetched web accessible resources. + const verifyFetch = (actual) => { + if (expectOk == (filename == actual)) { + resolve(); + } else { + reject(`Unexpected result. File: ${filename}. Found: ${actual}`); + } + }; + fetch(url) + .then(result => result.text()) + .catch(error => verifyFetch(error)) + .then(text => verifyFetch(text)); + }); + } + + // Run tests. + const testCases = [ + [true, 'ok_0.html'], + [true, 'ok_1.html'], + [false, 'no_0.html'], + [false, 'no_1.html'] + ]; + const tests = testCases.map(testCase => run(...testCase)); + Promise.all(tests).then(response => true); + )"; + std::string script = + base::StringPrintf(kScriptTemplate, extension->id().c_str()); + ASSERT_TRUE(content::EvalJs(web_contents, script).ExtractBool()); +} + +// Test loading of subresources using an initiator coming from a file:// scheme, +// and, notably, from within a content script context. +IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesApiTest, + FileSchemeInitiators_ContentScript) { + // Load extension. + TestExtensionDir test_dir; + const char* kManifestStub = R"({ + "name": "Test", + "version": "0.1", + "manifest_version": 3, + "background": {"service_worker": "service_worker.js"}, + "host_permissions": ["file:///*"], + "permissions": ["scripting"], + "web_accessible_resources": [ + { + "resources": [ "ok_0.html" ], + "matches": [ "file://*/*" ] + }, + { + "resources": [ "ok_1.html" ], + "matches": [ "<all_urls>" ] + }, + { + "resources": [ "no_0.html" ], + "matches": [ "http://*.example.com/*" ] + }, + { + "resources": [ "no_1.html" ], + "matches": [ "*://*/*" ] + } + ] + })"; + test_dir.WriteManifest(kManifestStub); + test_dir.WriteFile(FILE_PATH_LITERAL("ok_0.html"), "ok_0.html"); + test_dir.WriteFile(FILE_PATH_LITERAL("ok_1.html"), "ok_1.html"); + test_dir.WriteFile(FILE_PATH_LITERAL("no_0.html"), "no_0.html"); + test_dir.WriteFile(FILE_PATH_LITERAL("no_1.html"), "no_1.html"); + test_dir.WriteFile(FILE_PATH_LITERAL("service_worker.js"), ""); + const char* kTestJs = R"( + // Verify that web accessible resource can be fetched. + async function run(expectOk, filename) { + return new Promise(async resolve => { + const url = chrome.runtime.getURL(filename); + + // Fetch and verify the contents of fetched web accessible resources. + const verifyFetch = (actual) => { + chrome.test.assertEq(expectOk, filename == actual); + resolve(); + }; + fetch(url) + .then(result => result.text()) + .catch(error => verifyFetch(error)) + .then(text => verifyFetch(text)); + }); + } + + // Run tests. + const testCases = [ + [true, 'ok_0.html'], + [true, 'ok_1.html'], + [false, 'no_0.html'], + [false, 'no_1.html'] + ]; + const tests = testCases.map(testCase => run(...testCase)); + Promise.all(tests).then(() => chrome.test.succeed()); + )"; + test_dir.WriteFile(FILE_PATH_LITERAL("test.js"), kTestJs); + const Extension* extension = + LoadExtension(test_dir.UnpackedPath(), {.allow_file_access = true}); + + // Navigate to extension's index.html via file:// and test. + ResultCatcher catcher; + base::FilePath test_page; + base::PathService::Get(chrome::DIR_TEST_DATA, &test_page); + test_page = test_page.AppendASCII("simple.html"); + GURL gurl = net::FilePathToFileURL(test_page); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); + auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); + const int tab_id = ExtensionTabUtil::GetTabId(web_contents); + std::string script = base::StringPrintf( + R"( + (async () => { + await chrome.scripting.executeScript( + {target: {tabId: %d}, files: ['test.js']}) + })(); + )", + tab_id); + BackgroundScriptExecutor::ExecuteScriptAsync( + profile(), extension->id(), base::StringPrintf(script.c_str(), tab_id)); + ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +class WebAccessibleResourcesDynamicUrlApiTest : public ExtensionApiTest { + public: + WebAccessibleResourcesDynamicUrlApiTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionDynamicURLRedirection); + } + + void SetUpOnMainThread() override { + ExtensionApiTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + ASSERT_TRUE(StartEmbeddedTestServer()); + } + + protected: + const Extension* GetExtension(const char* manifest_piece) { + // manifest.json. + const char* kManifestStub = R"({ + "name": "Test", + "version": "1.0", + "manifest_version": 3, + "host_permissions": ["<all_urls>"], + "web_accessible_resources": [ + { + "resources": ["dynamic_web_accessible_resource.html"], + "matches": ["<all_urls>"], + "use_dynamic_url": true + }, + { + "resources": ["web_accessible_resource.html"], + "matches": ["<all_urls>"] + } + ], + %s + })"; + auto kManifest = base::StringPrintf(kManifestStub, manifest_piece); + test_dir_.WriteManifest(kManifest); + + // content.js + const char* kTestScript = R"( + // Verify that web accessible resource can be fetched. + async function run(expectOk, filename, identifier) { + return new Promise(async resolve => { + // Verify URL. + let expected = chrome.runtime.getURL(filename); + let url = `chrome-extension://${identifier}/${filename}`; + chrome.test.assertEq(expectOk, expected == url); + + // Verify contents of fetched web accessible resource. + const verify = (actual) => { + chrome.test.assertEq(expectOk, filename == actual); + resolve(); + }; + + // Fetch web accessible resource. + fetch(url) + .then(result => result.text()) + .catch(error => verify(error)) + .then(text => verify(text)); + }); + } + + // Verify that identifiers don't match. + const id = chrome.runtime.id; + const dynamicId = chrome.runtime.dynamicId; + chrome.test.assertTrue(id != dynamicId); + + // Run tests. + const testCases = [ + [true, 'dynamic_web_accessible_resource.html', dynamicId], + [true, 'web_accessible_resource.html', id], + [false, 'web_accessible_resource.html', 'error'], + [false, 'dynamic_web_accessible_resource.html', 'error'], + ]; + const tests = testCases.map(testCase => run(...testCase)); + Promise.all(tests).then(() => chrome.test.succeed()); + )"; + + // Write files and load extension. + WriteFile(FILE_PATH_LITERAL("content.js"), kTestScript); + WriteFile(FILE_PATH_LITERAL("dynamic_web_accessible_resource.html"), + "dynamic_web_accessible_resource.html"); + WriteFile(FILE_PATH_LITERAL("web_accessible_resource.html"), + "web_accessible_resource.html"); + const Extension* extension = LoadExtension(test_dir_.UnpackedPath()); + return extension; + } + + // Write file to extension directory. + void WriteFile(const base::FilePath::CharType* filename, + const char* contents) { + test_dir_.WriteFile(filename, contents); + } + + private: + base::test::ScopedFeatureList feature_list_; + ScopedCurrentChannel current_channel_{version_info::Channel::CANARY}; + TestExtensionDir test_dir_; +}; + +// Load dynamic web accessible resource from a content script. +IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesDynamicUrlApiTest, ContentScript) { + const char* kManifest = R"( + "content_scripts": [ + { + "matches": ["<all_urls>"], + "js": ["content.js"], + "run_at": "document_start" + } + ] + )"; + const Extension* extension = GetExtension(kManifest); + ASSERT_TRUE(extension); + + ResultCatcher catcher; + GURL gurl = embedded_test_server()->GetURL("example.com", "/empty.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); + ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +// Load dynamic web accessible resources via chrome.scripting.executeScript(). +IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesDynamicUrlApiTest, ExecuteScript) { + // Load extension. + WriteFile(FILE_PATH_LITERAL("worker.js"), "// Intentionally blank."); + const char* kManifest = R"( + "permissions": ["scripting"], + "background": {"service_worker": "worker.js"} + )"; + const Extension* extension = GetExtension(kManifest); + ASSERT_TRUE(extension); + + // Navigate to a non extension page and test. + ResultCatcher catcher; + GURL gurl = embedded_test_server()->GetURL("example.com", "/empty.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl)); + auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); + const int tab_id = ExtensionTabUtil::GetTabId(web_contents); + std::string script = base::StringPrintf( + R"( + (async () => { + await chrome.scripting.executeScript( + {target: {tabId: %d}, files: ['content.js']}) + })(); + )", + tab_id); + BackgroundScriptExecutor::ExecuteScriptAsync( + profile(), extension->id(), base::StringPrintf(script.c_str(), tab_id)); + ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +} // namespace +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/web_authentication_proxy/value_conversions.cc b/chromium/chrome/browser/extensions/api/web_authentication_proxy/value_conversions.cc index f8e99cbdd5a..444eb5758d8 100644 --- a/chromium/chrome/browser/extensions/api/web_authentication_proxy/value_conversions.cc +++ b/chromium/chrome/browser/extensions/api/web_authentication_proxy/value_conversions.cc @@ -92,52 +92,52 @@ std::vector<uint8_t> ToByteVector(const std::string& in) { } base::Value ToValue(const device::PublicKeyCredentialRpEntity& relying_party) { - base::Value value(base::Value::Type::DICTIONARY); - value.SetStringKey("id", relying_party.id); + base::Value::Dict value; + value.Set("id", relying_party.id); // `PublicKeyCredentialEntity.name` is required in the IDL but optional on the // mojo struct. - value.SetKey("name", base::Value(relying_party.name.value_or(""))); - return value; + value.Set("name", relying_party.name.value_or("")); + return base::Value(std::move(value)); } base::Value ToValue(const device::PublicKeyCredentialUserEntity& user) { - base::Value value(base::Value::Type::DICTIONARY); - value.SetStringKey("id", Base64UrlEncode(user.id)); + base::Value::Dict value; + value.Set("id", Base64UrlEncode(user.id)); // `PublicKeyCredentialEntity.name` is required in the IDL but optional on the // mojo struct. - value.SetKey("name", base::Value(user.name.value_or(""))); + value.Set("name", user.name.value_or("")); if (user.display_name) { - value.SetKey("displayName", base::Value(*user.display_name)); + value.Set("displayName", *user.display_name); } - return value; + return base::Value(std::move(value)); } base::Value ToValue( const device::PublicKeyCredentialParams::CredentialInfo& params) { - base::Value value(base::Value::Type::DICTIONARY); + base::Value::Dict value; switch (params.type) { case device::CredentialType::kPublicKey: - value.SetKey("type", base::Value(device::kPublicKey)); + value.Set("type", device::kPublicKey); } - value.SetIntKey("alg", params.algorithm); - return value; + value.Set("alg", params.algorithm); + return base::Value(std::move(value)); } base::Value ToValue(const device::PublicKeyCredentialDescriptor& descriptor) { - base::Value value(base::Value::Type::DICTIONARY); + base::Value::Dict value; switch (descriptor.credential_type) { case device::CredentialType::kPublicKey: - value.SetKey("type", base::Value(device::kPublicKey)); + value.Set("type", device::kPublicKey); } - value.SetStringKey("id", Base64UrlEncode(descriptor.id)); - std::vector<base::Value> transports; + value.Set("id", Base64UrlEncode(descriptor.id)); + base::Value::List transports; for (const device::FidoTransportProtocol& transport : descriptor.transports) { - transports.emplace_back(base::Value(ToString(transport))); + transports.Append(ToString(transport)); } if (!transports.empty()) { - value.SetKey("transports", base::Value(std::move(transports))); + value.Set("transports", std::move(transports)); } - return value; + return base::Value(std::move(value)); } base::Value ToValue( @@ -180,17 +180,17 @@ base::Value ToValue( base::Value ToValue( const device::AuthenticatorSelectionCriteria& authenticator_selection) { - base::Value value(base::Value::Type::DICTIONARY); + base::Value::Dict value; absl::optional<std::string> attachment; if (authenticator_selection.authenticator_attachment != device::AuthenticatorAttachment::kAny) { - value.SetKey("authenticatorAttachment", - ToValue(authenticator_selection.authenticator_attachment)); + value.Set("authenticatorAttachment", + ToValue(authenticator_selection.authenticator_attachment)); } - value.SetKey("residentKey", ToValue(authenticator_selection.resident_key)); - value.SetKey("userVerification", - ToValue(authenticator_selection.user_verification_requirement)); - return value; + value.Set("residentKey", ToValue(authenticator_selection.resident_key)); + value.Set("userVerification", + ToValue(authenticator_selection.user_verification_requirement)); + return base::Value(std::move(value)); } base::Value ToValue(const device::AttestationConveyancePreference& @@ -211,12 +211,11 @@ base::Value ToValue(const device::AttestationConveyancePreference& base::Value ToValue(const blink::mojom::RemoteDesktopClientOverride& remote_desktop_client_override) { - base::Value value(base::Value::Type::DICTIONARY); - value.SetStringKey("origin", - remote_desktop_client_override.origin.Serialize()); - value.SetBoolKey("sameOriginWithAncestors", - remote_desktop_client_override.same_origin_with_ancestors); - return value; + base::Value::Dict value; + value.Set("origin", remote_desktop_client_override.origin.Serialize()); + value.Set("sameOriginWithAncestors", + remote_desktop_client_override.same_origin_with_ancestors); + return base::Value(std::move(value)); } base::Value ToValue(const blink::mojom::ProtectionPolicy policy) { @@ -246,35 +245,30 @@ base::Value ToValue(const device::LargeBlobSupport large_blob) { } base::Value ToValue(const device::CableDiscoveryData& cable_authentication) { - base::Value value(base::Value::Type::DICTIONARY); + base::Value::Dict value; switch (cable_authentication.version) { case device::CableDiscoveryData::Version::INVALID: NOTREACHED(); break; case device::CableDiscoveryData::Version::V1: - value.SetKey("version", base::Value(1)); - value.SetKey( - "clientEid", - base::Value(Base64UrlEncode(cable_authentication.v1->client_eid))); - value.SetKey("authenticatorEid", - base::Value(Base64UrlEncode( - cable_authentication.v1->authenticator_eid))); - value.SetKey("sessionPreKey", - base::Value(Base64UrlEncode( - cable_authentication.v1->session_pre_key))); + value.Set("version", 1); + value.Set("clientEid", + Base64UrlEncode(cable_authentication.v1->client_eid)); + value.Set("authenticatorEid", + Base64UrlEncode(cable_authentication.v1->authenticator_eid)); + value.Set("sessionPreKey", + Base64UrlEncode(cable_authentication.v1->session_pre_key)); break; case device::CableDiscoveryData::Version::V2: - value.SetKey("version", base::Value(2)); - value.SetKey( - "clientEid", - base::Value(Base64UrlEncode(cable_authentication.v2->experiments))); - value.SetKey("authenticatorEid", base::Value("")); - value.SetKey("sessionPreKey", - base::Value(Base64UrlEncode( - cable_authentication.v2->server_link_data))); + value.Set("version", 2); + value.Set("clientEid", + Base64UrlEncode(cable_authentication.v2->experiments)); + value.Set("authenticatorEid", ""); + value.Set("sessionPreKey", + Base64UrlEncode(cable_authentication.v2->server_link_data)); break; } - return value; + return base::Value(std::move(value)); } absl::optional<device::FidoTransportProtocol> FidoTransportProtocolFromValue( @@ -308,32 +302,30 @@ NullableAuthenticatorAttachmentFromValue(const base::Value& value) { base::Value ToValue( const blink::mojom::PublicKeyCredentialCreationOptionsPtr& options) { - base::Value value(base::Value::Type::DICTIONARY); - value.SetKey("rp", ToValue(options->relying_party)); - value.SetKey("user", ToValue(options->user)); - value.SetStringKey("challenge", Base64UrlEncode(options->challenge)); - std::vector<base::Value> public_key_parameters; + base::Value::Dict value; + value.Set("rp", ToValue(options->relying_party)); + value.Set("user", ToValue(options->user)); + value.Set("challenge", Base64UrlEncode(options->challenge)); + base::Value::List public_key_parameters; for (const device::PublicKeyCredentialParams::CredentialInfo& params : options->public_key_parameters) { - public_key_parameters.push_back(ToValue(params)); + public_key_parameters.Append(ToValue(params)); } - value.SetKey("pubKeyCredParams", - base::Value(std::move(public_key_parameters))); - std::vector<base::Value> exclude_credentials; + value.Set("pubKeyCredParams", std::move(public_key_parameters)); + base::Value::List exclude_credentials; for (const device::PublicKeyCredentialDescriptor& descriptor : options->exclude_credentials) { - exclude_credentials.push_back(ToValue(descriptor)); + exclude_credentials.Append(ToValue(descriptor)); } - value.SetKey("excludeCredentials", - base::Value(std::move(exclude_credentials))); + value.Set("excludeCredentials", std::move(exclude_credentials)); if (options->authenticator_selection) { - value.SetKey("authenticatorSelection", - ToValue(*options->authenticator_selection)); + value.Set("authenticatorSelection", + ToValue(*options->authenticator_selection)); } - value.SetKey("attestation", ToValue(options->attestation)); + value.Set("attestation", ToValue(options->attestation)); base::Value::Dict& extensions = - value.GetDict().Set("extensions", base::Value::Dict())->GetDict(); + value.Set("extensions", base::Value::Dict())->GetDict(); if (options->hmac_create_secret) { extensions.Set("hmacCreateSecret", true); @@ -344,21 +336,20 @@ base::Value ToValue( extensions.Set("credentialProtectionPolicy", ToValue(options->protection_policy)); extensions.Set("enforceCredentialProtectionPolicy", - base::Value(options->enforce_protection_policy)); + options->enforce_protection_policy); } if (options->appid_exclude) { - extensions.Set("appIdExclude", base::Value(*options->appid_exclude)); + extensions.Set("appIdExclude", *options->appid_exclude); } if (options->cred_props) { - extensions.Set("credProps", base::Value(true)); + extensions.Set("credProps", true); } if (options->large_blob_enable != device::LargeBlobSupport::kNotRequested) { - base::Value large_blob_value(base::Value::Type::DICTIONARY); - large_blob_value.GetDict().Set("support", - ToValue(options->large_blob_enable)); + base::Value::Dict large_blob_value; + large_blob_value.Set("support", ToValue(options->large_blob_enable)); extensions.Set("largeBlob", std::move(large_blob_value)); } @@ -369,11 +360,11 @@ base::Value ToValue( } if (options->google_legacy_app_id_support) { - extensions.Set("googleLegacyAppidSupport", base::Value(true)); + extensions.Set("googleLegacyAppidSupport", true); } if (options->min_pin_length_requested) { - extensions.Set("minPinLength", base::Value(true)); + extensions.Set("minPinLength", true); } if (options->remote_desktop_client_override) { @@ -383,39 +374,38 @@ base::Value ToValue( DCHECK(!options->prf_enable); - return value; + return base::Value(std::move(value)); } base::Value ToValue( const blink::mojom::PublicKeyCredentialRequestOptionsPtr& options) { - base::Value value(base::Value::Type::DICTIONARY); - value.SetStringKey("challenge", Base64UrlEncode(options->challenge)); - value.SetStringKey("rpId", options->relying_party_id); + base::Value::Dict value; + value.Set("challenge", Base64UrlEncode(options->challenge)); + value.Set("rpId", options->relying_party_id); - std::vector<base::Value> allow_credentials; + base::Value::List allow_credentials; for (const device::PublicKeyCredentialDescriptor& descriptor : options->allow_credentials) { - allow_credentials.push_back(ToValue(descriptor)); + allow_credentials.Append(ToValue(descriptor)); } - value.SetKey("allowCredentials", base::Value(std::move(allow_credentials))); + value.Set("allowCredentials", std::move(allow_credentials)); - value.SetKey("userVerification", ToValue(options->user_verification)); + value.Set("userVerification", ToValue(options->user_verification)); base::Value::Dict& extensions = - value.GetDict().Set("extensions", base::Value::Dict())->GetDict(); + value.Set("extensions", base::Value::Dict())->GetDict(); if (options->appid) { - extensions.Set("appid", base::Value(*options->appid)); + extensions.Set("appid", *options->appid); } - std::vector<base::Value> cable_authentication_data; + base::Value::List cable_authentication_data; for (const device::CableDiscoveryData& cable : options->cable_authentication_data) { - cable_authentication_data.push_back(ToValue(cable)); + cable_authentication_data.Append(ToValue(cable)); } if (!cable_authentication_data.empty()) { - extensions.Set("cableAuthentication", - base::Value(std::move(cable_authentication_data))); + extensions.Set("cableAuthentication", std::move(cable_authentication_data)); } if (options->get_cred_blob) { @@ -423,13 +413,13 @@ base::Value ToValue( } if (options->large_blob_read || options->large_blob_write) { - base::Value large_blob_value(base::Value::Type::DICTIONARY); + base::Value::Dict large_blob_value; if (options->large_blob_read) { - large_blob_value.GetDict().Set({"read"}, base::Value(true)); + large_blob_value.Set("read", true); } if (options->large_blob_write) { - large_blob_value.GetDict().Set( - {"write"}, base::Value(Base64UrlEncode(*options->large_blob_write))); + large_blob_value.Set("write", + Base64UrlEncode(*options->large_blob_write)); } extensions.Set("largeBlob", std::move(large_blob_value)); } @@ -441,7 +431,7 @@ base::Value ToValue( DCHECK(!options->prf); - return value; + return base::Value(std::move(value)); } std::pair<blink::mojom::MakeCredentialAuthenticatorResponsePtr, std::string> diff --git a/chromium/chrome/browser/extensions/api/web_authentication_proxy/web_authentication_proxy_service.cc b/chromium/chrome/browser/extensions/api/web_authentication_proxy/web_authentication_proxy_service.cc index 9beda38b103..50f212b301d 100644 --- a/chromium/chrome/browser/extensions/api/web_authentication_proxy/web_authentication_proxy_service.cc +++ b/chromium/chrome/browser/extensions/api/web_authentication_proxy/web_authentication_proxy_service.cc @@ -214,13 +214,13 @@ void WebAuthenticationProxyService::OnParseCreateResponse( RequestId request_id, data_decoder::DataDecoder::ValueOrError value_or_error) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (value_or_error.error) { + if (!value_or_error.has_value()) { std::move(respond_callback) - .Run("Parsing responseJson failed: " + *value_or_error.error); + .Run("Parsing responseJson failed: " + value_or_error.error()); return; } auto [response, error] = - webauthn_proxy::MakeCredentialResponseFromValue(*value_or_error.value); + webauthn_proxy::MakeCredentialResponseFromValue(*value_or_error); if (!response) { std::move(respond_callback).Run("Invalid responseJson: " + error); return; @@ -245,13 +245,13 @@ void WebAuthenticationProxyService::OnParseGetResponse( RequestId request_id, data_decoder::DataDecoder::ValueOrError value_or_error) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (value_or_error.error) { + if (!value_or_error.has_value()) { std::move(respond_callback) - .Run("Parsing responseJson failed: " + *value_or_error.error); + .Run("Parsing responseJson failed: " + value_or_error.error()); return; } auto [response, error] = - webauthn_proxy::GetAssertionResponseFromValue(*value_or_error.value); + webauthn_proxy::GetAssertionResponseFromValue(*value_or_error); if (!response) { std::move(respond_callback).Run("Invalid responseJson: " + error); return; diff --git a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc index d1c95bb1c4c..b492b94bff2 100644 --- a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc +++ b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc @@ -542,9 +542,9 @@ ExtensionFunction::ResponseAction WebNavigationGetFrameFunction::Run() { ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } frame_details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(render_frame_host)); + ExtensionApiFrameIdMap::GetFrameType(render_frame_host); frame_details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(render_frame_host)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(render_frame_host); return RespondNow(ArgumentList(GetFrame::Results::Create(frame_details))); } @@ -565,10 +565,9 @@ ExtensionFunction::ResponseAction WebNavigationGetAllFramesFunction::Run() { std::vector<GetAllFrames::Results::DetailsType> result_list; - // We only iterate the frames in the active page. We currently do not - // expose back/forward cached frames or prerender frames in the GetAllFrames - // API. - web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost( + // We currently do not expose back/forward cached frames in the GetAllFrames + // API, but we do explicitly include prerendered frames. + web_contents->ForEachRenderFrameHost( base::BindRepeating( [](content::WebContents* web_contents, std::vector<GetAllFrames::Results::DetailsType>& result_list, @@ -588,6 +587,14 @@ ExtensionFunction::ResponseAction WebNavigationGetAllFramesFunction::Run() { return content::RenderFrameHost::FrameIterationAction::kContinue; } + // Skip back/forward cached frames. + if (render_frame_host->IsInLifecycleState( + content::RenderFrameHost::LifecycleState:: + kInBackForwardCache)) { + return content::RenderFrameHost::FrameIterationAction:: + kSkipChildren; + } + GetAllFrames::Results::DetailsType frame; frame.url = navigation_state->GetUrl().spec(); frame.frame_id = @@ -604,11 +611,10 @@ ExtensionFunction::ResponseAction WebNavigationGetAllFramesFunction::Run() { ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host) .ToString()); } - frame.frame_type = ToString( - ExtensionApiFrameIdMap::GetFrameType(render_frame_host)); + frame.frame_type = + ExtensionApiFrameIdMap::GetFrameType(render_frame_host); frame.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle( - render_frame_host)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(render_frame_host); frame.process_id = render_frame_host->GetProcess()->GetID(); frame.error_occurred = navigation_state->GetErrorOccurredInFrame(); result_list.push_back(std::move(frame)); diff --git a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc index 86a4e3e25cf..edba29be7f1 100644 --- a/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc +++ b/chromium/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc @@ -82,10 +82,9 @@ std::unique_ptr<Event> CreateOnBeforeNavigateEvent( details.parent_document_id = std::make_unique<std::string>( ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } - details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(navigation_handle)); + details.frame_type = ExtensionApiFrameIdMap::GetFrameType(navigation_handle); details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(navigation_handle)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(navigation_handle); details.time_stamp = MilliSecondsFromTime(base::Time::Now()); auto event = std::make_unique<Event>( @@ -168,9 +167,8 @@ void DispatchOnCommitted(events::HistogramValue histogram_value, content::BrowserContext* browser_context = navigation_handle->GetWebContents()->GetBrowserContext(); - auto event = std::make_unique<Event>( - histogram_value, event_name, - base::Value(std::move(args)).TakeListDeprecated(), browser_context); + auto event = std::make_unique<Event>(histogram_value, event_name, + std::move(args), browser_context); DispatchEvent(browser_context, std::move(event), url); } @@ -193,10 +191,9 @@ void DispatchOnDOMContentLoaded(content::WebContents* web_contents, details.parent_document_id = std::make_unique<std::string>( ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } - details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(frame_host)); + details.frame_type = ExtensionApiFrameIdMap::GetFrameType(frame_host); details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host); details.time_stamp = MilliSecondsFromTime(base::Time::Now()); content::BrowserContext* browser_context = web_contents->GetBrowserContext(); @@ -226,10 +223,9 @@ void DispatchOnCompleted(content::WebContents* web_contents, details.parent_document_id = std::make_unique<std::string>( ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } - details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(frame_host)); + details.frame_type = ExtensionApiFrameIdMap::GetFrameType(frame_host); details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host); details.time_stamp = MilliSecondsFromTime(base::Time::Now()); content::BrowserContext* browser_context = web_contents->GetBrowserContext(); @@ -298,10 +294,9 @@ void DispatchOnErrorOccurred(content::WebContents* web_contents, details.parent_document_id = std::make_unique<std::string>( ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } - details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(frame_host)); + details.frame_type = ExtensionApiFrameIdMap::GetFrameType(frame_host); details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(frame_host); details.time_stamp = MilliSecondsFromTime(base::Time::Now()); content::BrowserContext* browser_context = web_contents->GetBrowserContext(); @@ -333,10 +328,9 @@ void DispatchOnErrorOccurred(content::NavigationHandle* navigation_handle) { details.parent_document_id = std::make_unique<std::string>( ExtensionApiFrameIdMap::GetDocumentId(parent_frame_host).ToString()); } - details.frame_type = - ToString(ExtensionApiFrameIdMap::GetFrameType(navigation_handle)); + details.frame_type = ExtensionApiFrameIdMap::GetFrameType(navigation_handle); details.document_lifecycle = - ToString(ExtensionApiFrameIdMap::GetDocumentLifecycle(navigation_handle)); + ExtensionApiFrameIdMap::GetDocumentLifecycle(navigation_handle); details.time_stamp = MilliSecondsFromTime(base::Time::Now()); content::BrowserContext* browser_context = 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 8feee120d67..95a246aad4d 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 @@ -45,6 +45,7 @@ #include "content/public/test/download_test_observer.h" #include "content/public/test/fenced_frame_test_util.h" #include "content/public/test/no_renderer_crashes_assertion.h" +#include "content/public/test/prerender_test_util.h" #include "content/public/test/test_utils.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/switches.h" @@ -225,6 +226,10 @@ class WebNavigationApiTest : public ExtensionApiTest { // with deferred commits. command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); } + + content::WebContents* GetWebContents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } }; class WebNavigationApiBackForwardCacheTest : public WebNavigationApiTest { @@ -263,14 +268,48 @@ class WebNavigationApiTestWithContextType } }; +class WebNavigationApiPrerenderTestWithContextType + : public WebNavigationApiTest, + public testing::WithParamInterface<ContextType> { + public: + WebNavigationApiPrerenderTestWithContextType() + : WebNavigationApiTest(GetParam()) {} + ~WebNavigationApiPrerenderTestWithContextType() override = default; + WebNavigationApiPrerenderTestWithContextType( + const WebNavigationApiPrerenderTestWithContextType&) = delete; + WebNavigationApiPrerenderTestWithContextType& operator=( + const WebNavigationApiPrerenderTestWithContextType&) = delete; + + private: + content::test::ScopedPrerenderFeatureList prerender_feature_list_; +}; + IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, Api) { ASSERT_TRUE(RunExtensionTest("webnavigation/api")) << message_; } -IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, GetFrame) { +// TODO(crbug.com/1352957): Flakily timing out on win/mac/cros. +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) +#define MAYBE_GetFrame DISABLED_GetFrame +#else +#define MAYBE_GetFrame GetFrame +#endif +IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, MAYBE_GetFrame) { + ASSERT_TRUE(StartEmbeddedTestServer()); ASSERT_TRUE(RunExtensionTest("webnavigation/getFrame")) << message_; } +IN_PROC_BROWSER_TEST_P(WebNavigationApiPrerenderTestWithContextType, GetFrame) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(RunExtensionTest("webnavigation/getFrame")) << message_; +} + +IN_PROC_BROWSER_TEST_P(WebNavigationApiPrerenderTestWithContextType, + Prerendering) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(RunExtensionTest("webnavigation/prerendering")) << message_; +} + IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, GetFrameIncognito) { ASSERT_TRUE(StartEmbeddedTestServer()); @@ -291,6 +330,12 @@ INSTANTIATE_TEST_SUITE_P(PersistentBackground, INSTANTIATE_TEST_SUITE_P(ServiceWorker, WebNavigationApiTestWithContextType, testing::Values(ContextType::kServiceWorker)); +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + WebNavigationApiPrerenderTestWithContextType, + testing::Values(ContextType::kPersistentBackground)); +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + WebNavigationApiPrerenderTestWithContextType, + testing::Values(ContextType::kServiceWorker)); IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, ClientRedirect) { ASSERT_TRUE(RunExtensionTest("webnavigation/clientRedirect")) << message_; @@ -336,7 +381,7 @@ IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, ASSERT_TRUE(RunExtensionTest("webnavigation/serverRedirectSingleProcess")) << message_; - WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + WebContents* tab = GetWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); ResultCatcher catcher; @@ -408,7 +453,7 @@ IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, UserAction) { // Wait for the extension to set itself up and return control to us. ASSERT_TRUE(RunExtensionTest("webnavigation/userAction")) << message_; - WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + WebContents* tab = GetWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); ResultCatcher catcher; @@ -444,7 +489,7 @@ IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, RequestOpenTab) { // Wait for the extension to set itself up and return control to us. ASSERT_TRUE(RunExtensionTest("webnavigation/requestOpenTab")) << message_; - WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + WebContents* tab = GetWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); ResultCatcher catcher; @@ -483,7 +528,7 @@ IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, TargetBlank) { // Wait for the extension to set itself up and return control to us. ASSERT_TRUE(RunExtensionTest("webnavigation/targetBlank")) << message_; - WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + WebContents* tab = GetWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); ResultCatcher catcher; @@ -638,7 +683,7 @@ IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, Crash) { // Wait for the extension to set itself up and return control to us. ASSERT_TRUE(RunExtensionTest("webnavigation/crash")) << message_; - WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + WebContents* tab = GetWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); ResultCatcher catcher; 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 bfcf2e8896d..84679090272 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 @@ -58,11 +58,6 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom-forward.h" -#if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chromeos/login/login_state/scoped_test_public_session_login_state.h" -#include "components/crx_file/id_util.h" -#endif - namespace helpers = extension_web_request_api_helpers; namespace keys = extension_web_request_api_constants; namespace web_request = extensions::api::web_request; @@ -231,7 +226,6 @@ TEST_F(ExtensionWebRequestTest, BrowserContextShutdown) { // created and destroyed. Unfortunately, that doesn't work with test profiles, // so the test needs to simulate those calls event_router->OnOTRBrowserContextCreated(&profile_, otr_profile); - EXPECT_EQ(2u, event_router->cross_browser_context_map_.size()); EXPECT_EQ(0u, event_router->GetListenerCountForTesting(otr_profile, kEventName)); EXPECT_FALSE(event_router->HasAnyExtraHeadersListenerImpl(otr_profile)); @@ -252,7 +246,6 @@ TEST_F(ExtensionWebRequestTest, BrowserContextShutdown) { // Simulate the OTR being destroyed. event_router->OnOTRBrowserContextDestroyed(&profile_, otr_profile); - EXPECT_EQ(0u, event_router->cross_browser_context_map_.size()); EXPECT_EQ(0u, event_router->GetListenerCountForTesting(otr_profile, kEventName)); EXPECT_FALSE(event_router->HasAnyExtraHeadersListenerImpl(otr_profile)); 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 82e2696922f..361206dfe9a 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 @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <array> #include <memory> #include <utility> #include <vector> @@ -29,6 +30,7 @@ #include "build/chromeos_buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h" #include "chrome/browser/devtools/url_constants.h" #include "chrome/browser/extensions/active_tab_permission_granter.h" #include "chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h" @@ -62,7 +64,6 @@ #include "chrome/common/url_constants.h" #include "chrome/test/base/search_test_utils.h" #include "chrome/test/base/ui_test_utils.h" -#include "chromeos/login/login_state/scoped_test_public_session_login_state.h" #include "components/embedder_support/switches.h" #include "components/google/core/common/google_switches.h" #include "components/policy/core/common/mock_configuration_policy_provider.h" @@ -88,15 +89,19 @@ #include "content/public/common/page_type.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/prerender_test_util.h" #include "content/public/test/simple_url_loader_test_helper.h" +#include "content/public/test/test_navigation_observer.h" #include "content/public/test/url_loader_interceptor.h" #include "content/public/test/url_loader_monitor.h" #include "content/public/test/web_transport_simple_test_server.h" #include "extensions/browser/api/web_request/web_request_api.h" +#include "extensions/browser/background_script_executor.h" #include "extensions/browser/blocked_action_type.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/process_manager.h" +#include "extensions/browser/service_worker/service_worker_test_utils.h" #include "extensions/common/extension_builder.h" #include "extensions/common/extension_features.h" #include "extensions/common/features/feature.h" @@ -131,7 +136,6 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) #include "chrome/browser/ash/profiles/profile_helper.h" -#include "chromeos/login/login_state/login_state.h" #endif // BUILDFLAG(IS_CHROMEOS_ASH) using content::WebContents; @@ -245,23 +249,50 @@ void PerformXhrInFrame(content::RenderFrameHost* frame, EXPECT_TRUE(success); } +base::Value ExecuteScriptAndReturnValue(const ExtensionId& extension_id, + content::BrowserContext* context, + const std::string& script) { + return BackgroundScriptExecutor::ExecuteScript( + context, extension_id, script, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); +} + +absl::optional<bool> ExecuteScriptAndReturnBool( + const ExtensionId& extension_id, + content::BrowserContext* context, + const std::string& script) { + absl::optional<bool> result; + base::Value script_result = + ExecuteScriptAndReturnValue(extension_id, context, script); + if (script_result.is_bool()) + result = script_result.GetBool(); + return result; +} + +absl::optional<std::string> ExecuteScriptAndReturnString( + const ExtensionId& extension_id, + content::BrowserContext* context, + const std::string& script) { + absl::optional<std::string> result; + base::Value script_result = + ExecuteScriptAndReturnValue(extension_id, context, script); + if (script_result.is_string()) + result = script_result.GetString(); + return result; +} + // Returns the current count of a variable stored in the |extension| background // page. Returns -1 if something goes awry. int GetCountFromBackgroundPage(const Extension* extension, content::BrowserContext* context, const std::string& variable_name) { - ExtensionHost* host = - ProcessManager::Get(context)->GetBackgroundHostForExtension( - extension->id()); - if (!host || !host->host_contents()) + const std::string script = base::StringPrintf( + "chrome.test.sendScriptResult(%s)", variable_name.c_str()); + base::Value value = + ExecuteScriptAndReturnValue(extension->id(), context, script); + if (!value.is_int()) return -1; - - int count = -1; - if (!ExecuteScriptAndExtractInt( - host->host_contents(), - "window.domAutomationController.send(" + variable_name + ")", &count)) - return -1; - return count; + return value.GetInt(); } // Returns the current count of webRequests received by the |extension| in @@ -269,8 +300,7 @@ int GetCountFromBackgroundPage(const Extension* extension, // object). Returns -1 if something goes awry. int GetWebRequestCountFromBackgroundPage(const Extension* extension, content::BrowserContext* context) { - return GetCountFromBackgroundPage(extension, context, - "window.webRequestCount"); + return GetCountFromBackgroundPage(extension, context, "self.webRequestCount"); } // Returns true if the |extension|'s background page saw an event for a request @@ -278,22 +308,14 @@ int GetWebRequestCountFromBackgroundPage(const Extension* extension, bool HasSeenWebRequestInBackgroundPage(const Extension* extension, content::BrowserContext* context, const std::string& hostname) { - // TODO(devlin): Here and in Get*CountFromBackgroundPage(), we should leverage - // ExecuteScriptInBackgroundPage(). - ExtensionHost* host = - ProcessManager::Get(context)->GetBackgroundHostForExtension( - extension->id()); - if (!host || !host->host_contents()) - return false; - - bool seen = false; - std::string script = base::StringPrintf( - R"(domAutomationController.send( - window.requestedHostnames.includes('%s'));)", + const std::string script = base::StringPrintf( + R"(chrome.test.sendScriptResult( + self.requestedHostnames.includes('%s'));)", hostname.c_str()); - EXPECT_TRUE( - ExecuteScriptAndExtractBool(host->host_contents(), script, &seen)); - return seen; + base::Value value = + ExecuteScriptAndReturnValue(extension->id(), context, script); + DCHECK(value.is_bool()); + return value.GetBool(); } void WaitForExtraHeadersListener(base::WaitableEvent* event, @@ -311,8 +333,18 @@ void WaitForExtraHeadersListener(base::WaitableEvent* event, } // namespace +using ContextType = ExtensionBrowserTest::ContextType; + class ExtensionWebRequestApiTest : public ExtensionApiTest { public: + explicit ExtensionWebRequestApiTest( + ContextType context_type = ContextType::kFromManifest) + : ExtensionApiTest(context_type) {} + ExtensionWebRequestApiTest(const ExtensionWebRequestApiTest&) = delete; + ExtensionWebRequestApiTest& operator=(const ExtensionWebRequestApiTest&) = + delete; + ~ExtensionWebRequestApiTest() override = default; + void SetUpOnMainThread() override { ExtensionApiTest::SetUpOnMainThread(); host_resolver()->AddRule("*", "127.0.0.1"); @@ -326,12 +358,12 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest { kOriginTrialPublicKeyForTesting); } - void RunPermissionTest( - const char* extension_directory, - bool load_extension_with_incognito_permission, - bool wait_for_extension_loaded_in_incognito, - const char* expected_content_regular_window, - const char* exptected_content_incognito_window); + void RunPermissionTest(const char* extension_directory, + bool load_extension_with_incognito_permission, + bool wait_for_extension_loaded_in_incognito, + const char* expected_content_regular_window, + const char* exptected_content_incognito_window, + ContextType context_type); mojo::PendingRemote<network::mojom::URLLoaderFactory> CreateURLLoaderFactory() { @@ -452,20 +484,199 @@ class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest { IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestApi) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_api.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_api")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestSimple) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_simple.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_simple")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestComplex) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_complex.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_complex")) << message_; +} + +class ExtensionDevToolsProtocolTest + : public ExtensionWebRequestApiTest, + public content::TestDevToolsProtocolClient { + protected: + void Attach() { AttachToWebContents(web_contents()); } + + void TearDownOnMainThread() override { + DetachProtocolClient(); + ExtensionWebRequestApiTest::TearDownOnMainThread(); + } + + content::WebContents* web_contents() { + return browser()->tab_strip_model()->GetWebContentsAt(0); + } +}; + +IN_PROC_BROWSER_TEST_F(ExtensionDevToolsProtocolTest, + HeaderOverriddenByExtension) { + Attach(); + ASSERT_TRUE(embedded_test_server()->Start()); + TestExtensionDir test_dir; + test_dir.WriteManifest(R"({ + "name": "Header Override Test", + "manifest_version": 2, + "version": "0.1", + "background": { "scripts": ["background.js"], "persistent": true }, + "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] + })"); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( + chrome.webRequest.onHeadersReceived.addListener(function(details) { + var headers = details.responseHeaders; + headers.push({name: "extensionHeaderName", + value: "extensionHeaderValue"}); + return {responseHeaders: headers}; + }, + {urls: ['<all_urls>']}, + ['responseHeaders', 'extraHeaders', 'blocking']); + chrome.test.sendMessage('ready'); + )"); + + ExtensionTestMessageListener listener("ready"); + ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath())); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + + SendCommand("Network.enable", base::Value::Dict(), true); + const GURL url( + embedded_test_server()->GetURL("/set-cookie?cookieName=cookieValue")); + ui_test_utils::NavigateToURLWithDisposition( + browser(), url, WindowOpenDisposition::CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + + // Check that `Network.responseReceived` contains the response header added + // by the extension + base::Value::Dict response_received_result = + WaitForNotification("Network.responseReceived", false); + auto* extension_header = response_received_result.FindByDottedPath( + "response.headers.extensionHeaderName"); + ASSERT_TRUE(extension_header); + ASSERT_EQ(*extension_header, "extensionHeaderValue"); + + // Check that the cookie as specified in the original headers has been set + auto* get_all_cookies_result = + SendCommand("Network.getAllCookies", base::Value::Dict(), true); + const base::Value::List* cookies = + get_all_cookies_result->FindList("cookies"); + ASSERT_TRUE(cookies); + ASSERT_EQ(cookies->size(), 1u); + auto* cookie_name = cookies->front().FindStringKey("name"); + ASSERT_TRUE(cookie_name); + ASSERT_EQ(*cookie_name, "cookieName"); + auto* cookie_value = cookies->front().FindStringKey("value"); + ASSERT_TRUE(cookie_value); + ASSERT_EQ(*cookie_value, "cookieValue"); +} + +IN_PROC_BROWSER_TEST_F(ExtensionDevToolsProtocolTest, + HeaderOverrideViaProtocolAllowedByExtension) { + Attach(); + ASSERT_TRUE(embedded_test_server()->Start()); + TestExtensionDir test_dir; + test_dir.WriteManifest(R"({ + "name": "Header Override Test", + "manifest_version": 2, + "version": "0.1", + "background": { "scripts": ["background.js"], "persistent": true }, + "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] + })"); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( + chrome.webRequest.onHeadersReceived.addListener(function(details) { + var headers = details.responseHeaders; + headers.push({name: "extensionHeaderName", + value: "extensionHeaderValue"}); + return {responseHeaders: headers}; + }, + {urls: ['<all_urls>']}, + ['responseHeaders', 'extraHeaders', 'blocking']); + chrome.test.sendMessage('ready'); + )"); + + ExtensionTestMessageListener listener("ready"); + ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath())); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + + SendCommand("Network.enable", base::Value::Dict(), true); + + base::Value::Dict enable_params; + base::Value::List patterns; + base::Value::Dict pattern; + pattern.Set("requestStage", "Response"); + patterns.Append(std::move(pattern)); + enable_params.Set("patterns", std::move(patterns)); + SendCommand("Fetch.enable", std::move(enable_params), true); + + const GURL url( + embedded_test_server()->GetURL("/set-cookie?cookieName=cookieValue")); + ui_test_utils::NavigateToURLWithDisposition( + browser(), url, WindowOpenDisposition::CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + base::Value::Dict request_paused_result = + WaitForNotification("Fetch.requestPaused", true); + std::string* request_id = request_paused_result.FindString("requestId"); + + // Checks that `Fetch.requestPaused` contains the response headers added by + // the extension + base::Value::List* response_headers = + request_paused_result.FindListByDottedPath("responseHeaders"); + auto* header_name = response_headers->back().FindStringKey("name"); + ASSERT_TRUE(header_name); + ASSERT_EQ(*header_name, "extensionHeaderName"); + auto* header_value = response_headers->back().FindStringKey("value"); + ASSERT_TRUE(header_value); + ASSERT_EQ(*header_value, "extensionHeaderValue"); + + // Response headers are replaced by new overrides + base::Value::Dict params; + params.Set("requestId", *request_id); + base::Value::Dict header_1; + header_1.Set("name", "firstName"); + header_1.Set("value", "firstValue"); + base::Value::Dict header_2; + header_2.Set("name", "secondName"); + header_2.Set("value", "secondValue"); + base::Value::List headers; + headers.Append(std::move(header_1)); + headers.Append(std::move(header_2)); + params.Set("responseHeaders", std::move(headers)); + params.Set("responseCode", 200); + params.Set("body", ""); + SendCommand("Fetch.fulfillRequest", std::move(params), false); + + // Check that `Network.responseReceived` contains the response headers as + // specified via `Fetch.fulfillRequest` + base::Value::Dict response_received_result = + WaitForNotification("Network.responseReceived", false); + auto* first_header = + response_received_result.FindByDottedPath("response.headers.firstName"); + ASSERT_TRUE(first_header); + ASSERT_EQ(*first_header, "firstValue"); + auto* second_header = + response_received_result.FindByDottedPath("response.headers.secondName"); + ASSERT_TRUE(second_header); + ASSERT_EQ(*second_header, "secondValue"); + ASSERT_EQ(response_received_result.FindByDottedPath("response.headers") + ->GetDict() + .size(), + 2u); + + // Check that the cookie as specified in the original headers has been set + auto* get_all_cookies_result = + SendCommand("Network.getAllCookies", base::Value::Dict(), true); + const base::Value::List* cookies = + get_all_cookies_result->FindList("cookies"); + ASSERT_TRUE(cookies); + ASSERT_EQ(cookies->size(), 1u); + auto* cookie_name = cookies->front().FindStringKey("name"); + ASSERT_TRUE(cookie_name); + ASSERT_EQ(*cookie_name, "cookieName"); + auto* cookie_value = cookies->front().FindStringKey("value"); + ASSERT_TRUE(cookie_value); + ASSERT_EQ(*cookie_value, "cookieValue"); } // This test times out regularly on ASAN/MSAN trybots. See @@ -473,25 +684,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestComplex) { // TODO(crbug.com/1177120) Re-enable test IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_WebRequestTypes) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_types.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_types")) << message_; } -#if BUILDFLAG(IS_CHROMEOS_ASH) -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestPublicSession) { - ASSERT_TRUE(StartEmbeddedTestServer()); - chromeos::ScopedTestPublicSessionLoginState login_state; - // Disable a CHECK while doing api tests. - WebRequestPermissions::AllowAllExtensionLocationsInPublicSessionForTesting( - true); - ASSERT_TRUE( - RunExtensionTest("webrequest_public_session", {.page_url = "test.html"})) - << message_; - WebRequestPermissions::AllowAllExtensionLocationsInPublicSessionForTesting( - false); -} -#endif // BUILDFLAG(IS_CHROMEOS_ASH) - // Test that a request to an OpenSearch description document (OSDD) generates // an event with the expected details. // Flaky on Windows and Mac: https://crbug.com/1218893 @@ -509,15 +704,13 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestTestOSDD) { search_test_utils::WaitForTemplateURLServiceToLoad( TemplateURLServiceFactory::GetForProfile(profile())); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_osdd.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_osdd")) << message_; } // Test that the webRequest events are dispatched with the expected details when // a frame or tab is removed while a response is being received. -// Flaky: https://crbug.com/617865 IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, - DISABLED_WebRequestUnloadAfterRequest) { + WebRequestUnloadAfterRequest) { ASSERT_TRUE(StartEmbeddedTestServer()); ASSERT_TRUE( RunExtensionTest("webrequest", {.page_url = "test_unload.html?1"})) @@ -561,41 +754,79 @@ class ExtensionWebRequestApiAuthRequiredTest : public ExtensionWebRequestApiTest, public testing::WithParamInterface<ProfileMode> { protected: - bool GetEnableIncognito() const { + static bool GetEnableIncognito() { return GetParam() == ProfileMode::kIncognito; } + + static std::string FormatCustomArg(const char* test_name) { + static constexpr char custom_arg_format[] = + R"({"testName": "%s", "runInIncognito": %s})"; + + return base::StringPrintf(custom_arg_format, test_name, + GetEnableIncognito() ? "true" : "false"); + } }; -// Note: this is flaky on multiple platforms (crbug.com/1003598). IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, - DISABLED_WebRequestAuthRequired) { + WebRequestAuthRequired) { CancelLoginDialog login_dialog_helper; ASSERT_TRUE(StartEmbeddedTestServer()); - // Pass "debug" as a custom arg to debug test flakiness. - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_auth_required.html", - .custom_arg = R"({"debug": true})", - .open_in_incognito = GetEnableIncognito()}, - {.allow_in_incognito = GetEnableIncognito()})) + // If running in incognito, create an incognito browser so the test + // framework can create an incognito window. + const bool incognito = GetEnableIncognito(); + if (incognito) + CreateIncognitoBrowser(profile()); + + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required", + {.custom_arg = FormatCustomArg("authRequiredNonBlocking").c_str()}, + {.allow_in_incognito = incognito})) + << message_; + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required", + {.custom_arg = FormatCustomArg("authRequiredSyncNoAction").c_str()}, + {.allow_in_incognito = incognito})) + << message_; + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required", + {.custom_arg = FormatCustomArg("authRequiredSyncCancelAuth").c_str()}, + {.allow_in_incognito = incognito})) + << message_; + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required", + {.custom_arg = FormatCustomArg("authRequiredSyncSetAuth").c_str()}, + {.allow_in_incognito = incognito})) << message_; } -// Note: this is flaky on multiple platforms (crbug.com/1003598). Temporarily -// enabled to find flakiness cause. IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, - DISABLED_WebRequestAuthRequiredAsync) { + WebRequestAuthRequiredAsync) { CancelLoginDialog login_dialog_helper; ASSERT_TRUE(StartEmbeddedTestServer()); - // Pass "debug" as a custom arg to debug test flakiness. - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_auth_required_async.html", - .custom_arg = R"({"debug": true})", - .open_in_incognito = GetEnableIncognito()}, - {.allow_in_incognito = GetEnableIncognito()})) + // If running in incognito, create an incognito browser so the tests + // run in an incognito window. + const bool incognito = GetEnableIncognito(); + if (incognito) + CreateIncognitoBrowser(profile()); + + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required_async", + {.custom_arg = FormatCustomArg("authRequiredAsyncNoAction").c_str()}, + {.allow_in_incognito = incognito})) + << message_; + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required_async", + {.custom_arg = FormatCustomArg("authRequiredAsyncCancelAuth").c_str()}, + {.allow_in_incognito = incognito})) + << message_; + ASSERT_TRUE(RunExtensionTest( + "webrequest/test_auth_required_async", + {.custom_arg = FormatCustomArg("authRequiredAsyncSetAuth").c_str()}, + {.allow_in_incognito = incognito})) << message_; } @@ -605,11 +836,16 @@ IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest, DISABLED_WebRequestAuthRequiredParallel) { CancelLoginDialog login_dialog_helper; + const bool incognito = GetEnableIncognito(); + if (incognito) + CreateIncognitoBrowser(profile()); + + const char* const custom_arg = incognito ? R"({"runInIncognito": true})" + : R"({"runInIncognito": false})"; ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_auth_required_parallel.html", - .open_in_incognito = GetEnableIncognito()}, - {.allow_in_incognito = GetEnableIncognito()})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_auth_required_parallel", + {.custom_arg = custom_arg}, + {.allow_in_incognito = incognito})) << message_; } @@ -620,35 +856,42 @@ INSTANTIATE_TEST_SUITE_P(Incognito, ExtensionWebRequestApiAuthRequiredTest, ::testing::Values(ProfileMode::kIncognito)); +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestBlocking) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(RunExtensionTest("webrequest/test_blocking", + {.custom_arg = R"({"testSuite": "normal"})"})) + << message_; +} + // This test times out regularly on win_rel trybots. See http://crbug.com/122178 // Also on Linux/ChromiumOS debug, ASAN and MSAN builds. // https://crbug.com/670415 +// Slower and flaky tests should be isolated in the "slow" group of tests in +// the JS file. This prevents losing test coverage for those tests that are +// not causing timeouts and flakes. #if BUILDFLAG(IS_WIN) || !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \ defined(MEMORY_SANITIZER) -#define MAYBE_WebRequestBlocking DISABLED_WebRequestBlocking +#define MAYBE_WebRequestBlockingSlow DISABLED_WebRequestBlockingSlow #else -#define MAYBE_WebRequestBlocking WebRequestBlocking +#define MAYBE_WebRequestBlockingSlow WebRequestBlockingSlow #endif -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MAYBE_WebRequestBlocking) { +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, + MAYBE_WebRequestBlockingSlow) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE( - RunExtensionTest("webrequest", {.page_url = "test_blocking.html"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_blocking", + {.custom_arg = R"({"testSuite": "slow"})"})) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestBlockingSetCookieHeader) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE( - RunExtensionTest("webrequest", {.page_url = "test_blocking_cookie.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_blocking_cookie")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestExtraHeaders) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE( - RunExtensionTest("webrequest", {.page_url = "test_extra_headers.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_extra_headers")) << message_; } // Flaky on all platforms: https://crbug.com/1003661 @@ -657,38 +900,32 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, CancelLoginDialog login_dialog_helper; ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_extra_headers_auth.html"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_extra_headers_auth")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestChangeCSPHeaders) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_change_csp_headers.html"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_change_csp_headers")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestCORSWithExtraHeaders) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_cors.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_cors")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestRedirects) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE( - RunExtensionTest("webrequest", {.page_url = "test_redirects.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_redirects")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestRedirectsWithExtraHeaders) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_redirects.html", - .custom_arg = R"({"useExtraHeaders": true})"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_redirects", + {.custom_arg = R"({"useExtraHeaders": true})"})) << message_; } @@ -711,34 +948,30 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, std::string config_string; base::JSONWriter::Write(custom_args, &config_string); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_redirects_from_secure.html", - .custom_arg = config_string.c_str()})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_redirects_from_secure", + {.custom_arg = config_string.c_str()})) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestSubresourceRedirects) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_subresource_redirects.html"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_subresource_redirects")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestSubresourceRedirectsWithExtraHeaders) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE(RunExtensionTest("webrequest", - {.page_url = "test_subresource_redirects.html", - .custom_arg = R"({"useExtraHeaders": true})"})) + ASSERT_TRUE(RunExtensionTest("webrequest/test_subresource_redirects", + {.custom_arg = R"({"useExtraHeaders": true})"})) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestNewTab) { ASSERT_TRUE(StartEmbeddedTestServer()); // Wait for the extension to set itself up and return control to us. - ASSERT_TRUE(RunExtensionTest("webrequest", {.page_url = "test_newTab.html"})) - << message_; + ASSERT_TRUE(RunExtensionTest("webrequest/test_new_tab")) << message_; WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); EXPECT_TRUE(content::WaitForLoadStop(tab)); @@ -774,24 +1007,28 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestNewTab) { ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); } -// This test times out regularly on MSAN trybots. See https://crbug.com/733395. -// Also flaky. See https://crbug.com/846555. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, - DISABLED_WebRequestDeclarative1) { +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestDeclarative1) { ASSERT_TRUE(StartEmbeddedTestServer()); - ASSERT_TRUE( - RunExtensionTest("webrequest", {.page_url = "test_declarative1.html"})) + ASSERT_TRUE(RunExtensionTest("webrequest", + {.page_url = "test_declarative1.html", + .custom_arg = R"({"testSuite": "normal"})"})) << message_; } -// This test times out regularly on MSAN trybots. See https://crbug.com/733395. -#if defined(MEMORY_SANITIZER) -#define MAYBE_WebRequestDeclarative2 DISABLED_WebRequestDeclarative2 -#else -#define MAYBE_WebRequestDeclarative2 WebRequestDeclarative2 -#endif +// This test fixture runs all of the broken and flaky tests. It's disabled +// until these tests are fixed and moved to the set of tests that aren't +// broken or flaky. Should tests become flaky, they can be moved here. +// See https://crbug.com/846555. IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, - MAYBE_WebRequestDeclarative2) { + DISABLED_WebRequestDeclarative1Broken) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE(RunExtensionTest("webrequest", + {.page_url = "test_declarative1.html", + .custom_arg = R"({"testSuite": "broken"})"})) + << message_; +} + +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestDeclarative2) { ASSERT_TRUE(StartEmbeddedTestServer()); ASSERT_TRUE( RunExtensionTest("webrequest", {.page_url = "test_declarative2.html"})) @@ -803,7 +1040,8 @@ void ExtensionWebRequestApiTest::RunPermissionTest( bool load_extension_with_incognito_permission, bool wait_for_extension_loaded_in_incognito, const char* expected_content_regular_window, - const char* exptected_content_incognito_window) { + const char* exptected_content_incognito_window, + ContextType context_type) { ResultCatcher catcher; catcher.RestrictToBrowserContext(browser()->profile()); ResultCatcher catcher_incognito; @@ -816,7 +1054,8 @@ void ExtensionWebRequestApiTest::RunPermissionTest( ASSERT_TRUE(LoadExtension( test_data_dir_.AppendASCII("webrequest_permissions") .AppendASCII(extension_directory), - {.allow_in_incognito = load_extension_with_incognito_permission})); + {.allow_in_incognito = load_extension_with_incognito_permission, + .context_type = context_type})); // Test that navigation in regular window is properly redirected. EXPECT_TRUE(listener.WaitUntilSatisfied()); @@ -855,33 +1094,55 @@ void ExtensionWebRequestApiTest::RunPermissionTest( EXPECT_EQ(exptected_content_incognito_window, body); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +class ExtensionWebRequestApiTestWithContextType + : public ExtensionWebRequestApiTest, + public testing::WithParamInterface<ContextType> { + public: + ExtensionWebRequestApiTestWithContextType() + : ExtensionWebRequestApiTest(GetParam()) {} + ExtensionWebRequestApiTestWithContextType( + const ExtensionWebRequestApiTestWithContextType&) = delete; + ExtensionWebRequestApiTestWithContextType& operator=( + const ExtensionWebRequestApiTestWithContextType&) = delete; + ~ExtensionWebRequestApiTestWithContextType() override = default; +}; + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + ExtensionWebRequestApiTestWithContextType, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + ExtensionWebRequestApiTestWithContextType, + ::testing::Values(ContextType::kServiceWorker)); + +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestDeclarativePermissionSpanning1) { // Test spanning with incognito permission. ASSERT_TRUE(StartEmbeddedTestServer()); - RunPermissionTest("spanning", true, false, "redirected1", "redirected1"); + RunPermissionTest("spanning", true, false, "redirected1", "redirected1", + GetParam()); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestDeclarativePermissionSpanning2) { // Test spanning without incognito permission. ASSERT_TRUE(StartEmbeddedTestServer()); - RunPermissionTest("spanning", false, false, "redirected1", ""); + RunPermissionTest("spanning", false, false, "redirected1", "", GetParam()); } - -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestDeclarativePermissionSplit1) { // Test split with incognito permission. ASSERT_TRUE(StartEmbeddedTestServer()); - RunPermissionTest("split", true, true, "redirected1", "redirected2"); + RunPermissionTest("split", true, true, "redirected1", "redirected2", + GetParam()); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestDeclarativePermissionSplit2) { // Test split without incognito permission. ASSERT_TRUE(StartEmbeddedTestServer()); - RunPermissionTest("split", false, false, "redirected1", ""); + RunPermissionTest("split", false, false, "redirected1", "", GetParam()); } // TODO(crbug.com/238179): Cure these flaky tests. @@ -901,7 +1162,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, DISABLED_PostData2) { << message_; } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, DeclarativeSendMessage) { ASSERT_TRUE(StartEmbeddedTestServer()); ASSERT_TRUE(RunExtensionTest("webrequest_sendmessage")) << message_; @@ -910,7 +1171,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Check that reloading an extension that runs in incognito split mode and // has two active background pages with registered events does not crash the // browser. Regression test for http://crbug.com/224094 -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, IncognitoSplitModeReload) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + IncognitoSplitModeReload) { ASSERT_TRUE(StartEmbeddedTestServer()); // Wait for rules to be set up. ExtensionTestMessageListener listener("done"); @@ -936,7 +1198,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, IncognitoSplitModeReload) { EXPECT_TRUE(listener_incognito2.WaitUntilSatisfied()); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ExtensionRequests) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + ExtensionRequests) { ASSERT_TRUE(StartEmbeddedTestServer()); ExtensionTestMessageListener listener_main1("web_request_status1", ReplyBehavior::kWillReply); @@ -953,10 +1216,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ExtensionRequests) { EXPECT_TRUE(listener_main2.WaitUntilSatisfied()); // Perform some network activity in an app and another extension. + ASSERT_TRUE( + LoadExtension(test_data_dir_.AppendASCII("webrequest_extensions/app"), + {.context_type = ContextType::kFromManifest})); ASSERT_TRUE(LoadExtension( - test_data_dir_.AppendASCII("webrequest_extensions/app"))); - ASSERT_TRUE(LoadExtension( - test_data_dir_.AppendASCII("webrequest_extensions/extension"))); + test_data_dir_.AppendASCII("webrequest_extensions/extension"), + {.context_type = ContextType::kFromManifest})); EXPECT_TRUE(listener_app.WaitUntilSatisfied()); EXPECT_TRUE(listener_extension.WaitUntilSatisfied()); @@ -1041,7 +1306,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HostedAppRequest) { } // Tests that WebRequest works with runtime host permissions. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestWithWithheldPermissions) { content::SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); @@ -1162,7 +1427,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Test that extensions with granted runtime host permissions to a tab can // intercept cross-origin requests from that tab. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestWithheldPermissionsCrossOriginRequests) { content::SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); @@ -1212,7 +1477,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Tests behavior when an extension has withheld access to a request's URL, but // not the initiator's (tab's) URL. Regression test for // https://crbug.com/891586. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WithheldHostPermissionsForCrossOriginWithoutInitiator) { content::SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); @@ -1226,16 +1491,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, "name": "Web Request Withheld Hosts", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["*://b.com:*/*", "webRequest"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), - R"(window.webRequestCount = 0; - window.requestedHostnames = []; + R"(self.webRequestCount = 0; + self.requestedHostnames = []; chrome.webRequest.onBeforeRequest.addListener(function(details) { - ++window.webRequestCount; - window.requestedHostnames.push((new URL(details.url)).hostname); + ++self.webRequestCount; + self.requestedHostnames.push((new URL(details.url)).hostname); }, {urls:['<all_urls>']}); chrome.test.sendMessage('ready');)"); @@ -1278,7 +1543,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Verify that requests to clientsX.google.com are protected properly. // First test requests from a standard renderer and then a request from the // browser process. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestClientsGoogleComProtection) { ASSERT_TRUE(embedded_test_server()->Start()); @@ -1292,11 +1557,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, auto get_clients_google_request_count = [this, extension]() { return GetCountFromBackgroundPage(extension, profile(), - "window.clientsGoogleWebRequestCount"); + "self.clientsGoogleWebRequestCount"); }; auto get_yahoo_request_count = [this, extension]() { return GetCountFromBackgroundPage(extension, profile(), - "window.yahooWebRequestCount"); + "self.yahooWebRequestCount"); }; EXPECT_EQ(0, get_clients_google_request_count()); @@ -1370,7 +1635,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, } // Verify that requests for PAC scripts are protected properly. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestPacRequestProtection) { ASSERT_TRUE(embedded_test_server()->Start()); @@ -1400,11 +1665,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // The extension should not have seen the PAC request. EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(), - "window.pacRequestCount")); + "self.pacRequestCount")); // The extension should have seen the request for the main frame. EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.title2RequestCount")); + "self.title2RequestCount")); // The PAC request should have succeeded, as should the subsequent URL // request. @@ -1418,7 +1683,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Checks that the Dice response header is protected for Gaia URLs, but not // other URLs. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestDiceHeaderProtection) { // Load an extension that registers a listener for webRequest events, and // wait until it is initialized. @@ -1497,9 +1762,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Check that the Dice header cannot be read by the extension. EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(), - "window.diceResponseHeaderCount")); + "self.diceResponseHeaderCount")); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.controlResponseHeaderCount")); + "self.controlResponseHeaderCount")); // Navigate to a non-Gaia URL intercepted by the extension. test_webcontents_observer.Clear(); @@ -1517,9 +1782,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Check that the Dice header can be read by the extension. EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.diceResponseHeaderCount")); + "self.diceResponseHeaderCount")); EXPECT_EQ(2, GetCountFromBackgroundPage(extension, profile(), - "window.controlResponseHeaderCount")); + "self.controlResponseHeaderCount")); } // Test that the webRequest events are dispatched for the WebSocket handshake @@ -1617,7 +1882,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiWebTransportTest, ServiceWorker) { } // Test behavior when intercepting requests from a browser-initiated url fetch. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, WebRequestURLLoaderInterception) { // Create an extension that intercepts (and blocks) requests to example.com. TestExtensionDir test_dir; @@ -1628,7 +1893,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, "version": "0.1", "permissions": ["webRequest", "webRequestBlocking", "*://*/*"], "manifest_version": 2, - "background": { "scripts": ["background.js"] } + "background": { "scripts": ["background.js"], "persistent": true } })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(chrome.webRequest.onBeforeRequest.addListener( @@ -1784,7 +2049,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Test that extensions need host permissions to both the request url and // initiator to intercept a request. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, InitiatorAccessRequired) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + InitiatorAccessRequired) { ASSERT_TRUE(StartEmbeddedTestServer()); ExtensionTestMessageListener listener("ready"); @@ -1830,11 +2096,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, InitiatorAccessRequired) { // Run a script in the extensions background page to ensure that we have // received the initiator message from the extension. - ASSERT_EQ( - std::to_string(expected_requests_intercepted_count), - ExecuteScriptInBackgroundPage(extension->id(), - "window.domAutomationController.send(" - "requestsIntercepted.toString());")); + ASSERT_EQ(expected_requests_intercepted_count, + GetCountFromBackgroundPage(extension, profile(), + "self.requestsIntercepted")); if (testcase.expected_initiator.empty()) { EXPECT_FALSE(initiator_listener.was_satisfied()); @@ -1947,15 +2211,19 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, } // Test fixture which sets a custom NTP Page. -class NTPInterceptionWebRequestAPITest : public ExtensionApiTest { +class NTPInterceptionWebRequestAPITest + : public ExtensionApiTest, + public testing::WithParamInterface<ContextType> { public: NTPInterceptionWebRequestAPITest() - : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} + : ExtensionApiTest(GetParam()), + https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} NTPInterceptionWebRequestAPITest(const NTPInterceptionWebRequestAPITest&) = delete; NTPInterceptionWebRequestAPITest& operator=( const NTPInterceptionWebRequestAPITest&) = delete; + ~NTPInterceptionWebRequestAPITest() override = default; // ExtensionApiTest override: void SetUpOnMainThread() override { @@ -1978,9 +2246,17 @@ class NTPInterceptionWebRequestAPITest : public ExtensionApiTest { net::EmbeddedTestServer https_test_server_; }; +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + NTPInterceptionWebRequestAPITest, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + NTPInterceptionWebRequestAPITest, + ::testing::Values(ContextType::kServiceWorker)); + // Ensures that requests made by the NTP Instant renderer are hidden from the // Web Request API. Regression test for crbug.com/797461. -IN_PROC_BROWSER_TEST_F(NTPInterceptionWebRequestAPITest, +IN_PROC_BROWSER_TEST_P(NTPInterceptionWebRequestAPITest, NTPRendererRequestsHidden) { // Loads an extension which tries to intercept requests to // "fake_ntp_script.js", which will be loaded as part of the NTP renderer. @@ -1998,12 +2274,11 @@ IN_PROC_BROWSER_TEST_F(NTPInterceptionWebRequestAPITest, // Returns true if the given extension was able to intercept the request to // "fake_ntp_script.js". auto was_script_request_intercepted = - [this](const std::string& extension_id) { - const std::string result = ExecuteScriptInBackgroundPage( - extension_id, "getAndResetRequestIntercepted();"); - EXPECT_TRUE(result == "true" || result == "false") - << "Unexpected result " << result; - return result == "true"; + [this](const ExtensionId& extension_id) { + const absl::optional<bool> result = ExecuteScriptAndReturnBool( + extension_id, profile(), "getAndResetRequestIntercepted();"); + DCHECK(result); + return *result; }; // Returns true if the given |web_contents| has window.scriptExecuted set to @@ -2041,15 +2316,17 @@ IN_PROC_BROWSER_TEST_F(NTPInterceptionWebRequestAPITest, // the WebUI NTP can't be intercepted by extensions. class WebUiNtpInterceptionWebRequestAPITest : public ExtensionApiTest, - public OneGoogleBarServiceObserver { + public OneGoogleBarServiceObserver, + public testing::WithParamInterface<ContextType> { public: WebUiNtpInterceptionWebRequestAPITest() - : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} - + : ExtensionApiTest(GetParam()), + https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} WebUiNtpInterceptionWebRequestAPITest( const WebUiNtpInterceptionWebRequestAPITest&) = delete; WebUiNtpInterceptionWebRequestAPITest& operator=( const WebUiNtpInterceptionWebRequestAPITest&) = delete; + ~WebUiNtpInterceptionWebRequestAPITest() override = default; // ExtensionApiTest override: void SetUp() override { @@ -2122,7 +2399,15 @@ class WebUiNtpInterceptionWebRequestAPITest base::Lock lock_; }; -IN_PROC_BROWSER_TEST_F(WebUiNtpInterceptionWebRequestAPITest, +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + WebUiNtpInterceptionWebRequestAPITest, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + WebUiNtpInterceptionWebRequestAPITest, + ::testing::Values(ContextType::kServiceWorker)); + +IN_PROC_BROWSER_TEST_P(WebUiNtpInterceptionWebRequestAPITest, OneGoogleBarRequestsHidden) { // Loads an extension which tries to intercept requests to the OneGoogleBar. ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); @@ -2139,12 +2424,11 @@ IN_PROC_BROWSER_TEST_F(WebUiNtpInterceptionWebRequestAPITest, // Returns true if the given extension was able to intercept the request to // |one_google_bar_url()|. auto was_script_request_intercepted = - [this](const std::string& extension_id) { - const std::string result = ExecuteScriptInBackgroundPage( - extension_id, "getAndResetRequestIntercepted();"); - EXPECT_TRUE(result == "true" || result == "false") - << "Unexpected result " << result; - return result == "true"; + [this](const ExtensionId& extension_id) { + absl::optional<bool> result = ExecuteScriptAndReturnBool( + extension_id, profile(), "getAndResetRequestIntercepted();"); + DCHECK(result); + return *result; }; ASSERT_FALSE(GetAndResetOneGoogleBarRequestSeen()); @@ -2175,9 +2459,29 @@ IN_PROC_BROWSER_TEST_F(DevToolsFrontendInWebRequestApiTest, HiddenRequests) { << message_; } +class WebRequestApiTestWithManagementPolicy + : public ExtensionApiTestWithManagementPolicy, + public testing::WithParamInterface<ContextType> { + public: + WebRequestApiTestWithManagementPolicy() + : ExtensionApiTestWithManagementPolicy(GetParam()) {} + ~WebRequestApiTestWithManagementPolicy() override = default; + WebRequestApiTestWithManagementPolicy( + const WebRequestApiTestWithManagementPolicy&) = delete; + WebRequestApiTestWithManagementPolicy& operator=( + const WebRequestApiTestWithManagementPolicy&) = delete; +}; + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + WebRequestApiTestWithManagementPolicy, + ::testing::Values(ContextType::kPersistentBackground)); +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + WebRequestApiTestWithManagementPolicy, + ::testing::Values(ContextType::kServiceWorker)); + // Tests that the webRequest events aren't dispatched when the request initiator // is protected by policy. -IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, +IN_PROC_BROWSER_TEST_P(WebRequestApiTestWithManagementPolicy, InitiatorProtectedByPolicy) { // We expect that no request will be hidden or modification blocked. This // means that the request to example.com will be seen by the extension. @@ -2207,7 +2511,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, // The number of requests initiated by a protected origin is tracked in // the extension's background page under this variable name. - const std::string request_counter_name = "window.protectedOriginCount"; + const std::string request_counter_name = "self.protectedOriginCount"; EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(), request_counter_name)); @@ -2256,7 +2560,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, // Tests that the webRequest events aren't dispatched when the URL of the // request is protected by policy. -IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, +IN_PROC_BROWSER_TEST_P(WebRequestApiTestWithManagementPolicy, UrlProtectedByPolicy) { // Host protected by policy. const std::string protected_domain = "example.com"; @@ -2309,7 +2613,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, // navigation. This replicates most of the tests from // WebRequestWithWithheldPermissions with a protected host. Granting a tab // specific permission shouldn't bypass our policy. -IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, +IN_PROC_BROWSER_TEST_P(WebRequestApiTestWithManagementPolicy, WebRequestProtectedByPolicy) { // Host protected by policy. const std::string protected_domain = "example.com"; @@ -2374,7 +2678,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, // A test fixture which mocks the Time::Now() function to ensure that the // default clock returns monotonically increasing time. -class ExtensionWebRequestMockedClockTest : public ExtensionWebRequestApiTest { +class ExtensionWebRequestMockedClockTest + : public ExtensionWebRequestApiTestWithContextType { public: ExtensionWebRequestMockedClockTest() : scoped_time_clock_override_(&ExtensionWebRequestMockedClockTest::Now, @@ -2396,9 +2701,16 @@ class ExtensionWebRequestMockedClockTest : public ExtensionWebRequestApiTest { base::subtle::ScopedTimeClockOverrides scoped_time_clock_override_; }; +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + ExtensionWebRequestMockedClockTest, + ::testing::Values(ContextType::kPersistentBackground)); +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + ExtensionWebRequestMockedClockTest, + ::testing::Values(ContextType::kServiceWorker)); + // Tests that we correctly dispatch the OnActionIgnored event on an extension // if the extension's proposed redirect is ignored. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestMockedClockTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestMockedClockTest, OnActionIgnored_Redirect) { ASSERT_TRUE(StartEmbeddedTestServer()); @@ -2480,25 +2792,26 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestMockedClockTest, } // Regression test for http://crbug.com/1074282. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, StaleHeadersAfterRedirect) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + StaleHeadersAfterRedirect) { TestExtensionDir test_dir; test_dir.WriteManifest(R"({ "name": "Web Request Stale Headers Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( - window.locationCount = 0; - window.requestCount = 0; + self.locationCount = 0; + self.requestCount = 0; chrome.test.sendMessage('ready', function(reply) { chrome.webRequest.onResponseStarted.addListener(function(details) { - window.requestCount++; + self.requestCount++; var headers = details.responseHeaders; for (var i = 0; i < headers.length; i++) { if (headers[i].name === 'Location') { - window.locationCount++; + self.locationCount++; } } }, @@ -2553,14 +2866,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, StaleHeadersAfterRedirect) { embedded_test_server()->host_port_pair().host(), embedded_test_server()->port(), "redirect-and-wait"); EXPECT_EQ( - GetCountFromBackgroundPage(extension, profile(), "window.requestCount"), - 1); + GetCountFromBackgroundPage(extension, profile(), "self.requestCount"), 1); EXPECT_EQ( - GetCountFromBackgroundPage(extension, profile(), "window.locationCount"), + GetCountFromBackgroundPage(extension, profile(), "self.locationCount"), 0); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ChangeHeaderUMAs) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + ChangeHeaderUMAs) { using RequestHeaderType = extension_web_request_api_helpers::RequestHeaderType; using ResponseHeaderType = @@ -2573,7 +2886,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ChangeHeaderUMAs) { "name": "Web Request UMA Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( @@ -2656,7 +2969,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ChangeHeaderUMAs) { ResponseHeaderType::kNone, 1); } -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, RemoveHeaderUMAs) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + RemoveHeaderUMAs) { using RequestHeaderType = extension_web_request_api_helpers::RequestHeaderType; using ResponseHeaderType = @@ -2669,7 +2983,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, RemoveHeaderUMAs) { "name": "Web Request UMA Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( @@ -2727,12 +3041,29 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, RemoveHeaderUMAs) { ResponseHeaderType::kNone, 1); } -// The parameter is for opt_extraInfoSpec passed to addEventListener. -// 'blocking' and 'requestHeaders' if it's false, and 'extraHeaders' in addition -// to them if it's true. -class ServiceWorkerWebRequestApiTest : public testing::WithParamInterface<bool>, - public ExtensionApiTest { +struct SWTestParams { + SWTestParams(bool extra_info_spec, ContextType context_type) + : extra_info_spec(extra_info_spec), context_type(context_type) {} + + // This parameter is for opt_extraInfoSpec passed to addEventListener. + // 'blocking' and 'requestHeaders' if it's false, and 'extraHeaders' in + // addition to them if it's true. + bool extra_info_spec; + ContextType context_type; +}; + +class ServiceWorkerWebRequestApiTest + : public testing::WithParamInterface<SWTestParams>, + public ExtensionApiTest { public: + ServiceWorkerWebRequestApiTest() + : ExtensionApiTest(GetParam().context_type) {} + ~ServiceWorkerWebRequestApiTest() override = default; + ServiceWorkerWebRequestApiTest(const ServiceWorkerWebRequestApiTest&) = + delete; + ServiceWorkerWebRequestApiTest& operator=( + const ServiceWorkerWebRequestApiTest&) = delete; + // The options passed as opt_extraInfoSpec to addEventListener. enum class ExtraInfoSpec { // 'blocking', 'requestHeaders' @@ -2742,7 +3073,8 @@ class ServiceWorkerWebRequestApiTest : public testing::WithParamInterface<bool>, }; static ExtraInfoSpec GetExtraInfoSpec() { - return GetParam() ? ExtraInfoSpec::kExtraHeaders : ExtraInfoSpec::kDefault; + return GetParam().extra_info_spec ? ExtraInfoSpec::kExtraHeaders + : ExtraInfoSpec::kDefault; } void InstallRequestHeaderModifyingExtension() { @@ -2751,7 +3083,7 @@ class ServiceWorkerWebRequestApiTest : public testing::WithParamInterface<bool>, "name": "Web Request Header Modifying Extension", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); @@ -2824,9 +3156,25 @@ class ServiceWorkerWebRequestApiTest : public testing::WithParamInterface<bool>, } }; -INSTANTIATE_TEST_SUITE_P(All, - ServiceWorkerWebRequestApiTest, - ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P( + PersistentBackgroundWithExtraHeaders, + ServiceWorkerWebRequestApiTest, + ::testing::Values(SWTestParams(true, ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + PersistentBackground, + ServiceWorkerWebRequestApiTest, + ::testing::Values(SWTestParams(false, ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorkerWithExtraHeaders, + ServiceWorkerWebRequestApiTest, + ::testing::Values(SWTestParams(true, ContextType::kServiceWorker))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorker, + ServiceWorkerWebRequestApiTest, + ::testing::Values(SWTestParams(false, ContextType::kServiceWorker))); IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, ServiceWorkerFetch) { RunServiceWorkerFetchTest("fetch_event_respond_with_fetch.js"); @@ -3148,7 +3496,7 @@ IN_PROC_BROWSER_TEST_P(ServiceWorkerWebRequestApiTest, // Ensure we don't strip off initiator incorrectly in web request events when // both the normal and incognito contexts are active. Regression test for // crbug.com/934398. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, Initiator_SpanningIncognito) { embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(embedded_test_server()->Start()); @@ -3158,7 +3506,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, LoadExtension(test_data_dir_.AppendASCII("webrequest") .AppendASCII("initiator_spanning")); ASSERT_TRUE(extension); - const std::string extension_id = extension->id(); + // Save the ID because enabling the extension for incognito mode will + // invalidate |extension|. + const ExtensionId extension_id = extension->id(); EXPECT_TRUE(ready_listener.WaitUntilSatisfied()); Browser* incognito_browser = CreateIncognitoBrowser(profile()); @@ -3169,21 +3519,26 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, GURL url = embedded_test_server()->GetURL("google.com", "/iframe.html"); const std::string url_origin = url::Origin::Create(url).Serialize(); - const char* kScript = R"( - domAutomationController.send(JSON.stringify(window.initiators)); - window.initiators = []; + static constexpr char kScript[] = R"( + chrome.test.sendScriptResult(JSON.stringify(self.initiators)); + self.initiators = []; )"; ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); - EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), - ExecuteScriptInBackgroundPage(extension_id, kScript)); + absl::optional<std::string> result = + ExecuteScriptAndReturnString(extension_id, profile(), kScript); + ASSERT_TRUE(result); + EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), *result); // The extension isn't enabled in incognito. Se we shouldn't intercept the // request to |url|. ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, url)); - EXPECT_EQ("[]", ExecuteScriptInBackgroundPage(extension_id, kScript)); + result = ExecuteScriptAndReturnString(extension_id, profile(), kScript); + ASSERT_TRUE(result); + EXPECT_EQ("[]", *result); // Now enable the extension in incognito. + extension = nullptr; ready_listener.Reset(); extensions::util::SetIsIncognitoEnabled(extension_id, profile(), true /* enabled */); @@ -3191,14 +3546,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, // Now we should be able to intercept the incognito request. ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, url)); - EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), - ExecuteScriptInBackgroundPage(extension_id, kScript)); + result = ExecuteScriptAndReturnString(extension_id, profile(), kScript); + ASSERT_TRUE(result); + EXPECT_EQ(base::StringPrintf("[\"%s\"]", url_origin.c_str()), *result); } // Ensure we don't strip off initiator incorrectly in web request events when // both the normal and incognito contexts are active. Regression test for // crbug.com/934398. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, Initiator_SplitIncognito) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + Initiator_SplitIncognito) { embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(embedded_test_server()->Start()); @@ -3224,20 +3581,22 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, Initiator_SplitIncognito) { const std::string origin_incognito = url::Origin::Create(url_incognito).Serialize(); - const char* kScript = R"( - domAutomationController.send(JSON.stringify(window.initiators)); - window.initiators = []; + static constexpr char kScript[] = R"( + chrome.test.sendScriptResult(JSON.stringify(self.initiators)); + self.initiators = []; )"; ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_normal)); ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, url_incognito)); - EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_normal.c_str()), - browsertest_util::ExecuteScriptInBackgroundPage( - profile(), extension->id(), kScript)); - EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_incognito.c_str()), - browsertest_util::ExecuteScriptInBackgroundPage( - profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), - extension->id(), kScript)); + absl::optional<std::string> result = + ExecuteScriptAndReturnString(extension->id(), profile(), kScript); + ASSERT_TRUE(result); + EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_normal.c_str()), *result); + result = ExecuteScriptAndReturnString( + extension->id(), + profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), kScript); + ASSERT_TRUE(result); + EXPECT_EQ(base::StringPrintf("[\"%s\"]", origin_incognito.c_str()), *result); } // A request handler that sets the Access-Control-Allow-Origin header. @@ -3251,7 +3610,7 @@ std::unique_ptr<net::test_server::HttpResponse> HandleXHRRequest( // Regression test for http://crbug.com/971206. The responseHeaders should still // be present in onBeforeRedirect even for HSTS upgrade. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, ExtraHeadersWithHSTSUpgrade) { net::EmbeddedTestServer https_test_server( net::EmbeddedTestServer::TYPE_HTTPS); @@ -3264,12 +3623,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, "name": "Web Request HSTS Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( chrome.webRequest.onBeforeRedirect.addListener(function(details) { - window.headerCount = details.responseHeaders.length; + self.headerCount = details.responseHeaders.length; }, {urls: ['<all_urls>']}, ['responseHeaders', 'extraHeaders']); @@ -3299,8 +3658,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, https_test_server.host_port_pair().host(), https_test_server.port(), "echo"); EXPECT_GT( - GetCountFromBackgroundPage(extension, profile(), "window.headerCount"), - 0); + GetCountFromBackgroundPage(extension, profile(), "self.headerCount"), 0); } // Ensure that when an extension blocks a main-frame request, the resultant @@ -3324,7 +3682,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, } // Regression test for https://crbug.com/1019614. -IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HSTSUpgradeAfterRedirect) { +IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType, + HSTSUpgradeAfterRedirect) { net::EmbeddedTestServer https_test_server( net::EmbeddedTestServer::TYPE_HTTPS); https_test_server.ServeFilesFromDirectory( @@ -3338,7 +3697,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HSTSUpgradeAfterRedirect) { "name": "Web Request HSTS Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"( @@ -3375,10 +3734,110 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, HSTSUpgradeAfterRedirect) { EXPECT_EQ(final_url, web_contents->GetLastCommittedURL()); } +// Tests registering webRequest events in multiple contexts in the same +// extension (which will thus be in the same process). Regression test for +// https://crbug.com/1297276. +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, + ListenersInMultipleContexts) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + // Load an extension that has a page with two iframes. Each iframe registers + // a listener for the same event. + static constexpr char kManifest[] = + R"({ + "name": "ext", + "manifest_version": 3, + "version": "1", + "permissions": ["webRequest"], + "host_permissions": ["http://example.com/*"] + })"; + static constexpr char kParentHtml[] = + R"(<!doctype html> + <html> + Hello world + <iframe src="iframe.html" name="iframe1"></iframe> + <iframe src="iframe.html" name="iframe2"></iframe> + </html>)"; + static constexpr char kIframeHtml[] = + R"(<!doctype html> + <html> + Iframe + <script src="iframe.js"></script> + </html>)"; + static constexpr char kIframeJs[] = + R"(const frameName = window.name; + chrome.webRequest.onBeforeRequest.addListener( + (details) => { + chrome.test.sendMessage(frameName + ' event'); + }, + {urls: ['http://example.com/*'], types: ['main_frame']}); + chrome.test.sendMessage(frameName + ' ready');)"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("parent.html"), kParentHtml); + test_dir.WriteFile(FILE_PATH_LITERAL("iframe.html"), kIframeHtml); + test_dir.WriteFile(FILE_PATH_LITERAL("iframe.js"), kIframeJs); + + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + auto* router = ExtensionWebRequestEventRouter::GetInstance(); + ASSERT_TRUE(router); + + static constexpr char kEventName[] = "webRequest.onBeforeRequest"; + EXPECT_EQ(0u, router->GetListenerCountForTesting(profile(), kEventName)); + + // Load the extension page and wait for it to register its listeners. + { + ExtensionTestMessageListener listener1("iframe1 ready"); + ExtensionTestMessageListener listener2("iframe2 ready"); + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), extension->GetResourceURL("parent.html"))); + ASSERT_TRUE(listener1.WaitUntilSatisfied()); + ASSERT_TRUE(listener2.WaitUntilSatisfied()); + } + + // Two different listeners should be registered. + EXPECT_EQ(2u, router->GetListenerCountForTesting(profile(), kEventName)); + + // Trigger an event. Both listeners should fire. + { + ExtensionTestMessageListener listener1("iframe1 event"); + ExtensionTestMessageListener listener2("iframe2 event"); + ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition( + browser(), + embedded_test_server()->GetURL("example.com", "/title1.html"), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); + EXPECT_TRUE(listener1.WaitUntilSatisfied()); + EXPECT_TRUE(listener2.WaitUntilSatisfied()); + } +} + +struct SWBTestParams { + SWBTestParams(bool extra_info_spec, ContextType context_type) + : extra_info_spec(extra_info_spec), context_type(context_type) {} + + // The parameter is for opt_extraInfoSpec passed to addEventListener. + // 'blocking' if it's false, and 'extraHeaders' in addition to them + // if it's true. + bool extra_info_spec; + ContextType context_type; +}; + class SubresourceWebBundlesWebRequestApiTest - : public testing::WithParamInterface<bool>, + : public testing::WithParamInterface<SWBTestParams>, public ExtensionApiTest { public: + SubresourceWebBundlesWebRequestApiTest() + : ExtensionApiTest(GetParam().context_type) {} + ~SubresourceWebBundlesWebRequestApiTest() override = default; + SubresourceWebBundlesWebRequestApiTest( + const SubresourceWebBundlesWebRequestApiTest&) = delete; + SubresourceWebBundlesWebRequestApiTest& operator=( + const SubresourceWebBundlesWebRequestApiTest&) = delete; + void SetUp() override { feature_list_.InitWithFeatures({features::kSubresourceWebBundles}, {}); ExtensionApiTest::SetUp(); @@ -3394,7 +3853,8 @@ class SubresourceWebBundlesWebRequestApiTest }; static ExtraInfoSpec GetExtraInfoSpec() { - return GetParam() ? ExtraInfoSpec::kExtraHeaders : ExtraInfoSpec::kDefault; + return GetParam().extra_info_spec ? ExtraInfoSpec::kExtraHeaders + : ExtraInfoSpec::kDefault; } bool TryLoadScript(const std::string& script_src) { @@ -3496,9 +3956,26 @@ class SubresourceWebBundlesWebRequestApiTest base::test::ScopedFeatureList feature_list_; }; -INSTANTIATE_TEST_SUITE_P(All, - SubresourceWebBundlesWebRequestApiTest, - ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P( + PersistentBackgroundWithExtraHeaders, + SubresourceWebBundlesWebRequestApiTest, + ::testing::Values(SWBTestParams(true, ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + PersistentBackground, + SubresourceWebBundlesWebRequestApiTest, + ::testing::Values(SWBTestParams(false, + ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorkerWithExtraHeaders, + SubresourceWebBundlesWebRequestApiTest, + ::testing::Values(SWBTestParams(true, ContextType::kServiceWorker))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorker, + SubresourceWebBundlesWebRequestApiTest, + ::testing::Values(SWBTestParams(false, ContextType::kServiceWorker))); // Ensure web request listeners can intercept requests for a web bundle and its // subresources. @@ -3513,7 +3990,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest"] })"); @@ -3522,19 +3999,19 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, opt_extra_info_spec += "'extraHeaders'"; test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), base::StringPrintf(R"( - window.numMainResourceRequests = 0; - window.numWebBundleRequests = 0; - window.numScriptRequests = 0; - window.numUUIDInPackageScriptRequests = 0; + self.numMainResourceRequests = 0; + self.numWebBundleRequests = 0; + self.numScriptRequests = 0; + self.numUUIDInPackageScriptRequests = 0; chrome.webRequest.onBeforeRequest.addListener(function(details) { if (details.url.includes('test.html')) - window.numMainResourceRequests++; + self.numMainResourceRequests++; else if (details.url.includes('web_bundle.wbn')) - window.numWebBundleRequests++; + self.numWebBundleRequests++; else if (details.url.includes('test.js')) - window.numScriptRequests++; + self.numScriptRequests++; else if (details.url === '%s') - window.numUUIDInPackageScriptRequests++; + self.numUUIDInPackageScriptRequests++; }, {urls: ['<all_urls>']}, [%s]); chrome.test.sendMessage('ready'); @@ -3612,14 +4089,14 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.numMainResourceRequests")); + "self.numMainResourceRequests")); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.numWebBundleRequests")); + "self.numWebBundleRequests")); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.numScriptRequests")); - EXPECT_EQ( - 1, GetCountFromBackgroundPage(extension, profile(), - "window.numUUIDInPackageScriptRequests")); + "self.numScriptRequests")); + EXPECT_EQ(1, + GetCountFromBackgroundPage(extension, profile(), + "self.numUUIDInPackageScriptRequests")); } // Ensure web request API can block the requests for the subresources inside the @@ -3632,7 +4109,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); std::string pass_uuid_in_package_js_url = @@ -3644,22 +4121,22 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, opt_extra_info_spec += ", 'extraHeaders'"; test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), base::StringPrintf(R"( - window.numPassScriptRequests = 0; - window.numCancelScriptRequests = 0; - window.numUUIDInPackagePassScriptRequests = 0; - window.numUUIDInPackageCancelScriptRequests = 0; + self.numPassScriptRequests = 0; + self.numCancelScriptRequests = 0; + self.numUUIDInPackagePassScriptRequests = 0; + self.numUUIDInPackageCancelScriptRequests = 0; chrome.webRequest.onBeforeRequest.addListener(function(details) { if (details.url.includes('pass.js')) { - window.numPassScriptRequests++; + self.numPassScriptRequests++; return {cancel: false}; } else if (details.url.includes('cancel.js')) { - window.numCancelScriptRequests++; + self.numCancelScriptRequests++; return {cancel: true}; } else if (details.url === '%s') { - window.numUUIDInPackagePassScriptRequests++; + self.numUUIDInPackagePassScriptRequests++; return {cancel: false}; } else if (details.url === '%s') { - window.numUUIDInPackageCancelScriptRequests++; + self.numUUIDInPackageCancelScriptRequests++; return {cancel: true}; } }, {urls: ['<all_urls>']}, [%s]); @@ -3757,15 +4234,15 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, EXPECT_FALSE(TryLoadScript(cancel_uuid_in_package_js_url)); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.numPassScriptRequests")); + "self.numPassScriptRequests")); EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "window.numCancelScriptRequests")); - EXPECT_EQ(1, GetCountFromBackgroundPage( - extension, profile(), - "window.numUUIDInPackagePassScriptRequests")); + "self.numCancelScriptRequests")); + EXPECT_EQ( + 1, GetCountFromBackgroundPage(extension, profile(), + "self.numUUIDInPackagePassScriptRequests")); EXPECT_EQ(1, GetCountFromBackgroundPage( extension, profile(), - "window.numUUIDInPackageCancelScriptRequests")); + "self.numUUIDInPackageCancelScriptRequests")); } // Ensure web request API can change the headers of the subresources inside the @@ -3778,7 +4255,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, ChangeHeader) { "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); std::string opt_extra_info_spec = "'blocking', 'responseHeaders'"; @@ -3880,7 +4357,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); std::string opt_extra_info_spec = "'blocking', 'responseHeaders'"; @@ -3975,7 +4452,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); std::string opt_extra_info_spec = "'blocking'"; @@ -4116,7 +4593,7 @@ IN_PROC_BROWSER_TEST_P(SubresourceWebBundlesWebRequestApiTest, "name": "Web Request Subresource Web Bundles Test", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); std::string opt_extra_info_spec = "'blocking'"; @@ -4177,10 +4654,24 @@ enum class RedirectType { kOnHeadersReceived, }; +struct RITestParams { + RITestParams(RedirectType redirect_type, ContextType context_type) + : redirect_type(redirect_type), context_type(context_type) {} + + RedirectType redirect_type; + ContextType context_type; +}; + class RedirectInfoWebRequestApiTest - : public testing::WithParamInterface<RedirectType>, + : public testing::WithParamInterface<RITestParams>, public ExtensionApiTest { public: + RedirectInfoWebRequestApiTest() : ExtensionApiTest(GetParam().context_type) {} + ~RedirectInfoWebRequestApiTest() override = default; + RedirectInfoWebRequestApiTest(const RedirectInfoWebRequestApiTest&) = delete; + RedirectInfoWebRequestApiTest& operator=( + const RedirectInfoWebRequestApiTest&) = delete; + void SetUpOnMainThread() override { ExtensionApiTest::SetUpOnMainThread(); host_resolver()->AddRule("*", "127.0.0.1"); @@ -4194,12 +4685,11 @@ class RedirectInfoWebRequestApiTest "name": "Simple Redirect", "manifest_version": 2, "version": "0.1", - "background": { "scripts": ["background.js"] }, + "background": { "scripts": ["background.js"], "persistent": true }, "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"] })"); - test_dir.WriteFile( - FILE_PATH_LITERAL("background.js"), - base::StringPrintf(R"( + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), + base::StringPrintf(R"( chrome.webRequest.%s.addListener(function(details) { if (details.type == '%s' && details.url.includes('hello.html')) { @@ -4210,10 +4700,11 @@ class RedirectInfoWebRequestApiTest }, {urls: ['*://original.test/*']}, ['blocking']); chrome.test.sendMessage('ready'); )", - GetParam() == RedirectType::kOnBeforeRequest - ? "onBeforeRequest" - : "onHeadersReceived", - resource_type.c_str())); + GetParam().redirect_type == + RedirectType::kOnBeforeRequest + ? "onBeforeRequest" + : "onHeadersReceived", + resource_type.c_str())); ExtensionTestMessageListener listener("ready"); const Extension* extension = LoadExtension(test_dir.UnpackedPath()); ASSERT_TRUE(extension); @@ -4224,10 +4715,29 @@ class RedirectInfoWebRequestApiTest TestExtensionDir test_dir_; }; -INSTANTIATE_TEST_SUITE_P(RedirectMode, - RedirectInfoWebRequestApiTest, - ::testing::Values(RedirectType::kOnBeforeRequest, - RedirectType::kOnHeadersReceived)); +INSTANTIATE_TEST_SUITE_P( + PersistentBackgroundOnBeforeRequest, + RedirectInfoWebRequestApiTest, + ::testing::Values(RITestParams(RedirectType::kOnBeforeRequest, + ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + PersistentBackgroundOnHeadersReceived, + RedirectInfoWebRequestApiTest, + ::testing::Values(RITestParams(RedirectType::kOnHeadersReceived, + ContextType::kPersistentBackground))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorkerOnBeforeRequest, + RedirectInfoWebRequestApiTest, + ::testing::Values(RITestParams(RedirectType::kOnBeforeRequest, + ContextType::kServiceWorker))); + +INSTANTIATE_TEST_SUITE_P( + ServiceWorkerOnHeadersReceived, + RedirectInfoWebRequestApiTest, + ::testing::Values(RITestParams(RedirectType::kOnHeadersReceived, + ContextType::kServiceWorker))); // Test that a main frame request redirected by an extension has the correct // site_for_cookies and network_isolation_key parameters. @@ -4437,10 +4947,15 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiIdentifiabilityTest, blink::IdentifiableSurface::Type::kExtensionCancelRequest)); } -class ProxyCORSWebRequestApiTest : public ExtensionApiTest { +class ProxyCORSWebRequestApiTest + : public ExtensionApiTest, + public testing::WithParamInterface<ContextType> { public: - ProxyCORSWebRequestApiTest() = default; + ProxyCORSWebRequestApiTest() : ExtensionApiTest(GetParam()) {} ~ProxyCORSWebRequestApiTest() override = default; + ProxyCORSWebRequestApiTest(const ProxyCORSWebRequestApiTest&) = delete; + ProxyCORSWebRequestApiTest& operator=(const ProxyCORSWebRequestApiTest&) = + delete; protected: class ProceedLoginDialog : public content::NotificationObserver { @@ -4576,10 +5091,18 @@ class ProxyCORSWebRequestApiTest : public ExtensionApiTest { net::EmbeddedTestServer proxy_cors_server_; }; +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + ProxyCORSWebRequestApiTest, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + ProxyCORSWebRequestApiTest, + ::testing::Values(ContextType::kServiceWorker)); + // Regression test for crbug.com/1212625 // Test that CORS preflight request which requires proxy auth completes // successfully instead of being cancelled after proxy auth required response. -IN_PROC_BROWSER_TEST_F(ProxyCORSWebRequestApiTest, +IN_PROC_BROWSER_TEST_P(ProxyCORSWebRequestApiTest, PreflightCompletesSuccessfully) { ProceedLoginDialog login_dialog(kCORSProxyUser, kCORSProxyPass); ExtensionTestMessageListener ready_listener("ready"); @@ -4593,7 +5116,7 @@ IN_PROC_BROWSER_TEST_F(ProxyCORSWebRequestApiTest, browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents); ExtensionTestMessageListener preflight_listener("cors-preflight-succeeded"); - const char kCORSPreflightedRequest[] = R"( + static constexpr char kCORSPreflightedRequest[] = R"( var xhr = new XMLHttpRequest(); xhr.open('GET', '%s'); xhr.setRequestHeader('%s', 'testvalue'); @@ -4608,15 +5131,16 @@ IN_PROC_BROWSER_TEST_F(ProxyCORSWebRequestApiTest, &success)); EXPECT_TRUE(success); EXPECT_TRUE(preflight_listener.WaitUntilSatisfied()); - EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "preflightHeadersReceivedCount")); - EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "preflightProxyAuthRequiredCount")); - EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), - "preflightResponseStartedCount")); + EXPECT_EQ(1, GetCountFromBackgroundPage( + extension, profile(), "self.preflightHeadersReceivedCount")); + EXPECT_EQ(1, + GetCountFromBackgroundPage(extension, profile(), + "self.preflightProxyAuthRequiredCount")); + EXPECT_EQ(1, GetCountFromBackgroundPage( + extension, profile(), "self.preflightResponseStartedCount")); EXPECT_EQ(1, GetCountFromBackgroundPage( extension, profile(), - "preflightResponseStartedSuccessfullyCount")); + "self.preflightResponseStartedSuccessfullyCount")); } class ExtensionWebRequestApiFencedFrameTest @@ -4659,4 +5183,480 @@ INSTANTIATE_TEST_SUITE_P(ExtensionWebRequestApiFencedFrameTest, ExtensionWebRequestApiFencedFrameTest, testing::Bool()); +class ExtensionWebRequestApiPrerenderingTest + : public ExtensionWebRequestApiTest { + protected: + ExtensionWebRequestApiPrerenderingTest() = default; + ~ExtensionWebRequestApiPrerenderingTest() override = default; + + private: + content::test::ScopedPrerenderFeatureList prerender_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiPrerenderingTest, Load) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE( + RunExtensionTest("webrequest", {.page_url = "test_prerendering.html"})) + << message_; +} + +using ContextType = ExtensionBrowserTest::ContextType; + +class WebRequestApiTestWithContextType + : public ExtensionWebRequestApiTest, + public testing::WithParamInterface<ContextType> { + public: + WebRequestApiTestWithContextType() = default; + ~WebRequestApiTestWithContextType() override = default; +}; + +namespace { + +constexpr char kGetNumRequests[] = + R"((async function() { + // Wait for any pending storage writes to complete. + await flushStorage(); + chrome.storage.local.get( + {requestCount: -1}, + (result) => { + chrome.test.sendScriptResult(result.requestCount); + }); + })();)"; + +} // namespace + +// Tests that webRequest listeners are persistent across browser restarts. +IN_PROC_BROWSER_TEST_P(WebRequestApiTestWithContextType, + PRE_TestListenersArePersistent) { + // Load an extension that listens for webRequest events. + ASSERT_TRUE(StartEmbeddedTestServer()); + const Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("webrequest_persistent")); + ASSERT_TRUE(extension); + + // Navigate to example.com (a site the extension has access to). + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("example.com", "/simple.html"))); + + // Validate that we have a single request seen by the extension. + base::Value request_count = BackgroundScriptExecutor::ExecuteScript( + profile(), extension->id(), kGetNumRequests, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + ASSERT_TRUE(request_count.is_int()); + EXPECT_EQ(1, request_count.GetInt()); +} + +IN_PROC_BROWSER_TEST_P(WebRequestApiTestWithContextType, + TestListenersArePersistent) { + // Find the installed extension and wait for it to fully load. + ASSERT_TRUE(StartEmbeddedTestServer()); + const Extension* extension = nullptr; + for (const auto& candidate : extension_registry()->enabled_extensions()) { + if (candidate->name() == "Web Request Persistence") { + extension = candidate.get(); + break; + } + } + ASSERT_TRUE(extension); + WaitForExtensionViewsToLoad(); + + // Navigate once more to example.com. + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("example.com", "/simple.html"))); + + // We should now have two records seen by the extension. + base::Value request_count = BackgroundScriptExecutor::ExecuteScript( + profile(), extension->id(), kGetNumRequests, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + + ASSERT_TRUE(request_count.is_int()); + EXPECT_EQ(2, request_count.GetInt()); +} + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + WebRequestApiTestWithContextType, + ::testing::Values(ContextType::kPersistentBackground)); +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + WebRequestApiTestWithContextType, + ::testing::Values(ContextType::kServiceWorker)); + +class ManifestV3WebRequestApiTest : public ExtensionWebRequestApiTest { + public: + ManifestV3WebRequestApiTest() = default; + ~ManifestV3WebRequestApiTest() override = default; + + // Loads an extension contained within `test_dir` as a policy-installed + // extension. This is useful because webRequestBlocking is restricted to + // policy-installed extensions in Manifest V3. + // This assumes the extension script will send a "ready" message once it's + // done setting up. + const Extension* LoadPolicyExtension(TestExtensionDir& test_dir) { + // We need a "ready"-style listener here because `InstallExtension()` + // doesn't automagically wait for the extension to finish setting up. + ExtensionTestMessageListener listener("ready"); + // Since we may programmatically stop the worker, we also need to wait for + // the registration to be fully stored. + service_worker_test_utils::TestRegistrationObserver registration_observer( + profile()); + base::FilePath packed_path = test_dir.Pack(); + const Extension* extension = InstallExtension( + packed_path, 1, mojom::ManifestLocation::kExternalPolicyDownload); + EXPECT_TRUE(extension); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + registration_observer.WaitForRegistrationStored(); + + return extension; + } + + ExtensionWebRequestEventRouter* web_request_router() { + return ExtensionWebRequestEventRouter::GetInstance(); + } +}; + +// Tests a service worker-based extension intercepting requests with +// webRequestBlocking. +IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, WebRequestBlocking) { + ASSERT_TRUE(StartEmbeddedTestServer()); + static constexpr char kManifest[] = + R"({ + "name": "MV3 WebRequest", + "version": "0.1", + "manifest_version": 3, + "permissions": ["webRequest", "webRequestBlocking"], + "host_permissions": [ + "http://block.example/*", + "http://allow.example/*" + ], + "background": {"service_worker": "background.js"} + })"; + // An extension with a listener that cancels any requests that include + // block.example. + static constexpr char kBackgroundJs[] = + R"(chrome.webRequest.onBeforeRequest.addListener( + (details) => { + if (details.url.includes('block.example')) { + return {cancel: true} + } + return {}; + }, + {urls: ['<all_urls>'], types: ['main_frame']}, + ['blocking']); + chrome.test.sendMessage('ready');)"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); + const Extension* extension = LoadPolicyExtension(test_dir); + ASSERT_TRUE(extension); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + // Navigate to allow.example. This should succeed. + { + content::TestNavigationObserver nav_observer(web_contents); + EXPECT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("allow.example", "/simple.html"))); + EXPECT_EQ(net::OK, nav_observer.last_net_error_code()); + } + + // Now, navigate to block.example. This navigation should be blocked. + { + content::TestNavigationObserver nav_observer(web_contents); + EXPECT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("block.example", "/simple.html"))); + EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code()); + } +} + +// Tests a service worker-based extension registering multiple webRequest events +// in multiple contexts. This ensures the subevent name logic for service worker +// extensions doesn't result in any collisions of listener IDs, similar to the +// issue found in https://crbug.com/1297276. +IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, + MultipleListenersAndContexts) { + ASSERT_TRUE(StartEmbeddedTestServer()); + static constexpr char kManifest[] = + R"({ + "name": "MV3 WebRequest", + "version": "0.1", + "manifest_version": 3, + "permissions": ["webRequest", "storage"], + "host_permissions": [ + "http://first.example/*", + "http://second.example/*", + "http://third.example/*" + ], + "background": {"service_worker": "background.js"} + })"; + // The extension has two contexts: the background service worker (which + // registers two listeners) and a separate page (which also registers a + // listener). This ensures that a) service worker listeners do not conflict + // with each other and b) service worker listeners do not conflict with + // listeners registered in other contexts. + static constexpr char kBackgroundJs[] = + R"(self.firstCount = 0; + self.secondCount = 0; + function firstListener() { ++firstCount; } + function secondListener() { ++secondCount; } + chrome.webRequest.onBeforeRequest.addListener( + firstListener, + {urls: ['http://first.example/*'], types: ['main_frame']}, []); + chrome.webRequest.onBeforeRequest.addListener( + secondListener, + {urls: ['http://second.example/*'], types: ['main_frame']}, []);)"; + static constexpr char kPageHtml[] = + R"(<!doctype html> + <html> + Page + <script src="page.js"></script> + </html>)"; + static constexpr char kPageJs[] = + R"(self.thirdCount = 0; + function thirdListener() { ++thirdCount; } + chrome.webRequest.onBeforeRequest.addListener( + thirdListener, + {urls: ['http://third.example/*'], types: ['main_frame']}, []);)"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); + test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPageHtml); + test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kPageJs); + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + // Load the page with the extension listeners. + content::RenderFrameHost* page_host = ui_test_utils::NavigateToURL( + browser(), extension->GetResourceURL("page.html")); + ASSERT_TRUE(page_host); + + // At this point, 3 listeners should be registered. + EXPECT_EQ(3u, web_request_router()->GetListenerCountForTesting( + profile(), "webRequest.onBeforeRequest")); + + // Convenience lambdas for checking the count received in each listener. + auto get_first_count = [this, extension]() { + return GetCountFromBackgroundPage(extension, profile(), "firstCount"); + }; + auto get_second_count = [this, extension]() { + return GetCountFromBackgroundPage(extension, profile(), "secondCount"); + }; + auto get_third_count = [page_host]() { + int count = -1; + EXPECT_TRUE(content::ExecuteScriptAndExtractInt( + page_host, "domAutomationController.send(window.thirdCount);", &count)); + return count; + }; + + // No listeners should have fired yet. + EXPECT_EQ(0, get_first_count()); + EXPECT_EQ(0, get_second_count()); + EXPECT_EQ(0, get_third_count()); + + // Navigate to first.example (this first navigation needs to happen in a new + // tab so that we don't navigate the extension page). + ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition( + browser(), + embedded_test_server()->GetURL("first.example", "/title1.html"), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); + + // (Only) the first listener should have fired. + EXPECT_EQ(1, get_first_count()); + EXPECT_EQ(0, get_second_count()); + EXPECT_EQ(0, get_third_count()); + + // Navigate to second.example. The second listener should fire. + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("second.example", "/title1.html"))); + EXPECT_EQ(1, get_first_count()); + EXPECT_EQ(1, get_second_count()); + EXPECT_EQ(0, get_third_count()); + + // Navigate to third.example. The third listener should fire. + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("third.example", "/title1.html"))); + EXPECT_EQ(1, get_first_count()); + EXPECT_EQ(1, get_second_count()); + EXPECT_EQ(1, get_third_count()); +} + +// Tests that a service worker-based extension with webRequestBlocking can +// intercept requests after the service worker stops. +IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, + WebRequestBlocking_AfterWorkerShutdown) { + ASSERT_TRUE(StartEmbeddedTestServer()); + static constexpr char kManifest[] = + R"({ + "name": "MV3 WebRequest", + "version": "0.1", + "manifest_version": 3, + "permissions": ["webRequest", "webRequestBlocking"], + "host_permissions": [ + "http://block.example/*" + ], + "background": {"service_worker": "background.js"} + })"; + // An extension with a listener that cancels any requests that include + // block.example. + static constexpr char kBackgroundJs[] = + R"(chrome.webRequest.onBeforeRequest.addListener( + (details) => { + if (details.url.includes('block.example')) { + return {cancel: true} + } + return {}; + }, + {urls: ['<all_urls>'], types: ['main_frame']}, + ['blocking']); + chrome.test.sendMessage('ready');)"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); + const Extension* extension = LoadPolicyExtension(test_dir); + ASSERT_TRUE(extension); + + // A single webRequest listener should be registered. + EXPECT_EQ(1u, web_request_router()->GetListenerCountForTesting( + profile(), "webRequest.onBeforeRequest")); + + // Stop the service worker. + browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(), + extension->id()); + // Note: the task to remove listeners from ExtensionWebRequestEventRouter + // is async; run to flush the posted task. + base::RunLoop().RunUntilIdle(); + + // The listener should still be registered. + // TODO(https://crbug.com/1024211): This currently fails. + // EXPECT_EQ(1u, web_request_router()->GetListenerCountForTesting( + // profile(), "webRequest.onBeforeRequest")); + EXPECT_EQ(0u, web_request_router()->GetListenerCountForTesting( + profile(), "webRequest.onBeforeRequest")); + + // Navigate to block.example. The request should be blocked by the extension. + { + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + content::TestNavigationObserver nav_observer(web_contents); + EXPECT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("block.example", "/simple.html"))); + // TODO(https://crbug.com/1024211): This currently fails. + // EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, + // nav_observer.last_net_error_code()); + EXPECT_EQ(net::OK, nav_observer.last_net_error_code()); + } +} + +// Tests a service worker-based extension using webRequest for observational +// purposes receives events after the worker stops. +IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, + WebRequestObservation_AfterWorkerShutdown) { + ASSERT_TRUE(StartEmbeddedTestServer()); + static constexpr char kManifest[] = + R"({ + "name": "MV3 WebRequest", + "version": "0.1", + "manifest_version": 3, + "permissions": ["webRequest", "storage"], + "host_permissions": [ + "http://example.com/*" + ], + "background": {"service_worker": "background.js"} + })"; + // An extension that stores the number of matched requests in a count in + // extension storage. + // This is very similar to the test extension at + // chrome/test/data/extensions/api_test/webrequest_persistent, but is + // manifest V3. There's enough changes that our loading auto-conversion code + // won't quite work (mostly around permissions vs host_permissions), so we + // need a bit of a duplication here. + static constexpr char kBackgroundJs[] = + R"(let storageComplete = undefined; + let isUsingStorage = false; + + // Waits for any pending load to complete to avoid raciness in the + // test. + async function flushStorage() { + console.assert(!storageComplete); + if (!isUsingStorage) + return; + await new Promise((resolve) => { + storageComplete = resolve; + }); + storageComplete = undefined; + } + + chrome.webRequest.onBeforeRequest.addListener( + async (details) => { + isUsingStorage = true; + let {requestCount} = + await chrome.storage.local.get({requestCount: 0}); + requestCount++; + await chrome.storage.local.set({requestCount}); + isUsingStorage = false; + if (storageComplete) + storageComplete(); + }, + {urls: ['<all_urls>'], types: ['main_frame']});)"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); + const Extension* extension = LoadExtension( + test_dir.UnpackedPath(), {.wait_for_registration_stored = true}); + ASSERT_TRUE(extension); + + // A single listener should be registered. + EXPECT_EQ(1u, web_request_router()->GetListenerCountForTesting( + profile(), "webRequest.onBeforeRequest")); + + // Navigate to a URL. The request should be seen by the extension. + EXPECT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("example.com", "/simple.html"))); + + auto get_request_count = [this, extension]() { + base::Value request_count = BackgroundScriptExecutor::ExecuteScript( + profile(), extension->id(), kGetNumRequests, + BackgroundScriptExecutor::ResultCapture::kSendScriptResult); + return request_count.GetInt(); + }; + + EXPECT_EQ(1, get_request_count()); + + // Stop the extension's service worker. + browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(), + extension->id()); + // Note: the task to remove listeners from ExtensionWebRequestEventRouter + // is async; run to flush the posted task. + base::RunLoop().RunUntilIdle(); + + // The listener should still be registered. + // TODO(https://crbug.com/1024211): This currently fails. + // EXPECT_EQ(1u, web_request_router()->GetListenerCountForTesting( + // profile(), "webRequest.onBeforeRequest")); + EXPECT_EQ(0u, web_request_router()->GetListenerCountForTesting( + profile(), "webRequest.onBeforeRequest")); + + // Navigate again. The request should again be seen by the extension. + EXPECT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("example.com", "/simple.html"))); + + // TODO(https://crbug.com/1024211): This currently fails. Additionally, + // BackgroundScriptExecutor won't wake up a passive service worker, so + // we can't even call `get_request_count()`. + // EXPECT_EQ(2, get_request_count()); +} + } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc b/chromium/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc index 656354541fb..a3c79d1bf89 100644 --- a/chromium/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc +++ b/chromium/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc @@ -17,58 +17,6 @@ namespace extensions { -TEST(WebRequestEventDetailsTest, AllowlistedCopyForPublicSession) { - // Create original, and populate it with some values. - std::unique_ptr<WebRequestEventDetails> orig(new WebRequestEventDetails); - - const char* const safe_attributes[] = { - "method", "requestId", "timeStamp", "type", "tabId", "frameId", - "parentFrameId", "fromCache", "error", "ip", "statusLine", "statusCode" - }; - - orig->render_process_id_ = 1; - orig->extra_info_spec_ = 3; - - orig->request_body_ = std::make_unique<base::DictionaryValue>(); - orig->request_headers_ = std::make_unique<base::ListValue>(); - orig->response_headers_ = std::make_unique<base::ListValue>(); - - for (const char* safe_attr : safe_attributes) { - orig->dict_.SetStringKey(safe_attr, safe_attr); - } - - orig->dict_.SetStringKey("url", "http://www.foo.bar/baz"); - - // Add some extra dict_ values that should be filtered out. - orig->dict_.SetStringKey("requestBody", "request body value"); - orig->dict_.SetStringKey("requestHeaders", "request headers value"); - - // Get a filtered copy then check that filtering really works. - std::unique_ptr<WebRequestEventDetails> copy = - orig->CreatePublicSessionCopy(); - - EXPECT_EQ(orig->render_process_id_, copy->render_process_id_); - EXPECT_EQ(0, copy->extra_info_spec_); - - EXPECT_EQ(nullptr, copy->request_body_); - EXPECT_EQ(nullptr, copy->request_headers_); - EXPECT_EQ(nullptr, copy->response_headers_); - - for (const char* safe_attr : safe_attributes) { - std::string copy_str; - copy->dict_.GetString(safe_attr, ©_str); - EXPECT_EQ(safe_attr, copy_str); - } - - // URL is stripped down to origin. - std::string url; - copy->dict_.GetString("url", &url); - EXPECT_EQ("http://www.foo.bar/", url); - - // Extras are filtered out (+1 for url). - EXPECT_EQ(std::size(safe_attributes) + 1, copy->dict_.DictSize()); -} - TEST(WebRequestEventDetailsTest, SetResponseHeaders) { const int kFilter = extension_web_request_api_helpers::ExtraInfoSpec::RESPONSE_HEADERS; 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 25fa8187a1a..74c46ab9f9f 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 @@ -11,7 +11,6 @@ #include "chrome/browser/extensions/extension_service_test_base.h" #include "chrome/common/extensions/extension_test_util.h" #include "chrome/common/url_constants.h" -#include "chromeos/login/login_state/scoped_test_public_session_login_state.h" #include "content/public/test/browser_task_environment.h" #include "extensions/browser/api/web_request/permission_helper.h" #include "extensions/browser/api/web_request/web_request_info.h" @@ -23,10 +22,6 @@ #include "services/network/public/mojom/fetch_api.mojom-shared.h" #include "testing/gtest/include/gtest/gtest.h" -#if BUILDFLAG(IS_CHROMEOS_ASH) -#include "chromeos/login/login_state/login_state.h" -#endif // BUILDFLAG(IS_CHROMEOS_ASH) - using extension_test_util::LoadManifestUnchecked; using extensions::Extension; using extensions::ExtensionRegistry; @@ -237,11 +232,8 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, false, // crosses_incognito WebRequestPermissions::REQUIRE_ALL_URLS, initiator, kWebRequestType)); - // Public Sessions tests. -#if BUILDFLAG(IS_CHROMEOS_ASH) - const GURL org_url("http://example.org"); - // com_extension_ doesn't have host permission for .org URLs. + const GURL org_url("http://example.org"); EXPECT_EQ(PermissionsData::PageAccess::kDenied, WebRequestPermissions::CanExtensionAccessURL( permission_helper_, com_policy_extension_->id(), org_url, @@ -250,29 +242,8 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, absl::nullopt, kWebRequestType)); - chromeos::ScopedTestPublicSessionLoginState login_state; - - // Host permission checks are disabled in Public Sessions, instead all URLs - // are allowlisted. - EXPECT_EQ(PermissionsData::PageAccess::kAllowed, - WebRequestPermissions::CanExtensionAccessURL( - permission_helper_, com_policy_extension_->id(), org_url, - -1, // No tab id. - false, // crosses_incognito - WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, - absl::nullopt, kWebRequestType)); - - EXPECT_EQ(PermissionsData::PageAccess::kAllowed, - WebRequestPermissions::CanExtensionAccessURL( - permission_helper_, com_policy_extension_->id(), org_url, - -1, // No tab id. - false, // crosses_incognito - WebRequestPermissions::REQUIRE_ALL_URLS, absl::nullopt, - kWebRequestType)); - // Make sure that chrome:// URLs cannot be accessed. const GURL chrome_url("chrome://version/"); - EXPECT_EQ(PermissionsData::PageAccess::kDenied, WebRequestPermissions::CanExtensionAccessURL( permission_helper_, com_policy_extension_->id(), chrome_url, @@ -280,5 +251,4 @@ TEST_F(ExtensionWebRequestHelpersTestWithThreadsTest, false, // crosses_incognito WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL, absl::nullopt, kWebRequestType)); -#endif } 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 875c5a0846d..c0410fc00c5 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 @@ -93,7 +93,7 @@ void WebrtcAudioPrivateEventService::SignalEvent() { extension->permissions_data()->HasAPIPermission("webrtcAudioPrivate")) { std::unique_ptr<Event> event = std::make_unique<Event>(events::WEBRTC_AUDIO_PRIVATE_ON_SINKS_CHANGED, - kEventName, std::vector<base::Value>()); + kEventName, base::Value::List()); router->DispatchEventToExtension(extension_id, std::move(event)); } } 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 5fd89424dae..c250d738ab9 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 @@ -158,9 +158,9 @@ IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetSinks) { ++ix, ++it) { const base::Value& value = sink_list.GetListDeprecated()[ix]; EXPECT_TRUE(value.is_dict()); - const base::DictionaryValue& dict = base::Value::AsDictionaryValue(value); - std::string sink_id; - dict.GetString("sinkId", &sink_id); + const base::Value::Dict& dict = value.GetDict(); + const std::string* sink_id = dict.FindString("sinkId"); + EXPECT_TRUE(sink_id); std::string expected_id = media::AudioDeviceDescription::IsDefaultDevice(it->unique_id) @@ -170,16 +170,16 @@ IN_PROC_BROWSER_TEST_F(WebrtcAudioPrivateTest, GetSinks) { url::Origin::Create(source_url_.DeprecatedGetOriginAsURL()), it->unique_id); - EXPECT_EQ(expected_id, sink_id); - std::string sink_label; - dict.GetString("sinkLabel", &sink_label); - EXPECT_EQ(it->device_name, sink_label); + EXPECT_EQ(expected_id, *sink_id); + const std::string* sink_label = dict.FindString("sinkLabel"); + EXPECT_TRUE(sink_label); + EXPECT_EQ(it->device_name, *sink_label); // TODO(joi): Verify the contents of these once we start actually // filling them in. - EXPECT_TRUE(dict.FindKey("isDefault")); - EXPECT_TRUE(dict.FindKey("isReady")); - EXPECT_TRUE(dict.FindKey("sampleRate")); + EXPECT_TRUE(dict.Find("isDefault")); + EXPECT_TRUE(dict.Find("isReady")); + EXPECT_TRUE(dict.Find("sampleRate")); } } #endif // BUILDFLAG(IS_MAC) diff --git a/chromium/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc b/chromium/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc index fcfbf296c6b..0d045b7d99a 100644 --- a/chromium/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc +++ b/chromium/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc @@ -70,7 +70,10 @@ WebrtcDesktopCapturePrivateChooseDesktopMediaFunction::Run() { using Sources = std::vector<api::desktop_capture::DesktopCaptureSourceType>; Sources* sources = reinterpret_cast<Sources*>(¶ms->sources); - return Execute(*sources, rfh, origin, target_name); + + // TODO(crbug.com/1329129): Plumb systemAudio through here. + return Execute(*sources, /*exclude_system_audio=*/false, rfh, origin, + target_name); } WebrtcDesktopCapturePrivateCancelChooseDesktopMediaFunction:: diff --git a/chromium/chrome/browser/extensions/api/webstore_private/extension_install_status.cc b/chromium/chrome/browser/extensions/api/webstore_private/extension_install_status.cc index f550a6735cb..e6c4bfbd714 100644 --- a/chromium/chrome/browser/extensions/api/webstore_private/extension_install_status.cc +++ b/chromium/chrome/browser/extensions/api/webstore_private/extension_install_status.cc @@ -134,8 +134,8 @@ ExtensionInstallStatus GetWebstoreExtensionInstallStatus( return kBlockedByPolicy; if (profile->GetPrefs() - ->GetDictionary(prefs::kCloudExtensionRequestIds) - ->FindKey(extension_id)) { + ->GetValueDict(prefs::kCloudExtensionRequestIds) + .Find(extension_id)) { return kRequestPending; } 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 87bbf05ef0e..695efd4d4bc 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 @@ -40,7 +40,7 @@ #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/signin/identity_manager_factory.h" #include "chrome/browser/ui/app_list/app_list_util.h" -#include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/browser/ui/extensions/extensions_dialogs.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" @@ -91,7 +91,6 @@ namespace IsPendingCustodianApproval = api::webstore_private::IsPendingCustodianApproval; namespace IsInIncognitoMode = api::webstore_private::IsInIncognitoMode; namespace LaunchEphemeralApp = api::webstore_private::LaunchEphemeralApp; -namespace RequestExtension = api::webstore_private::RequestExtension; namespace SetStoreLogin = api::webstore_private::SetStoreLogin; namespace { @@ -243,9 +242,9 @@ void ShowBlockedByParentDialog(const Extension* extension, return; } - chrome::ShowExtensionInstallBlockedByParentDialog( - chrome::ExtensionInstalledBlockedByParentDialogAction::kAdd, extension, - contents, std::move(done_callback)); + extensions::ShowExtensionInstallBlockedByParentDialog( + extensions::ExtensionInstalledBlockedByParentDialogAction::kAdd, + extension, contents, std::move(done_callback)); } #endif // BUILDFLAG(ENABLE_SUPERVISED_USERS) @@ -871,7 +870,7 @@ bool WebstorePrivateBeginInstallWithManifest3Function::ShouldShowFrictionDialog( void WebstorePrivateBeginInstallWithManifest3Function:: ShowInstallFrictionDialog(content::WebContents* contents) { friction_dialog_shown_ = true; - chrome::ShowExtensionInstallFrictionDialog( + ShowExtensionInstallFrictionDialog( contents, base::BindOnce(&WebstorePrivateBeginInstallWithManifest3Function:: OnFrictionPromptDone, @@ -933,9 +932,9 @@ void WebstorePrivateBeginInstallWithManifest3Function:: return; } - chrome::ShowExtensionInstallBlockedDialog( - extension->id(), extension->name(), blocked_by_policy_error_message_, - image, contents, std::move(done_callback)); + ShowExtensionInstallBlockedDialog(extension->id(), extension->name(), + blocked_by_policy_error_message_, image, + contents, std::move(done_callback)); } WebstorePrivateCompleteInstallFunction:: @@ -1294,7 +1293,7 @@ WebstorePrivateGetExtensionStatusFunction::BuildResponseWithoutManifest( void WebstorePrivateGetExtensionStatusFunction::OnManifestParsed( const ExtensionId& extension_id, data_decoder::DataDecoder::ValueOrError result) { - if (!result.value || !result.value->is_dict()) { + if (!result.has_value() || !result->is_dict()) { Respond(Error(kWebstoreInvalidManifestError)); return; } @@ -1307,7 +1306,7 @@ void WebstorePrivateGetExtensionStatusFunction::OnManifestParsed( std::string error; auto dummy_extension = Extension::Create(base::FilePath(), mojom::ManifestLocation::kInternal, - base::Value::AsDictionaryValue(*result.value), + base::Value::AsDictionaryValue(*result), Extension::FROM_WEBSTORE, extension_id, &error); if (!dummy_extension) { @@ -1323,30 +1322,4 @@ void WebstorePrivateGetExtensionStatusFunction::OnManifestParsed( Respond(ArgumentList(GetExtensionStatus::Results::Create(api_status))); } -WebstorePrivateRequestExtensionFunction:: - WebstorePrivateRequestExtensionFunction() = default; -WebstorePrivateRequestExtensionFunction:: - ~WebstorePrivateRequestExtensionFunction() = default; - -ExtensionFunction::ResponseAction -WebstorePrivateRequestExtensionFunction::Run() { - std::unique_ptr<RequestExtension::Params> params( - RequestExtension::Params::Create(args())); - EXTENSION_FUNCTION_VALIDATE(params); - - const ExtensionId& extension_id = params->id; - - if (!crx_file::id_util::IdIsValid(extension_id)) - return RespondNow(Error(kWebstoreInvalidIdError)); - - Profile* profile = Profile::FromBrowserContext(browser_context()); - ExtensionInstallStatus status = - AddExtensionToPendingList(extension_id, profile, std::string()); - - api::webstore_private::ExtensionInstallStatus api_status = - ConvertExtensionInstallStatusForAPI(status); - return RespondNow( - ArgumentList(RequestExtension::Results::Create(api_status))); -} - } // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.h b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.h index 5a760f8490c..d5fc82d75f3 100644 --- a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.h +++ b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_api.h @@ -391,24 +391,6 @@ class WebstorePrivateGetExtensionStatusFunction : public ExtensionFunction { ExtensionFunction::ResponseAction Run() override; }; -class WebstorePrivateRequestExtensionFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("webstorePrivate.requestExtension", - WEBSTOREPRIVATE_REQUESTEXTENSION) - WebstorePrivateRequestExtensionFunction(); - - WebstorePrivateRequestExtensionFunction( - const WebstorePrivateRequestExtensionFunction&) = delete; - WebstorePrivateRequestExtensionFunction& operator=( - const WebstorePrivateRequestExtensionFunction&) = delete; - - private: - ~WebstorePrivateRequestExtensionFunction() override; - - // Extensionfunction: - ExtensionFunction::ResponseAction Run() override; -}; - } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_PRIVATE_WEBSTORE_PRIVATE_API_H_ 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 4d99658fb24..fca3f124bc8 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 @@ -598,7 +598,13 @@ class ExtensionWebstoreGetWebGLStatusTest : public InProcessBrowserTest { }; // Tests getWebGLStatus function when WebGL is allowed. -IN_PROC_BROWSER_TEST_F(ExtensionWebstoreGetWebGLStatusTest, Allowed) { +// Flaky on Mac. https://crbug.com/1346413. +#if BUILDFLAG(IS_MAC) +#define MAYBE_Allowed DISABLED_Allowed +#else +#define MAYBE_Allowed Allowed +#endif +IN_PROC_BROWSER_TEST_F(ExtensionWebstoreGetWebGLStatusTest, MAYBE_Allowed) { bool webgl_allowed = true; RunTest(webgl_allowed); } diff --git a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_unittest.cc b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_unittest.cc index ef7483b7ab9..67fd9d3319d 100644 --- a/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_unittest.cc +++ b/chromium/chrome/browser/extensions/api/webstore_private/webstore_private_unittest.cc @@ -35,7 +35,6 @@ namespace extensions { namespace { constexpr char kInvalidId[] = "Invalid id"; constexpr char kExtensionId[] = "abcdefghijklmnopabcdefghijklmnop"; -constexpr int kFakeTime = 12345; constexpr char kFakeJustification[] = "I need it!"; constexpr char kExtensionManifest[] = R"({ \"name\" : \"Extension\", @@ -56,18 +55,6 @@ constexpr char kBlockOneExtensionSettings[] = R"({ } })"; -constexpr char kAllowedExtensionSettings[] = R"({ - "abcdefghijklmnopabcdefghijklmnop" : { - "installation_mode": "allowed" - } -})"; - -constexpr char kBlockedExtensionSettings[] = R"({ - "abcdefghijklmnopabcdefghijklmnop" : { - "installation_mode": "blocked" - } -})"; - constexpr char kBlockedManifestTypeExtensionSettings[] = R"({ "*": { "allowed_types": ["theme", "hosted_app"] @@ -90,10 +77,6 @@ constexpr char kWebstoreUserCancelledError[] = "User cancelled install"; constexpr char kWebstoreBlockByPolicy[] = "Extension installation is blocked by policy"; -base::Time GetFaketime() { - return base::Time::FromJavaTime(kFakeTime); -} - // Helper test struct used for holding data related to extension requests. struct ExtensionRequestData { explicit ExtensionRequestData(base::Time timestamp) @@ -112,13 +95,12 @@ struct ExtensionRequestData { void VerifyPendingList(const std::map<ExtensionId, ExtensionRequestData>& expected_pending_requests, Profile* profile) { - const base::Value* actual_pending_requests = - profile->GetPrefs()->GetDictionary(prefs::kCloudExtensionRequestIds); - ASSERT_EQ(expected_pending_requests.size(), - actual_pending_requests->DictSize()); + const base::Value::Dict& actual_pending_requests = + profile->GetPrefs()->GetValueDict(prefs::kCloudExtensionRequestIds); + ASSERT_EQ(expected_pending_requests.size(), actual_pending_requests.size()); for (const auto& expected_request : expected_pending_requests) { auto* actual_pending_request = - actual_pending_requests->FindKey(expected_request.first); + actual_pending_requests.Find(expected_request.first); ASSERT_NE(nullptr, actual_pending_request); // All extensions in the pending list are expected to have a timestamp. @@ -256,106 +238,6 @@ TEST_F(WebstorePrivateGetExtensionStatusTest, response.get()); } -class WebstorePrivateRequestExtensionTest - : public WebstorePrivateExtensionInstallRequestBase { - public: - WebstorePrivateRequestExtensionTest() = default; - - void SetUp() override { - WebstorePrivateExtensionInstallRequestBase::SetUp(); - profile()->GetTestingPrefService()->SetManagedPref( - prefs::kCloudExtensionRequestEnabled, - std::make_unique<base::Value>(true)); - VerifyPendingList({}, profile()); - } - - void SetPendingList(const std::vector<ExtensionId>& ids) { - std::unique_ptr<base::Value> id_values = - std::make_unique<base::Value>(base::Value::Type::DICTIONARY); - for (const auto& id : ids) { - base::Value request_data(base::Value::Type::DICTIONARY); - request_data.SetKey(extension_misc::kExtensionRequestTimestamp, - ::base::TimeToValue(GetFaketime())); - id_values->SetKey(id, std::move(request_data)); - } - profile()->GetTestingPrefService()->SetUserPref( - prefs::kCloudExtensionRequestIds, std::move(id_values)); - } - -}; - -TEST_F(WebstorePrivateRequestExtensionTest, InvalidExtensionId) { - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - EXPECT_EQ(kInvalidId, - RunFunctionAndReturnError(function.get(), - GenerateArgs("invalid-extension-id"))); - VerifyPendingList({}, profile()); -} - -TEST_F(WebstorePrivateRequestExtensionTest, UnrequestableExtension) { - ExtensionRegistry::Get(profile())->AddEnabled(CreateExtension(kExtensionId)); - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - std::unique_ptr<base::Value> response = - RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId)); - VerifyResponse(ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_ENABLED, - response.get()); - VerifyPendingList({}, profile()); -} - -TEST_F(WebstorePrivateRequestExtensionTest, AlreadyApprovedExtension) { - SetExtensionSettings(kAllowedExtensionSettings, profile()); - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - std::unique_ptr<base::Value> response = - RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId)); - VerifyResponse(ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_INSTALLABLE, - response.get()); - VerifyPendingList({{kExtensionId, ExtensionRequestData(base::Time::Now())}}, - profile()); -} - -TEST_F(WebstorePrivateRequestExtensionTest, AlreadyRejectedExtension) { - SetExtensionSettings(kBlockedExtensionSettings, profile()); - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - std::unique_ptr<base::Value> response = - RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId)); - VerifyResponse( - ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_BLOCKED_BY_POLICY, - response.get()); - VerifyPendingList({{kExtensionId, ExtensionRequestData(base::Time::Now())}}, - profile()); -} - -TEST_F(WebstorePrivateRequestExtensionTest, AlreadyPendingExtension) { - SetPendingList({kExtensionId}); - VerifyPendingList({{kExtensionId, ExtensionRequestData(GetFaketime())}}, - profile()); - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - std::unique_ptr<base::Value> response = - RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId)); - VerifyResponse( - ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_REQUEST_PENDING, - response.get()); - VerifyPendingList({{kExtensionId, ExtensionRequestData(GetFaketime())}}, - profile()); -} - -TEST_F(WebstorePrivateRequestExtensionTest, RequestExtension) { - auto function = - base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>(); - std::unique_ptr<base::Value> response = - RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId)); - VerifyResponse( - ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_REQUEST_PENDING, - response.get()); - VerifyPendingList({{kExtensionId, ExtensionRequestData(base::Time::Now())}}, - profile()); -} - class WebstorePrivateBeginInstallWithManifest3Test : public ExtensionApiUnittest { public: |