diff --git a/DEPS b/DEPS index dc12b73e..1daa808b 100644 --- a/DEPS +++ b/DEPS
@@ -295,15 +295,15 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'src_internal_revision': '7033656091f15c89917d3fd040c03dd346684c3b', + 'src_internal_revision': '4c66676ccee672657c3b79f4321f6ce3b6fd9a32', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': 'a522cbe99752e25c919c985f53c388111f71fbeb', + 'skia_revision': '4e2d1834d3515c28e00dc20f3f9459e198a1ecaf', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '54cfc6784891b42308c9936263c13ca552f95ee9', + 'v8_revision': '340b39612320d80ca2afbf0467b472941aa031a6', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -315,11 +315,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': 'a9fd58f6fb0de203c721033bb06709770a2871bc', + 'pdfium_revision': '0be9b6bf152df319d17d9e4b05b2845d3226f5a5', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. - 'boringssl_revision': '2a514a51baebd5a232fc64f7b082f7a8b28cd29d', + 'boringssl_revision': '89973806bc2ef652189e157f2736a7d32229c404', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. @@ -371,7 +371,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '83c00a37f40a93932204f684172a13c62a379e7e', + 'catapult_revision': '5da8152ef93c63ea38e38c3ce1d9791b7e529b4d', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -391,7 +391,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': 'c56793a7e9ddabd1ff60ab33b100bee65475b50a', + 'devtools_frontend_revision': 'a27ccc3f91f79fc39b9c2d3c7d67069a211ffffb', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -439,7 +439,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling jetstream-main # and whatever else without interference from each other. - 'jetstream_main_revision': '19a8be2645191e2b149c8bd681cc7941678bd754', + 'jetstream_main_revision': '539ab943598b505832a25a2222aa8957f1a20d6f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling jetstream-v2.2 # and whatever else without interference from each other. @@ -499,7 +499,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'libunwind_revision': '2bd5f3cae13e8ad6727d0a77a2ec9cdc6b06becb', + 'libunwind_revision': '8575f4ae4fcf8892938bd9766cf1a5c90a0ed04e', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -822,10 +822,10 @@ 'condition': 'non_git_source', 'objects': [ { - 'object_name': '311eec6c718124cfa732a37a817cae28fce10adc', - 'sha256sum': 'b593e9bb1a2be2a5d6847bf70a0973de861496e1d46d77e61897641ddf5972cd', - 'size_bytes': 9623904, - 'generation': 1745950569344494, + 'object_name': '13eea9a3163f9e91f343e6a3197079986d7276b2', + 'sha256sum': 'd347ed7e5ef83e062d1044df222448e6857aa40d9ac6dace45591dc02f9c61b7', + 'size_bytes': 9650582, + 'generation': 1747160834666855, 'output_file': 'node_modules.tar.gz', }, ], @@ -1486,7 +1486,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '3a123c3809dc9420b94f4a54d3beae45eb56567b', + '055a0b8adbd2767a69d3a83093a386fcdbe41228', 'condition': 'checkout_android and checkout_src_internal', }, @@ -1716,7 +1716,7 @@ 'packages': [ { 'package': 'chromium/third_party/android_build_tools/error_prone', - 'version': 'FLtgIzcLepAc87BaSXIELZIJPT4yxnr8K3MmFaEHrK8C', + 'version': 'bd18Z8-1Y85m4C8xYA3z1J_VIhAaE2qRbkrBzn4_200C', }, ], 'condition': 'checkout_android and non_git_source', @@ -1738,7 +1738,7 @@ 'packages': [ { 'package': 'chromium/third_party/android_build_tools/lint', - 'version': 'QltgzNfGb4l4ekv1_GjIWtuew1hrBof1WVy2B8zrkYwC', + 'version': '7xZFl5M3w3LIJvhKsJYDQoTh5JZ3vr3I_-VWYXiGJC0C', }, ], 'condition': 'checkout_android and non_git_source', @@ -1749,7 +1749,7 @@ 'packages': [ { 'package': 'chromium/third_party/android_build_tools/manifest_merger', - 'version': 'hVPp69VTDru05sisN9HcrOf67Tk_Mhu6P6bsR24gOrEC', + 'version': 'Ym1eWXywKFRvxsgK0yC4bFj9SwZViDqGEPb_w3mAKqMC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1948,7 +1948,7 @@ # Tools used when building Chrome for Chrome OS. This affects both the Simple # Chrome workflow, as well as the chromeos-chrome ebuild. 'src/third_party/chromite': { - 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7ac1ad382fe7fe238e0785e62cc3c6d70b4685f7', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '48aa362d807edeb3393b18741c055f55772c2f0d', 'condition': 'checkout_chromeos', }, @@ -2229,7 +2229,7 @@ 'packages': [ { 'package': 'chromium/third_party/kotlin_stdlib', - 'version': 'aHCAN6w_ZZCJL7MOPlDbEieRsiUs6ftT33fCI4bbwVwC', + 'version': 'eYKSu9agsqusPORDTTSPHcg632V_j9kIvvufVJiULXIC', }, ], 'condition': 'checkout_android and non_git_source', @@ -2380,7 +2380,7 @@ Var('chromium_git') + '/webm/libvpx.git' + '@' + '6bb288633613c613c87d23b32e5f1d23102b1a43', 'src/third_party/libwebm/source': - Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e79a98159fdf6d1aa37b3500e32c6410a2cbe268', + Var('chromium_git') + '/webm/libwebm.git' + '@' + 'c4522d6cd68582d66f1adfd24debfa9bee202afa', 'src/third_party/libwebp/src': Var('chromium_git') + '/webm/libwebp.git' + '@' + '2af6c034ac871c967e04c8c9f8bf2dbc2e271b18', @@ -2526,7 +2526,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '060d748587295617a22efdc6e6336997deb250cb', + Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '9b47377deba086351a83567e04cc06f8c18996d0', 'src/base/tracing/test/data': { 'bucket': 'perfetto', @@ -2840,7 +2840,7 @@ 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3bb9a2c5d2ef0dc54ae9697f8a6e628cc06b9fd3', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@9e7c7717aab8bf9d8ad4794923488d64bdc2119b', 'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@963588074b26326ff0426c8953c1235213309bdb', 'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3', 'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@6d0784e9f1ab92c17eeea94821b2465c14a52be9', @@ -2849,7 +2849,7 @@ 'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@a8bec310845ce80af5c00342243ae972cbe95e3b', 'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@60b640cb931814fcc6dabe4fc61f4738c56579f6', 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@4f628210460c4df62029959cc7fb237ac75f7189', - 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@7647300983abe4aa095dd74c2544c2cbe5906a7c', + 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@a9894996b6df1b58546df2c8c211ae69d7de8442', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21', @@ -3038,7 +3038,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'nY9G59FTeOPZnJKHltXYIURSniRePQ3NtPnHuMDRKSgC', + 'version': 'aCz4S-3WgZoMTncDKeH9RDYo5VBdBSE3tNBGIKVQQrkC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -4614,7 +4614,7 @@ 'src/components/optimization_guide/internal': { 'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' + - 'c69f1d7a8ad0ed4c22595719e1d78b7bb8754516', + '9b0ca6aa4bd15459531457f03ecd60090cd69ed6', 'condition': 'checkout_src_internal', },
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index fa001839..a6cee39 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn
@@ -3550,6 +3550,7 @@ "//components/vector_icons", "//components/viz/client", "//components/viz/host", + "//components/webapps/isolated_web_apps:isolated_web_apps", "//dbus", "//device/bluetooth", "//extensions/common:common_constants", @@ -4786,6 +4787,10 @@ "//components/user_manager:test_support", "//components/viz/host", "//components/viz/test:test_support", + + # TODO(crbug.com/417407946): Remove this dependency once the usage indicator + # code for screen capture is moved to `c/b/`. + "//components/webapps/isolated_web_apps:isolated_web_apps", "//device/bluetooth", "//device/bluetooth:mocks", "//device/udev_linux:test_support",
diff --git a/ash/DEPS b/ash/DEPS index af92c16..aebeeec 100644 --- a/ash/DEPS +++ b/ash/DEPS
@@ -41,6 +41,7 @@ "+components/user_education/views", "+components/variations", "+components/vector_icons", + "+components/webapps/isolated_web_apps", "+components/version_info", "+components/viz/common", "+components/viz/host",
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index 66c62ca5..fa2d299 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -297,6 +297,21 @@ "BocaSequentialSessionLoad", base::FEATURE_ENABLED_BY_DEFAULT); +// Enables the updated lock / pause ui for boca. +BASE_FEATURE(kBocaLockPauseUpdate, + "BocaLockPauseUpdate", + base::FEATURE_ENABLED_BY_DEFAULT); + +// Enables the updated nav settings ui for boca. +BASE_FEATURE(kBocaNavSettingsDialog, + "BocaNavSettingsDialog", + base::FEATURE_ENABLED_BY_DEFAULT); + +// Enables the new caption toggle button for boca. +BASE_FEATURE(kBocaCaptionToggle, + "BocaCaptionToggle", + base::FEATURE_ENABLED_BY_DEFAULT); + BASE_FEATURE(kCrosSwitcher, "CrosSwitcher", base::FEATURE_DISABLED_BY_DEFAULT); // Indicates whether the camera super resolution is supported. Note that this @@ -3536,6 +3551,18 @@ return base::FeatureList::IsEnabled(kBocaSequentialSessionLoad); } +bool IsBocaLockPauseUpdateEnabled() { + return base::FeatureList::IsEnabled(kBocaLockPauseUpdate); +} + +bool IsBocaNavSettingsDialogEnabled() { + return base::FeatureList::IsEnabled(kBocaNavSettingsDialog); +} + +bool IsBocaCaptionToggleEnabled() { + return base::FeatureList::IsEnabled(kBocaCaptionToggle); +} + bool IsBrightnessControlInSettingsEnabled() { return base::FeatureList::IsEnabled(kEnableBrightnessControlInSettings); }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h index 01183c38..61bbded8 100644 --- a/ash/constants/ash_features.h +++ b/ash/constants/ash_features.h
@@ -105,6 +105,9 @@ BASE_DECLARE_FEATURE(kBocaAdjustCaptionBubbleOnExpand); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBocaKeepSWAOpenOnSessionEnded); +COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBocaLockPauseUpdate); +COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBocaNavSettingsDialog); +COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBocaCaptionToggle); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCameraSuperResSupported); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCrosSwitcher); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBorealisBigGl); @@ -1110,6 +1113,9 @@ bool IsBocaKeepSWAOpenOnSessionEndedEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBocaSequentialSessionLoadEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBocaLockPauseUpdateEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBocaNavSettingsDialogEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBocaCaptionToggleEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBrightnessControlInSettingsEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeEducationEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS)
diff --git a/ash/multi_capture/multi_capture_service.cc b/ash/multi_capture/multi_capture_service.cc index 775f3705..1cff196f 100644 --- a/ash/multi_capture/multi_capture_service.cc +++ b/ash/multi_capture/multi_capture_service.cc
@@ -5,6 +5,7 @@ #include "ash/multi_capture/multi_capture_service.h" #include "base/logging.h" +#include "url/origin.h" namespace ash { @@ -30,9 +31,10 @@ void MultiCaptureService::NotifyMultiCaptureStartedFromApp( const std::string& label, const std::string& app_id, - const std::string& app_short_name) { + const std::string& app_short_name, + const url::Origin& app_origin) { observers_.Notify(&Observer::MultiCaptureStartedFromApp, label, app_id, - app_short_name); + app_short_name, app_origin); } void MultiCaptureService::NotifyMultiCaptureStopped(const std::string& label) {
diff --git a/ash/multi_capture/multi_capture_service.h b/ash/multi_capture/multi_capture_service.h index dbb83d8ec4..8b48b87 100644 --- a/ash/multi_capture/multi_capture_service.h +++ b/ash/multi_capture/multi_capture_service.h
@@ -33,10 +33,10 @@ // The `app_id` is the id of the calling app (as given in the app service) // and `app_short_name` is the short name of the app (derived from the app // manifest or the app title). - virtual void MultiCaptureStartedFromApp( - const std::string& label, - const std::string& app_id, - const std::string& app_short_name) = 0; + virtual void MultiCaptureStartedFromApp(const std::string& label, + const std::string& app_id, + const std::string& app_short_name, + const url::Origin& app_origin) = 0; virtual void MultiCaptureStopped(const std::string& label) = 0; virtual void MultiCaptureServiceDestroyed() = 0; @@ -56,7 +56,8 @@ const url::Origin& origin); void NotifyMultiCaptureStartedFromApp(const std::string& label, const std::string& app_id, - const std::string& app_short_name); + const std::string& app_short_name, + const url::Origin& app_origin); void NotifyMultiCaptureStopped(const std::string& label); private:
diff --git a/ash/system/DEPS b/ash/system/DEPS index eb0d29c..ed15870 100644 --- a/ash/system/DEPS +++ b/ash/system/DEPS
@@ -6,6 +6,7 @@ "+components/device_event_log/device_event_log.h", "+components/global_media_controls", "+components/live_caption", + "+components/webapps/isolated_web_apps", "+components/prefs", "+components/signin/public/identity_manager/identity_manager.h", "+grit/ui_chromeos_resources.h",
diff --git a/ash/system/unified/screen_capture_tray_item_view.cc b/ash/system/unified/screen_capture_tray_item_view.cc index 04bdbda6..2b912145 100644 --- a/ash/system/unified/screen_capture_tray_item_view.cc +++ b/ash/system/unified/screen_capture_tray_item_view.cc
@@ -4,22 +4,28 @@ #include "ash/system/unified/screen_capture_tray_item_view.h" +#include <algorithm> + #include "ash/multi_capture/multi_capture_service.h" #include "ash/resources/vector_icons/vector_icons.h" #include "ash/shell.h" #include "ash/strings/grit/ash_strings.h" #include "ash/style/ash_color_provider.h" #include "ash/system/tray/tray_constants.h" +#include "base/check_deref.h" +#include "base/containers/contains.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" #include "chromeos/constants/chromeos_features.h" #include "components/vector_icons/vector_icons.h" +#include "components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/gfx/paint_vector_icon.h" #include "ui/gfx/vector_icon_types.h" #include "ui/views/controls/image_view.h" +#include "url/origin.h" namespace { constexpr base::TimeDelta kMinimumTimedelta = base::Seconds(6); @@ -77,15 +83,23 @@ void ScreenCaptureTrayItemView::MultiCaptureStarted(const std::string& label, const url::Origin& origin) { - requests_.emplace(label, ScreenCaptureTrayItemMetadata()); - Refresh(); + // TODO(crbug.com/417490624): Remove `CHECK_DEREF` once `GetInstance()` + // returns a reference. + if (!base::Contains( + CHECK_DEREF(web_app::IwaKeyDistributionInfoProvider::GetInstance()) + .GetSkipMultiCaptureNotificationBundleIds(), + origin.host())) { + requests_.emplace(label, ScreenCaptureTrayItemMetadata()); + Refresh(); + } } void ScreenCaptureTrayItemView::MultiCaptureStartedFromApp( const std::string& label, const std::string& app_id, - const std::string& app_short_name) { - MultiCaptureStarted(label, /*origin=*/{}); + const std::string& app_short_name, + const url::Origin& app_origin) { + MultiCaptureStarted(label, app_origin); } void ScreenCaptureTrayItemView::MultiCaptureStopped(const std::string& label) {
diff --git a/ash/system/unified/screen_capture_tray_item_view.h b/ash/system/unified/screen_capture_tray_item_view.h index 55a7919c..7741c40 100644 --- a/ash/system/unified/screen_capture_tray_item_view.h +++ b/ash/system/unified/screen_capture_tray_item_view.h
@@ -69,7 +69,8 @@ const url::Origin& origin) override; void MultiCaptureStartedFromApp(const std::string& label, const std::string& app_id, - const std::string& app_short_name) override; + const std::string& app_short_name, + const url::Origin& app_origin) override; void MultiCaptureStopped(const std::string& label) override; void MultiCaptureServiceDestroyed() override; @@ -85,6 +86,12 @@ FRIEND_TEST_ALL_PREFIXES( ScreenCaptureTrayItemViewTest, MultiOriginCaptureStartedAndEarlyStoppedExpectedDelayedStoppedCallback); + FRIEND_TEST_ALL_PREFIXES( + ScreenCaptureTrayItemViewTest, + MultiOriginCaptureStartedNotificationSkipAllowlistedMixedOrigins); + FRIEND_TEST_ALL_PREFIXES( + ScreenCaptureTrayItemViewTest, + MultiOriginCaptureStartedNotificationSkipAllowlistedOneOrigin); std::map<std::string, ScreenCaptureTrayItemMetadata> requests_;
diff --git a/ash/system/unified/screen_capture_tray_item_view_unittest.cc b/ash/system/unified/screen_capture_tray_item_view_unittest.cc index 73ad4ad2..97d79ba 100644 --- a/ash/system/unified/screen_capture_tray_item_view_unittest.cc +++ b/ash/system/unified/screen_capture_tray_item_view_unittest.cc
@@ -2,15 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <memory> - #include "ash/system/unified/screen_capture_tray_item_view.h" +#include <memory> + #include "ash/shelf/shelf.h" #include "ash/test/ash_test_base.h" +#include "base/check_deref.h" #include "base/run_loop.h" #include "base/test/task_environment.h" #include "base/time/time.h" +#include "components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h" #include "testing/gmock/include/gmock/gmock.h" #include "url/origin.h" @@ -54,7 +56,8 @@ TEST_F(ScreenCaptureTrayItemViewTest, SingleOriginCaptureStartedAndStopped) { EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(true)); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_1", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_1", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example.com", /*port=*/443)); EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); @@ -68,17 +71,20 @@ TEST_F(ScreenCaptureTrayItemViewTest, MultiOriginCaptureStartedAndStopped) { EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(true)).Times(3); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_1", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_1", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example.com", /*port=*/443)); EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_2", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_2", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example2.com", /*port=*/443)); EXPECT_EQ(2u, screen_capture_tray_item_view_->requests_.size()); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_1", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_1", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example2.com", /*port=*/443)); EXPECT_EQ(2u, screen_capture_tray_item_view_->requests_.size()); @@ -97,17 +103,20 @@ MultiOriginCaptureStartedAndEarlyStoppedExpectedDelayedStoppedCallback) { EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(true)).Times(3); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_1", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_1", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example.com", /*port=*/443)); EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_2", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_2", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example2.com", /*port=*/443)); EXPECT_EQ(2u, screen_capture_tray_item_view_->requests_.size()); screen_capture_tray_item_view_->MultiCaptureStarted( - /*label=*/"label_1", /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*label=*/"label_1", /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example2.com", /*port=*/443)); EXPECT_EQ(2u, screen_capture_tray_item_view_->requests_.size()); @@ -124,4 +133,98 @@ EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); } +TEST_F(ScreenCaptureTrayItemViewTest, + MultiOriginCaptureStartedNotificationSkipAllowlistedOneOrigin) { + const url::Origin origin_with_allowlisted_exception = + url::Origin::CreateFromNormalizedTuple( + /*scheme=*/"isolated-app", + /*host=*/"aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic", + /*port=*/0); + CHECK_DEREF(web_app::IwaKeyDistributionInfoProvider::GetInstance()) + .SetComponentDataForTesting( + web_app::IwaKeyDistributionInfoProvider::ComponentData( + /*version=*/base::Version("1.0.0"), + /*key_rotations=*/{}, + /*version=*/ + {{origin_with_allowlisted_exception.host(), + {.skip_capture_started_notification = true}}}, + /*managed_allowlist=*/{}, + /*special_app_permissions=*/true)); + EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(true)).Times(0); + + std::vector<url::Origin> skip_notification_origins = { + origin_with_allowlisted_exception}; + + // This origin is exempt from showing the indicator and therefore doesn't + // add another entry. + screen_capture_tray_item_view_->MultiCaptureStarted( + /*label=*/"label_1", /*origin=*/ + origin_with_allowlisted_exception); + EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); + + task_environment()->FastForwardBy(minimal_tray_item_presence_time - + base::Milliseconds(1)); + EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); + + screen_capture_tray_item_view_->MultiCaptureStopped(/*label=*/"label_1"); + EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); + + EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(false)).Times(0); + task_environment()->FastForwardBy(base::Milliseconds(2)); + EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); +} + +TEST_F(ScreenCaptureTrayItemViewTest, + MultiOriginCaptureStartedNotificationSkipAllowlistedMixedOrigins) { + const url::Origin origin_with_allowlisted_exception = + url::Origin::CreateFromNormalizedTuple( + /*scheme=*/"isolated-app", + /*host=*/"aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic", + /*port=*/0); + CHECK_DEREF(web_app::IwaKeyDistributionInfoProvider::GetInstance()) + .SetComponentDataForTesting( + web_app::IwaKeyDistributionInfoProvider::ComponentData( + /*version=*/base::Version("1.0.0"), + /*key_rotations=*/{}, + /*version=*/ + {{origin_with_allowlisted_exception.host(), + {.skip_capture_started_notification = true}}}, + /*managed_allowlist=*/{}, + /*special_app_permissions=*/true)); + + EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(true)).Times(1); + const url::Origin origin_with_indicator = + url::Origin::CreateFromNormalizedTuple( + /*scheme=*/"https", /*host=*/"example.com", /*port=*/443); + std::vector<url::Origin> skip_notification_origins = { + origin_with_allowlisted_exception}; + + screen_capture_tray_item_view_->MultiCaptureStarted( + /*label=*/"label_1", /*origin=*/ + origin_with_indicator); + EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); + + // This origin is exempt from showing the indicator and therefore + // doesn't add another entry. + screen_capture_tray_item_view_->MultiCaptureStarted( + /*label=*/"label_2", /*origin=*/ + origin_with_allowlisted_exception); + EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); + + task_environment()->FastForwardBy(minimal_tray_item_presence_time - + base::Milliseconds(1)); + EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); + + screen_capture_tray_item_view_->MultiCaptureStopped( + /*label=*/"label_1"); + EXPECT_EQ(1u, screen_capture_tray_item_view_->requests_.size()); + + screen_capture_tray_item_view_->MultiCaptureStopped( + /*label=*/"label_2"); + + EXPECT_CALL(*screen_capture_tray_item_view_, SetVisible(false)); + task_environment()->FastForwardBy(base::Milliseconds(2)); + EXPECT_EQ(0u, screen_capture_tray_item_view_->requests_.size()); +} + } // namespace ash
diff --git a/base/BUILD.gn b/base/BUILD.gn index 01c19144..da0e0b64 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -5452,6 +5452,7 @@ "test/android/javatests/src/org/chromium/base/test/util/Criteria.java", "test/android/javatests/src/org/chromium/base/test/util/CriteriaHelper.java", "test/android/javatests/src/org/chromium/base/test/util/CriteriaNotSatisfiedException.java", + "test/android/javatests/src/org/chromium/base/test/util/DestroyableHolder.java", "test/android/javatests/src/org/chromium/base/test/util/DisableIf.java", "test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java", "test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java",
diff --git a/base/android/android_info.cc b/base/android/android_info.cc index 2b745c7f..e59b963 100644 --- a/base/android/android_info.cc +++ b/base/android/android_info.cc
@@ -15,6 +15,7 @@ #include "base/android/scoped_java_ref.h" #include "base/check.h" #include "base/compiler_specific.h" +#include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" // Must come after all headers that specialize FromJniType() / ToJniType(). @@ -25,126 +26,118 @@ namespace { struct AndroidInfo { - // Const char* is used instead of std::strings because these values must be - // available even if the process is in a crash state. Sadly - // std::string.c_str() doesn't guarantee that memory won't be allocated when - // it is called. - const char* device; + const std::string device; - const char* manufacturer; + const std::string manufacturer; - const char* model; + const std::string model; - const char* brand; + const std::string brand; - const char* android_build_id; + const std::string android_build_id; - const char* build_type; + const std::string build_type; - const char* board; + const std::string board; - const char* android_build_fp; + const std::string android_build_fp; int sdk_int; bool is_debug_android; - const char* version_incremental; + const std::string version_incremental; - const char* hardware; + const std::string hardware; - const char* codename; + const std::string codename; // Available only on android S+. For S-, this method returns empty string. - const char* soc_manufacturer; + const std::string soc_manufacturer; - const char* abi_name; + const std::string abi_name; }; -std::optional<AndroidInfo> holder; +static std::optional<AndroidInfo>& get_holder() { + static base::NoDestructor<std::optional<AndroidInfo>> holder; + return *holder; +} const AndroidInfo& get_android_info() { - [[maybe_unused]] static auto once = [] { + const std::optional<AndroidInfo>& holder = get_holder(); + if (!holder.has_value()) { Java_AndroidInfo_nativeReadyForFields(AttachCurrentThread()); - return std::monostate(); - }(); - // holder should be initialized as the java is supposed to call the native - // method FillFields which will initialize the fields within the holder. - DCHECK(holder.has_value()); + } return *holder; } } // namespace -static void JNI_AndroidInfo_FillFields( - JNIEnv* env, - const jni_zero::JavaParamRef<jstring>& brand, - const jni_zero::JavaParamRef<jstring>& device, - const jni_zero::JavaParamRef<jstring>& buildId, - const jni_zero::JavaParamRef<jstring>& manufacturer, - const jni_zero::JavaParamRef<jstring>& model, - const jni_zero::JavaParamRef<jstring>& type, - const jni_zero::JavaParamRef<jstring>& board, - const jni_zero::JavaParamRef<jstring>& androidBuildFingerprint, - const jni_zero::JavaParamRef<jstring>& versionIncremental, - const jni_zero::JavaParamRef<jstring>& hardware, - const jni_zero::JavaParamRef<jstring>& codeName, - const jni_zero::JavaParamRef<jstring>& socManufacturer, - const jni_zero::JavaParamRef<jstring>& supportedAbis, - jint sdkInt, - jboolean isDebugAndroid) { +static void JNI_AndroidInfo_FillFields(JNIEnv* env, + std::string& brand, + std::string& device, + std::string& buildId, + std::string& manufacturer, + std::string& model, + std::string& type, + std::string& board, + std::string& androidBuildFingerprint, + std::string& versionIncremental, + std::string& hardware, + std::string& codeName, + std::string& socManufacturer, + std::string& supportedAbis, + jint sdkInt, + jboolean isDebugAndroid) { + std::optional<AndroidInfo>& holder = get_holder(); DCHECK(!holder.has_value()); - auto java_string_to_const_char = - [](const jni_zero::JavaParamRef<jstring>& str) { - return UNSAFE_TODO(strdup(ConvertJavaStringToUTF8(str).c_str())); - }; - holder = AndroidInfo{ - .device = java_string_to_const_char(device), - .manufacturer = java_string_to_const_char(manufacturer), - .model = java_string_to_const_char(model), - .brand = java_string_to_const_char(brand), - .android_build_id = java_string_to_const_char(buildId), - .build_type = java_string_to_const_char(type), - .board = java_string_to_const_char(board), - .android_build_fp = java_string_to_const_char(androidBuildFingerprint), - .sdk_int = sdkInt, - .is_debug_android = static_cast<bool>(isDebugAndroid), - .version_incremental = java_string_to_const_char(versionIncremental), - .hardware = java_string_to_const_char(hardware), - .codename = java_string_to_const_char(codeName), - .soc_manufacturer = java_string_to_const_char(socManufacturer), - .abi_name = java_string_to_const_char(supportedAbis)}; + holder.emplace( + AndroidInfo{.device = device, + .manufacturer = manufacturer, + .model = model, + .brand = brand, + .android_build_id = buildId, + .build_type = type, + .board = board, + .android_build_fp = androidBuildFingerprint, + .sdk_int = sdkInt, + .is_debug_android = static_cast<bool>(isDebugAndroid), + .version_incremental = versionIncremental, + .hardware = hardware, + .codename = codeName, + .soc_manufacturer = socManufacturer, + .abi_name = supportedAbis}); } -const char* device() { +const std::string& device() { return get_android_info().device; } -const char* manufacturer() { +const std::string& manufacturer() { return get_android_info().manufacturer; } -const char* model() { +const std::string& model() { return get_android_info().model; } -const char* brand() { +const std::string& brand() { return get_android_info().brand; } -const char* android_build_id() { +const std::string& android_build_id() { return get_android_info().android_build_id; } -const char* build_type() { +const std::string& build_type() { return get_android_info().build_type; } -const char* board() { +const std::string& board() { return get_android_info().board; } -const char* android_build_fp() { +const std::string& android_build_fp() { return get_android_info().android_build_fp; } @@ -156,24 +149,24 @@ return get_android_info().is_debug_android; } -const char* version_incremental() { +const std::string& version_incremental() { return get_android_info().version_incremental; } -const char* hardware() { +const std::string& hardware() { return get_android_info().hardware; } -const char* codename() { +const std::string& codename() { return get_android_info().codename; } // Available only on android S+. For S-, this method returns empty string. -const char* soc_manufacturer() { +const std::string& soc_manufacturer() { return get_android_info().soc_manufacturer; } -const char* abi_name() { +const std::string& abi_name() { return get_android_info().abi_name; }
diff --git a/base/android/android_info.h b/base/android/android_info.h index 556b13e..f4b8a6e 100644 --- a/base/android/android_info.h +++ b/base/android/android_info.h
@@ -5,6 +5,8 @@ #ifndef BASE_ANDROID_ANDROID_INFO_H_ #define BASE_ANDROID_ANDROID_INFO_H_ +#include <string> + #include "base/base_export.h" namespace base::android::android_info { @@ -35,36 +37,36 @@ SDK_VERSION_BAKLAVA = 36, }; -const char* device(); +const std::string& device(); -const char* manufacturer(); +const std::string& manufacturer(); -const char* model(); +const std::string& model(); -BASE_EXPORT const char* brand(); +BASE_EXPORT const std::string& brand(); -const char* android_build_id(); +const std::string& android_build_id(); -const char* build_type(); +const std::string& build_type(); -const char* board(); +const std::string& board(); -const char* android_build_fp(); +const std::string& android_build_fp(); BASE_EXPORT int sdk_int(); bool is_debug_android(); -const char* version_incremental(); +const std::string& version_incremental(); -BASE_EXPORT const char* hardware(); +BASE_EXPORT const std::string& hardware(); -const char* codename(); +const std::string& codename(); // Available only on android S+. For S-, this method returns empty string. -const char* soc_manufacturer(); +const std::string& soc_manufacturer(); -const char* abi_name(); +const std::string& abi_name(); } // namespace base::android::android_info
diff --git a/base/android/apk_info.cc b/base/android/apk_info.cc index 84527af9..30f09eb 100644 --- a/base/android/apk_info.cc +++ b/base/android/apk_info.cc
@@ -12,6 +12,7 @@ #include "base/android/jni_string.h" #include "base/android/scoped_java_ref.h" #include "base/compiler_specific.h" +#include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" // Must come after all headers that specialize FromJniType() / ToJniType(). @@ -22,96 +23,87 @@ namespace { struct ApkInfo { - // Const char* is used instead of std::strings because these values must be - // available even if the process is in a crash state. Sadly - // std::string.c_str() doesn't guarantee that memory won't be allocated when - // it is called. - const char* host_package_name; - const char* host_version_code; - const char* host_package_label; - const char* package_version_code; - const char* package_version_name; - const char* package_name; - const char* resources_version; - const char* installer_package_name; + const std::string host_package_name; + const std::string host_version_code; + const std::string host_package_label; + const std::string package_version_code; + const std::string package_version_name; + const std::string package_name; + const std::string resources_version; + const std::string installer_package_name; bool is_debug_app; int target_sdk_version; }; -std::optional<ApkInfo> holder; +static std::optional<ApkInfo>& get_holder() { + static base::NoDestructor<std::optional<ApkInfo>> holder; + return *holder; +} -ApkInfo& get_apk_info() { - [[maybe_unused]] static auto once = [] { +const ApkInfo& get_apk_info() { + const std::optional<ApkInfo>& holder = get_holder(); + if (!holder.has_value()) { Java_ApkInfo_nativeReadyForFields(AttachCurrentThread()); - return std::monostate(); - }(); - // holder should be initialized as the java is supposed to call the native - // method FillFields which will initialize the fields within the holder. - DCHECK(holder.has_value()); + } return *holder; } } // namespace -static void JNI_ApkInfo_FillFields( - JNIEnv* env, - const jni_zero::JavaParamRef<jstring>& hostPackageName, - const jni_zero::JavaParamRef<jstring>& hostVersionCode, - const jni_zero::JavaParamRef<jstring>& hostPackageLabel, - const jni_zero::JavaParamRef<jstring>& packageVersionCode, - const jni_zero::JavaParamRef<jstring>& packageVersionName, - const jni_zero::JavaParamRef<jstring>& packageName, - const jni_zero::JavaParamRef<jstring>& resourcesVersion, - const jni_zero::JavaParamRef<jstring>& installerPackageName, - jboolean isDebugApp, - jint targetSdkVersion) { +static void JNI_ApkInfo_FillFields(JNIEnv* env, + std::string& hostPackageName, + std::string& hostVersionCode, + std::string& hostPackageLabel, + std::string& packageVersionCode, + std::string& packageVersionName, + std::string& packageName, + std::string& resourcesVersion, + std::string& installerPackageName, + jboolean isDebugApp, + jint targetSdkVersion) { + std::optional<ApkInfo>& holder = get_holder(); DCHECK(!holder.has_value()); - auto java_string_to_const_char = - [](const jni_zero::JavaParamRef<jstring>& str) { - return UNSAFE_TODO(strdup(ConvertJavaStringToUTF8(str).c_str())); - }; - holder = ApkInfo{ - .host_package_name = java_string_to_const_char(hostPackageName), - .host_version_code = java_string_to_const_char(hostVersionCode), - .host_package_label = java_string_to_const_char(hostPackageLabel), - .package_version_code = java_string_to_const_char(packageVersionCode), - .package_version_name = java_string_to_const_char(packageVersionName), - .package_name = java_string_to_const_char(packageName), - .resources_version = java_string_to_const_char(resourcesVersion), - .installer_package_name = java_string_to_const_char(installerPackageName), - .is_debug_app = static_cast<bool>(isDebugApp), - .target_sdk_version = targetSdkVersion}; + holder.emplace(ApkInfo{.host_package_name = hostPackageName, + .host_version_code = hostVersionCode, + .host_package_label = hostPackageLabel, + .package_version_code = packageVersionCode, + .package_version_name = packageVersionName, + .package_name = packageName, + .resources_version = resourcesVersion, + .installer_package_name = installerPackageName, + .is_debug_app = static_cast<bool>(isDebugApp), + .target_sdk_version = targetSdkVersion}); } -const char* host_package_name() { +const std::string& host_package_name() { return get_apk_info().host_package_name; } -const char* host_version_code() { +const std::string& host_version_code() { return get_apk_info().host_version_code; } -const char* host_package_label() { +const std::string& host_package_label() { return get_apk_info().host_package_label; } -const char* package_version_code() { +const std::string& package_version_code() { return get_apk_info().package_version_code; } -const char* package_version_name() { +const std::string& package_version_name() { return get_apk_info().package_version_name; } -const char* package_name() { +const std::string& package_name() { return get_apk_info().package_name; } -const char* resources_version() { +const std::string& resources_version() { return get_apk_info().resources_version; } -const char* installer_package_name() { +const std::string& installer_package_name() { return get_apk_info().installer_package_name; }
diff --git a/base/android/apk_info.h b/base/android/apk_info.h index 6bba04d..a3d14ec3 100644 --- a/base/android/apk_info.h +++ b/base/android/apk_info.h
@@ -5,33 +5,35 @@ #ifndef BASE_ANDROID_APK_INFO_H_ #define BASE_ANDROID_APK_INFO_H_ +#include <string> + namespace base::android::apk_info { // The package name of the host app which has loaded WebView, retrieved from // the application context. In the context of the SDK Runtime, the package // name of the app that owns this particular instance of the SDK Runtime will // also be included. e.g. // com.google.android.sdksandbox:com:com.example.myappwithads -const char* host_package_name(); +const std::string& host_package_name(); // The application name (e.g. "Chrome"). For WebView, this is name of the // embedding app. In the context of the SDK Runtime, this is the name of the // app that owns this particular instance of the SDK Runtime. -const char* host_version_code(); +const std::string& host_version_code(); // By default: same as versionCode. For WebView: versionCode of the embedding // app. In the context of the SDK Runtime, this is the versionCode of the app // that owns this particular instance of the SDK Runtime. -const char* host_package_label(); +const std::string& host_package_label(); -const char* package_version_code(); +const std::string& package_version_code(); -const char* package_version_name(); +const std::string& package_version_name(); -const char* package_name(); +const std::string& package_name(); -const char* resources_version(); +const std::string& resources_version(); -const char* installer_package_name(); +const std::string& installer_package_name(); bool is_debug_app();
diff --git a/base/android/build_info.cc b/base/android/build_info.cc index 8acac8d..327559f 100644 --- a/base/android/build_info.cc +++ b/base/android/build_info.cc
@@ -72,7 +72,7 @@ BuildInfo::~BuildInfo() = default; -const char* BuildInfo::gms_version_code() const { +const std::string& BuildInfo::gms_version_code() const { return device_info::gms_version_code(); } @@ -81,7 +81,7 @@ device_info::set_gms_version_code_for_test(gms_version_code); } -std::string BuildInfo::host_signing_cert_sha256() { +const std::string BuildInfo::host_signing_cert_sha256() { JNIEnv* env = AttachCurrentThread(); return Java_BuildInfo_lazyGetHostSigningCertSha256(env); }
diff --git a/base/android/build_info.h b/base/android/build_info.h index b58b2bd6..82bb2e7a 100644 --- a/base/android/build_info.h +++ b/base/android/build_info.h
@@ -62,23 +62,19 @@ // should only be one instance of BuildInfo ever created. static BuildInfo* GetInstance(); - // Const char* is used instead of std::strings because these values must be - // available even if the process is in a crash state. Sadly - // std::string.c_str() doesn't guarantee that memory won't be allocated when - // it is called. - const char* device() const { return device_; } + const std::string& device() const { return device_; } - const char* manufacturer() const { return manufacturer_; } + const std::string& manufacturer() const { return manufacturer_; } - const char* model() const { return model_; } + const std::string& model() const { return model_; } - const char* brand() const { return brand_; } + const std::string& brand() const { return brand_; } - const char* android_build_id() const { return android_build_id_; } + const std::string& android_build_id() const { return android_build_id_; } - const char* android_build_fp() const { return android_build_fp_; } + const std::string& android_build_fp() const { return android_build_fp_; } - const char* gms_version_code() const; + const std::string& gms_version_code() const; void set_gms_version_code_for_test(const std::string& gms_version_code); @@ -87,37 +83,43 @@ // name of the app that owns this particular instance of the SDK Runtime will // also be included. e.g. // com.google.android.sdksandbox:com:com.example.myappwithads - const char* host_package_name() const { return host_package_name_; } + const std::string& host_package_name() const { return host_package_name_; } // The application name (e.g. "Chrome"). For WebView, this is name of the // embedding app. In the context of the SDK Runtime, this is the name of the // app that owns this particular instance of the SDK Runtime. - const char* host_version_code() const { return host_version_code_; } + const std::string& host_version_code() const { return host_version_code_; } // By default: same as versionCode. For WebView: versionCode of the embedding // app. In the context of the SDK Runtime, this is the versionCode of the app // that owns this particular instance of the SDK Runtime. - const char* host_package_label() const { return host_package_label_; } + const std::string& host_package_label() const { return host_package_label_; } // The SHA256 of the public certificate used to sign the host application. // This will default to an empty string if we were unable to retrieve it. - std::string host_signing_cert_sha256(); + const std::string host_signing_cert_sha256(); - const char* package_version_code() const { return package_version_code_; } + const std::string& package_version_code() const { + return package_version_code_; + } - const char* package_version_name() const { return package_version_name_; } + const std::string& package_version_name() const { + return package_version_name_; + } - const char* package_name() const { return package_name_; } + const std::string& package_name() const { return package_name_; } - const char* resources_version() const { return resources_version_; } + const std::string& resources_version() const { return resources_version_; } - const char* build_type() const { return build_type_; } + const std::string& build_type() const { return build_type_; } - const char* board() const { return board_; } + const std::string& board() const { return board_; } - const char* installer_package_name() const { return installer_package_name_; } + const std::string& installer_package_name() const { + return installer_package_name_; + } - const char* abi_name() const { return abi_name_; } + const std::string& abi_name() const { return abi_name_; } int sdk_int() const { return sdk_int_; } @@ -133,13 +135,15 @@ bool is_tv() const { return is_tv_; } - const char* version_incremental() const { return version_incremental_; } + const std::string& version_incremental() const { + return version_incremental_; + } - const char* hardware() const { return hardware_; } + const std::string& hardware() const { return hardware_; } bool is_automotive() const { return is_automotive_; } - const char* codename() const { return codename_; } + const std::string& codename() const { return codename_; } bool is_foldable() const { return is_foldable_; } @@ -149,7 +153,7 @@ int32_t vulkan_deqp_level() const { return vulkan_deqp_level_; } // Available only on android S+. For S-, this method returns empty string. - const char* soc_manufacturer() const { return soc_manufacturer_; } + const std::string& soc_manufacturer() const { return soc_manufacturer_; } bool is_debug_app() const { return is_debug_app_; } @@ -158,39 +162,34 @@ explicit BuildInfo(); - // Const char* is used instead of std::strings because these values must be - // available even if the process is in a crash state. Sadly - // std::string.c_str() doesn't guarantee that memory won't be allocated when - // it is called. - const char* const brand_; - const char* const device_; - const char* const android_build_id_; - const char* const manufacturer_; - const char* const model_; + const std::string brand_; + const std::string device_; + const std::string android_build_id_; + const std::string manufacturer_; + const std::string model_; const int sdk_int_; - const char* const build_type_; - const char* const board_; - const char* const host_package_name_; - const char* const host_version_code_; - const char* const host_package_label_; - const char* const package_name_; - const char* const package_version_code_; - const char* const package_version_name_; - const char* const android_build_fp_; - const char* const installer_package_name_; - const char* const abi_name_; - const char* const resources_version_; - // Not needed by breakpad. + const std::string build_type_; + const std::string board_; + const std::string host_package_name_; + const std::string host_version_code_; + const std::string host_package_label_; + const std::string package_name_; + const std::string package_version_code_; + const std::string package_version_name_; + const std::string android_build_fp_; + const std::string installer_package_name_; + const std::string abi_name_; + const std::string resources_version_; const int target_sdk_version_; const bool is_debug_android_; const bool is_tv_; - const char* const version_incremental_; - const char* const hardware_; + const std::string version_incremental_; + const std::string hardware_; const bool is_automotive_; - const char* const codename_; + const std::string codename_; const int32_t vulkan_deqp_level_; const bool is_foldable_; - const char* const soc_manufacturer_; + const std::string soc_manufacturer_; const bool is_debug_app_; const bool is_desktop_; };
diff --git a/base/android/device_info.cc b/base/android/device_info.cc index 645226c..2b0dbc3 100644 --- a/base/android/device_info.cc +++ b/base/android/device_info.cc
@@ -11,6 +11,7 @@ #include "base/android/jni_string.h" #include "base/android/scoped_java_ref.h" #include "base/compiler_specific.h" +#include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" // Must come after all headers that specialize FromJniType() / ToJniType(). @@ -20,11 +21,7 @@ namespace base::android::device_info { namespace { struct DeviceInfo { - // Const char* is used instead of std::strings because these values must be - // available even if the process is in a crash state. Sadly - // std::string.c_str() doesn't guarantee that memory won't be allocated when - // it is called. - const char* gms_version_code; + std::string gms_version_code; bool is_tv; bool is_automotive; bool is_foldable; @@ -33,50 +30,44 @@ int32_t vulkan_deqp_level; }; -std::optional<DeviceInfo> holder; +static std::optional<DeviceInfo>& get_holder() { + static base::NoDestructor<std::optional<DeviceInfo>> holder; + return *holder; +} DeviceInfo& get_device_info() { - [[maybe_unused]] static auto once = [] { + std::optional<DeviceInfo>& holder = get_holder(); + if (!holder.has_value()) { Java_DeviceInfo_nativeReadyForFields(AttachCurrentThread()); - return std::monostate(); - }(); - // holder should be initialized as the java is supposed to call the native - // method FillFields which will initialize the fields within the holder. - DCHECK(holder.has_value()); + } return *holder; } } // namespace -static void JNI_DeviceInfo_FillFields( - JNIEnv* env, - const jni_zero::JavaParamRef<jstring>& gmsVersionCode, - jboolean isTV, - jboolean isAutomotive, - jboolean isFoldable, - jboolean isDesktop, - jint vulkanDeqpLevel) { +static void JNI_DeviceInfo_FillFields(JNIEnv* env, + std::string& gmsVersionCode, + jboolean isTV, + jboolean isAutomotive, + jboolean isFoldable, + jboolean isDesktop, + jint vulkanDeqpLevel) { + std::optional<DeviceInfo>& holder = get_holder(); DCHECK(!holder.has_value()); - auto java_string_to_const_char = - [](const jni_zero::JavaParamRef<jstring>& str) { - return UNSAFE_TODO(strdup(ConvertJavaStringToUTF8(str).c_str())); - }; - holder = - DeviceInfo{.gms_version_code = java_string_to_const_char(gmsVersionCode), - .is_tv = static_cast<bool>(isTV), - .is_automotive = static_cast<bool>(isAutomotive), - .is_foldable = static_cast<bool>(isFoldable), - .is_desktop = static_cast<bool>(isDesktop), - .vulkan_deqp_level = vulkanDeqpLevel}; + holder.emplace(DeviceInfo{.gms_version_code = gmsVersionCode, + .is_tv = static_cast<bool>(isTV), + .is_automotive = static_cast<bool>(isAutomotive), + .is_foldable = static_cast<bool>(isFoldable), + .is_desktop = static_cast<bool>(isDesktop), + .vulkan_deqp_level = vulkanDeqpLevel}); } -const char* gms_version_code() { +const std::string& gms_version_code() { return get_device_info().gms_version_code; } void set_gms_version_code_for_test(const std::string& gms_version_code) { - get_device_info().gms_version_code = - UNSAFE_TODO(strdup(gms_version_code.c_str())); + get_device_info().gms_version_code = gms_version_code; Java_DeviceInfo_setGmsVersionCodeForTest(AttachCurrentThread(), gms_version_code); }
diff --git a/base/android/device_info.h b/base/android/device_info.h index ccf2f46..65dde11b 100644 --- a/base/android/device_info.h +++ b/base/android/device_info.h
@@ -8,7 +8,7 @@ #include <string> namespace base::android::device_info { -const char* gms_version_code(); +const std::string& gms_version_code(); void set_gms_version_code_for_test(const std::string& gms_version_code);
diff --git a/base/android/java/src/org/chromium/base/AndroidInfo.java b/base/android/java/src/org/chromium/base/AndroidInfo.java index 485dcf293..ac529c28 100644 --- a/base/android/java/src/org/chromium/base/AndroidInfo.java +++ b/base/android/java/src/org/chromium/base/AndroidInfo.java
@@ -9,6 +9,7 @@ import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; +import org.jni_zero.JniType; import org.jni_zero.NativeMethods; import org.chromium.build.annotations.NullMarked; @@ -69,19 +70,19 @@ @NativeMethods interface Natives { void fillFields( - String brand, - String device, - String buildId, - String manufacturer, - String model, - String type, - String board, - String androidBuildFingerprint, - String versionIncremental, - String hardware, - String codeName, - String socManufacturer, - String supportedAbis, + @JniType("std::string") String brand, + @JniType("std::string") String device, + @JniType("std::string") String buildId, + @JniType("std::string") String manufacturer, + @JniType("std::string") String model, + @JniType("std::string") String type, + @JniType("std::string") String board, + @JniType("std::string") String androidBuildFingerprint, + @JniType("std::string") String versionIncremental, + @JniType("std::string") String hardware, + @JniType("std::string") String codeName, + @JniType("std::string") String socManufacturer, + @JniType("std::string") String supportedAbis, int sdkInt, boolean isDebugAndroid); }
diff --git a/base/android/java/src/org/chromium/base/ApkInfo.java b/base/android/java/src/org/chromium/base/ApkInfo.java index bb5ba01..b4cc4443 100644 --- a/base/android/java/src/org/chromium/base/ApkInfo.java +++ b/base/android/java/src/org/chromium/base/ApkInfo.java
@@ -14,6 +14,7 @@ import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; +import org.jni_zero.JniType; import org.jni_zero.NativeMethods; import org.chromium.base.version_info.VersionInfo; @@ -322,14 +323,14 @@ @NativeMethods interface Natives { void fillFields( - String hostPackageName, - String hostVersionCode, - String hostPackageLabel, - String packageVersionCode, - String packageVersionName, - String packageName, - String resourcesVersion, - String installerPackageName, + @JniType("std::string") String hostPackageName, + @JniType("std::string") String hostVersionCode, + @JniType("std::string") String hostPackageLabel, + @JniType("std::string") String packageVersionCode, + @JniType("std::string") String packageVersionName, + @JniType("std::string") String packageName, + @JniType("std::string") String resourcesVersion, + @JniType("std::string") String installerPackageName, boolean isDebugApp, int targetSdkVersion); }
diff --git a/base/android/java/src/org/chromium/base/DeviceInfo.java b/base/android/java/src/org/chromium/base/DeviceInfo.java index 48d1349..dcb3b464 100644 --- a/base/android/java/src/org/chromium/base/DeviceInfo.java +++ b/base/android/java/src/org/chromium/base/DeviceInfo.java
@@ -196,7 +196,7 @@ @NativeMethods interface Natives { void fillFields( - String gmsVersionCode, + @JniType("std::string") String gmsVersionCode, boolean isTV, boolean isAutomotive, boolean isFoldable,
diff --git a/base/android/java/src/org/chromium/base/JniCallbackImpl.java b/base/android/java/src/org/chromium/base/JniCallbackImpl.java index f5c8b360..4109b70e 100644 --- a/base/android/java/src/org/chromium/base/JniCallbackImpl.java +++ b/base/android/java/src/org/chromium/base/JniCallbackImpl.java
@@ -58,7 +58,7 @@ JniCallbackImplJni.get().onResult(mIsRepeating, mNativePointer, result); if (!mIsRepeating) { mNativePointer = 0; - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } } @@ -68,7 +68,7 @@ if (mNativePointer != 0) { JniCallbackImplJni.get().destroy(mIsRepeating, mNativePointer); mNativePointer = 0; - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } }
diff --git a/base/android/java/src/org/chromium/base/RequiredCallback.java b/base/android/java/src/org/chromium/base/RequiredCallback.java index 269c6eb..6386dde0 100644 --- a/base/android/java/src/org/chromium/base/RequiredCallback.java +++ b/base/android/java/src/org/chromium/base/RequiredCallback.java
@@ -28,7 +28,7 @@ public void onResult(T result) { assert mCallback != null : "Callback was already called."; mCallback.onResult(result); - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); mCallback = null; } }
diff --git a/base/android/java/src/org/chromium/base/lifetime/LifetimeAssert.java b/base/android/java/src/org/chromium/base/lifetime/LifetimeAssert.java index d9ebf3c..5354abf0 100644 --- a/base/android/java/src/org/chromium/base/lifetime/LifetimeAssert.java +++ b/base/android/java/src/org/chromium/base/lifetime/LifetimeAssert.java
@@ -4,6 +4,8 @@ package org.chromium.base.lifetime; +import static org.chromium.build.NullUtil.assumeNonNull; + import androidx.annotation.VisibleForTesting; import org.chromium.base.task.PostTask; @@ -25,12 +27,12 @@ * * <pre> * class MyClassWithCleanup { - * private final mLifetimeAssert = LifetimeAssert.create(this); + * private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this); * * public void destroy() { * // If mLifetimeAssert is GC'ed before this is called, it will throw an exception * // with a stack trace showing the stack during LifetimeAssert.create(). - * LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + * LifetimeAssert.destroy(mLifetimeAssert, true); * } * } * </pre> @@ -147,6 +149,7 @@ new WrappedReference(target, new CreationException(), safeToGc), target); } + /** Most clients should probably use destroy() to ensure exactly 1 call. */ public static void setSafeToGc(@Nullable LifetimeAssert asserter, boolean value) { if (BuildConfig.ENABLE_ASSERTS) { assert asserter != null; @@ -163,6 +166,19 @@ } } + /** Asserts that mSafeToGc == false. */ + public static void assertNotDestroyed(@Nullable LifetimeAssert asserter) { + if (BuildConfig.ENABLE_ASSERTS) { + assert !assumeNonNull(asserter).mWrapper.mSafeToGc; + } + } + + /** Shorthand for assertNotDestroyed(_) + setSafeToGc(_, true). */ + public static void destroy(@Nullable LifetimeAssert asserter) { + assertNotDestroyed(asserter); + setSafeToGc(asserter, true); + } + /** * Asserts that the remaining objects used with LifetimeAssert do not need to be destroyed and * can be garbage collected. Always clears the set of tracked object, so consecutive invocations
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DestroyableHolder.java b/base/test/android/javatests/src/org/chromium/base/test/util/DestroyableHolder.java new file mode 100644 index 0000000..9d784f9 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/util/DestroyableHolder.java
@@ -0,0 +1,34 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test.util; + +import org.chromium.base.lifetime.Destroyable; +import org.chromium.build.annotations.Nullable; + +/** + * Similar to an optional, either holds an object or not. But when the reference changes, it will + * call destroy on the previous value. This is helpful for tests that sometimes test destroy inside + * of test cases, and then otherwise destroy in tear down. + * + * @param <E> The type of the object held. + */ +public class DestroyableHolder<E extends Destroyable> { + private @Nullable E mTarget; + + public void set(E target) { + if (mTarget != null) { + mTarget.destroy(); + } + mTarget = target; + } + + public @Nullable E get() { + return mTarget; + } + + public void destroy() { + set(null); + } +}
diff --git a/base/threading/hang_watcher.cc b/base/threading/hang_watcher.cc index 91a6a66..cf3657b9 100644 --- a/base/threading/hang_watcher.cc +++ b/base/threading/hang_watcher.cc
@@ -273,7 +273,7 @@ // Monitor(). Increasing or decreasing this does not modify the type of hangs // that can be detected. It instead increases the probability that a call to // Monitor() will happen at the right time to catch a hang. This has to be -// balanced with power/cpu use concerns as busy looping would catch amost all +// balanced with power/cpu use concerns as busy looping would catch almost all // hangs but present unacceptable overhead. NOTE: If this period is ever changed // then all metrics that depend on it like // HangWatcher.IsThreadHung need to be updated. @@ -304,7 +304,7 @@ // TODO(crbug.com/40111620): Check whether we are over deadline already for // the previous WatchHangsInScope here by issuing only one TimeTicks::Now() - // and resuing the value. + // and reusing the value. previous_deadline_ = old_deadline; TimeTicks deadline = TimeTicks::Now() + timeout; @@ -463,7 +463,7 @@ } } -void HangWatcher::UnitializeOnMainThreadForTesting() { +void HangWatcher::UninitializeOnMainThreadForTesting() { g_use_hang_watcher.store(false, std::memory_order_relaxed); g_threadpool_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed); g_io_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed); @@ -795,7 +795,7 @@ hung_counts_per_thread_type.fill(kInvalidHangCount); // Will be true if any of the hung threads has a logging level high enough, - // as defined through finch params, to warant dumping a crash. + // as defined through finch params, to warrant dumping a crash. bool any_hung_thread_has_dumping_enabled = false; // Copy hung thread information.
diff --git a/base/threading/hang_watcher.h b/base/threading/hang_watcher.h index c6f877f..51c3495 100644 --- a/base/threading/hang_watcher.h +++ b/base/threading/hang_watcher.h
@@ -128,7 +128,7 @@ // Notes on lifetime: // 1) The first invocation of the constructor will set the global instance // accessible through GetInstance(). - // 2) In production HangWatcher is always purposefuly leaked. + // 2) In production HangWatcher is always purposefully leaked. // 3) If not leaked HangWatcher is always constructed and destructed from // the same thread. // 4) There can never be more than one instance of HangWatcher at a time. @@ -155,7 +155,7 @@ // Returns the values that were set through InitializeOnMainThread() to their // default value. Used for testing since in prod initialization should happen // only once. - static void UnitializeOnMainThreadForTesting(); + static void UninitializeOnMainThreadForTesting(); // Thread safe functions to verify if hang watching is activated. If called // before InitializeOnMainThread returns the default value which is false. @@ -513,10 +513,10 @@ // |switch_bits_callback_for_testing_| is installed. uint64_t SwitchBitsForTesting(); - // Atomically sets persitent flag |flag|. Cannot fail. + // Atomically sets persistent flag |flag|. Cannot fail. void SetPersistentFlag(Flag flag); - // Atomically clears persitent flag |flag|. Cannot fail. + // Atomically clears persistent flag |flag|. Cannot fail. void ClearPersistentFlag(Flag flag); // Converts bits to TimeTicks with some sanity checks. Use to return the @@ -660,7 +660,7 @@ const AutoReset<HangWatchState*> resetter_; // If the deadline fails to be updated before TimeTicks::Now() ever - // reaches the value contained in it this constistutes a hang. + // reaches the value contained in it this constitutes a hang. HangWatchDeadline deadline_; // A unique ID of the thread under watch. Used for logging in crash reports
diff --git a/base/threading/hang_watcher_unittest.cc b/base/threading/hang_watcher_unittest.cc index 172d995..ac896af 100644 --- a/base/threading/hang_watcher_unittest.cc +++ b/base/threading/hang_watcher_unittest.cc
@@ -137,7 +137,9 @@ hang_watcher_.Start(); } - void TearDown() override { HangWatcher::UnitializeOnMainThreadForTesting(); } + void TearDown() override { + HangWatcher::UninitializeOnMainThreadForTesting(); + } HangWatcherTest(const HangWatcherTest& other) = delete; HangWatcherTest& operator=(const HangWatcherTest& other) = delete; @@ -199,7 +201,7 @@ void MonitorHangs() { // HangWatcher::Monitor() should not be set which would mean a call to - // HangWatcher::Monitor() happened and was unacounted for. + // HangWatcher::Monitor() happened and was unaccounted for. // ASSERT_FALSE(monitor_event_.IsSignaled()); // Trigger a monitoring on HangWatcher thread and verify results. @@ -245,7 +247,7 @@ // de-activate hang watching, base::HangWatcher::InvalidateActiveExpectations(); - // Redundently de-activate hang watching. + // Redundantly de-activate hang watching. base::HangWatcher::InvalidateActiveExpectations(); // Trigger a monitoring on HangWatcher thread and verify results. @@ -368,7 +370,7 @@ task_environment_.FastForwardBy(kHangTime); } - // New scope for which expecations are never invalidated. + // New scope for which expectations are never invalidated. WatchHangsInScope also_expires_instantly(base::TimeDelta{}); task_environment_.FastForwardBy(kHangTime); @@ -604,7 +606,9 @@ hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta); } - void TearDown() override { HangWatcher::UnitializeOnMainThreadForTesting(); } + void TearDown() override { + HangWatcher::UninitializeOnMainThreadForTesting(); + } HangWatcherSnapshotTest() = default; HangWatcherSnapshotTest(const HangWatcherSnapshotTest& other) = delete; @@ -681,7 +685,7 @@ // Verify that the hang capture fails when marking a thread for blocking fails. // This simulates a WatchHangsInScope completing between the time the hang -// was dected and the time it is recorded which would create a non-actionable +// was detected and the time it is recorded which would create a non-actionable // report. TEST_F(HangWatcherSnapshotTest, NonActionableReport) { hang_watcher_.SetOnHangClosureForTesting( @@ -865,7 +869,9 @@ HangWatcherPeriodicMonitoringTest& operator=( const HangWatcherPeriodicMonitoringTest& other) = delete; - void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); } + void TearDown() override { + hang_watcher_.UninitializeOnMainThreadForTesting(); + } protected: // Setup the callback invoked after waiting in HangWatcher to advance the @@ -928,7 +934,7 @@ // this is rare. } -// During normal execution periodic monitorings should take place. +// During normal execution periodic monitoring should take place. TEST_F(HangWatcherPeriodicMonitoringTest, PeriodicCallsTakePlace) { // HangWatcher::Monitor() will run once right away on thread registration. // We want to make sure it runs at a couple more times from being scheduled. @@ -1039,7 +1045,9 @@ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); } - void TearDown() override { HangWatcher::UnitializeOnMainThreadForTesting(); } + void TearDown() override { + HangWatcher::UninitializeOnMainThreadForTesting(); + } WatchHangsInScopeBlockingTest(const WatchHangsInScopeBlockingTest& other) = delete; @@ -1234,7 +1242,7 @@ // All flags back to original state. ASSERT_EQ(new_flags, old_flags); - // Deadline still unnafected. + // Deadline still unaffected. ASSERT_EQ(new_deadline, old_deadline); } @@ -1313,7 +1321,7 @@ // Verify that setting a persistent (kIgnoreCurrentWatchHangsInScope) when // the deadline or flags changed succeeds and has non side-effects. TEST_F(HangWatchDeadlineTest, - SetIgnoreCurrentHangWatchScopeEnableDeadlineChangedd) { + SetIgnoreCurrentHangWatchScopeEnableDeadlineChanged) { AssertNoFlagsSet(); auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
diff --git a/base/tracing/perfetto_platform.cc b/base/tracing/perfetto_platform.cc index 0a3ef838..07a6a106 100644 --- a/base/tracing/perfetto_platform.cc +++ b/base/tracing/perfetto_platform.cc
@@ -67,16 +67,17 @@ // Note that we override the producer name for the mojo backend in ProducerHost, // and thus this only affects the producer name for the system backend. std::string PerfettoPlatform::GetCurrentProcessName() { - const char* host_package_name = nullptr; #if BUILDFLAG(IS_ANDROID) - host_package_name = android::apk_info::host_package_name(); + const std::string& host_package_name = android::apk_info::host_package_name(); +#else + std::string host_package_name; #endif // BUILDFLAG(IS_ANDROID) // On Android we want to include if this is webview inside of an app or // Android Chrome. To aid this we add the host_package_name to differentiate // the various apps and sources. std::string process_name; - if (host_package_name) { + if (!host_package_name.empty()) { process_name = StrCat( {process_name_prefix_, host_package_name, "-", NumberToString(trace_event::TraceLog::GetInstance()->process_id())});
diff --git a/build/android/gyp/create_app_bundle.py b/build/android/gyp/create_app_bundle.py index 43efec3..1ca89f1 100755 --- a/build/android/gyp/create_app_bundle.py +++ b/build/android/gyp/create_app_bundle.py
@@ -37,6 +37,7 @@ # that Chrome won't crash on startup if its bundle is installed on a device # with an unsupported system locale (e.g. fur-rIT). _FALLBACK_LOCALE = 'en-US' +_FALLBACK_LOCALE_WITH_GENDER = 'en-US_OTHER' # List of split dimensions recognized by this tool. _ALL_SPLIT_DIMENSIONS = [ 'ABI', 'SCREEN_DENSITY', 'LANGUAGE' ] @@ -278,9 +279,13 @@ if not src_path.startswith(_LOCALES_SUBDIR) or not src_path.endswith('.pak'): return [src_path] + # Note that |locale| may include a gender as well as a language. locale = src_path[len(_LOCALES_SUBDIR):-4] android_locale = resource_utils.ToAndroidLocaleName(locale) + # Trim _GENDER suffix so it doesn't end up in a directory name. + android_locale = android_locale.split('_')[0] + # The locale format is <lang>-<region> or <lang> or BCP-47 (e.g b+sr+Latn). # Extract the language. pos = android_locale.find('-') @@ -292,7 +297,7 @@ else: android_language = android_locale - if locale == _FALLBACK_LOCALE: + if locale in (_FALLBACK_LOCALE, _FALLBACK_LOCALE_WITH_GENDER): # Fallback locale .pak files must be placed in a different directory # to ensure they are always stored in the base module. result_path = 'assets/fallback-locales/%s.pak' % locale
diff --git a/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template b/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template index b51a67d..4b470cc 100644 --- a/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template +++ b/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template
@@ -5,7 +5,7 @@ package test; public class NoSignatureChangeIncrementalJavacTestHelper { - private NoSignatureChangeIncrementalJavacTestHelper2 mHelper2 = + private final NoSignatureChangeIncrementalJavacTestHelper2 mHelper2 = new NoSignatureChangeIncrementalJavacTestHelper2(); public String foo() {
diff --git a/chrome/VERSION b/chrome/VERSION index 2fe67a9..f73867e9 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=138 MINOR=0 -BUILD=7178 +BUILD=7179 PATCH=0
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java index ab08bd9..0600d2ce 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinator.java
@@ -80,9 +80,11 @@ import org.chromium.components.browser_ui.widget.FadingShadowView; import org.chromium.components.browser_ui.widget.StrictButtonPressController.ButtonClickResult; import org.chromium.components.browser_ui.widget.gesture.BackPressHandler; +import org.chromium.components.tab_group_sync.LocalTabGroupId; import org.chromium.components.tab_group_sync.SavedTabGroup; import org.chromium.components.tab_group_sync.TabGroupSyncService; import org.chromium.components.tab_group_sync.TabGroupUiActionHandler; +import org.chromium.components.tab_group_sync.TriggerSource; import org.chromium.ui.interpolators.Interpolators; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modelutil.LayoutViewBuilder; @@ -281,6 +283,24 @@ } }; + private final TabGroupSyncService.Observer mTabGroupSyncObserver = + new TabGroupSyncService.Observer() { + @Override + public void onTabGroupUpdated(SavedTabGroup group, @TriggerSource int source) { + refreshArchivedTabList(); + } + + @Override + public void onTabGroupRemoved(LocalTabGroupId localId, @TriggerSource int source) { + refreshArchivedTabList(); + } + + @Override + public void onTabGroupRemoved(String syncId, @TriggerSource int source) { + refreshArchivedTabList(); + } + }; + /** * Observes the TabListEditor lifecycle to remove the view and hide the dialog. This is useful * for when (1) the TabListEditor is expecting the embedding view to be removed from the @@ -462,6 +482,10 @@ mPaneManagerSupplier = paneManagerSupplier; mTabGroupUiActionHandlerSupplier = tabGroupUiActionHandlerSupplier; mCurrentTabGroupModelFilterSupplier = currentTabGroupModelFilterSupplier; + + if (mTabGroupSyncService != null) { + mTabGroupSyncService.addObserver(mTabGroupSyncObserver); + } } /** Hides the dialog. */ @@ -487,6 +511,10 @@ mOnTabSelectingListener = null; } + if (mTabGroupSyncService != null) { + mTabGroupSyncService.removeObserver(mTabGroupSyncObserver); + } + mTabArchiveSettings.removeObserver(mTabArchiveSettingsObserver); mUndoBarController.destroy(); } @@ -801,6 +829,17 @@ getIphDescription(mActivity, mTabArchiveSettings)); } + private void refreshArchivedTabList() { + mTabListEditorCoordinator.resetWithListOfTabs( + TabModelUtils.convertTabListToListOfTabs(mArchivedTabModel), + getTabGroupSyncIds(), + /* quickMode= */ false); + if (mTabArchiveSettings.shouldShowDialogIph()) { + mTabListEditorCoordinator.addSpecialListItem( + 0, UiType.MESSAGE, mIphMessagePropertyModel); + } + } + @VisibleForTesting public static CharSequence getIphDescription( Context context, TabArchiveSettings tabArchiveSettings) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabOverflowMenuCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabOverflowMenuCoordinator.java index 6c508489..c7df71f 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabOverflowMenuCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabOverflowMenuCoordinator.java
@@ -195,7 +195,7 @@ mContext.unregisterComponentCallbacks(mComponentCallbacks); // If mLifetimeAssert is GC'ed before this is called, it will throw an exception // with a stack trace showing the stack during LifetimeAssert.create(). - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java index 4aa989a0..0a71d17c 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java
@@ -265,12 +265,12 @@ int secondTabId = secondPage.loadedTabElement.get().getId(); // Make sure all thumbnails are there before switching tabs. RegularTabSwitcherStation tabSwitcher = enterRegularHTSWithThumbnailChecking(secondPage); - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = - editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName("test_tab_group_name"); dialog = dialog.pickColor(TabGroupColorId.RED); dialog.pressDone(); @@ -424,10 +424,13 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); TabSwitcherGroupCardFacility card = dialog.pressBack(); // Verify the color icon exists and that the dialog is dismissed via another action @@ -460,10 +463,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs and edit group fields - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName("Test"); dialog = dialog.pickColor(TabGroupColorId.BLUE); dialog.pressDone(); @@ -491,10 +496,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog.pressDone(); // Assert that the expected fields are correct @@ -521,10 +528,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName(""); dialog = dialog.pickColor(TabGroupColorId.BLUE); dialog.pressBack(); @@ -553,10 +562,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName(""); dialog = dialog.pressDoneWithInvalidTitle(); @@ -588,10 +599,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName("Test"); dialog = dialog.pickColor(TabGroupColorId.BLUE); dialog.pressBack(); @@ -618,10 +631,12 @@ RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); // Group both tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog.pressDone(); // Close the tab group via the app menu @@ -629,7 +644,8 @@ tabSwitcher.expectGroupCard( List.of(firstTabId, secondTabId), TabSwitcherGroupCardFacility.DEFAULT_N_TABS_TITLE); - UndoSnackbarFacility undoSnackbar = tabGroupCard.openAppMenu().closeRegularTabGroup(); + UndoSnackbarFacility<RegularTabSwitcherStation> undoSnackbar = + tabGroupCard.openAppMenu().closeRegularTabGroup(); tabSwitcher.verifyTabSwitcherCardCount(0); // Press undo to verify that functionality
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java index ee9841c..de888baa 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherListEditorPTTest.java
@@ -102,8 +102,8 @@ TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor() - .groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName("test_tab_group_name"); dialog = dialog.pickColor(TabGroupColorId.RED); dialog.pressDone(); @@ -121,7 +121,8 @@ RegularNewTabPageStation secondPage = firstPage.openNewTabFast(); int secondTabId = secondPage.loadedTabElement.get().getId(); RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); @@ -145,11 +146,12 @@ RegularNewTabPageStation secondPage = firstPage.openNewTabFast(); int secondTabId = secondPage.loadedTabElement.get().getId(); RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName("test_tab_group_name"); dialog = dialog.pickColor(TabGroupColorId.RED); @@ -246,10 +248,12 @@ RegularTabSwitcherStation tabSwitcher = thirdPage.openRegularTabSwitcher(); // Group first and second tabs - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); - NewTabGroupDialogFacility dialog = editor.openAppMenuWithEditor().groupTabs(); + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = + editor.openAppMenuWithEditor().groupTabs(); dialog.pressDone(); TabBinningUtil.assertBinsEqual(tabModel, group(secondTabId, firstTabId), thirdTabId); @@ -261,7 +265,7 @@ TabBinningUtil.assertBinsEqual(tabModel, group(secondTabId, firstTabId, thirdTabId)); // Ungroup and revert to only first and second tabs grouped, and third tab by itself - UndoSnackbarFacility undoSnackbar = groupMergedResult.second; + UndoSnackbarFacility<RegularTabSwitcherStation> undoSnackbar = groupMergedResult.second; undoSnackbar.pressUndo(); tabSwitcher.expectGroupCard( List.of(firstTabId, secondTabId),
diff --git a/chrome/android/java/res/layout/bookmark_widget.xml b/chrome/android/java/res/layout/bookmark_widget.xml index 964e196..cf85e3eb 100644 --- a/chrome/android/java/res/layout/bookmark_widget.xml +++ b/chrome/android/java/res/layout/bookmark_widget.xml
@@ -20,8 +20,7 @@ android:background="@drawable/bookmark_widget_background" android:divider="@null" android:drawSelectorOnTop="true" - android:listSelector="@drawable/bookmark_widget_list_selector" - android:alpha="0.9" /> + android:listSelector="@drawable/bookmark_widget_list_selector" /> <TextView android:id="@+id/empty_message" @@ -34,4 +33,4 @@ android:textAppearance="@style/TextAppearance.TextSmall.Secondary" android:visibility="gone" /> -</FrameLayout> \ No newline at end of file +</FrameLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java index 390db6c..13b84496 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java
@@ -66,8 +66,8 @@ // Only load the native library early for bundle builds since some tests use the // "--disable-native-initialization" switch, and the CommandLine is not initialized at // this point to check. - if (BundleUtils.isBundle() - && !ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled()) { + if (BundleUtils.isBundle()) { + ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled(); // Kick off library loading in a separate thread so it's ready when we need it. new Thread(() -> LibraryLoader.getInstance().ensureInitialized()).start(); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java index ba21fe4..465bd0f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -3388,18 +3388,16 @@ mTabModelSelector.selectModel(false); RecordUserAction.record("MobileMenuSwitchOutOfIncognito"); } else if (id == R.id.ntp_customization_id) { + Supplier<Profile> profileSupplier = + () -> + getProfileProviderSupplier().hasValue() + ? getProfileProviderSupplier().get().getOriginalProfile() + : null; new NtpCustomizationCoordinator( this, mRootUiCoordinator.getBottomSheetController(), - () -> { - OneshotSupplier<ProfileProvider> profileProviderSupplier = - getProfileProviderSupplier(); - if (profileProviderSupplier.hasValue()) { - return getProfileProviderSupplier().get().getOriginalProfile(); - } - - return null; - }) + profileSupplier, + NtpCustomizationCoordinator.BottomSheetType.MAIN) .showBottomSheet(); NtpCustomizationMetricsUtils.recordOpenBottomSheetEntry( NtpCustomizationCoordinator.EntryPointType.MAIN_MENU);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java index 564f5fc..35c62159 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java
@@ -201,7 +201,8 @@ maybeInitProcessType(); - if (isBrowserProcess && !ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled()) { + if (isBrowserProcess) { + ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled(); performBrowserProcessPreloading(context); } @@ -296,11 +297,8 @@ } private void maybeInitChromeSplitAndPreloadNativeLibrary() { - if (isBrowserProcess() - && ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled() - && !BuildConfig.IS_FOR_TEST) { - new Thread(() -> LibraryLoader.getInstance().loadNow()).start(); - performBrowserProcessPreloading(this, true); + if (isBrowserProcess()) { + ChromeFeatureList.sSkipIsolatedSplitPreload.isEnabled(); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutRenderHost.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutRenderHost.java index e3a5748e..e706273 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutRenderHost.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutRenderHost.java
@@ -4,12 +4,14 @@ package org.chromium.chrome.browser.compositor.layouts; +import org.chromium.build.annotations.NullMarked; import org.chromium.ui.resources.ResourceManager; /** * {@link LayoutRenderHost} is the minimal interface the layouts need to know about its host to * render. */ +@NullMarked public interface LayoutRenderHost { /** Request layout and draw. */ void requestRender();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java index 95972d2..c00c113b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java
@@ -8,6 +8,7 @@ import android.content.res.Resources; import android.graphics.Color; +import org.chromium.build.annotations.NullMarked; import org.chromium.cc.input.OffsetTag; import org.chromium.chrome.browser.tab.Tab; import org.chromium.ui.modelutil.PropertyKey; @@ -17,6 +18,7 @@ * {@link LayoutTab} is used to keep track of a thumbnail's bitmap and position and to draw itself * onto the GL canvas at the desired Y Offset. */ +@NullMarked public class LayoutTab extends PropertyModel { // TODO(crbug.com/40126260): Make the following properties be part of the PropertyModel. // Begin section --------------
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerHandler.java index 4dd522a..ee091d1 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerHandler.java
@@ -4,6 +4,8 @@ package org.chromium.chrome.browser.compositor.layouts.content; +import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager.Observer; import org.chromium.chrome.browser.fullscreen.FullscreenOptions; @@ -13,6 +15,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; /** Helper class attaching Tab's content layer to {@link TabContentManager}. */ +@NullMarked public final class TabContentManagerHandler extends TabModelSelectorTabObserver { private final TabContentManager mTabContentManager; @@ -25,7 +28,7 @@ private boolean mShouldRemoveThumbnail; // A tab whose thumbnail needs to be removed. - private Tab mThumbnailTab; + private @Nullable Tab mThumbnailTab; public static void create( TabContentManager manager,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/BlackHoleEventFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/BlackHoleEventFilter.java index 8f2a724..bf65d60 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/BlackHoleEventFilter.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/BlackHoleEventFilter.java
@@ -7,9 +7,11 @@ import android.content.Context; import android.view.MotionEvent; +import org.chromium.build.annotations.NullMarked; import org.chromium.chrome.browser.layouts.EventFilter; /** A {@link BlackHoleEventFilter} eats all the events coming its way with no side effects. */ +@NullMarked public class BlackHoleEventFilter extends EventFilter { /** * Creates a {@link BlackHoleEventFilter}.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/MotionEventHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/MotionEventHandler.java index 7496058..d0ff6da 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/MotionEventHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/MotionEventHandler.java
@@ -4,7 +4,10 @@ package org.chromium.chrome.browser.compositor.layouts.eventfilter; +import org.chromium.build.annotations.NullMarked; + /** Interface that describes motion event callbacks. */ +@NullMarked public interface MotionEventHandler { /** * Called on down touch event.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackScroller.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackScroller.java index 5eee91c..f3cb0ba1 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackScroller.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackScroller.java
@@ -9,10 +9,13 @@ import android.util.Log; import android.view.ViewConfiguration; +import org.chromium.build.annotations.NullMarked; + /** * This class is vastly copied from {@link android.widget.OverScroller} but decouples the time from * the app time so it can be specified manually. */ +@NullMarked public class StackScroller { private int mMode;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java index 90fa56b..ec56ff29 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -48,6 +48,7 @@ import android.widget.TextView; import androidx.annotation.ColorInt; +import androidx.annotation.DimenRes; import androidx.annotation.Dimension; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; @@ -621,18 +622,32 @@ updateCustomActionButtonVisuals(button, drawable, description); - int buttonWidth = getResources().getDimensionPixelSize(R.dimen.toolbar_button_width); + int buttonWidth = getDimensionPx(R.dimen.toolbar_button_width); button.setLayoutParams(new ViewGroup.LayoutParams(buttonWidth, LayoutParams.MATCH_PARENT)); - int paddingStart = - getResources().getDimensionPixelSize(R.dimen.custom_tabs_toolbar_button_spacer_16); - int paddingEnd = - getResources().getDimensionPixelSize(R.dimen.custom_tabs_toolbar_button_spacer_8); + + // Set the padding to give 16dp spacing. |mCustomActionButtons| contains custom/optional + // buttons. Optional button, though not visible, is counted in #getChildCount() as ViewStub. + @Dimension int paddingStart; + @Dimension int paddingEnd; + if (mCustomActionButtons.getChildCount() < 2) { + // 16:act1:8 - 8:menu:16 + paddingStart = getDimensionPx(R.dimen.custom_tabs_toolbar_button_spacer_16); + paddingEnd = getDimensionPx(R.dimen.custom_tabs_toolbar_button_spacer_8); + } else { + // 24:act2:0 - 16:act1:8 - 8:menu:16 + paddingStart = getDimensionPx(R.dimen.custom_tabs_toolbar_button_spacer_24); + paddingEnd = 0; + } button.setPaddingRelative(paddingStart, /* top= */ 0, paddingEnd, /* bottom= */ 0); // Add the view at the beginning of the child list. mCustomActionButtons.addView(button, 0); } + private @Dimension int getDimensionPx(@DimenRes int resId) { + return getResources().getDimensionPixelSize(resId); + } + @Override protected void updateCustomActionButton(int index, Drawable drawable, String description) { ImageButton button = @@ -1558,10 +1573,8 @@ View firstButton = mCustomActionButtons.getChildAt(0); if (firstButton != optionalButton) { // Give 24dp/0dp padding to the first button to have even 16dp spacing. - int paddingStart = - getResources() - .getDimensionPixelSize( - R.dimen.custom_tabs_toolbar_button_spacer_24); + // TODO(crbug.com/416458104): Sort out the padding if the # of buttons reaches 4. + int paddingStart = getDimensionPx(R.dimen.custom_tabs_toolbar_button_spacer_24); firstButton.setPaddingRelative( paddingStart, /* top= */ 0, /* end= */ 0, /* bottom= */ 0); } @@ -2027,8 +2040,14 @@ final int backgroundColor = getBackground().getColor(); if (ThemeUtils.isUsingDefaultToolbarColor( context, /* isIncognito= */ false, backgroundColor)) { - progressBar.setBackgroundColor( - context.getColor(R.color.progress_bar_bg_color_list)); + if (ChromeFeatureList.sAndroidProgressBarVisualUpdate.isEnabled()) { + progressBar.setBackgroundColor( + SemanticColorUtils.getProgressBarTrackColor(context)); + } else { + progressBar.setBackgroundColor( + context.getColor(R.color.progress_bar_bg_color_list)); + } + progressBar.setForegroundColor( SemanticColorUtils.getProgressBarForeground(context)); } else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java index 6d2084c..03e79cb 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -68,6 +68,7 @@ import org.chromium.chrome.browser.magic_stack.ModuleRegistry; import org.chromium.chrome.browser.metrics.StartupMetricsTracker; import org.chromium.chrome.browser.native_page.ContextMenuManager; +import org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator; import org.chromium.chrome.browser.omnibox.OmniboxFocusReason; import org.chromium.chrome.browser.omnibox.OmniboxStub; import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; @@ -1382,7 +1383,16 @@ @Override public void customizeSettings() { - HomeModulesConfigManager.getInstance().onMenuClick(mContext); + if (ChromeFeatureList.isEnabled(ChromeFeatureList.NEW_TAB_PAGE_CUSTOMIZATION)) { + new NtpCustomizationCoordinator( + mContext, + mBottomSheetController, + mTab::getProfile, + NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS) + .showBottomSheet(); + } else { + HomeModulesConfigManager.getInstance().onMenuClick(mContext); + } } @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java index 1a040228..d6e96cbd 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
@@ -8,6 +8,8 @@ import android.os.Handler; import android.os.Message; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.chromium.base.metrics.RecordHistogram; @@ -26,6 +28,8 @@ import org.chromium.ui.base.WindowAndroid; import org.chromium.url.GURL; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashSet; import java.util.Set; @@ -40,6 +44,33 @@ /** The maximum amount of time to wait for a page to load before entering fullscreen. */ private static final long MAX_FULLSCREEN_LOAD_DELAY_MS = 3000; + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + @IntDef({ + LockReason.CHROME_URL, + LockReason.TAB_CONTENT_DANGEROUS, + LockReason.EDITABLE_NODE_FOCUS, + LockReason.TAB_ERROR, + LockReason.TAB_HIDDEN, + LockReason.FULLSCREEN_LOADING, + LockReason.A11Y_ENABLED, + LockReason.FULLSCREEN_DISABLED, + LockReason.NUM_TOTAL + }) + @Retention(RetentionPolicy.SOURCE) + @interface LockReason { + int CHROME_URL = 0; + int TAB_CONTENT_DANGEROUS = 1; + int EDITABLE_NODE_FOCUS = 2; + int TAB_ERROR = 3; + int TAB_HIDDEN = 4; + int FULLSCREEN_LOADING = 5; + int A11Y_ENABLED = 6; + int FULLSCREEN_DISABLED = 7; + + int NUM_TOTAL = 8; + } + private static boolean sDisableLoadingCheck; protected final TabImpl mTab; @@ -240,23 +271,46 @@ WebContents webContents = mTab.getWebContents(); if (webContents == null || webContents.isDestroyed()) return false; - GURL url = mTab.getUrl(); - boolean enableHidingBrowserControls = url != null; - enableHidingBrowserControls &= !url.getScheme().equals(UrlConstants.CHROME_SCHEME); - enableHidingBrowserControls &= !url.getScheme().equals(UrlConstants.CHROME_NATIVE_SCHEME); - - enableHidingBrowserControls &= - !SecurityStateModel.isContentDangerous(mTab.getWebContents()); - enableHidingBrowserControls &= !mIsFocusedNodeEditable; - enableHidingBrowserControls &= !mTab.isShowingErrorPage(); - enableHidingBrowserControls &= !mTab.isRendererUnresponsive(); - enableHidingBrowserControls &= !mTab.isHidden(); - enableHidingBrowserControls &= !mIsFullscreenWaitingForLoad; - + @NonNull GURL url = mTab.getUrl(); + boolean enableHidingBrowserControls = true; + if (url.getScheme().equals(UrlConstants.CHROME_SCHEME) + || url.getScheme().equals(UrlConstants.CHROME_NATIVE_SCHEME)) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.CHROME_URL); + } + if (SecurityStateModel.isContentDangerous(mTab.getWebContents())) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.TAB_CONTENT_DANGEROUS); + } + if (mIsFocusedNodeEditable) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.EDITABLE_NODE_FOCUS); + } + if (mTab.isShowingErrorPage() || mTab.isRendererUnresponsive()) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.TAB_ERROR); + } + if (mTab.isHidden()) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.TAB_HIDDEN); + } + if (mIsFullscreenWaitingForLoad) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.FULLSCREEN_LOADING); + } // TODO(tedchoc): AccessibilityUtil and DeviceClassManager checks do not belong in Tab // logic. They should be moved to application level checks. - enableHidingBrowserControls &= !ChromeAccessibilityUtil.get().isAccessibilityEnabled(); - enableHidingBrowserControls &= DeviceClassManager.enableFullscreen(); + if (ChromeAccessibilityUtil.get().isAccessibilityEnabled()) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.A11Y_ENABLED); + } + if (!DeviceClassManager.enableFullscreen()) { + enableHidingBrowserControls = false; + recordBrowserControlsLockReason(LockReason.FULLSCREEN_DISABLED); + } + + RecordHistogram.recordBooleanHistogram( + "Android.BrowserControls.LockedByTabState", !enableHidingBrowserControls); return enableHidingBrowserControls; } @@ -287,4 +341,9 @@ mIsFocusedNodeEditable = editable; updateVisibilityConstraints(); } + + private static void recordBrowserControlsLockReason(@LockReason int reason) { + RecordHistogram.recordEnumeratedHistogram( + "Android.BrowserControls.LockedByTabState.Reason", reason, LockReason.NUM_TOTAL); + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java index 7f73d73..817ab37 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -179,6 +179,7 @@ import org.chromium.components.messages.MessageContainer; import org.chromium.components.messages.MessageDispatcherProvider; import org.chromium.components.messages.MessagesFactory; +import org.chromium.components.search_engines.TemplateUrlService; import org.chromium.components.ukm.UkmRecorder; import org.chromium.content_public.browser.ActionModeCallbackHelper; import org.chromium.content_public.browser.BrowserContextHandle; @@ -1184,10 +1185,10 @@ /** Generate the LoadUrlParams necessary to load the specified search query. */ private static LoadUrlParams generateUrlParamsForSearch(Tab tab, String query) { - String url = - TemplateUrlServiceFactory.getForProfile(tab.getProfile()) - .getUrlForSearchQuery(query); - String headers = GeolocationHeader.getGeoHeader(url, tab); + Profile profile = tab.getProfile(); + TemplateUrlService service = TemplateUrlServiceFactory.getForProfile(profile); + String url = service.getUrlForSearchQuery(query); + String headers = GeolocationHeader.getGeoHeader(url, profile, service); LoadUrlParams loadUrlParams = new LoadUrlParams(url); loadUrlParams.setVerbatimHeaders(headers);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java index b65f43b..3beb4527 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
@@ -116,7 +116,7 @@ @Rule public final RenderTestRule mRenderTestRule = RenderTestRule.Builder.withPublicCorpus() - .setRevision(7) + .setRevision(8) .setBugComponent(RenderTestRule.Component.UI_BROWSER_MOBILE_CUSTOM_TABS) .build();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java index 9404427b..fb537a06 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
@@ -26,6 +26,7 @@ import org.chromium.base.test.util.RequiresRestart; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule; @@ -242,9 +243,9 @@ SessionModel.DURABLE); infoHttps.setContentSetting( ProfileManager.getLastUsedRegularProfile(), httpsPermission); - String header = - GeolocationHeader.getGeoHeader( - SEARCH_URL_1, mActivityTestRule.getActivity().getActivityTab()); + var profile = mActivityTestRule.getActivity().getActivityTab().getProfile(); + var service = TemplateUrlServiceFactory.getForProfile(profile); + String header = GeolocationHeader.getGeoHeader(SEARCH_URL_1, profile, service); assertHeaderState(header, locationTime, shouldBeNull); }); } @@ -291,7 +292,9 @@ final Tab tab = mActivityTestRule.loadUrlInNewTab("about:blank", isIncognito); ThreadUtils.runOnUiThreadBlocking( () -> { - Assert.assertNull(GeolocationHeader.getGeoHeader(url, tab)); + var profile = mActivityTestRule.getActivity().getActivityTab().getProfile(); + var service = TemplateUrlServiceFactory.getForProfile(profile); + Assert.assertNull(GeolocationHeader.getGeoHeader(url, profile, service)); }); } @@ -300,7 +303,10 @@ final Tab tab = mActivityTestRule.loadUrlInNewTab("about:blank", isIncognito); ThreadUtils.runOnUiThreadBlocking( () -> { - assertHeaderEquals(locationTime, GeolocationHeader.getGeoHeader(url, tab)); + var profile = mActivityTestRule.getActivity().getActivityTab().getProfile(); + var service = TemplateUrlServiceFactory.getForProfile(profile); + assertHeaderEquals( + locationTime, GeolocationHeader.getGeoHeader(url, profile, service)); }); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java index f82d776..9653c80 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java
@@ -28,8 +28,10 @@ import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures; import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarStatePredictor; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.R; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.page.WebPageStation; import org.chromium.chrome.test.util.ChromeTabUtils; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.components.embedder_support.util.UrlConstants; @@ -43,8 +45,8 @@ @Batch(Batch.PER_CLASS) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public final class ShareButtonControllerTest { - private final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + private final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); private final SigninTestRule mSigninTestRule = new SigninTestRule(); @@ -55,11 +57,12 @@ RuleChain.outerRule(mSigninTestRule).around(mActivityTestRule); private boolean mButtonExpected; + private WebPageStation mInitialPage; @Before public void setUp() { AdaptiveToolbarStatePredictor.setToolbarStateForTesting(AdaptiveToolbarButtonVariant.SHARE); - mActivityTestRule.startMainActivityOnBlankPage(); + mInitialPage = mActivityTestRule.startOnBlankPage(); int deviceWidth = mActivityTestRule.getActivity().getResources().getConfiguration().screenWidthDp;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java index 4d9755e..060793e 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java
@@ -8,7 +8,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Assert; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,8 +28,8 @@ import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; -import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule; +import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.components.browser_ui.share.ShareParams; import org.chromium.components.ui_metrics.CanonicalURLResult; @@ -52,13 +51,9 @@ private static final String PAGE_WITH_NO_CANONICAL_URL = "/chrome/test/data/android/share/link_share_no_canonical.html"; - @ClassRule - public static ChromeTabbedActivityTestRule sActivityTestRule = - new ChromeTabbedActivityTestRule(); - @Rule - public BlankCTATabInitialStateRule mInitialStateRule = - new BlankCTATabInitialStateRule(sActivityTestRule, false); + public AutoResetCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.fastAutoResetCtaActivityRule(); @Test @SmallTest @@ -108,7 +103,7 @@ private void verifyShareUrl( String pageUrl, String expectedShareUrl, @CanonicalURLResult int expectedUrlResult) throws IllegalArgumentException, TimeoutException { - sActivityTestRule.loadUrl(pageUrl); + mActivityTestRule.loadUrl(pageUrl); var urlResultHistogram = HistogramWatcher.newSingleRecordWatcher( ShareDelegateImpl.CANONICAL_URL_RESULT_HISTOGRAM, expectedUrlResult); @@ -144,23 +139,23 @@ }; new ShareDelegateImpl( - sActivityTestRule.getActivity(), - sActivityTestRule + mActivityTestRule.getActivity(), + mActivityTestRule .getActivity() .getRootUiCoordinatorForTesting() .getBottomSheetController(), - sActivityTestRule.getActivity().getLifecycleDispatcher(), - sActivityTestRule.getActivity().getActivityTabProvider(), - sActivityTestRule.getActivity().getTabModelSelectorSupplier(), + mActivityTestRule.getActivity().getLifecycleDispatcher(), + mActivityTestRule.getActivity().getActivityTabProvider(), + mActivityTestRule.getActivity().getTabModelSelectorSupplier(), new ObservableSupplierImpl<>(), delegate, false, - sActivityTestRule + mActivityTestRule .getActivity() .getRootUiCoordinatorForTesting() .getDataSharingTabManager()) .share( - sActivityTestRule.getActivity().getActivityTab(), + mActivityTestRule.getActivity().getActivityTab(), false, /* shareOrigin= */ 0); });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java index ba28c90..c58117d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java
@@ -25,7 +25,8 @@ import org.chromium.chrome.browser.profiles.ProfileManager; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.chrome.test.util.browser.signin.SigninTestUtil; import org.chromium.components.signin.base.CoreAccountInfo; @@ -65,8 +66,8 @@ @Rule public final SigninTestRule mSigninTestRule = new SigninTestRule(); @Rule - public final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + public final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); private final Observer mObserver = new Observer(); @@ -74,7 +75,7 @@ @Before public void setUp() { - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); ThreadUtils.runOnUiThreadBlocking( () -> { mIdentityManager =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java index a401896..fa89624 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java
@@ -23,7 +23,8 @@ import org.chromium.base.test.util.UserActionTester; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.chrome.test.util.browser.sync.SyncTestUtil; import org.chromium.components.externalauth.ExternalAuthUtils; @@ -42,13 +43,13 @@ @Rule public final SigninTestRule mSigninTestRule = new SigninTestRule(); @Rule - public final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + public final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); @Mock private ExternalAuthUtils mExternalAuthUtilsMock; private void signinWhenChildAccountIsTheOnlyAccount() { - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); mSigninTestRule.addAccount(TestAccounts.CHILD_ACCOUNT); @@ -71,7 +72,7 @@ } private void noSigninWhenChildAccountIsTheOnlyAccountButSigninIsNotAllowed() { - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); UserActionTester actionTester = new UserActionTester(); when(mExternalAuthUtilsMock.isGooglePlayServicesMissing(any())).thenReturn(true); ExternalAuthUtils.setInstanceForTesting(mExternalAuthUtilsMock); @@ -101,7 +102,7 @@ mSigninTestRule.addAccount("the.default.account@gmail.com"); mSigninTestRule.addAccount(TestAccounts.CHILD_ACCOUNT); - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); UserActionTester actionTester = new UserActionTester(); Assert.assertEquals( @@ -120,7 +121,7 @@ } private void signinWhenChildAccountIsFirstAccount() { - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); mSigninTestRule.addAccount(TestAccounts.CHILD_ACCOUNT); mSigninTestRule.addAccount("the.second.account@gmail.com");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHeaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHeaderTest.java index 4cf947a5..82ac969 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHeaderTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHeaderTest.java
@@ -30,7 +30,9 @@ import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.page.WebPageStation; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.content_public.browser.test.util.JavaScriptUtils; import org.chromium.net.test.EmbeddedTestServerRule; @@ -47,8 +49,8 @@ @Rule public final SigninTestRule mSigninTestRule = new SigninTestRule(); @Rule - public ChromeTabbedActivityTestRule mChromeActivityTestRule = - new ChromeTabbedActivityTestRule(); + public FreshCtaTransitTestRule mChromeActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); @Rule public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule(); @@ -56,6 +58,7 @@ @Rule public EmbeddedTestServerRule mEmbeddedTestServerRule = new EmbeddedTestServerRule(); private String mGAIAUrl; + private WebPageStation mInitialPage; private void launchTrustedWebActivity(Intent intent) throws TimeoutException { String url = intent.getData().toString(); @@ -72,7 +75,7 @@ // Specify a Gaia url path. CommandLine.getInstance() .appendSwitchWithValue("gaia-url", mEmbeddedTestServerRule.getServer().getURL("/")); - mChromeActivityTestRule.startMainActivityOnBlankPage(); + mInitialPage = mChromeActivityTestRule.startOnBlankPage(); mSigninTestRule.addTestAccountThenSignin(); mGAIAUrl = mEmbeddedTestServerRule.getServer().getURL("/echoheader?X-Chrome-Connected");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java index b499ddf..83c0539 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java
@@ -45,8 +45,9 @@ import org.chromium.chrome.browser.sync.settings.AccountManagementFragment; import org.chromium.chrome.browser.ui.signin.history_sync.HistorySyncHelper; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.R; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.components.externalauth.ExternalAuthUtils; import org.chromium.components.signin.base.CoreAccountInfo; @@ -66,8 +67,8 @@ private final SettingsActivityTestRule<MainSettings> mMainSettingsActivityTestRule = new SettingsActivityTestRule<>(MainSettings.class); - private final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + private final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); private final SigninTestRule mSigninTestRule = new SigninTestRule(); @@ -95,7 +96,7 @@ @Before public void setUp() { SigninMetricsUtilsJni.setInstanceForTesting(mSigninMetricsUtilsNativeMock); - mActivityTestRule.startMainActivityOnBlankPage(); + mActivityTestRule.startOnBlankPage(); ThreadUtils.runOnUiThreadBlocking( () -> { mSigninManager =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java index 5dc2208..a5c5457 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java
@@ -36,6 +36,8 @@ import org.chromium.base.test.util.CriteriaHelper; import org.chromium.base.test.util.DisableIf; import org.chromium.base.test.util.DoNotBatch; +import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.profiles.ProfileManager; import org.chromium.chrome.browser.settings.SettingsActivity; import org.chromium.chrome.browser.signin.SigninCheckerProvider; @@ -44,6 +46,7 @@ import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; +import org.chromium.components.browser_ui.site_settings.BinaryStatePermissionPreference; import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory; import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge; import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridgeJni; @@ -94,6 +97,9 @@ @Test @SmallTest + // TODO(crbug.com/415770550): Fix Text for managed settings and enable this + // test for raddio buttons. + @DisableFeatures(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) public void testDeletingOnDeviceDataBlockedForSupervisedUsers() { SettingsActivity settingsActivity = SiteSettingsTestUtils.startSiteSettingsCategory( @@ -143,10 +149,16 @@ PreferenceFragmentCompat preferenceFragment = (PreferenceFragmentCompat) settingsActivity.getMainFragment(); PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen(); - ChromeSwitchPreference binary_toggle = preferenceScreen.findPreference("binary_toggle"); + if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + BinaryStatePermissionPreference radioButton = + preferenceScreen.findPreference("binary_radio_button"); + Assert.assertTrue(radioButton.isEnabled()); + } else { + ChromeSwitchPreference binary_toggle = preferenceScreen.findPreference("binary_toggle"); + // When deleting cookies are not blocked through Family Link the toggle will be enabled + Assert.assertTrue(binary_toggle.isEnabled()); + } - // When deleting cookies are not blocked through Family Link the toggle will be enabled - Assert.assertTrue(binary_toggle.isEnabled()); settingsActivity.finish(); } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java index b218614..d4e6c50 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
@@ -214,14 +214,14 @@ private static final String[] NULL_ARRAY = new String[0]; private static final String[] BINARY_TOGGLE_AND_INFO_TEXT = - new String[] {"info_text", "binary_toggle"}; - private static final String[] BINARY_TOGGLE = new String[] {"binary_toggle"}; + new String[] {"info_text", "binary_radio_button"}; + private static final String[] BINARY_TOGGLE = new String[] {"binary_radio_button"}; private static final String[] BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT = - new String[] {"info_text", "binary_toggle", "add_exception"}; + new String[] {"info_text", "binary_radio_button", "add_exception"}; private static final String[] BINARY_TOGGLE_WITH_EXCEPTION = - new String[] {"binary_toggle", "add_exception"}; + new String[] {"binary_radio_button", "add_exception"}; private static final String[] BINARY_TOGGLE_WITH_OS_WARNING_EXTRA = - new String[] {"binary_toggle", "os_permissions_warning_extra"}; + new String[] {"info_text", "binary_radio_button", "os_permissions_warning_extra"}; private static final String[] CLEAR_BROWSING_DATA_LINK = new String[] {"clear_browsing_data_link", "clear_browsing_divider"}; private static final String[] ANTI_ABUSE_PREF_KEYS = { @@ -466,6 +466,7 @@ SiteSettingsCategory.Type.DEVICE_LOCATION, ContentSettingsType.GEOLOCATION, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); ThreadUtils.runOnUiThreadBlocking( () -> @@ -498,6 +499,7 @@ SiteSettingsCategory.Type.DEVICE_LOCATION, ContentSettingsType.GEOLOCATION, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); ThreadUtils.runOnUiThreadBlocking( () -> @@ -620,6 +622,14 @@ enabled ? CookieControlsMode.OFF : CookieControlsMode.BLOCK_THIRD_PARTY); + } else if (ChromeFeatureList.isEnabled( + ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) + && type != SiteSettingsCategory.Type.ANTI_ABUSE) { + BinaryStatePermissionPreference radioButton = + preferences.findPreference( + SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); + + preferences.onPreferenceChange(radioButton, enabled); } else { ChromeSwitchPreference toggle = preferences.findPreference( @@ -1349,6 +1359,7 @@ SiteSettingsCategory.Type.POPUPS, ContentSettingsType.POPUPS, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); // Test that the popup doesn't open. @@ -1369,6 +1380,7 @@ SiteSettingsCategory.Type.POPUPS, ContentSettingsType.POPUPS, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); // Test that a popup opens. @@ -1465,7 +1477,10 @@ @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesAds() { - testExpectedPreferences(SiteSettingsCategory.Type.ADS, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.ADS, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1483,7 +1498,9 @@ @Feature({"Preferences"}) public void testOnlyExpectedPreferencesAugmentedReality() { testExpectedPreferences( - SiteSettingsCategory.Type.AUGMENTED_REALITY, BINARY_TOGGLE, BINARY_TOGGLE); + SiteSettingsCategory.Type.AUGMENTED_REALITY, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1502,8 +1519,8 @@ public void testOnlyExpectedPreferencesAutomaticDownloads() { testExpectedPreferences( SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS, - BINARY_TOGGLE_WITH_EXCEPTION, - BINARY_TOGGLE); + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1512,8 +1529,8 @@ public void testOnlyExpectedPreferencesBackgroundSync() { testExpectedPreferences( SiteSettingsCategory.Type.BACKGROUND_SYNC, - BINARY_TOGGLE_WITH_EXCEPTION, - BINARY_TOGGLE); + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1535,21 +1552,28 @@ @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesCamera() { - testExpectedPreferences(SiteSettingsCategory.Type.CAMERA, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.CAMERA, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesClipboard() { - testExpectedPreferences(SiteSettingsCategory.Type.CLIPBOARD, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.CLIPBOARD, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesFileEditing() { - checkPreferencesForCategory(SiteSettingsCategory.Type.FILE_EDITING, BINARY_TOGGLE); + checkPreferencesForCategory( + SiteSettingsCategory.Type.FILE_EDITING, BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1889,7 +1913,9 @@ LocationSettingsTestUtil.setSystemLocationSettingEnabled(true); testExpectedPreferences( - SiteSettingsCategory.Type.DEVICE_LOCATION, BINARY_TOGGLE, BINARY_TOGGLE); + SiteSettingsCategory.Type.DEVICE_LOCATION, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); // Disable system location setting and check for the right preferences. LocationSettingsTestUtil.setSystemLocationSettingEnabled(false); @@ -1904,8 +1930,8 @@ public void testOnlyExpectedPreferencesFederatedIdentityApi() { testExpectedPreferences( SiteSettingsCategory.Type.FEDERATED_IDENTITY_API, - BINARY_TOGGLE_WITH_EXCEPTION, - BINARY_TOGGLE_WITH_EXCEPTION); + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT); } @Test @@ -1931,7 +1957,9 @@ @Feature({"Preferences"}) public void testOnlyExpectedPreferencesIdleDetection() { testExpectedPreferences( - SiteSettingsCategory.Type.IDLE_DETECTION, BINARY_TOGGLE, BINARY_TOGGLE); + SiteSettingsCategory.Type.IDLE_DETECTION, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1940,15 +1968,18 @@ public void testOnlyExpectedPreferencesJavascript() { testExpectedPreferences( SiteSettingsCategory.Type.JAVASCRIPT, - BINARY_TOGGLE_WITH_EXCEPTION, - BINARY_TOGGLE_WITH_EXCEPTION); + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT); } @Test @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesMicrophone() { - testExpectedPreferences(SiteSettingsCategory.Type.MICROPHONE, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.MICROPHONE, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -1957,7 +1988,10 @@ public void testOnlyExpectedPreferencesNfc() { NfcSystemLevelSetting.setNfcSettingForTesting(true); - testExpectedPreferences(SiteSettingsCategory.Type.NFC, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.NFC, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); // Disable system nfc setting and check for the right preferences. NfcSystemLevelSetting.setNfcSettingForTesting(false); @@ -1978,12 +2012,17 @@ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { notifications_enabled = new String[] { - "binary_toggle", "notifications_quiet_ui", "notifications_vibrate" + "info_text", + "binary_toggle", + "notifications_quiet_ui", + "notifications_vibrate" }; - notifications_disabled = new String[] {"binary_toggle", "notifications_vibrate"}; + notifications_disabled = + new String[] {"info_text", "binary_toggle", "notifications_vibrate"}; } else { - notifications_enabled = new String[] {"binary_toggle", "notifications_quiet_ui"}; - notifications_disabled = BINARY_TOGGLE; + notifications_enabled = + new String[] {"info_text", "binary_radio_button", "notifications_quiet_ui"}; + notifications_disabled = BINARY_TOGGLE_AND_INFO_TEXT; } testExpectedPreferences( @@ -1996,14 +2035,17 @@ @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesPopups() { - testExpectedPreferences(SiteSettingsCategory.Type.POPUPS, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.POPUPS, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesProtectedMedia() { - String[] protectedMedia = new String[] {"tri_state_toggle", "protected_content_learn_more"}; + String[] protectedMedia = new String[] {"info_text", "tri_state_toggle"}; setGlobalTriStateToggleForCategory( SiteSettingsCategory.Type.PROTECTED_MEDIA, ContentSettingValues.ALLOW); checkPreferencesForCategory(SiteSettingsCategory.Type.PROTECTED_MEDIA, protectedMedia); @@ -2019,10 +2061,12 @@ @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesRequestDesktopSite() { - String[] rdsEnabled = {"binary_toggle", "desktop_site_window", "add_exception"}; + String[] rdsEnabled = { + "info_text", "binary_radio_button", "desktop_site_window", "add_exception" + }; testExpectedPreferences( SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE, - BINARY_TOGGLE_WITH_EXCEPTION, + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, rdsEnabled); Assert.assertTrue( "SharedPreference USER_ENABLED_DESKTOP_SITE_GLOBAL_SETTING_PREFERENCE_KEY should be" @@ -2037,7 +2081,10 @@ @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesSensors() { - testExpectedPreferences(SiteSettingsCategory.Type.SENSORS, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.SENSORS, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -2046,15 +2093,18 @@ public void testOnlyExpectedPreferencesSound() { testExpectedPreferences( SiteSettingsCategory.Type.SOUND, - BINARY_TOGGLE_WITH_EXCEPTION, - BINARY_TOGGLE_WITH_EXCEPTION); + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT, + BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT); } @Test @SmallTest @Feature({"Preferences"}) public void testOnlyExpectedPreferencesUsb() { - testExpectedPreferences(SiteSettingsCategory.Type.USB, BINARY_TOGGLE, BINARY_TOGGLE); + testExpectedPreferences( + SiteSettingsCategory.Type.USB, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -2062,7 +2112,9 @@ @Feature({"Preferences"}) public void testOnlyExpectedPreferencesSerialPort() { testExpectedPreferences( - SiteSettingsCategory.Type.SERIAL_PORT, BINARY_TOGGLE, BINARY_TOGGLE); + SiteSettingsCategory.Type.SERIAL_PORT, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } @Test @@ -2077,7 +2129,9 @@ @Feature({"Preferences"}) public void testOnlyExpectedPreferencesVirtualReality() { testExpectedPreferences( - SiteSettingsCategory.Type.VIRTUAL_REALITY, BINARY_TOGGLE, BINARY_TOGGLE); + SiteSettingsCategory.Type.VIRTUAL_REALITY, + BINARY_TOGGLE_AND_INFO_TEXT, + BINARY_TOGGLE_AND_INFO_TEXT); } /** Tests system NFC support in Preferences. */ @@ -2115,6 +2169,7 @@ SiteSettingsCategory.Type.CAMERA, ContentSettingsType.MEDIASTREAM_CAMERA, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); // Test that the camera permission doesn't get requested. @@ -2140,6 +2195,7 @@ SiteSettingsCategory.Type.CAMERA, ContentSettingsType.MEDIASTREAM_CAMERA, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); initializeUpdateWaiter(/* expectGranted= */ true); @@ -2163,6 +2219,7 @@ SiteSettingsCategory.Type.MICROPHONE, ContentSettingsType.MEDIASTREAM_MIC, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); // Test that the microphone permission doesn't get requested. @@ -2188,6 +2245,7 @@ SiteSettingsCategory.Type.MICROPHONE, ContentSettingsType.MEDIASTREAM_MIC, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); // Launch a page that uses the microphone and make sure a permission prompt shows up. @@ -2210,6 +2268,7 @@ SiteSettingsCategory.Type.BACKGROUND_SYNC, ContentSettingsType.BACKGROUND_SYNC, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2222,6 +2281,7 @@ SiteSettingsCategory.Type.BACKGROUND_SYNC, ContentSettingsType.BACKGROUND_SYNC, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); } @@ -2236,6 +2296,7 @@ SiteSettingsCategory.Type.NOTIFICATIONS, ContentSettingsType.NOTIFICATIONS, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.NOTIFICATIONS_TRI_STATE_PREF_KEY) .run(); } @@ -2250,6 +2311,7 @@ SiteSettingsCategory.Type.NOTIFICATIONS, ContentSettingsType.NOTIFICATIONS, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2264,6 +2326,7 @@ SiteSettingsCategory.Type.DEVICE_LOCATION, ContentSettingsType.GEOLOCATION, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.LOCATION_TRI_STATE_PREF_KEY) .run(); } @@ -2278,6 +2341,7 @@ SiteSettingsCategory.Type.DEVICE_LOCATION, ContentSettingsType.GEOLOCATION, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2287,6 +2351,7 @@ public void testAllowUsb() { new TwoStatePermissionTestCase( "USB", SiteSettingsCategory.Type.USB, ContentSettingsType.USB_GUARD, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2296,6 +2361,7 @@ public void testBlockUsb() { new TwoStatePermissionTestCase( "USB", SiteSettingsCategory.Type.USB, ContentSettingsType.USB_GUARD, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2308,6 +2374,7 @@ SiteSettingsCategory.Type.SERIAL_PORT, ContentSettingsType.SERIAL_GUARD, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2320,6 +2387,7 @@ SiteSettingsCategory.Type.SERIAL_PORT, ContentSettingsType.SERIAL_GUARD, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2332,6 +2400,7 @@ SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS, ContentSettingsType.AUTOMATIC_DOWNLOADS, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2344,6 +2413,7 @@ SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS, ContentSettingsType.AUTOMATIC_DOWNLOADS, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); } @@ -2403,6 +2473,7 @@ NfcSystemLevelSetting.setNfcSettingForTesting(true); new TwoStatePermissionTestCase( "NFC", SiteSettingsCategory.Type.NFC, ContentSettingsType.NFC, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2413,6 +2484,7 @@ NfcSystemLevelSetting.setNfcSettingForTesting(true); new TwoStatePermissionTestCase( "NFC", SiteSettingsCategory.Type.NFC, ContentSettingsType.NFC, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2451,6 +2523,7 @@ SiteSettingsCategory.Type.AUGMENTED_REALITY, ContentSettingsType.AR, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2463,6 +2536,7 @@ SiteSettingsCategory.Type.AUGMENTED_REALITY, ContentSettingsType.AR, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2475,6 +2549,7 @@ SiteSettingsCategory.Type.VIRTUAL_REALITY, ContentSettingsType.VR, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2487,6 +2562,7 @@ SiteSettingsCategory.Type.VIRTUAL_REALITY, ContentSettingsType.VR, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2523,6 +2599,7 @@ SiteSettingsCategory.Type.IDLE_DETECTION, ContentSettingsType.IDLE_DETECTION, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2535,6 +2612,7 @@ SiteSettingsCategory.Type.IDLE_DETECTION, ContentSettingsType.IDLE_DETECTION, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .run(); } @@ -2590,6 +2668,7 @@ SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE, ContentSettingsType.REQUEST_DESKTOP_SITE, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.DESKTOP_SITE_WINDOW_TOGGLE_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); @@ -2604,6 +2683,7 @@ SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE, ContentSettingsType.REQUEST_DESKTOP_SITE, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); } @@ -2617,6 +2697,7 @@ SiteSettingsCategory.Type.FEDERATED_IDENTITY_API, ContentSettingsType.FEDERATED_IDENTITY_API, true) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); } @@ -2630,6 +2711,7 @@ SiteSettingsCategory.Type.FEDERATED_IDENTITY_API, ContentSettingsType.FEDERATED_IDENTITY_API, false) + .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY) .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY) .run(); } @@ -2693,18 +2775,32 @@ settingsActivity, new String[] { SingleCategorySettings.INFO_TEXT_KEY, - SingleCategorySettings.BINARY_TOGGLE_KEY, + ChromeFeatureList.isEnabled( + ChromeFeatureList + .PERMISSION_SITE_SETTING_RADIO_BUTTON) + ? SingleCategorySettings.BINARY_RADIO_BUTTON_KEY + : SingleCategorySettings.BINARY_TOGGLE_KEY, SingleCategorySettings.TOGGLE_DISABLE_REASON_KEY, SingleCategorySettings.ALLOWED_GROUP, SingleCategorySettings.ADD_EXCEPTION_KEY, }); - ChromeSwitchPreference binaryToggle = - (ChromeSwitchPreference) - singleCategorySettings.findPreference( - SingleCategorySettings.BINARY_TOGGLE_KEY); - Assert.assertFalse(binaryToggle.isChecked()); - Assert.assertFalse(binaryToggle.isEnabled()); + if (ChromeFeatureList.isEnabled( + ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + BinaryStatePermissionPreference binaryRadioButton = + (BinaryStatePermissionPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); + Assert.assertFalse(binaryRadioButton.isChecked()); + Assert.assertFalse(binaryRadioButton.isEnabled()); + } else { + ChromeSwitchPreference binaryToggle = + (ChromeSwitchPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_TOGGLE_KEY); + Assert.assertFalse(binaryToggle.isChecked()); + Assert.assertFalse(binaryToggle.isEnabled()); + } Preference toggleDisableReason = singleCategorySettings.findPreference( @@ -2742,16 +2838,29 @@ settingsActivity, new String[] { SingleCategorySettings.INFO_TEXT_KEY, - SingleCategorySettings.BINARY_TOGGLE_KEY, + ChromeFeatureList.isEnabled( + ChromeFeatureList + .PERMISSION_SITE_SETTING_RADIO_BUTTON) + ? SingleCategorySettings.BINARY_RADIO_BUTTON_KEY + : SingleCategorySettings.BINARY_TOGGLE_KEY, SingleCategorySettings.ADD_EXCEPTION_KEY }); - - ChromeSwitchPreference binaryToggle = - (ChromeSwitchPreference) - singleCategorySettings.findPreference( - SingleCategorySettings.BINARY_TOGGLE_KEY); - Assert.assertTrue(binaryToggle.isChecked()); - Assert.assertFalse(binaryToggle.isEnabled()); + if (ChromeFeatureList.isEnabled( + ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + BinaryStatePermissionPreference binaryRadioButton = + (BinaryStatePermissionPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); + Assert.assertTrue(binaryRadioButton.isChecked()); + Assert.assertFalse(binaryRadioButton.isEnabled()); + } else { + ChromeSwitchPreference binaryToggle = + (ChromeSwitchPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_TOGGLE_KEY); + Assert.assertTrue(binaryToggle.isChecked()); + Assert.assertFalse(binaryToggle.isEnabled()); + } onData(withKey(SingleCategorySettings.ALLOWED_GROUP)) .inAdapterView( @@ -3110,9 +3219,20 @@ SingleCategorySettings preferences = (SingleCategorySettings) settingsActivity.getMainFragment(); // Window setting is only available when the Global Setting is ON. - ChromeSwitchPreference toggle = - preferences.findPreference(SingleCategorySettings.BINARY_TOGGLE_KEY); - preferences.onPreferenceChange(toggle, true); + + if (ChromeFeatureList.isEnabled( + ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + BinaryStatePermissionPreference binaryRadioButton = + (BinaryStatePermissionPreference) + preferences.findPreference( + SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); + preferences.onPreferenceChange(binaryRadioButton, true); + } else { + ChromeSwitchPreference toggle = + preferences.findPreference( + SingleCategorySettings.BINARY_TOGGLE_KEY); + preferences.onPreferenceChange(toggle, true); + } ChromeBaseCheckBoxPreference windowSettingPref = preferences.findPreference( @@ -3506,10 +3626,9 @@ testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT); testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.POPUPS); testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.DEVICE_LOCATION); - testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT_OPTIMIZER); + // testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT_OPTIMIZER); // TODO(crbug.com/40879457): add a test for sensors once crash in the sensors settings page - // is - // resolved. + // is resolved. } private void testTwoStateToggleDisabledByPolicy(@SiteSettingsCategory.Type int type) { @@ -3517,12 +3636,23 @@ SiteSettingsTestUtils.startSiteSettingsCategory(type); SingleCategorySettings singleCategorySettings = (SingleCategorySettings) settingsActivity.getMainFragment(); - ChromeSwitchPreference binaryToggle = - (ChromeSwitchPreference) - singleCategorySettings.findPreference( - SingleCategorySettings.BINARY_TOGGLE_KEY); - Assert.assertFalse(binaryToggle.isEnabled()); + if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) + && type != SiteSettingsCategory.Type.ANTI_ABUSE) { + BinaryStatePermissionPreference binaryRadioButton = + (BinaryStatePermissionPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); + + Assert.assertFalse(binaryRadioButton.isEnabled()); + } else { + ChromeSwitchPreference binaryToggle = + (ChromeSwitchPreference) + singleCategorySettings.findPreference( + SingleCategorySettings.BINARY_TOGGLE_KEY); + + Assert.assertFalse(binaryToggle.isEnabled()); + } ApplicationTestUtils.finishActivity(settingsActivity); } @@ -3676,7 +3806,6 @@ if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) && siteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) { - mExpectedPreferenceKeys.add(SingleCategorySettings.INFO_TEXT_KEY); mExpectedPreferenceKeys.add(SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); } else { mExpectedPreferenceKeys.add(SingleCategorySettings.BINARY_TOGGLE_KEY); @@ -3705,8 +3834,8 @@ + mSiteSettingsType + ">."; - if (ChromeFeatureList.isEnabled( - ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) + && mSiteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) { BinaryStatePermissionPreference radioButton = singleCategorySettings.findPreference( SingleCategorySettings.BINARY_RADIO_BUTTON_KEY); @@ -3735,8 +3864,8 @@ /** Verfiy {@link ContentSettingsResources} is set correctly. */ private void assertToggleTitleAndSummary(SingleCategorySettings singleCategorySettings) { - if (ChromeFeatureList.isEnabled( - ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) { + if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON) + && mSiteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) { BinaryStatePermissionPreference radio_button = singleCategorySettings.findPreference( SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestSigninUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestSigninUtils.java index 51ae3da9..e7623dd 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestSigninUtils.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestSigninUtils.java
@@ -13,8 +13,7 @@ import org.chromium.chrome.test.util.browser.signin.SigninTestRule; import org.chromium.components.signin.AccountManagerFacade; import org.chromium.components.signin.AccountManagerFacadeProvider; -import org.chromium.components.signin.base.GaiaId; -import org.chromium.components.signin.test.util.FakeAccountManagerFacade; +import org.chromium.components.signin.base.AccountInfo; /** Utility class for sign-in functionalities in native Sync browser tests. */ @JNINamespace("sync_test_utils_android") @@ -23,20 +22,16 @@ /** Sets up the test account and signs in, but does not enable Sync. */ @CalledByNative - private static void setUpAccountAndSignInForTesting() { - sSigninTestRule.addTestAccountThenSignin(); - } - - /** Returns GaiaId for the default test account. */ - @CalledByNative - private static GaiaId getGaiaIdForDefaultTestAccount() { - return FakeAccountManagerFacade.toGaiaId(SigninTestRule.TEST_ACCOUNT_EMAIL); + private static void setUpAccountAndSignInForTesting( + @JniType("AccountInfo") AccountInfo accountInfo) { + sSigninTestRule.addAccountThenSignin(accountInfo); } /** Sets up the test account, signs in, and enables Sync-the-feature. */ @CalledByNative - private static void setUpAccountAndSignInAndEnableSyncForTesting() { - sSigninTestRule.addTestAccountThenSigninAndEnableSync(); + private static void setUpAccountAndSignInAndEnableSyncForTesting( + @JniType("AccountInfo") AccountInfo accountInfo) { + sSigninTestRule.addAccountThenSigninAndEnableSync(accountInfo); } /** Signs out from the current test account. */
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java index efba1cb..05ecc7e 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
@@ -38,6 +38,7 @@ import org.chromium.base.IntentUtils; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.util.CallbackHelper; +import org.chromium.base.test.util.DestroyableHolder; import org.chromium.base.test.util.Matchers; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; @@ -66,6 +67,8 @@ private static final ComponentName TEST_COMPONENT_NAME_2 = new ComponentName("test.package.two", "test.class.name.two"); + private final DestroyableHolder<WindowAndroid> mWindowDestroyRef = new DestroyableHolder<>(); + private WindowAndroid mWindow; private Activity mActivity; private Uri mImageUri; @@ -80,6 +83,7 @@ IntentRequestTracker.createFromActivity(mActivity), /* insetObserver= */ null, /* trackOcclusion= */ true); + mWindowDestroyRef.set(mWindow); mImageUri = Uri.parse(IMAGE_URI); } @@ -87,7 +91,7 @@ public void tearDown() { ChromeSharedPreferences.getInstance() .removeKey(ChromePreferenceKeys.SHARING_LAST_SHARED_COMPONENT_NAME); - mWindow.destroy(); + mWindowDestroyRef.destroy(); mActivity.finish(); } @@ -233,7 +237,7 @@ @Test public void doNotShareWhenWindowDestroying() { - mWindow.destroy(); + mWindowDestroyRef.destroy(); ShareHelper.shareWithSystemShareSheetUi(emptyShareParams(), null, true); Intent nextIntent = Shadows.shadowOf(mActivity).peekNextStartedActivity();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegateTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegateTest.java index 83aa649e..d7aa223 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegateTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegateTest.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.browser.tab; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -25,7 +26,10 @@ import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.base.test.util.HistogramWatcher; import org.chromium.cc.input.BrowserControlsState; +import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate.LockReason; +import org.chromium.components.security_state.ConnectionSecurityLevel; import org.chromium.components.security_state.SecurityStateModel; import org.chromium.components.security_state.SecurityStateModelJni; import org.chromium.content_public.browser.NavigationHandle; @@ -197,4 +201,97 @@ BrowserControlsState.BOTH, controlsVisibilityDelegate.calculateVisibilityConstraints()); } + + @Test + public void testLockReasonHistogram_ChromeUrl() { + TabStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate = + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + doReturn(JUnitTestGURLs.NTP_URL).when(mTabImpl).getUrl(); + doReturn(mWebContents).when(mTabImpl).getWebContents(); + + try (var ignored = expectBrowserControlLocked(LockReason.CHROME_URL)) { + controlsVisibilityDelegate.calculateVisibilityConstraints(); + } + } + + @Test + public void testLockReasonHistogram_TabContentDagerous() { + TabStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate = + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + doReturn(JUnitTestGURLs.BLUE_1).when(mTabImpl).getUrl(); + doReturn(mWebContents).when(mTabImpl).getWebContents(); + doReturn(ConnectionSecurityLevel.DANGEROUS) + .when(mSecurityStateModelNatives) + .getSecurityLevelForWebContents(mWebContents); + + try (var ignored = expectBrowserControlLocked(LockReason.TAB_CONTENT_DANGEROUS)) { + controlsVisibilityDelegate.calculateVisibilityConstraints(); + } + } + + @Test + public void testLockReasonHistogram_EditableNodeFocus() { + TabStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate = + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + doReturn(JUnitTestGURLs.BLUE_1).when(mTabImpl).getUrl(); + doReturn(mWebContents).when(mTabImpl).getWebContents(); + controlsVisibilityDelegate.onNodeAttributeUpdated(true, true); + + try (var ignored = expectBrowserControlLocked(LockReason.EDITABLE_NODE_FOCUS)) { + controlsVisibilityDelegate.calculateVisibilityConstraints(); + } + } + + @Test + public void testLockReasonHistogram_TabError() { + TabStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate = + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + doReturn(JUnitTestGURLs.BLUE_1).when(mTabImpl).getUrl(); + doReturn(mWebContents).when(mTabImpl).getWebContents(); + doReturn(true).when(mTabImpl).isShowingErrorPage(); + + try (var ignored = expectBrowserControlLocked(LockReason.TAB_ERROR)) { + controlsVisibilityDelegate.calculateVisibilityConstraints(); + } + } + + @Test + public void testLockReasonHistogram_TabHidden() { + TabStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate = + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + doReturn(JUnitTestGURLs.BLUE_1).when(mTabImpl).getUrl(); + doReturn(mWebContents).when(mTabImpl).getWebContents(); + doReturn(true).when(mTabImpl).isHidden(); + + try (var ignored = expectBrowserControlLocked(LockReason.TAB_HIDDEN)) { + controlsVisibilityDelegate.calculateVisibilityConstraints(); + } + } + + @Test + public void testLockReasonHistogram_IsLoadingFullscreen() { + when(mTabImpl.getUrl()).thenReturn(JUnitTestGURLs.BLUE_1); + when(mNavigationHandle1.getNavigationId()).thenReturn(1L); + when(mNavigationHandle1.getUrl()).thenReturn(JUnitTestGURLs.BLUE_1); + when(mNavigationHandle1.isSameDocument()).thenReturn(false); + + new TabStateBrowserControlsVisibilityDelegate(mTabImpl); + verify(mTabImpl).addObserver(mTabObserverCaptor.capture()); + TabObserver tabObserver = mTabObserverCaptor.getValue(); + + // Set this after constructor to dodge the ImeAdapter#fromWebContents(). + when(mTabImpl.getWebContents()).thenReturn(mWebContents); + + try (var ignored = expectBrowserControlLocked(LockReason.FULLSCREEN_LOADING)) { + tabObserver.onDidStartNavigationInPrimaryMainFrame(mTabImpl, mNavigationHandle1); + } + } + + HistogramWatcher expectBrowserControlLocked(@LockReason int reason) { + return HistogramWatcher.newBuilder() + .expectBooleanRecord("Android.BrowserControls.LockedByTabState", true) + .expectIntRecord("Android.BrowserControls.LockedByTabState.Reason", reason) + .allowExtraRecordsForHistogramsAbove() + .build(); + } }
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd index 7868e98..ef22aef9 100644 --- a/chrome/app/chromium_strings.grd +++ b/chrome/app/chromium_strings.grd
@@ -703,10 +703,10 @@ </if> <message name="IDS_DEFAULT_BROWSER_INFOBAR_TEXT" desc="Text to show in an infobar when Chromium is not the current default browser."> Chromium isn't your default browser - </message> - <message name="IDS_DEFAULT_BROWSER_PIN_INFOBAR_TEXT" desc="Text to show in an infobar when Chromium is not the current default browser and can be pinned to the taskbar."> - Set Chromium as your default browser and pin it to your taskbar - </message> + </message> + <message name="IDS_DEFAULT_BROWSER_PIN_INFOBAR_TEXT" desc="Text to show in an infobar when Chromium is not the current default browser and can be pinned to the taskbar."> + Set Chromium as your default browser and pin it to your taskbar + </message> <if expr="is_win or is_macosx"> <message name="IDS_PDF_INFOBAR_TEXT" desc="Text to show in an infobar when Chromium is not the default PDF viewer."> Set Chromium as your default PDF viewer
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 186cbeb0..fe62b0a6 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -1415,17 +1415,11 @@ "send_tab_to_self/send_tab_to_self_util.cc", "send_tab_to_self/send_tab_to_self_util.h", "serial/chrome_serial_delegate.cc", - "serial/chrome_serial_delegate.h", "serial/serial_blocklist.cc", - "serial/serial_blocklist.h", "serial/serial_chooser_context.cc", "serial/serial_chooser_context_factory.cc", - "serial/serial_chooser_context_factory.h", - "serial/serial_chooser_histograms.h", "serial/serial_policy_allowed_ports.cc", - "serial/serial_policy_allowed_ports.h", "serial/web_serial_chooser.cc", - "serial/web_serial_chooser.h", "sessions/chrome_serialized_navigation_driver.cc", "sessions/chrome_serialized_navigation_driver.h", "sessions/chrome_tab_restore_service_client.cc", @@ -3219,7 +3213,6 @@ "segmentation_platform/client_util/tab_data_collection_util.h", "serial/android/serial_bridge.cc", "serial/android/web_serial_chooser_android.cc", - "serial/android/web_serial_chooser_android.h", "sessions/session_restore_android.cc", "sharing/click_to_call/click_to_call_message_handler_android.cc", "sharing/click_to_call/click_to_call_message_handler_android.h", @@ -4148,7 +4141,6 @@ "send_tab_to_self/desktop_notification_handler.cc", "send_tab_to_self/desktop_notification_handler.h", "serial/web_serial_chooser_desktop.cc", - "serial/web_serial_chooser_desktop.h", "sessions/restore_on_startup_policy_handler.cc", "sessions/restore_on_startup_policy_handler.h", "share/share_metrics.cc", @@ -4479,7 +4471,7 @@ "//components/user_education/webui:webui", "//components/vector_icons", "//components/web_modal", - "//components/webapps/isolated_web_apps", + "//components/webapps/isolated_web_apps:isolated_web_apps", "//components/webauthn/content/browser", "//components/webauthn/core/browser", "//components/webauthn/core/browser:passkey_model",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 831c7461..e822c86 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -6179,10 +6179,6 @@ flag_descriptions::kTouchTextEditingRedesignDescription, kOsAll, FEATURE_VALUE_TYPE(features::kTouchTextEditingRedesign)}, #if BUILDFLAG(IS_MAC) - {"enable-extensible-enterprise-sso", - flag_descriptions::kEnableExtensibleEnterpriseSSOName, - flag_descriptions::kEnableExtensibleEnterpriseSSODescription, kOsMac, - FEATURE_VALUE_TYPE(enterprise_auth::kEnableExtensibleEnterpriseSSO)}, {"enable-retry-capture-device-enumeration-on-crash", flag_descriptions::kRetryGetVideoCaptureDeviceInfosName, flag_descriptions::kRetryGetVideoCaptureDeviceInfosDescription, kOsMac,
diff --git a/chrome/browser/ai/ai_language_model_unittest.cc b/chrome/browser/ai/ai_language_model_unittest.cc index 19dab67..5258322 100644 --- a/chrome/browser/ai/ai_language_model_unittest.cc +++ b/chrome/browser/ai/ai_language_model_unittest.cc
@@ -243,13 +243,12 @@ // fake service would look something like this: // - s1.Prompt("foo") // - Adds "UfooEM" to the session -// - Gets output of ["Context: UfooEM\n"] from fake service -// - Adds "Context: UfooEM\nE" to the session (fake response + end token) +// - Gets output of ["UfooEM"] from fake service +// - Adds "UfooEME" to the session (fake response + end token) // - s1.Prompt("bar") // - Adds "UbarEM" to the session -// - Gets output of ["Context: UfooEM\n", "Context: Context: UfooEM\nE\n", -// "Context: UbarEM\n"]. -// - Adds "Context: UfooEM\nContext: Context: UfooEM\nE\nContext: UbarEM\n" +// - Gets output of ["UfooEM", "UfooEME", "UbarEM"]. +// - Adds "UfooEMUfooEMEUbarEM" // (concatenated output from fake service) to the session // This behavior verifies the correct inputs and outputs are being returned from // the model, and this helper makes it easier to construct these expectations. @@ -260,10 +259,10 @@ std::string last_output; for (const std::string& response : responses) { if (!last_output.empty()) { - formatted.push_back("Context: " + last_output + "E\n"); + formatted.push_back(last_output + "E"); last_output += formatted.back(); } - formatted.push_back("Context: " + response + "\n"); + formatted.push_back(response); last_output += formatted.back(); } return formatted; @@ -383,7 +382,7 @@ auto session = CreateSession(); Append(*session, MakeInput("foo")); EXPECT_THAT(Prompt(*session, MakeInput("bar")), - ElementsAre("Context: UfooE\n", "Context: UbarEM\n")); + ElementsAre("UfooE", "UbarEM")); } TEST_F(AILanguageModelTest, PromptTokenCounts) { @@ -464,9 +463,9 @@ auto fork = Fork(*session); EXPECT_THAT(Prompt(*session, MakeInput("foo")), - ElementsAre("Context: UfooEM\n", "TopK: 2, Temp: 1\n")); + ElementsAre("UfooEM", "TopK: 2, Temp: 1")); EXPECT_THAT(Prompt(*fork, MakeInput("bar")), - ElementsAre("Context: UbarEM\n", "TopK: 2, Temp: 1\n")); + ElementsAre("UbarEM", "TopK: 2, Temp: 1")); } TEST_F(AILanguageModelTest, SamplingParamsTopKOutOfRange) { @@ -479,7 +478,7 @@ auto session = CreateSession(std::move(options)); EXPECT_THAT(Prompt(*session, MakeInput("foo")), - ElementsAre("Context: UfooEM\n", "TopK: 1, Temp: 1.5\n")); + ElementsAre("UfooEM", "TopK: 1, Temp: 1.5")); } TEST_F(AILanguageModelTest, SamplingParamsTemperatureOutOfRange) { @@ -492,7 +491,7 @@ auto session = CreateSession(std::move(options)); EXPECT_THAT(Prompt(*session, MakeInput("foo")), - ElementsAre("Context: UfooEM\n", "TopK: 2, Temp: 0\n")); + ElementsAre("UfooEM", "TopK: 2, Temp: 0")); } TEST_F(AILanguageModelTest, MaxSamplingParams) { @@ -505,7 +504,7 @@ auto session = CreateSession(std::move(options)); EXPECT_THAT(Prompt(*session, MakeInput("foo")), - ElementsAre("Context: UfooEM\n", "TopK: 5, Temp: 1.5\n")); + ElementsAre("UfooEM", "TopK: 5, Temp: 1.5")); } TEST_F(AILanguageModelTest, InitialPrompts) { @@ -515,7 +514,7 @@ auto session = CreateSession(std::move(options)); EXPECT_THAT(Prompt(*session, MakeInput("foo")), - ElementsAre("Context: ShiEUbyeE\n", "Context: UfooEM\n")); + ElementsAre("ShiEUbyeE", "UfooEM")); } TEST_F(AILanguageModelTest, InitialPromptsInstanceInfo) { @@ -591,8 +590,7 @@ // Response should include input/output of previous prompt with the original // long prompt not present. EXPECT_THAT(responder.responses(), - ElementsAre("Context: SinitE\n", "Context: UfooEMhiE\n", - "Context: U" + long_prompt + "EM\n")); + ElementsAre("SinitE", "UfooEMhiE", "U" + long_prompt + "EM")); } TEST_F(AILanguageModelTest, QuotaOverflowOnAppend) { @@ -609,10 +607,8 @@ responder.WaitForQuotaOverflow(); EXPECT_TRUE(responder.WaitForCompletion()); - EXPECT_THAT( - Prompt(*session, MakeInput("foo")), - ElementsAre("Context: SinitE\n", "Context: U" + long_prompt + "E\n", - "Context: UfooEM\n")); + EXPECT_THAT(Prompt(*session, MakeInput("foo")), + ElementsAre("SinitE", "U" + long_prompt + "E", "UfooEM")); } TEST_F(AILanguageModelTest, QuotaOverflowOnOutput) { @@ -643,8 +639,7 @@ // - "bar" from the current prompt call fake_broker_.settings().set_execute_result({}); EXPECT_THAT(Prompt(*session, MakeInput("bar")), - ElementsAre("Context: UfooEM" + long_response + "E\n", - "Context: UbarEM\n")); + ElementsAre("UfooEM" + long_response + "E", "UbarEM")); } TEST_F(AILanguageModelTest, Destroy) { @@ -949,7 +944,7 @@ EXPECT_THAT( Prompt(*session, MakeInput("foo"), on_device_model::mojom::ResponseConstraint::NewRegex("reg")), - ElementsAre("Constraint: regex reg\n", "Context: UfooEM\n")); + ElementsAre("Constraint: regex reg", "UfooEM")); } TEST_F(AILanguageModelTest, ServiceCrash) { @@ -1112,11 +1107,11 @@ main_rfh()->GetRenderWidgetHost()->GetView()->Hide(); EXPECT_THAT(Prompt(*session, MakeInput("bar")), - ElementsAre("Priority: background\n", "hi")); + ElementsAre("Priority: background", "hi")); auto fork = Fork(*session); EXPECT_THAT(Prompt(*fork, MakeInput("bar")), - ElementsAre("Priority: background\n", "hi")); + ElementsAre("Priority: background", "hi")); main_rfh()->GetRenderWidgetHost()->GetView()->Show(); EXPECT_THAT(Prompt(*session, MakeInput("baz")), ElementsAre("hi"));
diff --git a/chrome/browser/ash/crosapi/multi_capture_service_ash.cc b/chrome/browser/ash/crosapi/multi_capture_service_ash.cc index aa6ef3f..e4b959d3 100644 --- a/chrome/browser/ash/crosapi/multi_capture_service_ash.cc +++ b/chrome/browser/ash/crosapi/multi_capture_service_ash.cc
@@ -30,20 +30,14 @@ void MultiCaptureServiceAsh::MultiCaptureStarted(const std::string& label, const std::string& host) { - // TODO(crbug.com/40249909): Origin cannot be used in a crosapi interface as - // it is not stable. Currently, only the host of the origin is used. Pass the - // complete origin when the `Origin` interface becomes stable. - ash::Shell::Get()->multi_capture_service()->NotifyMultiCaptureStarted( - label, url::Origin::CreateFromNormalizedTuple(/*scheme=*/"https", host, - /*port=*/443)); + NOTREACHED(); } void MultiCaptureServiceAsh::MultiCaptureStartedFromApp( const std::string& label, const std::string& app_id, const std::string& app_name) { - ash::Shell::Get()->multi_capture_service()->NotifyMultiCaptureStartedFromApp( - label, app_id, app_name); + NOTREACHED(); } void MultiCaptureServiceAsh::MultiCaptureStopped(const std::string& label) {
diff --git a/chrome/browser/ash/notifications/multi_capture_notifications.cc b/chrome/browser/ash/notifications/multi_capture_notifications.cc index de30b60..b8613ee 100644 --- a/chrome/browser/ash/notifications/multi_capture_notifications.cc +++ b/chrome/browser/ash/notifications/multi_capture_notifications.cc
@@ -139,7 +139,8 @@ void MultiCaptureNotifications::MultiCaptureStartedFromApp( const std::string& label, const std::string& app_id, - const std::string& app_short_name) { + const std::string& app_short_name, + const url::Origin& app_origin) { MultiCaptureStartedInternal( label, base::StrCat({kMultiCaptureId, ":", label}), app_short_name); }
diff --git a/chrome/browser/ash/notifications/multi_capture_notifications.h b/chrome/browser/ash/notifications/multi_capture_notifications.h index 25c99d6..a97af9a 100644 --- a/chrome/browser/ash/notifications/multi_capture_notifications.h +++ b/chrome/browser/ash/notifications/multi_capture_notifications.h
@@ -65,7 +65,8 @@ const url::Origin& origin) override; void MultiCaptureStartedFromApp(const std::string& label, const std::string& app_id, - const std::string& app_short_name) override; + const std::string& app_short_name, + const url::Origin& app_origin) override; void MultiCaptureStopped(const std::string& label) override; void MultiCaptureServiceDestroyed() override;
diff --git a/chrome/browser/ash/notifications/multi_capture_notifications_unittest.cc b/chrome/browser/ash/notifications/multi_capture_notifications_unittest.cc index 062c9b1f2..f792e4d 100644 --- a/chrome/browser/ash/notifications/multi_capture_notifications_unittest.cc +++ b/chrome/browser/ash/notifications/multi_capture_notifications_unittest.cc
@@ -231,11 +231,13 @@ CaptureNotificationsWithDifferentOriginsStartedAndStoppedAfterSixSeconds) { multi_capture_notifications_->MultiCaptureStarted( /*label=*/"test_label_1", - /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example.com", /*port=*/443)); multi_capture_notifications_->MultiCaptureStarted( /*label=*/"test_label_2", - /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"anotherexample.com", /*port=*/443)); CheckCaptureNotification(u"example.com"); CheckCaptureNotification(u"anotherexample.com"); @@ -278,11 +280,13 @@ CaptureFastNotificationsWithDifferentOriginsStartedAndStoppedExpectedClosingDelay) { multi_capture_notifications_->MultiCaptureStarted( /*label=*/"test_label_1", - /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"example.com", /*port=*/443)); multi_capture_notifications_->MultiCaptureStarted( /*label=*/"test_label_2", - /*origin=*/url::Origin::CreateFromNormalizedTuple( + /*origin=*/ + url::Origin::CreateFromNormalizedTuple( /*scheme=*/"https", /*host=*/"anotherexample.com", /*port=*/443)); CheckCaptureNotification(u"example.com"); CheckCaptureNotification(u"anotherexample.com");
diff --git a/chrome/browser/ash/system_web_apps/apps/boca_web_app_config.cc b/chrome/browser/ash/system_web_apps/apps/boca_web_app_config.cc index f6468664..41f7853 100644 --- a/chrome/browser/ash/system_web_apps/apps/boca_web_app_config.cc +++ b/chrome/browser/ash/system_web_apps/apps/boca_web_app_config.cc
@@ -57,6 +57,11 @@ source->AddString("spotlightUrlTemplate", features::kBocaSpotlightUrlTemplate.Get()); } + source->AddBoolean("sessionControlsUpdate", + features::IsBocaLockPauseUpdateEnabled()); + source->AddBoolean("navSettingsDialog", + features::IsBocaNavSettingsDialogEnabled()); + source->AddBoolean("captionToggle", features::IsBocaCaptionToggleEnabled()); } private:
diff --git a/chrome/browser/auxiliary_search/fetch_and_rank_helper.cc b/chrome/browser/auxiliary_search/fetch_and_rank_helper.cc index eae0d83a..6476f86 100644 --- a/chrome/browser/auxiliary_search/fetch_and_rank_helper.cc +++ b/chrome/browser/auxiliary_search/fetch_and_rank_helper.cc
@@ -229,10 +229,11 @@ kInvalidTabId)); }, [&](const URLVisitAggregate::HistoryData& history_data) { - bool is_custom_tab = - history_data.last_visited.context_annotations.on_visit - .browser_type == - history::VisitContextAnnotations::BrowserType::kCustomTab; + bool is_custom_tab = history_data.last_visited.context_annotations + .on_visit.browser_type == + history::VisitContextAnnotations:: + BrowserType::kCustomTab || + history_data.last_app_id != std::nullopt; if (!is_custom_tab) { return; }
diff --git a/chrome/browser/auxiliary_search/java/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonor.java b/chrome/browser/auxiliary_search/java/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonor.java index 8e38e423..011ce72 100644 --- a/chrome/browser/auxiliary_search/java/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonor.java +++ b/chrome/browser/auxiliary_search/java/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonor.java
@@ -259,7 +259,7 @@ SetSchemaRequest.Builder requestBuilder = new SetSchemaRequest.Builder() .setForceOverride(true) - .addDocumentClasses(getSupportedDocumentClasses()); + .addDocumentClasses(WebPage.class); AuxiliarySearchControllerFactory.getInstance() .setSchemaTypeVisibilityForPackage( (schemaClass, packageName, sha256Certificate) -> @@ -275,14 +275,6 @@ } } - /** Returns a list of supported document classes. */ - @VisibleForTesting - List<Class<?>> getSupportedDocumentClasses() { - List<Class<?>> documents = new ArrayList<>(); - documents.add(WebPage.class); - return documents; - } - private void setDocumentClassVisibilityImpl( SetSchemaRequest.Builder requestBuilder, Class<?> schemaClass,
diff --git a/chrome/browser/auxiliary_search/junit/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonorUnitTest.java b/chrome/browser/auxiliary_search/junit/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonorUnitTest.java index cec5b4a9..554b296 100644 --- a/chrome/browser/auxiliary_search/junit/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonorUnitTest.java +++ b/chrome/browser/auxiliary_search/junit/src/org/chromium/chrome/browser/auxiliary_search/AuxiliarySearchDonorUnitTest.java
@@ -575,17 +575,6 @@ assertTrue(mAuxiliarySearchDonor.isShareTabsWithOsEnabledKeyExist()); } - @Test - public void testGetSupportedDocumentClasses() { - // Enables multiple data source. - when(mHooks.isMultiDataTypeEnabledOnDevice()).thenReturn(true); - assertTrue(AuxiliarySearchControllerFactory.getInstance().isMultiDataTypeEnabledOnDevice()); - createAndInitAuxiliarySearchDonor(); - List<Class<?>> list = mAuxiliarySearchDonor.getSupportedDocumentClasses(); - assertEquals(1, list.size()); - assertTrue(list.contains(WebPage.class)); - } - private SearchResult createSearchResult(int applicationType, @NonNull String schemaType) { GlobalSearchApplicationInfo appInfo = new GlobalSearchApplicationInfo.Builder("namespace", "id", applicationType)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index eb452088..28bbb1a 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -528,7 +528,7 @@ #include "chromeos/components/kiosk/kiosk_utils.h" #include "chromeos/constants/chromeos_features.h" #include "third_party/cros_system_api/switches/chrome_switches.h" -#endif +#endif // BUILDFLAG(IS_CHROMEOS) #if !BUILDFLAG(IS_ANDROID) #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" @@ -1217,6 +1217,8 @@ void NotifyMultiCaptureStarted(const std::string& label, content::WebContents* web_contents, const webapps::AppId* app_id) { + const url::Origin origin = + url::Origin::Create(web_contents->GetLastCommittedURL()); if (app_id) { CHECK_DEREF(ash::Shell::Get()) .multi_capture_service() @@ -1224,14 +1226,14 @@ label, *app_id, web_app::WebAppProvider::GetForWebContents(web_contents) ->registrar_unsafe() - .GetAppShortName(*app_id)); + .GetAppShortName(*app_id), + origin); } else { // TODO(b/319317165): Remove this case once the pivot to web apps is // complete. CHECK_DEREF(ash::Shell::Get()) .multi_capture_service() - ->NotifyMultiCaptureStarted( - label, url::Origin::Create(web_contents->GetLastCommittedURL())); + ->NotifyMultiCaptureStarted(label, origin); } }
diff --git a/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc b/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc index 7f9b43f..8e0e273f 100644 --- a/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc +++ b/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc
@@ -82,9 +82,9 @@ // The UI has 3 radio buttons which are inside several layers of shadow DOM. // The buttons are: - // (0) Sites can ask to send notifications - // (1) Use quieter messaging - // (2) Don't allow sites to send notifications + // (0) Collapse all requests in the address bar + // (1) Collapse unwanted requests (recommended) + // (2) Expand all requests // Query the checked and disabled state of the radio buttons. std::string kGetRadios = "let radios = " @@ -115,9 +115,9 @@ switch (GetParam()) { case 0: // Policy not set. - EXPECT_TRUE(radios_checked_list[0].GetBool()); - EXPECT_FALSE(radios_checked_list[1].GetBool()); - EXPECT_TRUE(radios_checked_list[2].GetBool()); + EXPECT_FALSE(radios_checked_list[0].GetBool()); + EXPECT_TRUE(radios_checked_list[1].GetBool()); + EXPECT_FALSE(radios_checked_list[2].GetBool()); EXPECT_TRUE(radios_enabled_list[0].GetBool()); EXPECT_TRUE(radios_enabled_list[1].GetBool()); EXPECT_TRUE(radios_enabled_list[2].GetBool()); @@ -126,7 +126,7 @@ // Allow sites to show desktop notifications. EXPECT_FALSE(radios_checked_list[0].GetBool()); EXPECT_FALSE(radios_checked_list[1].GetBool()); - EXPECT_FALSE(radios_checked_list[2].GetBool()); + EXPECT_TRUE(radios_checked_list[2].GetBool()); EXPECT_TRUE(radios_enabled_list[0].GetBool()); EXPECT_TRUE(radios_enabled_list[1].GetBool()); EXPECT_TRUE(radios_enabled_list[2].GetBool()); @@ -142,9 +142,9 @@ break; case 3: // Ask every time a site wants to show desktop notifications. - EXPECT_TRUE(radios_checked_list[0].GetBool()); + EXPECT_FALSE(radios_checked_list[0].GetBool()); EXPECT_FALSE(radios_checked_list[1].GetBool()); - EXPECT_FALSE(radios_checked_list[2].GetBool()); + EXPECT_TRUE(radios_checked_list[2].GetBool()); EXPECT_TRUE(radios_enabled_list[0].GetBool()); EXPECT_TRUE(radios_enabled_list[1].GetBool()); EXPECT_TRUE(radios_enabled_list[2].GetBool());
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java index a22cdba..54c4484d 100644 --- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java +++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
@@ -41,7 +41,7 @@ * The time interval during which a download update is considered recent enough to show * in Just Now section. */ - public final long justNowThresholdSeconds; + public final long justNowThresholdSeconds = 30 * 60; /** Whether or not grouping items into a single card is supported. */ public final boolean supportsGrouping; @@ -60,7 +60,6 @@ supportFullWidthImages = builder.mSupportFullWidthImages; inMemoryThumbnailCacheSizeBytes = builder.mInMemoryThumbnailCacheSizeBytes; maxThumbnailScaleFactor = builder.mMaxThumbnailScaleFactor; - justNowThresholdSeconds = builder.mJustNowThresholdSeconds; supportsGrouping = builder.mSupportsGrouping; showPaginationHeaders = builder.mShowPaginationHeaders; startWithPrefetchedContent = builder.mStartWithPrefetchedContent; @@ -68,9 +67,6 @@ /** Helper class for building a {@link DownloadManagerUiConfig}. */ public static class Builder { - /** The threshold time interval to show up in Just Now section. */ - private static final int JUST_NOW_THRESHOLD_SECONDS = 30 * 60; - private static final int IN_MEMORY_THUMBNAIL_CACHE_SIZE_BYTES = 15 * BYTES_PER_MEGABYTE; private static final float MAX_THUMBNAIL_SCALE_FACTOR = 1.5f; /* hdpi scale factor. */ @@ -81,7 +77,6 @@ private boolean mSupportFullWidthImages; private int mInMemoryThumbnailCacheSizeBytes = IN_MEMORY_THUMBNAIL_CACHE_SIZE_BYTES; private float mMaxThumbnailScaleFactor = MAX_THUMBNAIL_SCALE_FACTOR; - private final long mJustNowThresholdSeconds = JUST_NOW_THRESHOLD_SECONDS; private boolean mSupportsGrouping; private boolean mShowPaginationHeaders; private boolean mStartWithPrefetchedContent;
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc index 562cc25..2cdcf2d 100644 --- a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc +++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
@@ -33,7 +33,7 @@ #if BUILDFLAG(IS_WIN) #include "chrome/browser/browser_process.h" #include "chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/test_constants.h" #include "chrome/browser/policy/chrome_browser_policy_connector.h" #include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h" #endif // #if BUILDFLAG(IS_WIN) @@ -137,8 +137,10 @@ test::DeviceTrustBrowserTestBase::SetUpInProcessBrowserTestFixture(); #if BUILDFLAG(IS_WIN) device_trust_test_environment_win_.emplace(); - device_trust_test_environment_win_->SetExpectedDMToken(kBrowserDmToken); - device_trust_test_environment_win_->SetExpectedClientID(kBrowserClientId); + device_trust_test_environment_win_->SetExpectedDMToken( + enterprise::test::kBrowserDmToken); + device_trust_test_environment_win_->SetExpectedClientID( + enterprise::test::kBrowserClientId); // This will set up a key before DeviceTrustKeyManager initializes. // DTKM should just try to load this key instead of creating one itself.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn index 6112286..031924b 100644 --- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn +++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/BUILD.gn
@@ -111,6 +111,7 @@ "//base/test:test_support", "//chrome/browser/enterprise/connectors/device_trust/common", "//chrome/browser/enterprise/connectors/device_trust/test:test_support", + "//components/policy/core/common:test_support", "//testing/gtest", ]
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/test/BUILD.gn index 0aa3bd64..d5946e8 100644 --- a/chrome/browser/enterprise/connectors/device_trust/test/BUILD.gn +++ b/chrome/browser/enterprise/connectors/device_trust/test/BUILD.gn
@@ -19,7 +19,7 @@ public_deps = [ "//chrome/browser/enterprise/connectors/device_trust/common", - "//chrome/browser/enterprise/connectors/test:test_support", + "//chrome/browser/enterprise/test:test_support", ] deps = [
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_browsertest_base.cc b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_browsertest_base.cc index a4e8a96..cc1d638 100644 --- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_browsertest_base.cc +++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_browsertest_base.cc
@@ -8,7 +8,6 @@ #include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h" #include "chrome/browser/enterprise/connectors/device_trust/test/test_constants.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" #include "chrome/browser/ui/browser.h" #include "content/public/test/browser_test_utils.h" #include "net/dns/mock_host_resolver.h"
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.cc b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.cc index bc61d2dc..26db094 100644 --- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.cc +++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.cc
@@ -21,6 +21,8 @@ namespace enterprise_connectors::test { +using ManagementContext = enterprise::test::ManagementContext; + namespace { base::Value GetAllowedHostValue(const std::string& url) { @@ -63,10 +65,11 @@ : InProcessBrowserTestMixin(host), test_base_(test_base), device_trust_state_(std::move(device_trust_state)), - management_context_mixin_(ManagementContextMixin::Create( - host, - test_base, - ToManagementContext(device_trust_state_))) {} + management_context_mixin_( + enterprise::test::ManagementContextMixin::Create( + host, + test_base, + ToManagementContext(device_trust_state_))) {} DeviceTrustManagementMixin::~DeviceTrustManagementMixin() = default;
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.h b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.h index 55a34a6..8910a8d 100644 --- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.h +++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_management_mixin.h
@@ -6,7 +6,7 @@ #define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_TEST_DEVICE_TRUST_MANAGEMENT_MIXIN_H_ #include "chrome/browser/enterprise/connectors/device_trust/test/test_constants.h" -#include "chrome/browser/enterprise/connectors/test/management_context_mixin.h" +#include "chrome/browser/enterprise/test/management_context_mixin.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/mixin_based_in_process_browser_test.h" @@ -79,7 +79,8 @@ private: const raw_ptr<InProcessBrowserTest> test_base_; DeviceTrustConnectorState device_trust_state_; - std::unique_ptr<ManagementContextMixin> management_context_mixin_; + std::unique_ptr<enterprise::test::ManagementContextMixin> + management_context_mixin_; }; } // namespace enterprise_connectors::test
diff --git a/chrome/browser/enterprise/connectors/test/BUILD.gn b/chrome/browser/enterprise/connectors/test/BUILD.gn index 38db142..f1d4cc99 100644 --- a/chrome/browser/enterprise/connectors/test/BUILD.gn +++ b/chrome/browser/enterprise/connectors/test/BUILD.gn
@@ -6,15 +6,9 @@ source_set("test_support") { testonly = true - public = [ - "mock_realtime_reporting_client.h", - "test_constants.h", - ] + public = [ "mock_realtime_reporting_client.h" ] - sources = [ - "mock_realtime_reporting_client.cc", - "test_constants.cc", - ] + sources = [ "mock_realtime_reporting_client.cc" ] public_deps = [ "//base", @@ -33,7 +27,6 @@ "fake_clipboard_request_handler.h", "fake_content_analysis_delegate.h", "fake_files_request_handler.h", - "management_context_mixin.h", "uploader_test_utils.h", ] @@ -43,7 +36,6 @@ "fake_clipboard_request_handler.cc", "fake_content_analysis_delegate.cc", "fake_files_request_handler.cc", - "management_context_mixin.cc", "uploader_test_utils.cc", ] @@ -92,26 +84,4 @@ "//third_party/content_analysis_sdk:liblcasdk", ] } - - if (is_win || is_linux || is_mac) { - public += [ "browser/management_context_mixin_browser.h" ] - sources += [ "browser/management_context_mixin_browser.cc" ] - - deps += [ - "//chrome/browser:browser_process", - "//chrome/test:test_support", - "//components/enterprise", - "//components/enterprise:test_support", - ] - } - - if (is_chromeos) { - public += [ "ash/management_context_mixin_ash.h" ] - sources += [ "ash/management_context_mixin_ash.cc" ] - - deps += [ - "//chrome/browser/ash/policy/core", - "//chrome/browser/ash/policy/core:test_support", - ] - } }
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc index 5bda314b..a21c761 100644 --- a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc +++ b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
@@ -10,6 +10,6 @@ BASE_FEATURE(kEnableExtensibleEnterpriseSSO, "EnableExtensibleEnterpriseSSO", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); } // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/reporting/cloud_profile_reporting_browsertest.cc b/chrome/browser/enterprise/reporting/cloud_profile_reporting_browsertest.cc index 538cab1..811c35e 100644 --- a/chrome/browser/enterprise/reporting/cloud_profile_reporting_browsertest.cc +++ b/chrome/browser/enterprise/reporting/cloud_profile_reporting_browsertest.cc
@@ -69,7 +69,7 @@ ASSERT_TRUE(report_scheduler); EXPECT_TRUE(report_scheduler->IsNextReportScheduledForTesting() || report_scheduler->GetActiveTriggerForTesting() == - ReportScheduler::kTriggerTimer); + ReportTrigger::kTriggerTimer); } #if !BUILDFLAG(IS_ANDROID) @@ -108,19 +108,19 @@ auto active_config = report_scheduler->GetActiveGenerationConfigForTesting(); if (signals_reporting_enabled() && profile_reporting_enabled()) { - EXPECT_EQ(active_trigger, ReportScheduler::kTriggerTimer); + EXPECT_EQ(active_trigger, ReportTrigger::kTriggerTimer); EXPECT_EQ(active_config.security_signals_mode, SecuritySignalsMode::kSignalsAttached); } else if (profile_reporting_enabled()) { - EXPECT_EQ(active_trigger, ReportScheduler::kTriggerTimer); + EXPECT_EQ(active_trigger, ReportTrigger::kTriggerTimer); EXPECT_EQ(active_config.security_signals_mode, SecuritySignalsMode::kNoSignals); } else if (signals_reporting_enabled()) { - EXPECT_EQ(active_trigger, ReportScheduler::kTriggerSecurity); + EXPECT_EQ(active_trigger, ReportTrigger::kTriggerSecurity); EXPECT_EQ(active_config.security_signals_mode, SecuritySignalsMode::kSignalsOnly); } else { - EXPECT_EQ(active_trigger, ReportScheduler::kTriggerNone); + EXPECT_EQ(active_trigger, ReportTrigger::kTriggerNone); } }
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_browsertest.cc b/chrome/browser/enterprise/reporting/report_scheduler_browsertest.cc index 4312243e..6cddc7b 100644 --- a/chrome/browser/enterprise/reporting/report_scheduler_browsertest.cc +++ b/chrome/browser/enterprise/reporting/report_scheduler_browsertest.cc
@@ -111,7 +111,7 @@ // The report should be either scheduled or being uploaded. EXPECT_TRUE(report_scheduler()->IsNextReportScheduledForTesting() || report_scheduler()->GetActiveTriggerForTesting() == - ReportScheduler::kTriggerTimer); + ReportTrigger::kTriggerTimer); } } // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc b/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc index c982d0d..c94b9ba 100644 --- a/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc +++ b/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc
@@ -105,8 +105,7 @@ chrome::kChromeVersion && last_upload + upload_interval > base::Time::Now() && !trigger_report_callback_.is_null()) { - trigger_report_callback_.Run( - ReportScheduler::ReportTrigger::kTriggerNewVersion); + trigger_report_callback_.Run(ReportTrigger::kTriggerNewVersion); } } @@ -153,8 +152,7 @@ void ReportSchedulerDesktop::OnReportEventTriggered( SecurityReportTrigger trigger) { if (!trigger_report_callback_.is_null()) { - trigger_report_callback_.Run( - ReportScheduler::ReportTrigger::kTriggerSecurity); + trigger_report_callback_.Run(ReportTrigger::kTriggerSecurity); } } @@ -169,8 +167,7 @@ // for it to take effect. Send a basic report (without profile info) // immediately. if (!trigger_report_callback_.is_null()) { - trigger_report_callback_.Run( - ReportScheduler::ReportTrigger::kTriggerUpdate); + trigger_report_callback_.Run(ReportTrigger::kTriggerUpdate); } }
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc index 16a1419..16780de 100644 --- a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc +++ b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
@@ -334,12 +334,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -363,7 +364,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerTimer, + ReportType::kProfileReport, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -395,12 +397,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kTransientError)); CreateScheduler(); @@ -421,12 +424,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kPersistentError)); CreateScheduler(); @@ -481,12 +485,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -506,12 +511,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -532,12 +538,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -603,12 +610,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -636,12 +644,13 @@ EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerManual, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -662,12 +671,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerManual, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -693,12 +703,13 @@ // Callback for timer report will be held. ReportUploader::ReportCallback saved_timer_callback; - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce([&saved_timer_callback]( ReportGenerationConfig config, ReportRequestQueue requests, ReportUploader::ReportCallback callback) { @@ -736,7 +747,8 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kBrowserVersion, + ReportGenerationConfig(ReportTrigger::kTriggerUpdate, + ReportType::kBrowserVersion, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -765,7 +777,8 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kBrowserVersion, + ReportGenerationConfig(ReportTrigger::kTriggerUpdate, + ReportType::kBrowserVersion, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -806,7 +819,8 @@ ReportUploader::ReportCallback saved_callback; EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kBrowserVersion, + ReportGenerationConfig(ReportTrigger::kTriggerUpdate, + ReportType::kBrowserVersion, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -834,12 +848,13 @@ EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); auto new_uploader = std::make_unique<MockReportUploader>(); - EXPECT_CALL(*new_uploader, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *new_uploader, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); std::move(saved_callback).Run(ReportUploader::kSuccess); ExpectLastUploadTimestampUpdated(false); @@ -871,7 +886,8 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kBrowserVersion, + ReportGenerationConfig(ReportTrigger::kTriggerNewVersion, + ReportType::kBrowserVersion, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -904,12 +920,13 @@ EXPECT_CALL_SetupRegistration(); EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _)) .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); - EXPECT_CALL(*uploader_, - SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + EXPECT_CALL( + *uploader_, + SetRequestAndUpload(ReportGenerationConfig( + ReportTrigger::kTriggerTimer, ReportType::kFull, + SecuritySignalsMode::kNoSignals, + /*use_cookies=*/false), + _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -953,7 +970,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerTimer, + ReportType::kProfileReport, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -981,7 +999,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerSecurity, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/true), _, _)) @@ -1016,7 +1035,8 @@ .WillRepeatedly(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerTimer, + ReportType::kProfileReport, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -1038,7 +1058,8 @@ auto second_uploader = std::make_unique<MockReportUploader>(); EXPECT_CALL(*second_uploader, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerSecurity, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/true), _, _)) @@ -1058,7 +1079,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerManual, + ReportType::kProfileReport, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), _, _)) @@ -1090,7 +1112,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerSecurity, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/true), _, _)) @@ -1114,7 +1137,8 @@ auto second_uploader = std::make_unique<MockReportUploader>(); EXPECT_CALL(*second_uploader, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerManual, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsAttached, /*use_cookies=*/true), _, _)) @@ -1139,7 +1163,8 @@ .WillOnce(WithArgs<0>(ScheduleProfileRequestGeneratorCallback())); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerSecurity, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/true), _, _)) @@ -1162,7 +1187,8 @@ auto second_uploader = std::make_unique<MockReportUploader>(); EXPECT_CALL(*second_uploader, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerSecurity, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/true), _, _))
diff --git a/chrome/browser/enterprise/test/BUILD.gn b/chrome/browser/enterprise/test/BUILD.gn new file mode 100644 index 0000000..8834bc95 --- /dev/null +++ b/chrome/browser/enterprise/test/BUILD.gn
@@ -0,0 +1,58 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//components/enterprise/buildflags/buildflags.gni") + +source_set("test_support") { + testonly = true + public = [ + "management_context_mixin.h", + "test_constants.h", + ] + + sources = [ + "management_context_mixin.cc", + "test_constants.cc", + ] + + public_deps = [ + "//base", + "//chrome/browser/ui", + "//chrome/test:test_support_ui", + "//components/enterprise/common/proto:connectors_proto", + "//components/policy/core/common:common_constants", + "//components/policy/core/common:test_support", + "//testing/gmock", + ] + + deps = [ + "//chrome/browser", + "//components/enterprise/connectors/core", + "//components/policy/core/browser", + "//components/policy/core/common", + ] + + if (!is_chromeos) { + public += [ "browser/management_context_mixin_browser.h" ] + sources += [ "browser/management_context_mixin_browser.cc" ] + + deps += [ + "//chrome/browser:browser_process", + "//chrome/test:test_support", + "//components/enterprise", + "//components/enterprise:test_support", + ] + } + + if (is_chromeos) { + public += [ "ash/management_context_mixin_ash.h" ] + sources += [ "ash/management_context_mixin_ash.cc" ] + + deps += [ + "//chrome/browser/ash/login/test:test_support", + "//chrome/browser/ash/policy/core", + "//chrome/browser/ash/policy/core:test_support", + ] + } +}
diff --git a/chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.cc b/chrome/browser/enterprise/test/ash/management_context_mixin_ash.cc similarity index 90% rename from chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.cc rename to chrome/browser/enterprise/test/ash/management_context_mixin_ash.cc index 27eb170..9e9ab82 100644 --- a/chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.cc +++ b/chrome/browser/enterprise/test/ash/management_context_mixin_ash.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.h" +#include "chrome/browser/enterprise/test/ash/management_context_mixin_ash.h" #include <array> #include <utility> @@ -10,13 +10,13 @@ #include "base/check.h" #include "chrome/browser/ash/login/test/device_state_mixin.h" #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/test_constants.h" #include "chrome/browser/policy/dm_token_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/test/base/in_process_browser_test.h" #include "components/policy/core/common/cloud/dm_token.h" -namespace enterprise_connectors::test { +namespace enterprise::test { ManagementContextMixinAsh::ManagementContextMixinAsh( InProcessBrowserTestMixinHost* host, @@ -65,4 +65,4 @@ kFakeCustomerId); } -} // namespace enterprise_connectors::test +} // namespace enterprise::test
diff --git a/chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.h b/chrome/browser/enterprise/test/ash/management_context_mixin_ash.h similarity index 74% rename from chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.h rename to chrome/browser/enterprise/test/ash/management_context_mixin_ash.h index 177e0bd..323cea6 100644 --- a/chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.h +++ b/chrome/browser/enterprise/test/ash/management_context_mixin_ash.h
@@ -2,16 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_ -#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_ +#ifndef CHROME_BROWSER_ENTERPRISE_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_ +#define CHROME_BROWSER_ENTERPRISE_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_ #include "chrome/browser/ash/login/test/device_state_mixin.h" #include "chrome/browser/ash/login/test/scoped_policy_update.h" #include "chrome/browser/ash/policy/core/device_policy_cros_test_helper.h" -#include "chrome/browser/enterprise/connectors/test/management_context_mixin.h" +#include "chrome/browser/enterprise/test/management_context_mixin.h" #include "chrome/test/base/mixin_based_in_process_browser_test.h" -namespace enterprise_connectors::test { +namespace enterprise::test { class ManagementContextMixinAsh : public ManagementContextMixin { public: @@ -41,6 +41,6 @@ ash::DeviceStateMixin device_state_mixin_; }; -} // namespace enterprise_connectors::test +} // namespace enterprise::test -#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_ +#endif // CHROME_BROWSER_ENTERPRISE_TEST_ASH_MANAGEMENT_CONTEXT_MIXIN_ASH_H_
diff --git a/chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.cc b/chrome/browser/enterprise/test/browser/management_context_mixin_browser.cc similarity index 81% rename from chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.cc rename to chrome/browser/enterprise/test/browser/management_context_mixin_browser.cc index 24ee969..a1c1ecd8 100644 --- a/chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.cc +++ b/chrome/browser/enterprise/test/browser/management_context_mixin_browser.cc
@@ -2,24 +2,43 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.h" +#include "chrome/browser/enterprise/test/browser/management_context_mixin_browser.h" #include "build/branding_buildflags.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/test_constants.h" #include "chrome/browser/policy/chrome_browser_policy_connector.h" #include "chrome/browser/profiles/profile.h" #include "chrome/test/base/in_process_browser_test.h" #include "components/enterprise/browser/controller/fake_browser_dm_token_storage.h" #include "components/enterprise/browser/enterprise_switches.h" #include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h" +#include "components/policy/core/common/cloud/mock_cloud_policy_client.h" #include "components/policy/core/common/cloud/user_cloud_policy_manager.h" #include "components/policy/core/common/policy_map.h" #include "components/policy/core/common/policy_types.h" #include "components/policy/proto/device_management_backend.pb.h" -namespace enterprise_connectors::test { +namespace enterprise::test { + +namespace { + +void SetProfileDMToken(Profile* profile, const std::string& dm_token) { + auto policy_data = std::make_unique<enterprise_management::PolicyData>(); + policy_data->set_request_token(dm_token); + profile->GetCloudPolicyManager() + ->core() + ->store() + ->set_policy_data_for_testing(std::move(policy_data)); + + auto client = std::make_unique<policy::MockCloudPolicyClient>(); + client->SetDMToken(dm_token); + + profile->GetUserCloudPolicyManager()->Connect( + g_browser_process->local_state(), std::move(client)); +} + +} // namespace ManagementContextMixinBrowser::ManagementContextMixinBrowser( InProcessBrowserTestMixinHost* host, @@ -99,4 +118,4 @@ MergeNewChromePolicies(policy_map); } -} // namespace enterprise_connectors::test +} // namespace enterprise::test
diff --git a/chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.h b/chrome/browser/enterprise/test/browser/management_context_mixin_browser.h similarity index 76% rename from chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.h rename to chrome/browser/enterprise/test/browser/management_context_mixin_browser.h index 32d5672..25415c3 100644 --- a/chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.h +++ b/chrome/browser/enterprise/test/browser/management_context_mixin_browser.h
@@ -2,20 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_ -#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_ +#ifndef CHROME_BROWSER_ENTERPRISE_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_ +#define CHROME_BROWSER_ENTERPRISE_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_ #include <memory> #include "build/branding_buildflags.h" -#include "chrome/browser/enterprise/connectors/test/management_context_mixin.h" +#include "chrome/browser/enterprise/test/management_context_mixin.h" #include "chrome/test/base/mixin_based_in_process_browser_test.h" namespace policy { class FakeBrowserDMTokenStorage; } // namespace policy -namespace enterprise_connectors::test { +namespace enterprise::test { class ManagementContextMixinBrowser : public ManagementContextMixin { public: @@ -53,6 +53,6 @@ std::unique_ptr<policy::FakeBrowserDMTokenStorage> browser_dm_token_storage_; }; -} // namespace enterprise_connectors::test +} // namespace enterprise::test -#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_ +#endif // CHROME_BROWSER_ENTERPRISE_TEST_BROWSER_MANAGEMENT_CONTEXT_MIXIN_BROWSER_H_
diff --git a/chrome/browser/enterprise/connectors/test/management_context_mixin.cc b/chrome/browser/enterprise/test/management_context_mixin.cc similarity index 89% rename from chrome/browser/enterprise/connectors/test/management_context_mixin.cc rename to chrome/browser/enterprise/test/management_context_mixin.cc index bcab7bf..17d5889 100644 --- a/chrome/browser/enterprise/connectors/test/management_context_mixin.cc +++ b/chrome/browser/enterprise/test/management_context_mixin.cc
@@ -2,26 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/connectors/test/management_context_mixin.h" +#include "chrome/browser/enterprise/test/management_context_mixin.h" #include <utility> #include "base/check.h" #include "base/run_loop.h" #include "build/build_config.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/test_constants.h" #include "components/policy/core/browser/browser_policy_connector.h" #include "components/policy/core/common/policy_map.h" #include "components/policy/core/common/policy_namespace.h" #include "components/policy/core/common/policy_types.h" #if BUILDFLAG(IS_CHROMEOS) -#include "chrome/browser/enterprise/connectors/test/ash/management_context_mixin_ash.h" +#include "chrome/browser/enterprise/test/ash/management_context_mixin_ash.h" #else -#include "chrome/browser/enterprise/connectors/test/browser/management_context_mixin_browser.h" +#include "chrome/browser/enterprise/test/browser/management_context_mixin_browser.h" #endif // BUILDFLAG(IS_CHROMEOS) -namespace enterprise_connectors::test { +namespace enterprise::test { // static std::unique_ptr<ManagementContextMixin> ManagementContextMixin::Create( @@ -108,4 +108,4 @@ base::RunLoop().RunUntilIdle(); } -} // namespace enterprise_connectors::test +} // namespace enterprise::test
diff --git a/chrome/browser/enterprise/connectors/test/management_context_mixin.h b/chrome/browser/enterprise/test/management_context_mixin.h similarity index 92% rename from chrome/browser/enterprise/connectors/test/management_context_mixin.h rename to chrome/browser/enterprise/test/management_context_mixin.h index 46403e23..f6ecb90 100644 --- a/chrome/browser/enterprise/connectors/test/management_context_mixin.h +++ b/chrome/browser/enterprise/test/management_context_mixin.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_MANAGEMENT_CONTEXT_MIXIN_H_ -#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_MANAGEMENT_CONTEXT_MIXIN_H_ +#ifndef CHROME_BROWSER_ENTERPRISE_TEST_MANAGEMENT_CONTEXT_MIXIN_H_ +#define CHROME_BROWSER_ENTERPRISE_TEST_MANAGEMENT_CONTEXT_MIXIN_H_ #include <memory> #include <optional> @@ -25,7 +25,7 @@ } // namespace ash #endif // BUILDFLAG(IS_CHROMEOS) -namespace enterprise_connectors::test { +namespace enterprise::test { struct ManagementContext { bool is_cloud_user_managed = false; @@ -103,6 +103,6 @@ ManagementContext management_context_; }; -} // namespace enterprise_connectors::test +} // namespace enterprise::test -#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_MANAGEMENT_CONTEXT_MIXIN_H_ +#endif // CHROME_BROWSER_ENTERPRISE_TEST_MANAGEMENT_CONTEXT_MIXIN_H_
diff --git a/chrome/browser/enterprise/connectors/test/test_constants.cc b/chrome/browser/enterprise/test/test_constants.cc similarity index 81% rename from chrome/browser/enterprise/connectors/test/test_constants.cc rename to chrome/browser/enterprise/test/test_constants.cc index ed43a28..af27ea3 100644 --- a/chrome/browser/enterprise/connectors/test/test_constants.cc +++ b/chrome/browser/enterprise/test/test_constants.cc
@@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/test_constants.h" -namespace enterprise_connectors::test { +namespace enterprise::test { const char kFakeCustomerId[] = "customer_id"; const char kDifferentCustomerId[] = "other_customer_id"; @@ -23,4 +23,4 @@ const char kEnrollmentToken[] = "enrollment_token"; -} // namespace enterprise_connectors::test +} // namespace enterprise::test
diff --git a/chrome/browser/enterprise/connectors/test/test_constants.h b/chrome/browser/enterprise/test/test_constants.h similarity index 65% rename from chrome/browser/enterprise/connectors/test/test_constants.h rename to chrome/browser/enterprise/test/test_constants.h index 5711687..7f475116 100644 --- a/chrome/browser/enterprise/connectors/test/test_constants.h +++ b/chrome/browser/enterprise/test/test_constants.h
@@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_TEST_CONSTANTS_H_ -#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_TEST_CONSTANTS_H_ +#ifndef CHROME_BROWSER_ENTERPRISE_TEST_TEST_CONSTANTS_H_ +#define CHROME_BROWSER_ENTERPRISE_TEST_TEST_CONSTANTS_H_ -namespace enterprise_connectors::test { +namespace enterprise::test { extern const char kFakeCustomerId[]; extern const char kDifferentCustomerId[]; @@ -24,6 +24,6 @@ extern const char kEnrollmentToken[]; -} // namespace enterprise_connectors::test +} // namespace enterprise::test -#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_TEST_TEST_CONSTANTS_H_ +#endif // CHROME_BROWSER_ENTERPRISE_TEST_TEST_CONSTANTS_H_
diff --git a/chrome/browser/extensions/api/sessions/sessions_apitest.cc b/chrome/browser/extensions/api/sessions/sessions_apitest.cc index 65a0557..4c08aaf 100644 --- a/chrome/browser/extensions/api/sessions/sessions_apitest.cc +++ b/chrome/browser/extensions/api/sessions/sessions_apitest.cc
@@ -27,6 +27,8 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/session_sync_service_factory.h" #include "chrome/browser/ui/tabs/tab_enums.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/tabs/tab_strip.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/in_process_browser_test.h" @@ -339,19 +341,16 @@ // Set up a browser with a non-editable tabstrip, simulating one in the midst // of a tab dragging session. - std::unique_ptr<TestBrowserWindow> browser_window = - std::make_unique<TestBrowserWindow>(); - Browser::CreateParams params(browser()->profile(), true); - params.type = Browser::TYPE_NORMAL; - params.window = browser_window.get(); - std::unique_ptr<Browser> browser = - std::unique_ptr<Browser>(Browser::Create(params)); - browser_window->SetIsTabStripEditable(false); + Browser* non_editable_browser = + Browser::Create(Browser::CreateParams(browser()->profile(), true)); + non_editable_browser->GetBrowserView() + .tabstrip() + ->SetTabStripNotEditableForTesting(); EXPECT_TRUE(base::MatchPattern( utils::RunFunctionAndReturnError( CreateFunction<SessionsRestoreFunction>(true).get(), "[\"1\"]", - browser->profile()), + non_editable_browser->profile()), ExtensionTabUtil::kTabStripNotEditableError)); }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 10c63ee5..e216b529 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -3314,11 +3314,6 @@ "expiry_milestone": 130 }, { - "name": "enable-extensible-enterprise-sso", - "owners": [ "ydago@chromium.org", "cbe-magic@google.com" ], - "expiry_milestone": 135 - }, - { "name": "enable-extension-ai-data-collection", "owners": [ "ryansturm@chromium.org" ], "expiry_milestone": 150 @@ -6105,6 +6100,11 @@ "expiry_milestone": 140 }, { + "name": "lens-load-aim-in-lens-result-page", + "owners": [ "cmyang@google.com", "lens-chrome@google.com" ], + "expiry_milestone": 145 + }, + { "name": "lens-overlay-alternative-onboarding", "owners": [ "stkhapugin@chromium.org", "christianxu@chromium.org", "lens-chrome@google.com" ], "expiry_milestone": 140
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 4c81eed..f036b85 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -5870,10 +5870,6 @@ #if BUILDFLAG(IS_MAC) -const char kEnableExtensibleEnterpriseSSOName[] = "Extensible Enterprise SSO"; -const char kEnableExtensibleEnterpriseSSODescription[] = - "Enables support for extensible enterprise SSO in Chrome"; - const char kImmersiveFullscreenName[] = "Immersive Fullscreen Toolbar"; const char kImmersiveFullscreenDescription[] = "Automatically hide and show the toolbar in fullscreen.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index facb659..294719c01 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -3451,9 +3451,6 @@ #if BUILDFLAG(IS_MAC) -extern const char kEnableExtensibleEnterpriseSSOName[]; -extern const char kEnableExtensibleEnterpriseSSODescription[]; - extern const char kImmersiveFullscreenName[]; extern const char kImmersiveFullscreenDescription[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc index 996fd3dc..db76e9d9 100644 --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -525,11 +525,11 @@ BASE_FEATURE(kAndroidKeyboardA11y, "AndroidKeyboardA11y", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kAndroidMetaClickHistoryNavigation, "AndroidMetaClickHistoryNavigation", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kAndroidNativePagesInNewTab, "AndroidNativePagesInNewTab",
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn index 69423f3..66a1e6d 100644 --- a/chrome/browser/glic/BUILD.gn +++ b/chrome/browser/glic/BUILD.gn
@@ -267,6 +267,7 @@ "media/glic_media_context_unittest.cc", "media/glic_media_integration_unittest.cc", "media/glic_media_page_cache_unittest.cc", + "widget/local_hotkey_manager_unittest.cc", ] deps = [ ":glic", @@ -300,6 +301,7 @@ "glic_user_status_browsertest.cc", "host/context/glic_page_context_eligibility_observer_browsertest.cc", "host/guest_util_browsertest.cc", + "widget/application_hotkey_delegate_browsertest.cc", ] deps = [ ":glic",
diff --git a/chrome/browser/glic/e2e_test/glic_e2e_test.cc b/chrome/browser/glic/e2e_test/glic_e2e_test.cc index ff3cdc9..0cc9838 100644 --- a/chrome/browser/glic/e2e_test/glic_e2e_test.cc +++ b/chrome/browser/glic/e2e_test/glic_e2e_test.cc
@@ -185,8 +185,7 @@ std::ref(window_controller())), WaitForState(kGlicWindowControllerState, GlicWindowController::State::kOpen), - Steps(InstrumentNonTabWebView(kGlicHostElementId, - GlicView::kWebViewElementIdForTesting), + Steps(InstrumentNonTabWebView(kGlicHostElementId, kGlicViewElementId), InstrumentInnerWebContents(kGlicContentsElementId, kGlicHostElementId, 0), WaitForWebContentsReady(kGlicContentsElementId)),
diff --git a/chrome/browser/glic/host/glic_page_handler.cc b/chrome/browser/glic/host/glic_page_handler.cc index 18a4439..5527f936 100644 --- a/chrome/browser/glic/host/glic_page_handler.cc +++ b/chrome/browser/glic/host/glic_page_handler.cc
@@ -848,8 +848,8 @@ } webui_frame->ForEachRenderFrameHostWithAction( [&web_view_guest](content::RenderFrameHost* rfh) { - if (auto* web_view = - extensions::WebViewGuest::FromRenderFrameHost(rfh)) { + auto* web_view = extensions::WebViewGuest::FromRenderFrameHost(rfh); + if (web_view && web_view->attached()) { web_view_guest = web_view; return content::RenderFrameHost::FrameIterationAction::kStop; }
diff --git a/chrome/browser/glic/host/webui_contents_container.cc b/chrome/browser/glic/host/webui_contents_container.cc index 9acc17e..1be9091 100644 --- a/chrome/browser/glic/host/webui_contents_container.cc +++ b/chrome/browser/glic/host/webui_contents_container.cc
@@ -49,7 +49,7 @@ const input::NativeWebKeyboardEvent& event) { GlicView* glic_view = glic_window_controller_->GetGlicView(); return glic_view && unhandled_keyboard_event_handler_.HandleKeyboardEvent( - event, glic_view->web_view()->GetFocusManager()); + event, glic_view->GetFocusManager()); } void WebUIContentsContainer::RequestMediaAccessPermission(
diff --git a/chrome/browser/glic/test_support/interactive_glic_test.h b/chrome/browser/glic/test_support/interactive_glic_test.h index 968904f..6f5c84cd 100644 --- a/chrome/browser/glic/test_support/interactive_glic_test.h +++ b/chrome/browser/glic/test_support/interactive_glic_test.h
@@ -186,8 +186,8 @@ Api::ObserveState(internal::kGlicWindowControllerState, std::ref(window_controller)), Api::InAnyContext(Api::Steps( - Api::InstrumentNonTabWebView( - kGlicHostElementId, GlicView::kWebViewElementIdForTesting), + Api::InstrumentNonTabWebView(kGlicHostElementId, + kGlicViewElementId), Api::InstrumentInnerWebContents(kGlicContentsElementId, kGlicHostElementId, 0), Api::WaitForWebContentsReady(kGlicContentsElementId))), @@ -201,8 +201,8 @@ Api::UninstrumentWebContents(kGlicHostElementId, false), Api::ObserveState(internal::kGlicWindowControllerState, std::ref(window_controller)), - Api::InAnyContext(Api::InstrumentNonTabWebView( - kGlicHostElementId, GlicView::kWebViewElementIdForTesting)), + Api::InAnyContext(Api::InstrumentNonTabWebView(kGlicHostElementId, + kGlicViewElementId)), Api::WaitForState( internal::kGlicWindowControllerState, testing::Matcher<GlicWindowController::State>(testing::AnyOf(
diff --git a/chrome/browser/glic/test_support/mock_glic_window_controller.h b/chrome/browser/glic/test_support/mock_glic_window_controller.h index 5950a806..fd85f854 100644 --- a/chrome/browser/glic/test_support/mock_glic_window_controller.h +++ b/chrome/browser/glic/test_support/mock_glic_window_controller.h
@@ -65,8 +65,8 @@ MOCK_METHOD(void, PreloadFre, (), (override)); MOCK_METHOD(void, Reload, (), (override)); MOCK_METHOD(bool, IsWarmed, (), (const, override)); - MOCK_METHOD(base::WeakPtr<GlicWindowController>, GetWeakPtr, (), (override)); MOCK_METHOD(GlicView*, GetGlicView, (), (override)); + MOCK_METHOD(base::WeakPtr<views::View>, GetGlicViewAsView, (), (override)); MOCK_METHOD(GlicWidget*, GetGlicWidget, (), (override)); MOCK_METHOD(content::WebContents*, GetFreWebContents, (), (override)); MOCK_METHOD(Browser*, attached_browser, (), (override)); @@ -79,6 +79,13 @@ MOCK_METHOD(gfx::Rect, GetInitialBounds, (Browser*), (override)); MOCK_METHOD(void, ShowDetachedForTesting, (), (override)); MOCK_METHOD(void, SetPreviousPositionForTesting, (gfx::Point), (override)); + + base::WeakPtr<GlicWindowController> GetWeakPtr() override { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + base::WeakPtrFactory<MockGlicWindowController> weak_ptr_factory_{this}; }; } // namespace glic
diff --git a/chrome/browser/glic/widget/application_hotkey_delegate.cc b/chrome/browser/glic/widget/application_hotkey_delegate.cc index 2f92e32..14e76c2a 100644 --- a/chrome/browser/glic/widget/application_hotkey_delegate.cc +++ b/chrome/browser/glic/widget/application_hotkey_delegate.cc
@@ -119,10 +119,10 @@ std::unique_ptr<LocalHotkeyManager> MakeApplicationHotkeyManager( base::WeakPtr<GlicWindowController> window_controller) { - auto application_hotkey_delegate = std::make_unique<LocalHotkeyManager>( + auto hotkey_manager = std::make_unique<LocalHotkeyManager>( window_controller, std::make_unique<ApplicationHotkeyDelegate>(window_controller)); - application_hotkey_delegate->InitializeAccelerators(); - return application_hotkey_delegate; + hotkey_manager->InitializeAccelerators(); + return hotkey_manager; } } // namespace glic
diff --git a/chrome/browser/glic/widget/application_hotkey_delegate_browsertest.cc b/chrome/browser/glic/widget/application_hotkey_delegate_browsertest.cc new file mode 100644 index 0000000..6af0531 --- /dev/null +++ b/chrome/browser/glic/widget/application_hotkey_delegate_browsertest.cc
@@ -0,0 +1,137 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/glic/widget/application_hotkey_delegate.h" + +#include <memory> + +#include "base/memory/weak_ptr.h" +#include "base/test/metrics/user_action_tester.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/glic/test_support/mock_glic_window_controller.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "content/public/test/browser_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/views/focus/focus_manager.h" + +namespace glic { + +namespace { + +class MockAcceleratorTarget : public ui::AcceleratorTarget { + public: + MOCK_METHOD(bool, + AcceleratorPressed, + (const ui::Accelerator& accelerator), + (override)); + MOCK_METHOD(bool, CanHandleAccelerators, (), (const, override)); + + base::WeakPtr<MockAcceleratorTarget> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + base::WeakPtrFactory<MockAcceleratorTarget> weak_factory_{this}; +}; + +class ApplicationHotkeyDelegateTest : public InProcessBrowserTest { + protected: + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + delegate_.emplace(mock_controller_.GetWeakPtr()); + } + + BrowserView* GetBrowserView() { + return BrowserView::GetBrowserViewForBrowser(browser()); + } + + BrowserView* GetBrowserViewForBrowser(Browser* b) { + return BrowserView::GetBrowserViewForBrowser(b); + } + + MockGlicWindowController mock_controller_; + std::optional<ApplicationHotkeyDelegate> delegate_; + base::UserActionTester user_action_tester_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(ApplicationHotkeyDelegateTest, GetSupportedHotkeys) { + CHECK(g_browser_process); + auto supported = delegate_->GetSupportedHotkeys(); + // kFocusToggle is the only supported hotkey for application-wide scope. + EXPECT_THAT(supported, + testing::ElementsAre(LocalHotkeyManager::Hotkey::kFocusToggle)); +} + +IN_PROC_BROWSER_TEST_F(ApplicationHotkeyDelegateTest, + AcceleratorPressedFocusToggle) { + EXPECT_CALL(mock_controller_, FocusIfOpen()).Times(1); + + EXPECT_TRUE( + delegate_->AcceleratorPressed(LocalHotkeyManager::Hotkey::kFocusToggle)); + EXPECT_EQ(1, user_action_tester_.GetActionCount("Glic.FocusHotKey")); +} + +IN_PROC_BROWSER_TEST_F(ApplicationHotkeyDelegateTest, + AcceleratorPressedControllerNull) { + // Create a delegate with an invalidated controller WeakPtr. + auto controller = std::make_unique<MockGlicWindowController>(); + auto delegate_with_null_controller = + std::make_unique<ApplicationHotkeyDelegate>(controller->GetWeakPtr()); + controller.reset(); // Invalidate the WeakPtr + + EXPECT_FALSE(delegate_with_null_controller->AcceleratorPressed( + LocalHotkeyManager::Hotkey::kFocusToggle)); + EXPECT_EQ(0, user_action_tester_.GetActionCount("Glic.FocusHotKey")); +} + +IN_PROC_BROWSER_TEST_F(ApplicationHotkeyDelegateTest, + CreateScopedHotkeyRegistration) { + ui::Accelerator test_accel(ui::VKEY_A, ui::EF_NONE); + MockAcceleratorTarget mock_target; + + ASSERT_TRUE(GetBrowserView()) << "BrowserView not available"; + views::FocusManager* focus_manager = GetBrowserView()->GetFocusManager(); + ASSERT_TRUE(focus_manager) << "FocusManager not available"; + + EXPECT_FALSE(focus_manager->IsAcceleratorRegistered(test_accel)); + + auto registration = delegate_->CreateScopedHotkeyRegistration( + test_accel, mock_target.GetWeakPtr()); + ASSERT_TRUE(registration); + + // Verify registration with the first browser. + EXPECT_TRUE(focus_manager->IsAcceleratorRegistered(test_accel)); + + // Test adding a new browser after registration. + Browser* browser2 = CreateBrowser(browser()->profile()); + BrowserView* browser_view2 = GetBrowserViewForBrowser(browser2); + ASSERT_TRUE(browser_view2); + views::FocusManager* focus_manager2 = nullptr; + + focus_manager2 = browser_view2->GetFocusManager(); + ASSERT_TRUE(focus_manager2); + + EXPECT_TRUE(focus_manager2->IsAcceleratorRegistered(test_accel)); + + registration.reset(); + + EXPECT_FALSE(focus_manager->IsAcceleratorRegistered(test_accel)); + EXPECT_FALSE(focus_manager2->IsAcceleratorRegistered(test_accel)); + + // BrowserList will take care of cleaning up browser2. +} + +IN_PROC_BROWSER_TEST_F(ApplicationHotkeyDelegateTest, + MakeApplicationHotkeyManager) { + auto manager = MakeApplicationHotkeyManager(mock_controller_.GetWeakPtr()); + EXPECT_TRUE(manager); +} + +} // namespace glic
diff --git a/chrome/browser/glic/widget/glic_view.cc b/chrome/browser/glic/widget/glic_view.cc index 4761e34..95c19b2 100644 --- a/chrome/browser/glic/widget/glic_view.cc +++ b/chrome/browser/glic/widget/glic_view.cc
@@ -36,12 +36,7 @@ : accelerator_delegate_(accelerator_delegate) { SetProperty(views::kElementIdentifierKey, kGlicViewElementId); SetLayoutManager(std::make_unique<views::FillLayout>()); - auto web_view = std::make_unique<views::WebView>(profile); - web_view->SetProperty(views::kElementIdentifierKey, - kWebViewElementIdForTesting); - web_view_ = web_view.get(); - web_view->SetSize(initial_size); - AddChildView(std::move(web_view)); + SetSize(initial_size); // As there is no WebContents yet, this will apply the default background. UpdateBackgroundColor(); } @@ -70,11 +65,6 @@ draggable_areas_[0].set_width(width()); } -void GlicView::SetWebContents(content::WebContents* web_contents) { - web_view_->SetWebContents(web_contents); - UpdateBackgroundColor(); -} - void GlicView::UpdateBackgroundColor() { std::optional<SkColor> client_background = GetClientBackgroundColor(); if (client_background) { @@ -93,7 +83,7 @@ } std::optional<SkColor> GlicView::GetClientBackgroundColor() { - content::WebContents* host = web_view_->GetWebContents(); + content::WebContents* host = GetWebContents(); if (!host) { return std::nullopt; }
diff --git a/chrome/browser/glic/widget/glic_view.h b/chrome/browser/glic/widget/glic_view.h index 19581804..01a6a45 100644 --- a/chrome/browser/glic/widget/glic_view.h +++ b/chrome/browser/glic/widget/glic_view.h
@@ -11,13 +11,9 @@ #include "ui/base/interaction/element_identifier.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/gfx/geometry/size.h" -#include "ui/views/view.h" +#include "ui/views/controls/webview/webview.h" #include "ui/views/widget/unique_widget_ptr.h" -namespace content { -class WebContents; -} - namespace gfx { class Rect; } // namespace gfx @@ -30,8 +26,8 @@ namespace glic { -class GlicView : public views::View { - METADATA_HEADER(GlicView, views::View) +class GlicView : public views::WebView { + METADATA_HEADER(GlicView, views::WebView) public: GlicView(Profile* profile, @@ -47,16 +43,12 @@ bool IsPointWithinDraggableArea(const gfx::Point& point); - void SetWebContents(content::WebContents* web_contents); - // Try to get the background color from the web UI and use it as this view's // background color. Only call after the client is initialized. void UpdateBackgroundColor(); void UpdatePrimaryDraggableAreaOnResize(); - views::WebView* web_view() { return web_view_; } - bool AcceleratorPressed(const ui::Accelerator& accelerator) override; base::WeakPtr<GlicView> GetWeakPtr() {
diff --git a/chrome/browser/glic/widget/glic_window_animator.cc b/chrome/browser/glic/widget/glic_window_animator.cc index 52b02c6e..6545851 100644 --- a/chrome/browser/glic/widget/glic_window_animator.cc +++ b/chrome/browser/glic/widget/glic_window_animator.cc
@@ -95,13 +95,6 @@ ResetLastTargetSize(); } -void GlicWindowAnimator::SetGlicWebViewVisibility(bool is_visible) { - views::WebView* web_view = window_controller_->GetGlicView()->web_view(); - if (web_view->GetVisible() != is_visible) { - web_view->SetVisible(is_visible); - } -} - void GlicWindowAnimator::ResizeFinished() { // Destroy window_resize_animation_. window_resize_animation_.reset();
diff --git a/chrome/browser/glic/widget/glic_window_animator.h b/chrome/browser/glic/widget/glic_window_animator.h index db6b1e7c..8aa3061 100644 --- a/chrome/browser/glic/widget/glic_window_animator.h +++ b/chrome/browser/glic/widget/glic_window_animator.h
@@ -41,9 +41,6 @@ base::TimeDelta duration, base::OnceClosure callback); - // Sets the visibility of the GlicView's web view. - void SetGlicWebViewVisibility(bool is_visible); - // Called when the programmatic resize has finished. Public for use by // GlicWindowResizeAnimation. void ResizeFinished();
diff --git a/chrome/browser/glic/widget/glic_window_controller.h b/chrome/browser/glic/widget/glic_window_controller.h index 8983e7b..01bc4d3 100644 --- a/chrome/browser/glic/widget/glic_window_controller.h +++ b/chrome/browser/glic/widget/glic_window_controller.h
@@ -70,7 +70,7 @@ GlicWindowController(const GlicWindowController&) = delete; GlicWindowController& operator=(const GlicWindowController&) = delete; GlicWindowController() = default; - ~GlicWindowController() = default; + virtual ~GlicWindowController() = default; // Show, summon, or activate the panel if needed, or close it if it's already // active and prevent_close is false. @@ -195,6 +195,8 @@ virtual GlicView* GetGlicView() = 0; + virtual base::WeakPtr<views::View> GetGlicViewAsView() = 0; + // Returns the widget that backs the glic window. virtual GlicWidget* GetGlicWidget() = 0;
diff --git a/chrome/browser/glic/widget/glic_window_controller_impl.cc b/chrome/browser/glic/widget/glic_window_controller_impl.cc index 11be751..f002a87 100644 --- a/chrome/browser/glic/widget/glic_window_controller_impl.cc +++ b/chrome/browser/glic/widget/glic_window_controller_impl.cc
@@ -472,7 +472,7 @@ } else if (state_ == State::kOpen) { // TODO(crbug.com/404601783): Bring focus to the textbox. GetGlicWidget()->Activate(); - GetGlicView()->web_view()->GetWebContents()->Focus(); + GetGlicView()->GetWebContents()->Focus(); } } @@ -566,7 +566,7 @@ void GlicWindowControllerImpl::FocusIfOpen() { if (IsShowing() && !IsActive()) { GetGlicWidget()->Activate(); - GetGlicView()->web_view()->GetWebContents()->Focus(); + GetGlicView()->GetWebContents()->Focus(); } } @@ -670,6 +670,7 @@ // Immediately hook up the WebView to the WebContents. GetGlicView()->SetWebContents(host().webui_contents()); + GetGlicView()->UpdateBackgroundColor(); } void GlicWindowControllerImpl::SetupGlicWidgetAccessibilityText() { @@ -795,21 +796,20 @@ return; } - // Update the background color after fading in the webview so the transition + // Update the background color after showing the webview so the transition // isn't visible. This will be the widget background color the user sees next // time. GetGlicView()->UpdateBackgroundColor(); // In the case that the open animation was skipped, the web view should still // be visible now. - glic_window_animator_->SetGlicWebViewVisibility(true); SetWindowState(State::kOpen); // Whenever the glic window is shown, it should have focus. The following line // of code appears to be necessary but not sufficient and there are still some // edge cases. // TODO(crbug.com/390637019): Fully fix and remove this comment. - GetGlicView()->web_view()->GetWebContents()->Focus(); + GetGlicView()->GetWebContents()->Focus(); SetDraggingAreasAndWatchForMouseEvents(); NotifyIfPanelStateChanged(); @@ -835,6 +835,13 @@ return static_cast<GlicView*>(GetGlicWidget()->GetContentsView()); } +base::WeakPtr<views::View> GlicWindowControllerImpl::GetGlicViewAsView() { + if (auto* view = GetGlicView()) { + return view->GetWeakPtr(); + } + return nullptr; +} + GlicWidget* GlicWindowControllerImpl::GetGlicWidget() { return glic_widget_.get(); } @@ -1012,12 +1019,7 @@ const bool reopen_detached = state_ == State::kClosingToReopenDetached; DCHECK(!reopen_detached || reopen_detached_source.has_value()); - // The webview should be faded out instead. - if (GetGlicView()) { - glic_window_animator_->SetGlicWebViewVisibility(false); - } - - CloseFinish(reopen_detached, reopen_detached_source); + CloseFinish(reopen_detached, reopen_detached_source); } void GlicWindowControllerImpl::CloseFinish( @@ -1048,6 +1050,9 @@ window_activation_callback_list_.Notify(false); host().PanelWasClosed(); + if (base::FeatureList::IsEnabled(features::kGlicUnloadOnClose)) { + host().Shutdown(); + } if (reopen_detached) { Show(nullptr, *reopen_detached_source); @@ -1118,6 +1123,11 @@ views::Widget::MoveLoopEscapeBehavior::kDontHide); in_move_loop_ = false; scoped_glic_button_indicator_.reset(); + + // Only handle positioning if glic wasn't closed during the drag. + if (state_ == State::kClosed) { + return; + } // Dragging stops animations. This makes sure we honor the last resize // request. glic_window_animator_->MaybeAnimateToTargetSize(); @@ -1450,5 +1460,4 @@ Browser* GlicWindowControllerImpl::attached_browser() { return attached_browser_; } - } // namespace glic
diff --git a/chrome/browser/glic/widget/glic_window_controller_impl.h b/chrome/browser/glic/widget/glic_window_controller_impl.h index 4084eb296..8ea780f4 100644 --- a/chrome/browser/glic/widget/glic_window_controller_impl.h +++ b/chrome/browser/glic/widget/glic_window_controller_impl.h
@@ -109,6 +109,7 @@ base::WeakPtr<GlicWindowController> GetWeakPtr() override; GlicView* GetGlicView() override; + base::WeakPtr<views::View> GetGlicViewAsView() override; GlicWidget* GetGlicWidget() override; content::WebContents* GetFreWebContents() override;
diff --git a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc index fbdb66b..2dac9d3 100644 --- a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc +++ b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
@@ -451,6 +451,30 @@ ASSERT_EQ(initial_bounds.origin(), gfx::Point(20, 10)); } +class GlicWindowControllerUnloadOnCloseTest + : public GlicWindowControllerUiTest { + public: + GlicWindowControllerUnloadOnCloseTest() { + features_.InitAndEnableFeature(features::kGlicUnloadOnClose); + } + ~GlicWindowControllerUnloadOnCloseTest() override = default; + + auto CheckWebUiContentsExist(bool exist) { + return CheckResult( + [this]() { return !!glic_service()->host().webui_contents(); }, exist, + "CheckWebUiContentsExist"); + } + + private: + base::test::ScopedFeatureList features_; +}; + +IN_PROC_BROWSER_TEST_F(GlicWindowControllerUnloadOnCloseTest, UnloadOnClose) { + RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached), + CheckControllerHasWidget(true), CheckWebUiContentsExist(true), + CloseGlicWindow(), CheckWebUiContentsExist(false)); +} + class GlicWindowControllerWithMemoryPressureUiTest : public GlicWindowControllerUiTest { public:
diff --git a/chrome/browser/glic/widget/glic_window_hotkey_delegate.cc b/chrome/browser/glic/widget/glic_window_hotkey_delegate.cc index 1a32fb8e..8a4bb2f 100644 --- a/chrome/browser/glic/widget/glic_window_hotkey_delegate.cc +++ b/chrome/browser/glic/widget/glic_window_hotkey_delegate.cc
@@ -23,12 +23,11 @@ namespace { -static constexpr std::array<glic::LocalHotkeyManager::Hotkey, 3> - kSupportedHotkeys = { - glic::LocalHotkeyManager::Hotkey::kClose, - glic::LocalHotkeyManager::Hotkey::kFocusToggle, +static constexpr std::array kSupportedHotkeys = { + glic::LocalHotkeyManager::Hotkey::kClose, + glic::LocalHotkeyManager::Hotkey::kFocusToggle, #if BUILDFLAG(IS_WIN) - glic::LocalHotkeyManager::Hotkey::kTitleBarContextMenu, + glic::LocalHotkeyManager::Hotkey::kTitleBarContextMenu, #endif }; @@ -38,7 +37,7 @@ : public LocalHotkeyManager::ScopedHotkeyRegistration { public: GlicWindowScopedHotkeyRegistration(ui::Accelerator accelerator, - base::WeakPtr<GlicView> glic_view) + base::WeakPtr<views::View> glic_view) : accelerator_(accelerator), glic_view_(glic_view) { CHECK(!accelerator.IsEmpty()); CHECK(glic_view_); @@ -54,7 +53,7 @@ private: ui::Accelerator accelerator_; - base::WeakPtr<GlicView> glic_view_; + base::WeakPtr<views::View> glic_view_; }; } // namespace @@ -109,9 +108,8 @@ ui::Accelerator accelerator, base::WeakPtr<ui::AcceleratorTarget> target) { CHECK(window_controller_); - CHECK(window_controller_->GetGlicView()); return std::make_unique<GlicWindowScopedHotkeyRegistration>( - accelerator, window_controller_->GetGlicView()->GetWeakPtr()); + accelerator, window_controller_->GetGlicViewAsView()); } std::unique_ptr<LocalHotkeyManager> MakeGlicWindowHotkeyManager(
diff --git a/chrome/browser/glic/widget/glic_window_hotkey_delegate_unittest.cc b/chrome/browser/glic/widget/glic_window_hotkey_delegate_unittest.cc new file mode 100644 index 0000000..82ffa40 --- /dev/null +++ b/chrome/browser/glic/widget/glic_window_hotkey_delegate_unittest.cc
@@ -0,0 +1,197 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/glic/widget/glic_window_hotkey_delegate.h" + +#include <memory> +#include <vector> + +#include "base/containers/contains.h" +#include "base/memory/weak_ptr.h" +#include "base/test/metrics/user_action_tester.h" +#include "build/build_config.h" +#include "chrome/browser/glic/test_support/mock_glic_window_controller.h" +#include "chrome/browser/glic/widget/glic_view.h" +#include "chrome/browser/glic/widget/glic_window_controller.h" +#include "chrome/browser/glic/widget/local_hotkey_manager.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/test/base/scoped_testing_local_state.h" +#include "chrome/test/base/test_browser_window.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/views/view.h" + +namespace glic { + +namespace { + +class MockGlicView : public views::View { + public: + MockGlicView() = default; + ~MockGlicView() override = default; + + base::WeakPtr<MockGlicView> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + base::WeakPtrFactory<MockGlicView> weak_ptr_factory_{this}; +}; + +class MockBrowserWindow : public TestBrowserWindow { + public: + MOCK_METHOD(void, Activate, ()); +}; + +class GlicWindowHotkeyDelegateTest : public testing::Test { + public: + GlicWindowHotkeyDelegateTest() + : local_state_(TestingBrowserProcess::GetGlobal()) {} + + void SetUp() override { + testing::Test::SetUp(); + + // Profile needed for Browser::Create + profile_ = std::make_unique<TestingProfile>(); + + // Create the mock window first + mock_browser_window_ = std::make_unique<MockBrowserWindow>(); + + // Create the fake Browser, providing the mock window. + // This Browser instance will be added to the BrowserList automatically. + Browser::CreateParams params(profile_.get(), true); + params.window = mock_browser_window_.get(); // Assign raw pointer + fake_browser_ = std::unique_ptr<Browser>(Browser::Create(params)); + + // Create other mocks + mock_controller_ = std::make_unique<MockGlicWindowController>(); + mock_glic_view_ = std::make_unique<MockGlicView>(); + ON_CALL(*mock_controller_, GetGlicViewAsView()) + .WillByDefault(testing::Return(mock_glic_view_->GetWeakPtr())); + + delegate_ = std::make_unique<GlicWindowHotkeyDelegate>( + mock_controller_->GetWeakPtr()); + } + + void TearDown() override { + delegate_.reset(); + mock_glic_view_.reset(); + mock_controller_.reset(); + // Destroy the browser before the window it points to. + // The Browser destructor should remove it from the BrowserList. + fake_browser_.reset(); + mock_browser_window_.reset(); + profile_.reset(); + + testing::Test::TearDown(); + } + + protected: + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr<MockGlicWindowController> mock_controller_; + std::unique_ptr<MockGlicView> mock_glic_view_; + std::unique_ptr<GlicWindowHotkeyDelegate> delegate_; + base::UserActionTester user_action_tester_; + ScopedTestingLocalState local_state_; + std::unique_ptr<TestingProfile> profile_; + std::unique_ptr<MockBrowserWindow> mock_browser_window_; + std::unique_ptr<Browser> + fake_browser_; // Use real Browser, created minimally +}; + +} // namespace + +TEST_F(GlicWindowHotkeyDelegateTest, GetSupportedHotkeys) { + auto supported = delegate_->GetSupportedHotkeys(); + const std::vector<LocalHotkeyManager::Hotkey> kExpectedHotkeys = { + LocalHotkeyManager::Hotkey::kClose, + LocalHotkeyManager::Hotkey::kFocusToggle, +#if BUILDFLAG(IS_WIN) + LocalHotkeyManager::Hotkey::kTitleBarContextMenu, +#endif + }; + EXPECT_THAT(supported, testing::ElementsAreArray(kExpectedHotkeys)); +} + +TEST_F(GlicWindowHotkeyDelegateTest, AcceleratorPressedClose) { + EXPECT_CALL(*mock_controller_, Close()).Times(1); + EXPECT_TRUE( + delegate_->AcceleratorPressed(LocalHotkeyManager::Hotkey::kClose)); +} + +TEST_F(GlicWindowHotkeyDelegateTest, AcceleratorPressedFocusToggleAttached) { + EXPECT_CALL(*mock_controller_, IsAttached()).WillOnce(testing::Return(true)); + // Provide the fake browser to the mock controller + ON_CALL(*mock_controller_, attached_browser()) + .WillByDefault(testing::Return(fake_browser_.get())); + + EXPECT_CALL(*mock_browser_window_, Activate()).Times(1); + + EXPECT_TRUE( + delegate_->AcceleratorPressed(LocalHotkeyManager::Hotkey::kFocusToggle)); + EXPECT_EQ(0, user_action_tester_.GetActionCount("Glic.FocusHotKey")); +} + +TEST_F(GlicWindowHotkeyDelegateTest, + AcceleratorPressedFocusToggleDetachedWithLastActive) { + // Set the fake browser as the last active one for this scenario. + // It's already in the BrowserList thanks to Browser::Create in SetUp. + BrowserList::SetLastActive(fake_browser_.get()); + + EXPECT_CALL(*mock_controller_, IsAttached()).WillOnce(testing::Return(false)); + EXPECT_CALL(*mock_browser_window_, Activate()).Times(1); + + EXPECT_TRUE( + delegate_->AcceleratorPressed(LocalHotkeyManager::Hotkey::kFocusToggle)); + EXPECT_EQ(1, user_action_tester_.GetActionCount("Glic.FocusHotKey")); +} + +TEST_F(GlicWindowHotkeyDelegateTest, + AcceleratorPressedFocusToggleDetachedNoLastActive) { + fake_browser_.reset(); + + EXPECT_CALL(*mock_controller_, IsAttached()).WillOnce(testing::Return(false)); + // Ensure BrowserList returns nullptr + ASSERT_EQ(BrowserList::GetInstance()->GetLastActive(), nullptr); + + EXPECT_FALSE( + delegate_->AcceleratorPressed(LocalHotkeyManager::Hotkey::kFocusToggle)); + EXPECT_EQ(0, user_action_tester_.GetActionCount("Glic.FocusHotKey")); +} + +#if BUILDFLAG(IS_WIN) +TEST_F(GlicWindowHotkeyDelegateTest, AcceleratorPressedTitleBarContextMenu) { + EXPECT_CALL(*mock_controller_, ShowTitleBarContextMenuAt(gfx::Point())) + .Times(1); + EXPECT_TRUE(delegate_->AcceleratorPressed( + LocalHotkeyManager::Hotkey::kTitleBarContextMenu)); +} +#endif + +TEST_F(GlicWindowHotkeyDelegateTest, CreateScopedHotkeyRegistration) { + ui::Accelerator test_accel(ui::VKEY_A, ui::EF_NONE); + + auto registration = + delegate_->CreateScopedHotkeyRegistration(test_accel, nullptr); + ASSERT_TRUE(registration); + EXPECT_THAT(mock_glic_view_->GetAccelerators(), + testing::ElementsAre(test_accel)); + + registration.reset(); + EXPECT_EQ(mock_glic_view_->GetAccelerators().size(), 0u); +} + +TEST_F(GlicWindowHotkeyDelegateTest, MakeGlicWindowHotkeyManager) { + auto manager = MakeGlicWindowHotkeyManager(mock_controller_->GetWeakPtr()); + EXPECT_TRUE(manager); +} + +} // namespace glic
diff --git a/chrome/browser/glic/widget/local_hotkey_manager.cc b/chrome/browser/glic/widget/local_hotkey_manager.cc index 31f3f803..e7f0a45 100644 --- a/chrome/browser/glic/widget/local_hotkey_manager.cc +++ b/chrome/browser/glic/widget/local_hotkey_manager.cc
@@ -42,6 +42,7 @@ std::unique_ptr<Delegate> delegate) : window_controller_(window_controller), delegate_(std::move(delegate)) { CHECK(delegate_); + CHECK(g_browser_process); pref_registrar_.Init(g_browser_process->local_state()); for (Hotkey hotkey : delegate_->GetSupportedHotkeys()) { auto pref_name_iter = kHotkeyToPrefMap.find(hotkey);
diff --git a/chrome/browser/glic/widget/local_hotkey_manager_unittest.cc b/chrome/browser/glic/widget/local_hotkey_manager_unittest.cc new file mode 100644 index 0000000..b1d3463 --- /dev/null +++ b/chrome/browser/glic/widget/local_hotkey_manager_unittest.cc
@@ -0,0 +1,236 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/glic/widget/local_hotkey_manager.h" + +#include <memory> +#include <vector> + +#include "base/containers/flat_set.h" +#include "base/memory/weak_ptr.h" +#include "base/test/task_environment.h" +#include "chrome/browser/glic/glic_pref_names.h" +#include "chrome/browser/glic/test_support/mock_glic_window_controller.h" +#include "chrome/browser/glic/widget/glic_window_controller.h" +#include "chrome/test/base/scoped_testing_local_state.h" +#include "chrome/test/base/testing_browser_process.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/command.h" +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace glic { + +namespace { +class FakeScopedHotkeyRegistration + : public LocalHotkeyManager::ScopedHotkeyRegistration { + public: + FakeScopedHotkeyRegistration(ui::Accelerator accelerator, + base::OnceClosure destruction_callback) + : accelerator_(accelerator), + destruction_callback_(std::move(destruction_callback)) {} + + ~FakeScopedHotkeyRegistration() override { + if (destruction_callback_) { + std::move(destruction_callback_).Run(); + } + } + + ui::Accelerator accelerator() const { return accelerator_; } + + private: + ui::Accelerator accelerator_; + base::OnceClosure destruction_callback_; +}; + +class FakeLocalHotkeyDelegate : public LocalHotkeyManager::Delegate { + public: + FakeLocalHotkeyDelegate() = default; + ~FakeLocalHotkeyDelegate() override = default; + + // LocalHotkeyManager::Delegate: + const base::span<const LocalHotkeyManager::Hotkey> GetSupportedHotkeys() + const override { + return supported_hotkeys_; + } + + std::unique_ptr<FakeScopedHotkeyRegistration::ScopedHotkeyRegistration> + CreateScopedHotkeyRegistration( + ui::Accelerator accelerator, + base::WeakPtr<ui::AcceleratorTarget> target) override { + last_registered_accelerator_ = accelerator; + registration_count_++; + auto registration = std::make_unique<FakeScopedHotkeyRegistration>( + accelerator, + base::BindOnce(&FakeLocalHotkeyDelegate::OnRegistrationDestroyed, + base::Unretained(this), accelerator)); + active_registrations_.insert(accelerator); + return registration; + } + + bool AcceleratorPressed(LocalHotkeyManager::Hotkey hotkey) override { + last_pressed_hotkey_ = hotkey; + return true; // Assume handled + } + + // Test helpers + void SetSupportedHotkeys(std::vector<LocalHotkeyManager::Hotkey> hotkeys) { + supported_hotkeys_ = std::move(hotkeys); + } + + std::optional<LocalHotkeyManager::Hotkey> last_pressed_hotkey() const { + return last_pressed_hotkey_; + } + + std::optional<ui::Accelerator> last_registered_accelerator() const { + return last_registered_accelerator_; + } + + int registration_count() const { return registration_count_; } + int destruction_count() const { return destruction_count_; } + bool IsRegistered(ui::Accelerator acc) const { + return active_registrations_.contains(acc); + } + + private: + void OnRegistrationDestroyed(ui::Accelerator accelerator) { + destruction_count_++; + active_registrations_.erase(accelerator); + } + + std::vector<LocalHotkeyManager::Hotkey> supported_hotkeys_ = { + LocalHotkeyManager::Hotkey::kClose, + LocalHotkeyManager::Hotkey::kFocusToggle}; + std::optional<LocalHotkeyManager::Hotkey> last_pressed_hotkey_; + std::optional<ui::Accelerator> last_registered_accelerator_; + int registration_count_ = 0; + int destruction_count_ = 0; + base::flat_set<ui::Accelerator> active_registrations_; +}; + +class LocalHotkeyManagerTest : public testing::Test { + public: + LocalHotkeyManagerTest() : local_state_(TestingBrowserProcess::GetGlobal()) {} + + void SetUp() override { + mock_controller_ = std::make_unique<MockGlicWindowController>(); + auto fake_delegate = std::make_unique<FakeLocalHotkeyDelegate>(); + fake_delegate_ = fake_delegate.get(); // Keep a raw pointer for access + + manager_ = std::make_unique<LocalHotkeyManager>( + mock_controller_->GetWeakPtr(), std::move(fake_delegate)); + } + + protected: + base::test::TaskEnvironment task_environment_; + ScopedTestingLocalState local_state_; + std::unique_ptr<MockGlicWindowController> mock_controller_; + std::unique_ptr<LocalHotkeyManager> manager_; + raw_ptr<FakeLocalHotkeyDelegate> fake_delegate_; +}; + +} // namespace + +TEST_F(LocalHotkeyManagerTest, InitializationRegistersSupportedHotkeys) { + EXPECT_EQ(fake_delegate_->registration_count(), 0); + + manager_->InitializeAccelerators(); + + // Close and FocusToggle should be registered by default. + EXPECT_EQ(fake_delegate_->registration_count(), 2); + EXPECT_TRUE( + fake_delegate_->IsRegistered(LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kClose))); + EXPECT_TRUE( + fake_delegate_->IsRegistered(LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle))); +} + +TEST_F(LocalHotkeyManagerTest, AcceleratorPressedCallsDelegate) { + manager_->InitializeAccelerators(); + ui::Accelerator close_acc = LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kClose); + + EXPECT_FALSE(fake_delegate_->last_pressed_hotkey().has_value()); + EXPECT_TRUE(manager_->AcceleratorPressed(close_acc)); + ASSERT_TRUE(fake_delegate_->last_pressed_hotkey().has_value()); + EXPECT_EQ(fake_delegate_->last_pressed_hotkey().value(), + LocalHotkeyManager::Hotkey::kClose); +} + +TEST_F(LocalHotkeyManagerTest, CanHandleAcceleratorsDependsOnController) { + EXPECT_CALL(*mock_controller_, IsShowing()).WillOnce(testing::Return(false)); + EXPECT_FALSE(manager_->CanHandleAccelerators()); + + EXPECT_CALL(*mock_controller_, IsShowing()).WillOnce(testing::Return(true)); + EXPECT_TRUE(manager_->CanHandleAccelerators()); +} + +TEST_F(LocalHotkeyManagerTest, PrefChangeUpdatesRegistration) { + manager_->InitializeAccelerators(); + EXPECT_EQ(fake_delegate_->registration_count(), 2); + EXPECT_EQ(fake_delegate_->destruction_count(), 0); + + ui::Accelerator default_focus_acc = LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle); + EXPECT_TRUE(fake_delegate_->IsRegistered(default_focus_acc)); + + // Change the pref to a new valid accelerator. + ui::Accelerator new_focus_acc(ui::VKEY_X, ui::EF_CONTROL_DOWN); + local_state_.Get()->SetString( + prefs::kGlicFocusToggleHotkey, + ui::Command::AcceleratorToString(new_focus_acc)); + + // Should destroy the old registration and create a new one. + EXPECT_EQ(fake_delegate_->registration_count(), 3); + EXPECT_EQ(fake_delegate_->destruction_count(), 1); + EXPECT_FALSE(fake_delegate_->IsRegistered(default_focus_acc)); + EXPECT_TRUE(fake_delegate_->IsRegistered(new_focus_acc)); + + // Change the pref to an empty string (clears the shortcut). + local_state_.Get()->SetString(prefs::kGlicFocusToggleHotkey, ""); + + // Should destroy the custom registration, no new one created. + EXPECT_EQ(fake_delegate_->registration_count(), 3); + EXPECT_EQ(fake_delegate_->destruction_count(), 2); + EXPECT_FALSE(fake_delegate_->IsRegistered(new_focus_acc)); +} + +TEST_F(LocalHotkeyManagerTest, GetAcceleratorRespectsPrefs) { + ui::Accelerator default_focus_acc = LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle); + ui::Accelerator default_close_acc = LocalHotkeyManager::GetDefaultAccelerator( + LocalHotkeyManager::Hotkey::kClose); + + // Close is not backed by a pref, should always return default. + EXPECT_EQ( + LocalHotkeyManager::GetAccelerator(LocalHotkeyManager::Hotkey::kClose), + default_close_acc); + + // FocusToggle is backed by a pref. Default initially. + EXPECT_EQ(LocalHotkeyManager::GetAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle), + default_focus_acc); + + // Set a valid pref. + ui::Accelerator new_focus_acc(ui::VKEY_X, ui::EF_CONTROL_DOWN); + local_state_.Get()->SetString( + prefs::kGlicFocusToggleHotkey, + ui::Command::AcceleratorToString(new_focus_acc)); + EXPECT_EQ(LocalHotkeyManager::GetAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle), + new_focus_acc); + + // Set an invalid pref string (e.g., just a modifier). + local_state_.Get()->SetString(prefs::kGlicFocusToggleHotkey, "Ctrl"); + EXPECT_TRUE(LocalHotkeyManager::GetAccelerator( + LocalHotkeyManager::Hotkey::kFocusToggle) + .IsEmpty()); +} + +} // namespace glic
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManager.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManager.java index 58d4458..ffa6203 100644 --- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManager.java +++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManager.java
@@ -229,7 +229,7 @@ */ private int getResourceIdForMenuItem(@ContextMenuItemId int id, ModuleProvider moduleProvider) { if (id == ContextMenuItemId.SHOW_CUSTOMIZE_SETTINGS) { - return R.string.home_modules_context_menu_customize; + return R.string.home_modules_context_menu_more_settings; } return moduleProvider.getResourceIdOfContextMenuItem(id);
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManagerUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManagerUnitTest.java index f3f0b40..b330345 100644 --- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManagerUnitTest.java +++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesContextMenuManagerUnitTest.java
@@ -51,7 +51,7 @@ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); private static final String EXPECTED_OPTION_1 = "Hide Chrome tips card"; - private static final String EXPECTED_OPTION_2 = "Customize"; + private static final String EXPECTED_OPTION_2 = "More settings"; private static final int MODULE_TYPE = ModuleType.PRICE_CHANGE; @Mock private ModuleProvider mModuleProvider; @Mock private View mView; @@ -209,7 +209,8 @@ @Test @SmallTest public void testShouldShowItem() { - // Verifies that the "customize" and "hide" menu items are default shown for all modules. + // Verifies that the "more settings" and "hide" menu items are default shown for all + // modules. assertTrue( mManager.shouldShowItem( ContextMenuItemId.SHOW_CUSTOMIZE_SETTINGS, mModuleProvider));
diff --git a/chrome/browser/media/DEPS b/chrome/browser/media/DEPS index 5a572d1..34dd6cdc 100644 --- a/chrome/browser/media/DEPS +++ b/chrome/browser/media/DEPS
@@ -1,5 +1,6 @@ include_rules = [ "+chrome/android/features/media_router/jni_headers", + "+components/cdm/common", "+components/webrtc", "+media/audio", "+media/base",
diff --git a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc index ef190770..b94c846 100644 --- a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc +++ b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
@@ -25,6 +25,7 @@ #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_launcher_utils.h" #include "chrome/test/base/ui_test_utils.h" +#include "components/cdm/common/buildflags.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" @@ -48,6 +49,10 @@ #error This file needs to be updated to run on Android. #endif +#if BUILDFLAG(ENABLE_PLAYREADY) +#include "media/base/win/mf_feature_checks.h" +#endif + namespace { const char* kClearKey = media::kClearKeyKeySystem; @@ -156,6 +161,23 @@ #define EXPECT_WV_HW_SECURE_PERSISTENT_SESSION EXPECT_UNSUPPORTED #endif +#if BUILDFLAG(ENABLE_PLAYREADY) +#define SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE() \ + if (!media::SupportMediaFoundationEncryptedPlayback()) { \ + GTEST_SKIP() << "This PlayReady implementation is not" \ + << "available on this version of Windows"; \ + return; \ + } + +const char kPlayReadyKeySystemBase[] = "com.microsoft.playready"; +const char kPlayReadyKeySystemRecommendationDefault[] = + "com.microsoft.playready.recommendation"; +const char kPlayReadyKeySystemRecommendationHWSecure[] = + "com.microsoft.playready.recommendation.3000"; +const char kPlayReadySoftwareSecureRobustness[] = "2000"; +const char kPlayReadyHardwareSecureRobustness[] = "3000"; +#endif // BUILDFLAG(ENABLE_PLAYREADY) + } // namespace class EncryptedMediaSupportedTypesTest : public InProcessBrowserTest { @@ -595,6 +617,29 @@ } }; +#if BUILDFLAG(ENABLE_PLAYREADY) +class EncryptedMediaSupportedTypesPlayReadyTest + : public EncryptedMediaSupportedTypesTest { + public: + EncryptedMediaSupportedTypesPlayReadyTest( + const EncryptedMediaSupportedTypesPlayReadyTest&) = delete; + EncryptedMediaSupportedTypesPlayReadyTest& operator=( + const EncryptedMediaSupportedTypesPlayReadyTest&) = delete; + + protected: + EncryptedMediaSupportedTypesPlayReadyTest() = default; + + void SetUpCommandLine(base::CommandLine* command_line) override { + EncryptedMediaSupportedTypesTest::SetUpCommandLine(command_line); + // Use this switch to always allow the protected media identifier for + // testing purpose. Note that the test page is hosted on "127.0.0.1". See + // net::EmbeddedTestServer for details. + command_line->AppendSwitchASCII( + switches::kUnsafelyAllowProtectedMediaIdentifierForDomain, "127.0.0.1"); + } +}; +#endif // BUILDFLAG(ENABLE_PLAYREADY) + #if BUILDFLAG(IS_CHROMEOS) class EncryptedMediaSupportedTypesDevModeTest : public EncryptedMediaSupportedTypesTest { @@ -736,6 +781,54 @@ } }; +#if BUILDFLAG(ENABLE_PLAYREADY) +class EncryptedMediaSupportedTypesPlayReadyHwSecureTest + : public EncryptedMediaSupportedTypesPlayReadyTest { + public: + EncryptedMediaSupportedTypesPlayReadyHwSecureTest( + const EncryptedMediaSupportedTypesPlayReadyHwSecureTest&) = delete; + EncryptedMediaSupportedTypesPlayReadyHwSecureTest& operator=( + const EncryptedMediaSupportedTypesPlayReadyHwSecureTest&) = delete; + + protected: + EncryptedMediaSupportedTypesPlayReadyHwSecureTest() { + EnableFeature(media::kHardwareSecureDecryption); +#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION) + EnableFeature(media::kPlatformEncryptedDolbyVision); +#endif + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + EncryptedMediaSupportedTypesPlayReadyTest::SetUpCommandLine(command_line); + // Pretend that we support hardware secure decryption for vp8, vp9 and + // dolbyvision, but not for avc1. This will also pretend that there is + // support for vorbis audio. + command_line->AppendSwitchASCII( + switches::kOverrideHardwareSecureCodecsForTesting, + "vp8,vp9,av01-no-clearlead,dolbyvision,vorbis"); + } +}; + +class EncryptedMediaSupportedTypesPlayReadyHwSecureForceClearLeadSupportTest + : public EncryptedMediaSupportedTypesPlayReadyTest { + protected: + EncryptedMediaSupportedTypesPlayReadyHwSecureForceClearLeadSupportTest() { + enabled_features_.push_back({media::kHardwareSecureDecryption, + {{"force_support_clear_lead", "true"}}}); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + EncryptedMediaSupportedTypesPlayReadyTest::SetUpCommandLine(command_line); + // Pretend that we support hardware secure decryption for vp8 and vp9 with + // clearlead fix supported and av01 without clear lead support. This will + // also pretend that there is support for vorbis audio. + command_line->AppendSwitchASCII( + switches::kOverrideHardwareSecureCodecsForTesting, + "vp8,vp9,av01-no-clearlead,vorbis"); + } +}; +#endif // BUILDFLAG(ENABLE_PLAYREADY) + #if BUILDFLAG(ENABLE_LIBRARY_CDMS) // Registers ClearKey CDM with the wrong path (filename). class EncryptedMediaSupportedTypesClearKeyCdmRegisteredWithWrongPathTest @@ -1898,6 +1991,248 @@ #endif // BUILDFLAG(IS_WIN) } +#if BUILDFLAG(ENABLE_PLAYREADY) +// +// PlayReady test cases. +// +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + Robustness_HardwareSecure) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // com.microsoft.playready.recommendation.3000 does not require a robustness + // level to be specified. If a robustness is specified then only "3000" is + // allowed. + EXPECT_SUCCESS(IsVideoRobustnessSupported( + kPlayReadyKeySystemRecommendationHWSecure, nullptr)); + EXPECT_SUCCESS( + IsVideoRobustnessSupported(kPlayReadyKeySystemRecommendationHWSecure, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS(IsAudioRobustnessSupported( + kPlayReadyKeySystemRecommendationHWSecure, nullptr)); + EXPECT_SUCCESS( + IsAudioRobustnessSupported(kPlayReadyKeySystemRecommendationHWSecure, + kPlayReadyHardwareSecureRobustness)); + + // This is not a valid keysystem + robustness combination. + EXPECT_UNSUPPORTED( + IsVideoRobustnessSupported(kPlayReadyKeySystemRecommendationHWSecure, + kPlayReadySoftwareSecureRobustness)); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + Robustness_RecommendationDefault) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + EXPECT_SUCCESS( + IsVideoRobustnessSupported(kPlayReadyKeySystemRecommendationDefault, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS( + IsAudioRobustnessSupported(kPlayReadyKeySystemRecommendationDefault, + kPlayReadyHardwareSecureRobustness)); + + // Software secure PlayReady should not be supported. + EXPECT_UNSUPPORTED(IsVideoRobustnessSupported( + kPlayReadyKeySystemRecommendationDefault, nullptr)); + EXPECT_UNSUPPORTED(IsAudioRobustnessSupported( + kPlayReadyKeySystemRecommendationDefault, nullptr)); + EXPECT_UNSUPPORTED( + IsVideoRobustnessSupported(kPlayReadyKeySystemRecommendationDefault, + kPlayReadySoftwareSecureRobustness)); + EXPECT_UNSUPPORTED( + IsAudioRobustnessSupported(kPlayReadyKeySystemRecommendationDefault, + kPlayReadySoftwareSecureRobustness)); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + Robustness_Base) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // Verify that the base key system is not supported. + EXPECT_UNSUPPORTED(IsVideoRobustnessSupported( + kPlayReadyKeySystemBase, kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsVideoRobustnessSupported( + kPlayReadyKeySystemBase, kPlayReadySoftwareSecureRobustness)); + EXPECT_UNSUPPORTED( + IsVideoRobustnessSupported(kPlayReadyKeySystemBase, nullptr)); + EXPECT_UNSUPPORTED(IsAudioRobustnessSupported( + kPlayReadyKeySystemBase, kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsAudioRobustnessSupported( + kPlayReadyKeySystemBase, kPlayReadySoftwareSecureRobustness)); + EXPECT_UNSUPPORTED( + IsAudioRobustnessSupported(kPlayReadyKeySystemBase, nullptr)); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + SessionType_HardwareSecure) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // Temporary session is always supported for hardware secure key systems. + EXPECT_SUCCESS(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationHWSecure, SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationHWSecure, SessionType::kTemporary)); + + // Persistent license is not supported for hardware secure key systems + EXPECT_UNSUPPORTED(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationHWSecure, + SessionType::kPersistentLicense, kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED( + IsSessionTypeSupported(kPlayReadyKeySystemRecommendationHWSecure, + SessionType::kPersistentLicense)); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + SessionType_RecommendationDefault) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // Temporary session is always supported for hardware secure key systems. + EXPECT_SUCCESS(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationDefault, SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + + // Software secure PlayReady is not supported. + EXPECT_UNSUPPORTED(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationDefault, SessionType::kTemporary)); + + // Persistent license is not supported for hardware secure key systems + EXPECT_UNSUPPORTED(IsSessionTypeSupported( + kPlayReadyKeySystemRecommendationDefault, SessionType::kPersistentLicense, + kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED( + IsSessionTypeSupported(kPlayReadyKeySystemRecommendationDefault, + SessionType::kPersistentLicense)); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + SupportedCodecs_HardwareSecure) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // This test checks that the playready implementation works + // as expected with the command line overridden codecs which + // are supplied in the EncryptedMediaSupportedTypesPlayReadyHwSecureTest + // constructor. AV1 codecs are not supported in this test because + // they are marked as not supporting clear lead and the + // force_support_clear_lead flag has not been set (defaults to false). + + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoWebMMimeType, video_webm_codecs())); + EXPECT_UNSUPPORTED( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoMP4MimeType, video_mp4_codecs())); + EXPECT_UNSUPPORTED( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kAudioMP4MimeType, audio_mp4_codecs())); + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kAudioWebMMimeType, audio_webm_codecs())); + EXPECT_UNSUPPORTED( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kAudioMP4MimeType, audio_mp4_flac_codecs())); + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoWebMMimeType, vp9_profile2_codecs())); + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoMP4MimeType, vp9_profile2_codecs())); + EXPECT_UNSUPPORTED( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoMP4MimeType, av1_codecs())); +} + +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + SupportedCodecs_RecommendationDefault) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // This test checks that the playready implementation works + // as expected with the command line overridden codecs which + // are supplied in the EncryptedMediaSupportedTypesPlayReadyHwSecureTest + // constructor. AV1 codecs are not supported in this test because + // they are marked as not supporting clear lead and the + // force_support_clear_lead flag has not been set (defaults to false). + + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoWebMMimeType, + video_webm_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoMP4MimeType, + video_mp4_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kAudioMP4MimeType, + audio_mp4_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kAudioWebMMimeType, + audio_webm_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kAudioMP4MimeType, + audio_mp4_flac_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoWebMMimeType, + vp9_profile2_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoMP4MimeType, + vp9_profile2_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + EXPECT_UNSUPPORTED(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoMP4MimeType, av1_codecs(), + SessionType::kTemporary, kPlayReadyHardwareSecureRobustness)); +} + +#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION) +IN_PROC_BROWSER_TEST_F(EncryptedMediaSupportedTypesPlayReadyHwSecureTest, + DolbyVision) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoMP4MimeType, + dolby_vision_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoMP4MimeType, dolby_vision_codecs())); +} +#endif // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION) + +IN_PROC_BROWSER_TEST_F( + EncryptedMediaSupportedTypesPlayReadyHwSecureForceClearLeadSupportTest, + SupportedCodecs) { + SKIP_IF_WINDOWS_PLAYREADY_INCOMPATIBLE(); + + // This test checks that the playready implementation works as expected + // with the command line overridden codecs which are supplied in the + // EncryptedMediaSupportedTypesPlayReadyHwSecureForceClearLeadSupportTest + // constructor. These checks verify that the force_support_clear_lead + // HardwareSecureDecryption feature param works as expected with the + // playready implementation. For exammple, av1 is supported here + // even though it is marked as not supporting clear lead when setting + // up the command line overridden codecs. + + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoWebMMimeType, + video_webm_codecs(), SessionType::kTemporary, + kPlayReadyHardwareSecureRobustness)); + + EXPECT_SUCCESS(IsSupportedByKeySystem( + kPlayReadyKeySystemRecommendationDefault, kVideoMP4MimeType, av1_codecs(), + SessionType::kTemporary, kPlayReadyHardwareSecureRobustness)); + + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoWebMMimeType, video_webm_codecs())); + EXPECT_SUCCESS( + IsSupportedByKeySystem(kPlayReadyKeySystemRecommendationHWSecure, + kVideoMP4MimeType, av1_codecs())); +} +#endif // BUILDFLAG(ENABLE_PLAYREADY) + // // Misc failure test cases. //
diff --git a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_feed_bottom_sheet.xml b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_feed_bottom_sheet.xml index af2f256..77c1112 100644 --- a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_feed_bottom_sheet.xml +++ b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_feed_bottom_sheet.xml
@@ -23,21 +23,21 @@ android:src="@drawable/btn_back" app:tint="@macro/default_icon_color" android:contentDescription="@string/back" - app:layout_constraintTop_toTopOf="@id/feed_bottom_sheet_title" - app:layout_constraintBottom_toBottomOf="@id/feed_bottom_sheet_title" + app:layout_constraintTop_toTopOf="@id/bottom_sheet_title" + app:layout_constraintBottom_toBottomOf="@id/bottom_sheet_title" app:layout_constraintStart_toStartOf="parent" /> <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/feed_bottom_sheet_title" + android:id="@+id/bottom_sheet_title" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="start" android:textAppearance="@style/TextAppearance.Headline.Primary" android:text="@string/ntp_customization_feed_settings_title" - android:layout_marginStart="@dimen/ntp_customization_feed_margin_top" + android:layout_marginStart="@dimen/ntp_customization_ntp_cards_title_margin" android:layout_marginTop="@dimen/ntp_customization_back_button_margin" android:layout_marginEnd="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom" - app:layout_constraintStart_toEndOf="@id/back_button" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toStartOf="@id/learn_more_button" /> @@ -50,9 +50,9 @@ android:src="@drawable/ic_help_and_feedback" app:tint="@macro/default_icon_color" android:contentDescription="@string/learn_more" - app:layout_constraintTop_toTopOf="@id/feed_bottom_sheet_title" - app:layout_constraintBottom_toBottomOf="@id/feed_bottom_sheet_title" - app:layout_constraintStart_toEndOf="@id/feed_bottom_sheet_title" + app:layout_constraintTop_toTopOf="@id/bottom_sheet_title" + app:layout_constraintBottom_toBottomOf="@id/bottom_sheet_title" + app:layout_constraintStart_toEndOf="@id/bottom_sheet_title" app:layout_constraintEnd_toEndOf="parent" /> <LinearLayout @@ -61,7 +61,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom" android:layout_marginHorizontal="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom" - app:layout_constraintTop_toBottomOf="@id/feed_bottom_sheet_title" + app:layout_constraintTop_toBottomOf="@id/bottom_sheet_title" app:layout_constraintStart_toStartOf="parent" android:background="@drawable/ntp_customization_bottom_sheet_list_item_background_single">
diff --git a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml index 03bdb9f..13ab471 100644 --- a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml +++ b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml
@@ -33,10 +33,10 @@ android:gravity="start" android:textAppearance="@style/TextAppearance.Headline.Primary" android:text="@string/home_modules_configuration" - android:layout_marginStart="@dimen/ntp_customization_feed_margin_top" + android:layout_marginStart="@dimen/ntp_customization_ntp_cards_title_margin" android:layout_marginTop="@dimen/ntp_customization_back_button_margin" android:layout_marginEnd="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom" - app:layout_constraintStart_toEndOf="@id/back_button" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" />
diff --git a/chrome/browser/ntp_customization/java/res/values/dimens.xml b/chrome/browser/ntp_customization/java/res/values/dimens.xml index 90e49074..8d78f799 100644 --- a/chrome/browser/ntp_customization/java/res/values/dimens.xml +++ b/chrome/browser/ntp_customization/java/res/values/dimens.xml
@@ -17,6 +17,7 @@ <dimen name="ntp_customization_bottom_sheet_layout_padding_bottom">24dp</dimen> <dimen name="ntp_customization_ntp_cards_margin">20dp</dimen> <dimen name="ntp_customization_back_button_margin">36dp</dimen> + <dimen name="ntp_customization_ntp_cards_title_margin">64dp</dimen> <dimen name="ntp_customization_bottom_sheet_list_item_icon_size">24dp</dimen> <dimen name="ntp_customization_edit_icon_background_size">40dp</dimen> <dimen name="ntp_customization_feed_list_items_title_margin_top">40dp</dimen>
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java index 623057ff..68ec79f 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java
@@ -25,4 +25,14 @@ /** Handles back button clicks in the bottom sheet. */ void backPressOnCurrentBottomSheet(); + + /** + * Determines whether a bottom sheet should be displayed in a standalone mode, isolated from the + * navigation flow staring from the main bottom sheet. + * + * @return True if the bottom sheet should be shown by itself (i.e., without the main bottom + * sheet); False if it should be part of the full navigation flow starting from the main + * bottom sheet. + */ + boolean shouldShowAlone(); }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java index a96b93c..655e6a6 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java
@@ -7,18 +7,39 @@ import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER; import android.view.View; +import android.view.ViewGroup; import org.chromium.build.annotations.NullMarked; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; -/** Class responsible for binding a back press handler to the back button in a bottom sheet. */ +/** + * Class responsible for binding a back press handler to the back button or removing the back button + * in a bottom sheet. + */ @NullMarked public class BottomSheetViewBinder { public static void bind(PropertyModel model, View view, PropertyKey propertyKey) { if (propertyKey == BACK_PRESS_HANDLER) { View backButton = view.findViewById(R.id.back_button); backButton.setOnClickListener(model.get(BACK_PRESS_HANDLER)); + + // If the the handler is null, removes the back button from the bottom sheet. + if (model.get(BACK_PRESS_HANDLER) == null) { + backButton.setVisibility(View.GONE); + + View titleView = view.findViewById(R.id.bottom_sheet_title); + if (titleView != null) { + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) titleView.getLayoutParams(); + params.setMarginStart( + view.getResources() + .getDimensionPixelSize( + R.dimen + .ntp_customization_bottom_sheet_layout_padding_bottom)); + titleView.setLayoutParams(params); + } + } } } }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java index 30da11e..aa84d93 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java
@@ -45,6 +45,7 @@ private final Context mContext; private final Supplier<Profile> mProfileSupplier; + private final int mBottomSheetType; private NtpCustomizationMediator mMediator; private @MonotonicNonNull NtpCardsCoordinator mNtpCardsCoordinator; private @Nullable FeedSettingsCoordinator mFeedSettingsCoordinator; @@ -67,12 +68,24 @@ int NUM_ENTRIES = 2; } + /** + * @param context The Context used for displaying the bottom sheet. + * @param bottomSheetController A controller for managing the bottom sheet's lifecycle and + * behavior. + * @param profileSupplier A supplier for the profile, used to fetch the state of the feeds + * section. + * @param bottomSheetType The bottom sheet type to display independently. If set to `MAIN`, the + * main bottom sheet will be shown instead, enabling its full navigation flow, otherwise the + * bottom sheet of the bottomSheetType will show by itself. + */ public NtpCustomizationCoordinator( Context context, BottomSheetController bottomSheetController, - Supplier<Profile> profileSupplier) { + Supplier<Profile> profileSupplier, + @BottomSheetType int bottomSheetType) { mContext = context; mProfileSupplier = profileSupplier; + mBottomSheetType = bottomSheetType; View contentView = LayoutInflater.from(mContext) .inflate(R.layout.ntp_customization_bottom_sheet, /* root= */ null); @@ -83,21 +96,21 @@ // unexpectedly triggering the click listeners of its child list items. mViewFlipperView.setOnClickListener(v -> {}); - NtpCustomizationBottomSheetContent bottomSheetContent = - new NtpCustomizationBottomSheetContent( - contentView, - /* backPressRunnable= */ () -> mMediator.backPressOnCurrentBottomSheet(), - this::destroy, - () -> mMediator.getCurrentBottomSheetType()); + NtpCustomizationBottomSheetContent bottomSheetContent = initBottomSheetContent(contentView); // The containerPropertyModel is responsible for managing a BottomSheetDelegate which // provides list content and event handlers to a list container view in the bottom sheet. - View mainBottomSheetView = mViewFlipperView.findViewById(R.id.main_bottom_sheet); - PropertyModel containerPropertyModel = new PropertyModel(LIST_CONTAINER_KEYS); - PropertyModelChangeProcessor.create( - containerPropertyModel, - mainBottomSheetView.findViewById(R.id.ntp_customization_options_container), - BottomSheetListContainerViewBinder::bind); + PropertyModel containerPropertyModel = null; + // Skips creating property model for the main bottom sheet if only one bottom sheet + // should show. + if (mBottomSheetType == MAIN) { + View mainBottomSheetView = mViewFlipperView.findViewById(R.id.main_bottom_sheet); + containerPropertyModel = new PropertyModel(LIST_CONTAINER_KEYS); + PropertyModelChangeProcessor.create( + containerPropertyModel, + mainBottomSheetView.findViewById(R.id.ntp_customization_options_container), + BottomSheetListContainerViewBinder::bind); + } // The viewFlipperPropertyModel is responsible for controlling which bottom sheet layout to // display. @@ -118,16 +131,57 @@ mDelegate = createBottomSheetDelegate(); - // The click listener for each list item in the main bottom sheet should be registered - // before calling renderListContent(). - mMediator.registerClickListener(NTP_CARDS, getOptionClickListener(NTP_CARDS)); - mMediator.registerClickListener(FEED, getOptionClickListener(FEED)); - mMediator.renderListContent(); + // Skips creating main bottom sheet content if only one bottom sheet should show. + if (mBottomSheetType == MAIN) { + // The click listener for each list item in the main bottom sheet should be registered + // before calling renderListContent(). + mMediator.registerClickListener(NTP_CARDS, getOptionClickListener(NTP_CARDS)); + mMediator.registerClickListener(FEED, getOptionClickListener(FEED)); + mMediator.renderListContent(); + } } - /** Opens the NTP customization main bottom sheet. */ + @VisibleForTesting + NtpCustomizationBottomSheetContent initBottomSheetContent(View contentView) { + return new NtpCustomizationBottomSheetContent( + contentView, + mBottomSheetType == MAIN + ? () -> mMediator.backPressOnCurrentBottomSheet() + : () -> mMediator.dismissBottomSheet(), + this::destroy, + () -> mMediator.getCurrentBottomSheetType()); + } + + /** + * Opens the NTP customization bottom sheet. Depending on the value of `mStandAlonePage`, the + * function will either show a specific bottom sheet independently or show the main bottom sheet + * and enable a full navigation flow that begins from the main bottom sheet. + */ public void showBottomSheet() { - mMediator.showBottomSheet(MAIN); + switch (mBottomSheetType) { + case MAIN -> mMediator.showBottomSheet(MAIN); + case NTP_CARDS -> showNtpCardsBottomSheet(); + case FEED -> showFeedBottomSheet(); + default -> { + assert false : "Bottom sheet type not supported!"; + } + } + } + + private void showNtpCardsBottomSheet() { + if (mNtpCardsCoordinator == null) { + mNtpCardsCoordinator = new NtpCardsCoordinator(mContext, mDelegate); + } + mMediator.showBottomSheet(NTP_CARDS); + } + + private void showFeedBottomSheet() { + if (mFeedSettingsCoordinator == null) { + mFeedSettingsCoordinator = + new FeedSettingsCoordinator( + mContext, mDelegate, mProfileSupplier.get().getOriginalProfile()); + } + mMediator.showBottomSheet(FEED); } /** @@ -137,27 +191,16 @@ @VisibleForTesting View.OnClickListener getOptionClickListener(@BottomSheetType int type) { switch (type) { - case NTP_CARDS: - return v -> { - if (mNtpCardsCoordinator == null) { - mNtpCardsCoordinator = new NtpCardsCoordinator(mContext, mDelegate); - } - mMediator.showBottomSheet(NTP_CARDS); - }; - case FEED: - return v -> { - if (mFeedSettingsCoordinator == null) { - mFeedSettingsCoordinator = - new FeedSettingsCoordinator( - mContext, - mDelegate, - mProfileSupplier.get().getOriginalProfile()); - } - mMediator.showBottomSheet(FEED); - }; - default: + case NTP_CARDS -> { + return v -> showNtpCardsBottomSheet(); + } + case FEED -> { + return v -> showFeedBottomSheet(); + } + default -> { assert false : "Bottom sheet type not supported!"; return assumeNonNull(null); + } } } @@ -177,6 +220,11 @@ public void backPressOnCurrentBottomSheet() { mMediator.backPressOnCurrentBottomSheet(); } + + @Override + public boolean shouldShowAlone() { + return mBottomSheetType != MAIN; + } }; }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java index 969b387..36da5217 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java
@@ -55,8 +55,8 @@ private final BottomSheetObserver mBottomSheetObserver; private final PropertyModel mViewFlipperPropertyModel; private List<Integer> mListContent; - private final PropertyModel mContainerPropertyModel; private final Supplier<Profile> mProfileSupplier; + private final @Nullable PropertyModel mContainerPropertyModel; private @Nullable Profile mProfile; private @Nullable Integer mCurrentBottomSheet; private static @Nullable PrefService sPrefServiceForTest; @@ -65,7 +65,7 @@ BottomSheetController bottomSheetController, NtpCustomizationBottomSheetContent bottomSheetContent, PropertyModel viewFlipperPropertyModel, - PropertyModel containerPropertyModel, + @Nullable PropertyModel containerPropertyModel, Supplier<Profile> profileSupplier) { mBottomSheetController = bottomSheetController; mBottomSheetContent = bottomSheetContent; @@ -126,8 +126,7 @@ if (mCurrentBottomSheet == null) return; if (mCurrentBottomSheet == MAIN) { - mBottomSheetController.hideContent(mBottomSheetContent, true); - mCurrentBottomSheet = null; + dismissBottomSheet(); } else { showBottomSheet(MAIN); @@ -137,6 +136,12 @@ } } + /** Closes the entire bottom sheet view and returns to the New Tab Page. */ + void dismissBottomSheet() { + mBottomSheetController.hideContent(mBottomSheetContent, true); + mCurrentBottomSheet = null; + } + /** * Returns {@link ListContainerViewDelegate} that defines the content of each list item in the * main bottom sheet. @@ -195,6 +200,7 @@ /** Renders the options list in the main bottom sheet. */ void renderListContent() { + assumeNonNull(mContainerPropertyModel); mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, createListDelegate()); } @@ -241,6 +247,7 @@ * @param isFeedVisible True when the feed is visible to the user. */ void updateFeedSectionSubtitle(boolean isFeedVisible) { + assumeNonNull(mContainerPropertyModel); mContainerPropertyModel.set( MAIN_BOTTOM_SHEET_FEED_SECTION_SUBTITLE, isFeedVisible ? R.string.text_on : R.string.text_off);
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediator.java index 1de2abf..e685e4b 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediator.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediator.java
@@ -90,8 +90,14 @@ mListItemsContent = buildFeedListContent(); mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, createListDelegate()); + + // Hides the back button when the feed settings bottom sheet is displayed standalone. mBottomSheetPropertyModel.set( - BACK_PRESS_HANDLER, v -> mBottomSheetDelegate.backPressOnCurrentBottomSheet()); + BACK_PRESS_HANDLER, + delegate.shouldShowAlone() + ? null + : v -> mBottomSheetDelegate.backPressOnCurrentBottomSheet()); + if (mListItemsContent.isEmpty()) { mFeedSettingsPropertyModel.set(IS_FEED_LIST_ITEMS_TITLE_VISIBLE, false); }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediator.java index 0504ce2..791f3b5 100644 --- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediator.java +++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediator.java
@@ -40,8 +40,10 @@ mBottomSheetPropertyModel = bottomSheetPropertyModel; mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, createListDelegate()); + // Hides the back button when the NTP Cards bottom sheet is displayed standalone. mBottomSheetPropertyModel.set( - BACK_PRESS_HANDLER, v -> delegate.backPressOnCurrentBottomSheet()); + BACK_PRESS_HANDLER, + delegate.shouldShowAlone() ? null : v -> delegate.backPressOnCurrentBottomSheet()); } /** Returns {@link ListContainerViewDelegate} that defines the content of each list item. */
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java index 70f1d703..d6e0a42 100644 --- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java +++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java
@@ -4,19 +4,19 @@ package org.chromium.chrome.browser.ntp_customization; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER; -import android.app.Activity; import android.content.Context; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.ViewFlipper; import androidx.test.core.app.ApplicationProvider; -import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Rule; @@ -24,7 +24,6 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import org.robolectric.Robolectric; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.ui.modelutil.PropertyModel; @@ -38,33 +37,33 @@ private Context mContext; private View mContentView; private PropertyModel mPropertyModel; + private ViewFlipper mViewFlipper; @Before public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); - Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); - activity.setTheme(R.style.Theme_BrowserUI_DayNight); + mContext = + new ContextThemeWrapper( + ApplicationProvider.getApplicationContext(), + R.style.Theme_BrowserUI_DayNight); mContentView = LayoutInflater.from(mContext) .inflate(R.layout.ntp_customization_bottom_sheet, /* root= */ null); - activity.setContentView(mContentView); mPropertyModel = new PropertyModel(NtpCustomizationViewProperties.BOTTOM_SHEET_KEYS); + mViewFlipper = mContentView.findViewById(R.id.ntp_customization_view_flipper); } @Test - @SmallTest public void testBindOnNtpCardsBottomSheet() { // Adds the ntp cards bottom sheet to the view of the activity. - ViewFlipper viewFlipperView = - mContentView.findViewById(R.id.ntp_customization_view_flipper); View ntpCardsBottomSheet = LayoutInflater.from(mContext) .inflate( R.layout.ntp_customization_ntp_cards_bottom_sheet, - viewFlipperView, + mViewFlipper, true); - // Verifies the back press handler is added on the ntp cards bottom sheet. + // Verifies that the back back is not removed from the bottom sheet and the given listener + // is set when BACK_PRESS_HANDLER is not null. PropertyModelChangeProcessor.create( mPropertyModel, ntpCardsBottomSheet, BottomSheetViewBinder::bind); View.OnClickListener backPressHandler = mock(View.OnClickListener.class); @@ -72,5 +71,37 @@ View backButton = mContentView.findViewById(R.id.back_button); backButton.performClick(); verify(backPressHandler).onClick(backButton); + + // Verifies that the back button is removed from the bottom sheet when BACK_PRESS_HANDLER + // is set to null. + assertEquals(View.VISIBLE, backButton.getVisibility()); + mPropertyModel.set(BACK_PRESS_HANDLER, null); + assertEquals(View.GONE, backButton.getVisibility()); + } + + @Test + public void testBindOnFeedSettingsBottomSheet() { + // Adds the feed settings bottom sheet to the view of the activity. + View feedBottomSheet = + LayoutInflater.from(mContext) + .inflate(R.layout.ntp_customization_feed_bottom_sheet, mViewFlipper, true); + PropertyModelChangeProcessor.create( + mPropertyModel, feedBottomSheet, BottomSheetViewBinder::bind); + + // Verifies that the back back is not removed from the bottom sheet and the given listener + // is set when the given back press handler is not null. + PropertyModelChangeProcessor.create( + mPropertyModel, feedBottomSheet, BottomSheetViewBinder::bind); + View.OnClickListener backPressHandler = mock(View.OnClickListener.class); + mPropertyModel.set(BACK_PRESS_HANDLER, backPressHandler); + View backButton = mContentView.findViewById(R.id.back_button); + backButton.performClick(); + verify(backPressHandler).onClick(backButton); + + // Verifies that the back button is removed from the bottom sheet when BACK_PRESS_HANDLER + // is set to null. + assertEquals(View.VISIBLE, backButton.getVisibility()); + mPropertyModel.set(BACK_PRESS_HANDLER, null); + assertEquals(View.GONE, backButton.getVisibility()); } }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java index 61aa3b689..db9e5bd 100644 --- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java +++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java
@@ -4,11 +4,15 @@ package org.chromium.chrome.browser.ntp_customization; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.FEED; import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.MAIN; import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS; @@ -29,6 +33,7 @@ import org.chromium.base.supplier.Supplier; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.chrome.browser.ntp_customization.ntp_cards.NtpCardsCoordinator; +import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; /** Unit tests for {@link NtpCustomizationCoordinator} */ @@ -49,7 +54,7 @@ mContext = ApplicationProvider.getApplicationContext(); mNtpCustomizationCoordinator = new NtpCustomizationCoordinator( - mContext, mBottomSheetController, mock(Supplier.class)); + mContext, mBottomSheetController, mock(Supplier.class), MAIN); mNtpCustomizationCoordinator.setViewFlipperForTesting(mViewFlipper); mNtpCustomizationCoordinator.setMediatorForTesting(mMediator); } @@ -75,6 +80,63 @@ } @Test + public void testShouldShowAloneInBottomSheetDelegate() { + // Verifies that if being requested to show the main bottom sheet with its full navigation + // flow, shouldShowAlone() should return False. + assertFalse( + mNtpCustomizationCoordinator.getBottomSheetDelegateForTesting().shouldShowAlone()); + + // Verifies that if being requested to show the NTP Cards bottom sheet alone, + // shouldShowAlone returns True. + mNtpCustomizationCoordinator = + new NtpCustomizationCoordinator( + mContext, mBottomSheetController, mock(Supplier.class), NTP_CARDS); + assertTrue( + mNtpCustomizationCoordinator.getBottomSheetDelegateForTesting().shouldShowAlone()); + + // Verifies that if being requested to show the Feed settings bottom sheet alone, + // shouldShowAlone return True. + mNtpCustomizationCoordinator = + new NtpCustomizationCoordinator( + mContext, mBottomSheetController, mock(Supplier.class), FEED); + assertTrue( + mNtpCustomizationCoordinator.getBottomSheetDelegateForTesting().shouldShowAlone()); + } + + @Test + public void testInitBottomSheetContent() { + // Verifies that if the bottom sheet type is MAIN, backPressOnCurrentBottomSheet() is called + // to handle back presses. + mNtpCustomizationCoordinator = + new NtpCustomizationCoordinator( + mContext, mBottomSheetController, mock(Supplier.class), MAIN); + mNtpCustomizationCoordinator.setMediatorForTesting(mMediator); + BottomSheetContent bottomSheetContent = + mNtpCustomizationCoordinator.initBottomSheetContent(mView); + bottomSheetContent.handleBackPress(); + verify(mMediator).backPressOnCurrentBottomSheet(); + + // Verifies that if the bottom sheet type is not MAIN, dismissBottomSheet() is called + // to handle back presses. + mNtpCustomizationCoordinator = + new NtpCustomizationCoordinator( + mContext, mBottomSheetController, mock(Supplier.class), NTP_CARDS); + mNtpCustomizationCoordinator.setMediatorForTesting(mMediator); + bottomSheetContent = mNtpCustomizationCoordinator.initBottomSheetContent(mView); + bottomSheetContent.handleBackPress(); + verify(mMediator).dismissBottomSheet(); + + clearInvocations(mMediator); + mNtpCustomizationCoordinator = + new NtpCustomizationCoordinator( + mContext, mBottomSheetController, mock(Supplier.class), FEED); + mNtpCustomizationCoordinator.setMediatorForTesting(mMediator); + bottomSheetContent = mNtpCustomizationCoordinator.initBottomSheetContent(mView); + bottomSheetContent.handleBackPress(); + verify(mMediator).dismissBottomSheet(); + } + + @Test public void testGetOptionClickListener() { View.OnClickListener listener = mNtpCustomizationCoordinator.getOptionClickListener(NTP_CARDS);
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediatorUnitTest.java index dcbd076..03c300b 100644 --- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediatorUnitTest.java +++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/feed/FeedSettingsMediatorUnitTest.java
@@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -107,11 +108,6 @@ verify(mContainerPropertyModel) .set(eq(LIST_CONTAINER_VIEW_DELEGATE), any(ListContainerViewDelegate.class)); - verify(mBottomSheetPropertyModel) - .set(eq(BACK_PRESS_HANDLER), mBackPressHandlerCaptor.capture()); - mBackPressHandlerCaptor.getValue().onClick(mView); - verify(mDelegate).backPressOnCurrentBottomSheet(); - verify(mFeedSettingsPropertyModel).set(eq(IS_FEED_LIST_ITEMS_TITLE_VISIBLE), anyBoolean()); verify(mFeedSettingsPropertyModel) .set( @@ -123,6 +119,36 @@ } @Test + public void testBackPressHandler() { + // Verifies that when the feed settings bottom sheet should show alone, the back press + // handler should be set to null. + when(mDelegate.shouldShowAlone()).thenReturn(true); + new FeedSettingsMediator( + mContainerPropertyModel, + mBottomSheetPropertyModel, + mFeedSettingsPropertyModel, + mDelegate, + mProfile); + verify(mBottomSheetPropertyModel).set(BACK_PRESS_HANDLER, null); + + // Verifies that when the feed settings bottom sheet is part of the navigation flow starting + // from the main bottom sheet, and the back press handler should be set to + // backPressOnCurrentBottomSheet() + clearInvocations(mBottomSheetPropertyModel); + when(mDelegate.shouldShowAlone()).thenReturn(false); + new FeedSettingsMediator( + mContainerPropertyModel, + mBottomSheetPropertyModel, + mFeedSettingsPropertyModel, + mDelegate, + mProfile); + verify(mBottomSheetPropertyModel) + .set(eq(BACK_PRESS_HANDLER), mBackPressHandlerCaptor.capture()); + mBackPressHandlerCaptor.getValue().onClick(mView); + verify(mDelegate).backPressOnCurrentBottomSheet(); + } + + @Test public void testDestroy() { mFeedSettingsMediator.destroy(); verify(mPrefChangeRegistrar).removeObserver(Pref.ARTICLES_LIST_VISIBLE);
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediatorUnitTest.java index 08af8ef..404c8e1 100644 --- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediatorUnitTest.java +++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediatorUnitTest.java
@@ -7,7 +7,10 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.AUXILIARY_SEARCH; import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.DEFAULT_BROWSER_PROMO; @@ -24,7 +27,6 @@ import android.view.View; import androidx.test.core.app.ApplicationProvider; -import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Rule; @@ -66,18 +68,12 @@ } @Test - @SmallTest public void testConstructor() { verify(mContainerPropertyModel) .set(eq(LIST_CONTAINER_VIEW_DELEGATE), any(ListContainerViewDelegate.class)); - verify(mBottomSheetPropertyModel) - .set(eq(BACK_PRESS_HANDLER), mBackPressHandlerCaptor.capture()); - mBackPressHandlerCaptor.getValue().onClick(new View(mContext)); - verify(mDelegate).backPressOnCurrentBottomSheet(); } @Test - @SmallTest public void testListContainerViewDelegate() { ListContainerViewDelegate delegate = mNtpCardsMediator.createListDelegate(); HomeModulesConfigManager homeModulesConfigManager = HomeModulesConfigManager.getInstance(); @@ -106,7 +102,27 @@ } @Test - @SmallTest + public void testBackPressHandler() { + // Verifies that when the feed settings bottom sheet should show alone, the back press + // handler should be set to null. + when(mDelegate.shouldShowAlone()).thenReturn(true); + new NtpCardsMediator(mContainerPropertyModel, mBottomSheetPropertyModel, mDelegate); + verify(mBottomSheetPropertyModel).set(BACK_PRESS_HANDLER, null); + + // Verifies that when the feed settings bottom sheet is part of the navigation flow starting + // from the main bottom sheet, and the back press handler should be set to + // backPressOnCurrentBottomSheet() + View backButton = mock(View.class); + clearInvocations(mBottomSheetPropertyModel); + when(mDelegate.shouldShowAlone()).thenReturn(false); + new NtpCardsMediator(mContainerPropertyModel, mBottomSheetPropertyModel, mDelegate); + verify(mBottomSheetPropertyModel) + .set(eq(BACK_PRESS_HANDLER), mBackPressHandlerCaptor.capture()); + mBackPressHandlerCaptor.getValue().onClick(backButton); + verify(mDelegate).backPressOnCurrentBottomSheet(); + } + + @Test public void testDestroy() { mNtpCardsMediator.destroy();
diff --git a/chrome/browser/permissions/permission_settings_page_browsertest.cc b/chrome/browser/permissions/permission_settings_page_browsertest.cc index 4e8a685..f85a9ed 100644 --- a/chrome/browser/permissions/permission_settings_page_browsertest.cc +++ b/chrome/browser/permissions/permission_settings_page_browsertest.cc
@@ -35,7 +35,8 @@ "settings-basic-page", "settings-privacy-page", "settings-notifications-page", - "#notification-ask-radio-button"}; + "settings-category-default-radio-group", + "#enabledRadioOption"}; const WebContentsInteractionTestUtil::DeepQuery kQuietButton{ "settings-ui", @@ -67,7 +68,8 @@ "settings-basic-page", "settings-privacy-page", "settings-notifications-page", - "#notification-block"}; + "settings-category-default-radio-group", + "#disabledRadioOption"}; } // namespace
diff --git a/chrome/browser/prefs/BUILD.gn b/chrome/browser/prefs/BUILD.gn index 1842ec28..53029c0 100644 --- a/chrome/browser/prefs/BUILD.gn +++ b/chrome/browser/prefs/BUILD.gn
@@ -71,6 +71,7 @@ "//chrome/browser/profiles:profile", "//chrome/browser/profiles:profile_util", "//chrome/browser/search", + "//chrome/browser/serial", "//chrome/browser/sync", "//chrome/browser/task_manager", "//chrome/browser/ui/hats",
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn index 99de6b4..e6686e3f 100644 --- a/chrome/browser/profiles/BUILD.gn +++ b/chrome/browser/profiles/BUILD.gn
@@ -250,6 +250,7 @@ "//chrome/browser/safe_browsing:verdict_cache_manager_factory", "//chrome/browser/search_engine_choice", "//chrome/browser/search_engines", + "//chrome/browser/serial", "//chrome/browser/storage_access_api", "//chrome/browser/sync", "//chrome/browser/ui",
diff --git a/chrome/browser/resources/new_tab_page/app.ts b/chrome/browser/resources/new_tab_page/app.ts index 2073384..f9e942d8 100644 --- a/chrome/browser/resources/new_tab_page/app.ts +++ b/chrome/browser/resources/new_tab_page/app.ts
@@ -241,25 +241,25 @@ } protected accessor oneGoogleBarIframeOrigin_: string = OGB_IFRAME_ORIGIN; - protected accessor oneGoogleBarIframePath_: string; - protected accessor oneGoogleBarLoaded_: boolean; + protected accessor oneGoogleBarIframePath_: string|undefined; + protected accessor oneGoogleBarLoaded_: boolean = false; protected accessor theme_: Theme|undefined; - protected accessor showCustomize_: boolean; - protected accessor showCustomizeChromeText_: boolean; + protected accessor showCustomize_: boolean = false; + protected accessor showCustomizeChromeText_: boolean = false; protected accessor showWallpaperSearch_: boolean = false; - private accessor selectedCustomizeDialogPage_: string|null; + private accessor selectedCustomizeDialogPage_: string|null = null; protected accessor showVoiceSearchOverlay_: boolean = false; - protected accessor showBackgroundImage_: boolean; - protected accessor backgroundImageAttribution1_: string; - protected accessor backgroundImageAttribution2_: string; - protected accessor backgroundImageAttributionUrl_: string; - protected accessor backgroundColor_: SkColor|null; - protected accessor colorSourceIsBaseline: boolean; + protected accessor showBackgroundImage_: boolean = false; + protected accessor backgroundImageAttribution1_: string = ''; + protected accessor backgroundImageAttribution2_: string = ''; + protected accessor backgroundImageAttributionUrl_: string = ''; + protected accessor backgroundColor_: SkColor|null = null; + protected accessor colorSourceIsBaseline: boolean = false; protected accessor logoColor_: SkColor|null = null; - protected accessor singleColoredLogo_: boolean; - accessor realboxCanShowSecondarySide: boolean; - accessor realboxHadSecondarySide: boolean; - protected accessor realboxShown_: boolean; + protected accessor singleColoredLogo_: boolean = false; + accessor realboxCanShowSecondarySide: boolean = false; + accessor realboxHadSecondarySide: boolean = false; + protected accessor realboxShown_: boolean = false; protected accessor showLensUploadDialog_: boolean = false; protected accessor logoEnabled_: boolean = loadTimeData.getBoolean('logoEnabled'); @@ -273,19 +273,19 @@ loadTimeData.getBoolean('modulesEnabled'); private accessor middleSlotPromoLoaded_: boolean = false; private accessor modulesLoaded_: boolean = false; - protected accessor modulesShownToUser: boolean; + protected accessor modulesShownToUser: boolean = false; protected accessor microsoftModuleEnabled_: boolean = loadTimeData.getBoolean('microsoftModuleEnabled'); protected accessor microsoftAuthIframePath_: string = MSAL_IFRAME_ORIGIN; protected accessor promoAndModulesLoaded_: boolean = false; - protected accessor lazyRender_: boolean; + protected accessor lazyRender_: boolean = false; protected accessor scrolledToTop_: boolean = document.documentElement.scrollTop <= 0; protected accessor wallpaperSearchButtonAnimationEnabled_: boolean = loadTimeData.getBoolean('wallpaperSearchButtonAnimationEnabled'); protected accessor wallpaperSearchButtonEnabled_: boolean = loadTimeData.getBoolean('wallpaperSearchButtonEnabled'); - protected accessor showWallpaperSearchButton_: boolean; + protected accessor showWallpaperSearchButton_: boolean = false; private callbackRouter_: PageCallbackRouter; private pageHandler_: PageHandlerRemote; @@ -295,8 +295,8 @@ private setCustomizeChromeSidePanelVisibilityListener_: number|null = null; private setWallpaperSearchButtonVisibilityListener_: number|null = null; private eventTracker_: EventTracker = new EventTracker(); - private shouldPrintPerformance_: boolean; - private backgroundImageLoadStartEpoch_: number; + private shouldPrintPerformance_: boolean = false; + private backgroundImageLoadStartEpoch_: number = 0; private backgroundImageLoadStart_: number = 0; private showWebstoreToastListenerId_: number|null = null;
diff --git a/chrome/browser/resources/new_tab_page/doodle_share_dialog.ts b/chrome/browser/resources/new_tab_page/doodle_share_dialog.ts index c9314a2..27f3c315 100644 --- a/chrome/browser/resources/new_tab_page/doodle_share_dialog.ts +++ b/chrome/browser/resources/new_tab_page/doodle_share_dialog.ts
@@ -57,7 +57,7 @@ }; } - override accessor title: string; + override accessor title: string = ''; accessor url: Url = {url: ''}; protected onFacebookClick_() {
diff --git a/chrome/browser/resources/new_tab_page/iframe.ts b/chrome/browser/resources/new_tab_page/iframe.ts index 9f559b0f..c642523 100644 --- a/chrome/browser/resources/new_tab_page/iframe.ts +++ b/chrome/browser/resources/new_tab_page/iframe.ts
@@ -42,8 +42,8 @@ }; } - accessor allow: string; - accessor src: string; + accessor allow: string = ''; + accessor src: string = ''; // Sends message to iframe. postMessage(message: any) {
diff --git a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts index 05e4c17..bb2abaf 100644 --- a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts +++ b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts
@@ -146,12 +146,12 @@ }; } - protected accessor isHidden_: boolean; - protected accessor isError_: boolean; - protected accessor isNormalOrError_: boolean; - protected accessor isDragging_: boolean; - protected accessor isLoading_: boolean; - protected accessor isOffline_: boolean; + protected accessor isHidden_: boolean = false; + protected accessor isError_: boolean = false; + protected accessor isNormalOrError_: boolean = false; + protected accessor isDragging_: boolean = false; + protected accessor isLoading_: boolean = false; + protected accessor isOffline_: boolean = false; private accessor dialogState_ = DialogState.HIDDEN; private accessor lensErrorMessage_ = LensErrorMessage.NONE; private outsideHandlerAttached_ = false;
diff --git a/chrome/browser/resources/new_tab_page/logo.ts b/chrome/browser/resources/new_tab_page/logo.ts index 75c8259..b9161882 100644 --- a/chrome/browser/resources/new_tab_page/logo.ts +++ b/chrome/browser/resources/new_tab_page/logo.ts
@@ -84,23 +84,23 @@ accessor singleColored: boolean = false; accessor dark: boolean; - accessor backgroundColor: SkColor; - private accessor loaded_: boolean; - protected accessor doodle_: Doodle|null; - protected accessor imageDoodle_: ImageDoodle|null; - protected accessor showLogo_: boolean; - protected accessor showDoodle_: boolean; - private accessor doodleBoxed_: boolean; - protected accessor imageUrl_: string; + accessor backgroundColor: SkColor|null = null; + private accessor loaded_: boolean = false; + protected accessor doodle_: Doodle|null = null; + protected accessor imageDoodle_: ImageDoodle|null = null; + protected accessor showLogo_: boolean = false; + protected accessor showDoodle_: boolean = false; + private accessor doodleBoxed_: boolean = false; + protected accessor imageUrl_: string = ''; protected accessor showAnimation_: boolean = false; - protected accessor animationUrl_: string; - protected accessor iframeUrl_: string; + protected accessor animationUrl_: string = ''; + protected accessor iframeUrl_: string = ''; private accessor duration_: string; private accessor height_: string; private accessor width_: string; - protected accessor expanded_: boolean; - protected accessor showShareDialog_: boolean; - protected accessor imageDoodleTabIndex_: number; + protected accessor expanded_: boolean = false; + protected accessor showShareDialog_: boolean = false; + protected accessor imageDoodleTabIndex_: number = -1; private eventTracker_: EventTracker = new EventTracker(); private pageHandler_: PageHandlerRemote;
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/dummy/module.ts b/chrome/browser/resources/new_tab_page/modules/v2/dummy/module.ts index 8d3efd6..ec7da3e6 100644 --- a/chrome/browser/resources/new_tab_page/modules/v2/dummy/module.ts +++ b/chrome/browser/resources/new_tab_page/modules/v2/dummy/module.ts
@@ -50,8 +50,8 @@ }; } - accessor tiles: FooDataItem[]; - override accessor title: string; + accessor tiles: FooDataItem[] = []; + override accessor title: string = ''; protected getMenuItemGroups_(): MenuItem[][] { return [
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/file_suggestion.ts b/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/file_suggestion.ts index 014c6f9..1947db5 100644 --- a/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/file_suggestion.ts +++ b/chrome/browser/resources/new_tab_page/modules/v2/file_suggestion/file_suggestion.ts
@@ -41,7 +41,7 @@ } accessor files: File[] = []; - accessor moduleName: string; + accessor moduleName: string = ''; protected onFileClick_(e: Event) { const clickFileEvent = new Event('usage', {composed: true, bubbles: true}); @@ -50,11 +50,11 @@ const index = Number(currentTarget.dataset['index']); chrome.metricsPrivate.recordSmallCount( `NewTabPage.${this.moduleName}.FileClick`, index); - if (this.files[index].recommendationType != null) { + const recommendationType = this.files[index].recommendationType; + if (recommendationType != null) { chrome.metricsPrivate.recordEnumerationValue( `NewTabPage.${this.moduleName}.RecommendationTypeClick`, - this.files[index].recommendationType, - RecommendationType.MAX_VALUE + 1); + recommendationType, RecommendationType.MAX_VALUE + 1); } } }
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page.ts b/chrome/browser/resources/new_tab_page/new_tab_page.ts index cbe26b1..f4a81ea 100644 --- a/chrome/browser/resources/new_tab_page/new_tab_page.ts +++ b/chrome/browser/resources/new_tab_page/new_tab_page.ts
@@ -19,7 +19,6 @@ export {BrowserProxyImpl} from 'chrome://resources/js/metrics_reporter/browser_proxy.js'; export {MetricsReporterImpl} from 'chrome://resources/js/metrics_reporter/metrics_reporter.js'; export {getTrustedHTML} from 'chrome://resources/js/static_types.js'; -export {DomIf} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; export {AppElement, CUSTOMIZE_CHROME_BUTTON_ELEMENT_ID, NtpCustomizeChromeEntryPoint, NtpElement} from './app.js'; export {BackgroundManager} from './background_manager.js'; export {CustomizeDialogPage} from './customize_dialog_types.js';
diff --git a/chrome/browser/resources/new_tab_page/voice_search_overlay.ts b/chrome/browser/resources/new_tab_page/voice_search_overlay.ts index 715b0e5..3bab864 100644 --- a/chrome/browser/resources/new_tab_page/voice_search_overlay.ts +++ b/chrome/browser/resources/new_tab_page/voice_search_overlay.ts
@@ -206,8 +206,8 @@ }; } - protected accessor interimResult_: string; - protected accessor finalResult_: string; + protected accessor interimResult_: string = ''; + protected accessor finalResult_: string = ''; private accessor state_: State = State.UNINITIALIZED; private accessor error_: Error; protected accessor helpUrl_: string =
diff --git a/chrome/browser/resources/print_preview/data/document_info.ts b/chrome/browser/resources/print_preview/data/document_info.ts index 95c7d28..f347fc5b 100644 --- a/chrome/browser/resources/print_preview/data/document_info.ts +++ b/chrome/browser/resources/print_preview/data/document_info.ts
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js'; -import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {WebUiListenerMixinLit} from 'chrome://resources/cr_elements/web_ui_listener_mixin_lit.js'; +import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; import {Coordinate2d} from './coordinate2d.js'; import {Margins} from './margins.js'; @@ -47,7 +47,7 @@ }; } -const PrintPreviewDocumentInfoElementBase = WebUiListenerMixin(PolymerElement); +const PrintPreviewDocumentInfoElementBase = WebUiListenerMixinLit(CrLitElement); export class PrintPreviewDocumentInfoElement extends PrintPreviewDocumentInfoElementBase { @@ -55,17 +55,11 @@ return 'print-preview-document-info'; } - static get properties() { + static override get properties() { return { documentSettings: { type: Object, notify: true, - value: () => createDocumentSettings(), - }, - - inFlightRequestId: { - type: Number, - value: -1, }, margins: { @@ -81,9 +75,6 @@ pageSize: { type: Object, notify: true, - value() { - return new Size(612, 792); - }, }, /** @@ -92,18 +83,16 @@ printableArea: { type: Object, notify: true, - value() { - return new PrintableArea(new Coordinate2d(0, 0), new Size(612, 792)); - }, }, }; } - declare documentSettings: DocumentSettings; - declare inFlightRequestId: number; - declare margins: Margins; - declare pageSize: Size; - declare printableArea: PrintableArea; + accessor documentSettings: DocumentSettings = createDocumentSettings(); + inFlightRequestId: number = -1; + accessor margins: Margins; + accessor pageSize: Size = new Size(612, 792); + accessor printableArea: PrintableArea = + new PrintableArea(new Coordinate2d(0, 0), new Size(612, 792)); private isInitialized_: boolean = false; override connectedCallback() {
diff --git a/chrome/browser/resources/print_preview/data/state.ts b/chrome/browser/resources/print_preview/data/state.ts index be5d502..5ce180a 100644 --- a/chrome/browser/resources/print_preview/data/state.ts +++ b/chrome/browser/resources/print_preview/data/state.ts
@@ -3,7 +3,7 @@ // found in the LICENSE file. import {assert} from 'chrome://resources/js/assert.js'; -import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; export enum State { NOT_READY = 0, @@ -33,68 +33,81 @@ } -export class PrintPreviewStateElement extends PolymerElement { +export class PrintPreviewStateElement extends CrLitElement { static get is() { return 'print-preview-state'; } - static get properties() { + static override get properties() { return { - state: { - type: Number, - notify: true, - value: State.NOT_READY, - }, - error: { type: Number, notify: true, - value: Error.NONE, }, }; } - declare state: State; - declare error: Error; + private state_: State = State.NOT_READY; + accessor error: Error = Error.NONE; + + override connectedCallback() { + super.connectedCallback(); + this.sendStateChanged_(); + } + + private sendStateChanged_() { + this.dispatchEvent( + new CustomEvent('state-changed', {detail: {value: this.state_}})); + } transitTo(newState: State) { switch (newState) { case (State.NOT_READY): assert( - this.state === State.NOT_READY || this.state === State.READY || - this.state === State.ERROR); + this.state_ === State.NOT_READY || this.state_ === State.READY || + this.state_ === State.ERROR); break; case (State.READY): assert( - this.state === State.ERROR || this.state === State.NOT_READY || - this.state === State.PRINTING); + this.state_ === State.ERROR || this.state_ === State.NOT_READY || + this.state_ === State.PRINTING); break; case (State.PRINT_PENDING): - assert(this.state === State.READY); + assert(this.state_ === State.READY); break; case (State.HIDDEN): - assert(this.state === State.PRINT_PENDING); + assert(this.state_ === State.PRINT_PENDING); break; case (State.PRINTING): assert( - this.state === State.READY || this.state === State.HIDDEN || - this.state === State.PRINT_PENDING); + this.state_ === State.READY || this.state_ === State.HIDDEN || + this.state_ === State.PRINT_PENDING); break; case (State.SYSTEM_DIALOG): assert( - this.state !== State.HIDDEN && this.state !== State.PRINTING && - this.state !== State.CLOSING); + this.state_ !== State.HIDDEN && this.state_ !== State.PRINTING && + this.state_ !== State.CLOSING); break; case (State.ERROR): assert( - this.state === State.ERROR || this.state === State.NOT_READY || - this.state === State.READY); + this.state_ === State.ERROR || this.state_ === State.NOT_READY || + this.state_ === State.READY); break; case (State.CLOSING): - assert(this.state !== State.HIDDEN); + assert(this.state_ !== State.HIDDEN); break; } - this.state = newState; + + const oldState = this.state_; + this.state_ = newState; + + if (oldState !== newState) { + // Fire a manual 'state-changed' event to ensure that all states changes + // are reported, even if a state is changed twice in the same cycle, which + // wouldn't be the case if CrLitElement's 'notify: true' was used. + this.sendStateChanged_(); + } + if (newState !== State.ERROR && newState !== State.FATAL_ERROR) { this.error = Error.NONE; }
diff --git a/chrome/browser/resources/print_preview/ui/app.html b/chrome/browser/resources/print_preview/ui/app.html index 8f42091..4579e24f 100644 --- a/chrome/browser/resources/print_preview/ui/app.html +++ b/chrome/browser/resources/print_preview/ui/app.html
@@ -1,4 +1,4 @@ -<print-preview-state id="state" state="${this.state}" +<print-preview-state id="state" @state-changed="${this.onStateChanged_}" error="${this.error_}" @error-changed="${this.onErrorChanged_}"> </print-preview-state>
diff --git a/chrome/browser/resources/privacy_sandbox/BUILD.gn b/chrome/browser/resources/privacy_sandbox/BUILD.gn index ae0c2aca..d813153 100644 --- a/chrome/browser/resources/privacy_sandbox/BUILD.gn +++ b/chrome/browser/resources/privacy_sandbox/BUILD.gn
@@ -41,9 +41,12 @@ "base_dialog_app.html.ts", "base_dialog_app.ts", "base_dialog_browser_proxy.ts", + "topics_consent.html.ts", + "topics_consent.ts", ] css_files = [ + "base_dialog_app.css", "privacy_sandbox_privacy_policy_dialog.css", "shared_style.css", "shared_vars.css", @@ -61,7 +64,11 @@ webui_context_type = "trusted" mojo_files_deps = [ + "//chrome/browser/privacy_sandbox/notice:notice_mojom_ts__generator", "//chrome/browser/ui/webui/privacy_sandbox:mojo_bindings_ts__generator", ] - mojo_files = [ "$root_gen_dir/chrome/browser/ui/webui/privacy_sandbox/base_dialog.mojom-webui.ts" ] + mojo_files = [ + "$root_gen_dir/chrome/browser/ui/webui/privacy_sandbox/base_dialog.mojom-webui.ts", + "$root_gen_dir/chrome/browser/privacy_sandbox/notice/notice.mojom-webui.ts", + ] }
diff --git a/chrome/browser/resources/privacy_sandbox/base_dialog_app.css b/chrome/browser/resources/privacy_sandbox/base_dialog_app.css new file mode 100644 index 0000000..e197343 --- /dev/null +++ b/chrome/browser/resources/privacy_sandbox/base_dialog_app.css
@@ -0,0 +1,16 @@ +/* Copyright 2025 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * #scheme=relative + * #css_wrapper_metadata_end */ + +cr-view-manager { + display: block; +} + +cr-view-manager .active { + position: relative; +}
diff --git a/chrome/browser/resources/privacy_sandbox/base_dialog_app.html.ts b/chrome/browser/resources/privacy_sandbox/base_dialog_app.html.ts index f561ad2..97a217e 100644 --- a/chrome/browser/resources/privacy_sandbox/base_dialog_app.html.ts +++ b/chrome/browser/resources/privacy_sandbox/base_dialog_app.html.ts
@@ -5,12 +5,16 @@ import {html} from '//resources/lit/v3_0/lit.rollup.js'; import type {BaseDialogApp} from './base_dialog_app.js'; +import {PrivacySandboxNotice} from './notice.mojom-webui.js'; export function getHtml(this: BaseDialogApp) { return html` - <div>Base Dialog App Placeholder</div> - <cr-button id="closeButton" @click="${this.onCloseButton_}"> - Close - </cr-button> + <cr-view-manager id="viewManager"> + <topics-consent id="${ + this.getNoticeId(PrivacySandboxNotice.kTopicsConsentNotice)}" + slot="view" + fill-content> + </topics-consent> + </cr-view-manager> `; }
diff --git a/chrome/browser/resources/privacy_sandbox/base_dialog_app.ts b/chrome/browser/resources/privacy_sandbox/base_dialog_app.ts index e165aa1..3acf690 100644 --- a/chrome/browser/resources/privacy_sandbox/base_dialog_app.ts +++ b/chrome/browser/resources/privacy_sandbox/base_dialog_app.ts
@@ -4,18 +4,35 @@ import 'chrome://resources/cr_elements/cr_shared_style.css.js'; import 'chrome://resources/cr_elements/cr_button/cr_button.js'; +import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js'; +import './topics_consent.js'; +import '/strings.m.js'; import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; +import type {CrViewManagerElement} from 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import type {BaseDialogPageHandlerInterface} from './base_dialog.mojom-webui.js'; +import {getCss} from './base_dialog_app.css.js'; import {getHtml} from './base_dialog_app.html.js'; import {BaseDialogBrowserProxy} from './base_dialog_browser_proxy.js'; +import {PrivacySandboxNotice} from './notice.mojom-webui.js'; + +export interface BaseDialogApp { + $: { + viewManager: CrViewManagerElement, + }; +} export class BaseDialogApp extends CrLitElement { static get is() { return 'base-dialog-app'; } + static override get styles() { + return getCss(); + } + override render() { return getHtml.bind(this)(); } @@ -23,8 +40,24 @@ private handler_: BaseDialogPageHandlerInterface; override firstUpdated() { - this.handler_ = BaseDialogBrowserProxy.getInstance().getHandler(); - this.resizeAndShowNativeDialog(); + this.handler_ = BaseDialogBrowserProxy.getInstance().handler; + this.navigateToStep_( + loadTimeData.getInteger('noticeIdToShow') as PrivacySandboxNotice) + .then(() => this.resizeAndShowNativeDialog()); + } + + private navigateToStep_(step: PrivacySandboxNotice): Promise<void> { + return this.$.viewManager.switchView( + this.getNoticeId(step), 'fade-in', 'fade-out'); + } + + /** + * Converts a PrivacySandboxNotice enum value (number) into its corresponding + * string key/name (e.g., 0 becomes "kTopicsConsentNotice"). + * Used to generate unique string IDs for the different dialog steps. + */ + protected getNoticeId(step: PrivacySandboxNotice): string { + return PrivacySandboxNotice[step]; } private resizeAndShowNativeDialog(): Promise<void> { @@ -40,12 +73,6 @@ resolve(); }); } - - // TODO(crbug.com/398005782): Temporary callback to close the dialog for - // manual testing. - protected onCloseButton_() { - this.handler_.closeDialog(); - } } declare global {
diff --git a/chrome/browser/resources/privacy_sandbox/base_dialog_browser_proxy.ts b/chrome/browser/resources/privacy_sandbox/base_dialog_browser_proxy.ts index a87e932..bdab7be9 100644 --- a/chrome/browser/resources/privacy_sandbox/base_dialog_browser_proxy.ts +++ b/chrome/browser/resources/privacy_sandbox/base_dialog_browser_proxy.ts
@@ -6,7 +6,7 @@ import {BaseDialogPageHandler} from './base_dialog.mojom-webui.js'; export class BaseDialogBrowserProxy { - private handler: BaseDialogPageHandlerInterface; + handler: BaseDialogPageHandlerInterface; constructor() { this.handler = BaseDialogPageHandler.getRemote(); @@ -19,10 +19,6 @@ static getInstance(): BaseDialogBrowserProxy { return instance || (instance = new BaseDialogBrowserProxy()); } - - getHandler(): BaseDialogPageHandlerInterface { - return this.handler; - } } let instance: BaseDialogBrowserProxy|null = null;
diff --git a/chrome/browser/resources/privacy_sandbox/topics_consent.html.ts b/chrome/browser/resources/privacy_sandbox/topics_consent.html.ts new file mode 100644 index 0000000..5de52965 --- /dev/null +++ b/chrome/browser/resources/privacy_sandbox/topics_consent.html.ts
@@ -0,0 +1,16 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {TopicsConsent} from './topics_consent.js'; + +export function getHtml(this: TopicsConsent) { + return html` + <div>Topics Consent Placeholder</div> + <cr-button id="consentButton" @click="${this.onConsentButton_}"> + Consent Placeholder + </cr-button> + `; +}
diff --git a/chrome/browser/resources/privacy_sandbox/topics_consent.ts b/chrome/browser/resources/privacy_sandbox/topics_consent.ts new file mode 100644 index 0000000..dacb9135 --- /dev/null +++ b/chrome/browser/resources/privacy_sandbox/topics_consent.ts
@@ -0,0 +1,39 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://resources/cr_elements/cr_button/cr_button.js'; + +import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {BaseDialogPageHandlerInterface} from './base_dialog.mojom-webui.js'; +import {BaseDialogBrowserProxy} from './base_dialog_browser_proxy.js'; +import {getHtml} from './topics_consent.html.js'; + +export class TopicsConsent extends CrLitElement { + static get is() { + return 'topics-consent'; + } + + override render() { + return getHtml.bind(this)(); + } + + private handler_: BaseDialogPageHandlerInterface; + + override firstUpdated() { + this.handler_ = BaseDialogBrowserProxy.getInstance().handler; + } + + protected onConsentButton_() { + this.handler_.closeDialog(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'topics-consent': TopicsConsent; + } +} + +customElements.define(TopicsConsent.is, TopicsConsent);
diff --git a/chrome/browser/resources/side_panel/read_anything/app.html.ts b/chrome/browser/resources/side_panel/read_anything/app.html.ts index f6a1aa84..dfca4b2 100644 --- a/chrome/browser/resources/side_panel/read_anything/app.html.ts +++ b/chrome/browser/resources/side_panel/read_anything/app.html.ts
@@ -25,10 +25,11 @@ @voice-language-toggle="${this.onVoiceLanguageToggle_}" @preview-voice="${this.onPreviewVoice_}" @voice-menu-close="${this.onVoiceMenuClose_}" + @voice-menu-open="${this.onVoiceMenuOpen_}" @play-pause-click="${this.onPlayPauseClick_}" @font-size-change="${this.onFontSizeChange_}" @font-change="${this.onFontChange_}" - @rate-change="${this.resetSpeechPostSettingChange_}" + @rate-change="${this.onSpeechRateChange_}" @next-granularity-click="${this.playNextGranularity_}" @previous-granularity-click="${this.playPreviousGranularity_}" @links-toggle="${this.updateLinks_}"
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts index 09e1c82..673bc76 100644 --- a/chrome/browser/resources/side_panel/read_anything/app.ts +++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -31,8 +31,6 @@ import {WordBoundaries} from './read_aloud/word_boundaries.js'; import {ReadAnythingLogger, TimeFrom} from './read_anything_logger.js'; import type {ReadAnythingToolbarElement} from './read_anything_toolbar.js'; -import type {SpeechBrowserProxy} from './speech_browser_proxy.js'; -import {SpeechBrowserProxyImpl} from './speech_browser_proxy.js'; import {VoiceNotificationManager} from './voice_notification_manager.js'; const AppElementBase = WebUiListenerMixinLit(CrLitElement); @@ -128,7 +126,6 @@ private notificationManager_ = VoiceNotificationManager.getInstance(); private logger_: ReadAnythingLogger = ReadAnythingLogger.getInstance(); private styleUpdater_: AppStyleUpdater; - private speech_: SpeechBrowserProxy = SpeechBrowserProxyImpl.getInstance(); private highlighter_: ReadAloudHighlighter = ReadAloudHighlighter.getInstance(); private wordBoundaries_: WordBoundaries = WordBoundaries.getInstance(); @@ -191,7 +188,6 @@ // Clear state. We don't do this in disconnectedCallback because that's // not always reliabled called. - this.speech_.cancel(); this.hasContent_ = false; this.nodeStore_.clearDomNodes(); } @@ -252,14 +248,7 @@ this.$.containerParent.onscroll = () => { chrome.readingMode.onScroll(this.scrollingOnSelection_); this.scrollingOnSelection_ = false; - - // If the reading mode panel was scrolled while read aloud is speaking, - // we should disable autoscroll if the highlights are no longer visible, - // and we should re-enable autoscroll if the highlights are now - // visible. - if (this.speechController_.isSpeechActive()) { - this.highlighter_.updateAutoScroll(); - } + this.speechController_.onScroll(); }; // Pass copy commands to main page. Copy commands will not work if they are @@ -478,7 +467,6 @@ this.emptyStateSubheading_ = ''; this.hasContent_ = false; if (this.isReadAloudEnabled_) { - this.speech_.cancel(); this.speechController_.clearReadAloudState(); } } @@ -498,7 +486,6 @@ const previousSpeechPlayingState = {...this.speechController_.getState()}; const previousWordBoundaryState = {...this.wordBoundaries_.state}; - this.speech_.cancel(); this.speechController_.clearReadAloudState(); const container = this.$.container; @@ -782,17 +769,16 @@ this.speechController_.previewVoice(event.detail.previewVoice); } - protected onVoiceMenuClose_( - event: CustomEvent<{voicePlayingWhenMenuOpened: boolean}>) { + protected onVoiceMenuOpen_(event: CustomEvent) { event.preventDefault(); event.stopPropagation(); + this.speechController_.onVoiceMenuOpen(); + } - // TODO: crbug.com/323912186 - Handle when menu is closed mid-preview and - // the user presses play/pause button. - if (!this.speechController_.isSpeechActive() && - event.detail.voicePlayingWhenMenuOpened) { - this.playSpeech(); - } + protected onVoiceMenuClose_(event: CustomEvent) { + event.preventDefault(); + event.stopPropagation(); + this.speechController_.onVoiceMenuClose(); } protected onPlayPauseClick_() { @@ -828,10 +814,6 @@ this.previewVoicePlaying_ = this.speechController_.getPreviewVoicePlaying(); } - onSpeechRateChange(): void { - this.resetSpeechPostSettingChange_(); - } - onEnabledLangsChange(): void { this.enabledLangs_ = this.voicePackController_.getEnabledLangs(); } @@ -844,7 +826,7 @@ onCurrentVoiceChange(): void { this.selectedVoice_ = this.voicePackController_.getCurrentVoice(); - this.resetSpeechPostSettingChange_(); + this.speechController_.onSpeechSettingsChange(); } protected playNextGranularity_() { @@ -855,12 +837,6 @@ this.speechController_.playPreviousGranularity(); } - playSpeech() { - const container = this.$.container; - this.speechController_.playSpeech( - this.getSelection(), container.textContent); - } - private getSelectedIds(): { anchorNodeId: number|undefined, anchorOffset: number, @@ -893,18 +869,7 @@ event: CustomEvent<{selectedVoice: SpeechSynthesisVoice}>) { event.preventDefault(); event.stopPropagation(); - - const currentVoice = this.voicePackController_.getCurrentVoice(); - this.voicePackController_.setUserPreferredVoice(event.detail.selectedVoice); - - // If the locales are identical, the voices are likely from the same - // voice pack and use the same TTS engine, therefore, we don't need - // to reset the word boundary state. - if (currentVoice?.lang.toLowerCase() !== - event.detail.selectedVoice.lang.toLowerCase()) { - this.wordBoundaries_.resetToDefaultState( - /*possibleWordBoundarySupportChange=*/ true); - } + this.speechController_.onVoiceSelected(event.detail.selectedVoice); } protected onVoiceLanguageToggle_(event: CustomEvent<{language: string}>) { @@ -913,12 +878,8 @@ this.voicePackController_.onLanguageToggle(event.detail.language); } - protected resetSpeechPostSettingChange_() { - // If speech was playing when a setting was changed, continue playing - // speech - if (this.speechController_.onSpeechSettingsChange()) { - this.playSpeech(); - } + protected onSpeechRateChange_() { + this.speechController_.onSpeechSettingsChange(); } private restoreSettingsFromPrefs_() {
diff --git a/chrome/browser/resources/side_panel/read_anything/common.ts b/chrome/browser/resources/side_panel/read_anything/common.ts index 146bcd2..d064568 100644 --- a/chrome/browser/resources/side_panel/read_anything/common.ts +++ b/chrome/browser/resources/side_panel/read_anything/common.ts
@@ -31,6 +31,8 @@ PLAY_PREVIEW = 'preview-voice', LANGUAGE_MENU_OPEN = 'language-menu-open', LANGUAGE_MENU_CLOSE = 'language-menu-close', + VOICE_MENU_OPEN = 'voice-menu-open', + VOICE_MENU_CLOSE = 'voice-menu-close', } // The user settings stored in preferences and restored on re-opening Reading
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts index 571a3815..4050d28 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts
@@ -26,7 +26,6 @@ onIsAudioCurrentlyPlayingChange(): void; onEngineStateChange(): void; onPreviewVoicePlaying(): void; - onSpeechRateChange(): void; } export class SpeechController { @@ -164,22 +163,37 @@ // If the screen is locked during speech, we should stop speaking. onLockScreen() { if (this.isSpeechActive()) { - this.stopSpeech(PauseActionSource.DEFAULT); + this.stopSpeech_(PauseActionSource.DEFAULT); } } - onSpeechSettingsChange(): boolean { + onVoiceSelected(selectedVoice: SpeechSynthesisVoice) { + const currentVoice = this.voicePackController_.getCurrentVoice(); + this.voicePackController_.setUserPreferredVoice(selectedVoice); + + // If the locales are identical, the voices are likely from the same + // voice pack and use the same TTS engine, therefore, we don't need + // to reset the word boundary state. + if (currentVoice?.lang.toLowerCase() !== selectedVoice.lang.toLowerCase()) { + this.wordBoundaries_.resetToDefaultState( + /*possibleWordBoundarySupportChange=*/ true); + } + } + + onSpeechSettingsChange(): void { // Don't call stopSpeech() if the speech tree hasn't been initialized or // if speech hasn't been triggered yet. if (!this.isSpeechTreeInitialized() || !this.hasSpeechBeenTriggered()) { - return false; + return; } - const playSpeechOnChange = this.isSpeechActive(); + const resumeSpeechOnChange = this.isSpeechActive(); // Cancel the queued up Utterance using the old speech settings - this.stopSpeech(PauseActionSource.VOICE_SETTINGS_CHANGE); - return playSpeechOnChange; + this.stopSpeech_(PauseActionSource.VOICE_SETTINGS_CHANGE); + if (resumeSpeechOnChange) { + this.resumeSpeech_(null); + } } onHighlightGranularityChange(newGranularity: number) { @@ -195,14 +209,14 @@ onPlayPauseToggle(selection: Selection|null, textContent: string|null) { if (this.isSpeechActive()) { - this.stopSpeech(PauseActionSource.BUTTON_CLICK); + this.stopSpeech_(PauseActionSource.BUTTON_CLICK); } else { - this.playSpeech(selection, textContent); + this.playSpeech_(selection, textContent); this.model_.setPlaySessionStartTime(Date.now()); } } - playSpeech(selection: Selection|null, textContent: string|null) { + private playSpeech_(selection: Selection|null, textContent: string|null) { if (this.hasSpeechBeenTriggered() && !this.isSpeechActive()) { this.resumeSpeech_(selection); } else { @@ -518,7 +532,7 @@ // is not supported by the synthesizer. Since we're only setting the // speech rate, update the speech rate to the WebSpeech default of 1. chrome.readingMode.onSpeechRateChange(1); - this.listeners_.forEach(l => l.onSpeechRateChange()); + this.onSpeechSettingsChange(); return; } @@ -527,7 +541,7 @@ // something went wrong. // TODO: crbug.com/40927698 - Consider showing an error message. this.logger_.logSpeechStopSource(chrome.readingMode.engineErrorStopSource); - this.stopSpeech(PauseActionSource.DEFAULT); + this.stopSpeech_(PauseActionSource.DEFAULT); // No appropriate voice is available for the language designated in // SpeechSynthesisUtterance lang. @@ -561,7 +575,7 @@ return utteranceText; } - stopSpeech(pauseSource: PauseActionSource) { + private stopSpeech_(pauseSource: PauseActionSource) { this.setIsSpeechActive(false); this.setIsAudioCurrentlyPlaying(false); this.model_.setPauseSource(pauseSource); @@ -640,7 +654,7 @@ } previewVoice(previewVoice: SpeechSynthesisVoice|null) { - this.stopSpeech(PauseActionSource.VOICE_PREVIEW); + this.stopSpeech_(PauseActionSource.VOICE_PREVIEW); // If there's no previewVoice, return after stopping the current preview if (!previewVoice) { @@ -676,6 +690,19 @@ this.speakWithDefaults_(utterance); } + onVoiceMenuOpen() { + this.model_.setResumeSpeechOnVoiceMenuClose(this.isSpeechActive()); + } + + onVoiceMenuClose() { + // TODO: crbug.com/323912186 - Handle when menu is closed mid-preview and + // the user presses play/pause button. + if (!this.isSpeechActive() && + this.model_.getResumeSpeechOnVoiceMenuClose()) { + this.resumeSpeech_(null); + } + } + private onSpeechInterrupted_() { // SpeechSynthesis.cancel() was called, which could have originated // either within or outside of reading mode. If it originated from @@ -692,7 +719,7 @@ // updated. this.logger_.logSpeechStopSource( chrome.readingMode.engineInterruptStopSource); - this.stopSpeech(PauseActionSource.ENGINE_INTERRUPT); + this.stopSpeech_(PauseActionSource.ENGINE_INTERRUPT); } } @@ -704,9 +731,19 @@ this.logSpeechPlaySession_(); } + onScroll() { + // If the reading mode panel was scrolled while read aloud is speaking, + // we should disable autoscroll if the highlights are no longer visible, + // and we should re-enable autoscroll if the highlights are now + // visible. + if (this.isSpeechActive()) { + this.highlighter_.updateAutoScroll(); + } + } + clearReadAloudState() { + this.speech_.cancel(); this.reset(); - this.model_.setFirstTextNode(null); this.highlighter_.clearHighlightFormatting(); this.wordBoundaries_.resetToDefaultState(); }
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts index fccc43e3..7868df8 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_model.ts
@@ -75,6 +75,8 @@ // has been set. This is null if the id has not been set. private firstTextNodeSetForReadAloud_: number|null = null; + private resumeSpeechOnVoiceMenuClose_: boolean = false; + reset(): void { this.speechPlayingState_ = { isSpeechTreeInitialized: false, @@ -86,6 +88,16 @@ }; this.speechEngineState_ = SpeechEngineState.NONE; this.previewVoicePlaying_ = null; + this.firstTextNodeSetForReadAloud_ = null; + this.resumeSpeechOnVoiceMenuClose_ = false; + } + + getResumeSpeechOnVoiceMenuClose(): boolean { + return this.resumeSpeechOnVoiceMenuClose_; + } + + setResumeSpeechOnVoiceMenuClose(shouldResume: boolean) { + this.resumeSpeechOnVoiceMenuClose_ = shouldResume; } getFirstTextNode(): number|null {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html.ts index c111138..af23350 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html.ts
@@ -78,7 +78,6 @@ .availableVoices="${this.availableVoices}" .enabledLangs="${this.enabledLangs}" .localeToDisplayName="${this.localeToDisplayName}" - .isSpeechActive="${this.isSpeechActive}" .previewVoicePlaying="${this.previewVoicePlaying}"> </voice-selection-menu> <cr-icon-button class="toolbar-button" id="highlight" tabindex="-1"
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts index 680026e..3de45bb 100644 --- a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts +++ b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
@@ -83,7 +83,6 @@ previewVoicePlaying: {type: Object}, currentNotifications_: {type: Object}, previewVoiceInitiated: {type: Object}, - isSpeechActive: {type: Boolean}, localeToDisplayName: {type: Object}, showLanguageMenuDialog_: {type: Boolean}, downloadingMessages_: {type: Boolean}, @@ -96,7 +95,6 @@ accessor previewVoicePlaying: SpeechSynthesisVoice|null = null; accessor enabledLangs: string[] = []; accessor availableVoices: SpeechSynthesisVoice[] = []; - accessor isSpeechActive: boolean = false; // The current notifications that should be used in the voice menu. private accessor currentNotifications_: @@ -108,7 +106,6 @@ protected accessor voiceGroups_: VoiceDropdownGroup[] = []; protected accessor showLanguageMenuDialog_: boolean = false; - private voicePlayingWhenMenuOpened_: boolean = false; private readonly spBodyPadding_ = Number.parseInt( window.getComputedStyle(document.body) .getPropertyValue('--sp-body-padding'), @@ -155,7 +152,6 @@ } onVoiceSelectionMenuClick(targetElement: HTMLElement) { - this.voicePlayingWhenMenuOpened_ = this.isSpeechActive; this.notificationManager_.addListener(this); const menu = this.$.voiceSelectionMenu.get(); @@ -166,6 +162,7 @@ // Scroll to the selected voice. requestAnimationFrame(() => { + this.fire(ToolbarEvent.VOICE_MENU_OPEN); const selectedItem = menu.querySelector<HTMLElement>('.item-invisible-false'); selectedItem?.scrollIntoViewIfNeeded(); @@ -297,13 +294,7 @@ protected onClose_() { this.notificationManager_.removeListener(this); this.currentNotifications_ = {}; - this.dispatchEvent(new CustomEvent('voice-menu-close', { - bubbles: true, - composed: true, - detail: { - voicePlayingWhenMenuOpened: this.voicePlayingWhenMenuOpened_, - }, - })); + this.fire(ToolbarEvent.VOICE_MENU_CLOSE); } private shouldAllowPropagation_(
diff --git a/chrome/browser/resources/tab_search/split_view/app.css b/chrome/browser/resources/tab_search/split_view/app.css index dfb556a..35cf26c 100644 --- a/chrome/browser/resources/tab_search/split_view/app.css +++ b/chrome/browser/resources/tab_search/split_view/app.css
@@ -15,7 +15,7 @@ height: 100vh; } -.header { +#header { display: flex; flex-direction: column; } @@ -48,7 +48,7 @@ .tab-list:not([hidden]) { display: flex; - min-height: 16px; + max-height: calc(100% - 92px - 4 * 16px); background: var(--color-tab-search-background); border-radius: 16px; margin: 16px auto;
diff --git a/chrome/browser/resources/tab_search/split_view/app.html.ts b/chrome/browser/resources/tab_search/split_view/app.html.ts index c23dd548..df5088c 100644 --- a/chrome/browser/resources/tab_search/split_view/app.html.ts +++ b/chrome/browser/resources/tab_search/split_view/app.html.ts
@@ -8,7 +8,7 @@ export function getHtml(this: SplitNewTabPageAppElement) { return html`<!--_html_template_start_--> -<div class="header"> +<div id="header"> <cr-icon-button id="closeButton" iron-icon="tab-search:close" @click="${this.onClose_}"> @@ -23,6 +23,7 @@ <cr-lazy-list id="splitTabsList" class="scroller" .items="${this.allInvisibleTabs_}" item-size="66" + .minViewportHeight="${this.minViewportHeight_}" .scrollTarget="${this.scrollTarget_}" @keydown="${this.onTabClick_}" @viewport-filled="${this.updateFocusedItem_}"
diff --git a/chrome/browser/resources/tab_search/split_view/app.ts b/chrome/browser/resources/tab_search/split_view/app.ts index 755fc7d..650e34c4 100644 --- a/chrome/browser/resources/tab_search/split_view/app.ts +++ b/chrome/browser/resources/tab_search/split_view/app.ts
@@ -25,6 +25,7 @@ export interface SplitNewTabPageAppElement { $: { + header: HTMLElement, splitTabsList: CrLazyListElement, }; } @@ -48,6 +49,7 @@ scrollTarget_: {type: Object}, focusedIndex_: {type: Number}, focusedItem_: {type: Object}, + minViewportHeight_: {type: Number}, }; } @@ -55,6 +57,7 @@ protected accessor scrollTarget_: HTMLElement|null = null; protected accessor focusedIndex_: number = -1; protected accessor focusedItem_: HTMLElement|null = null; + protected accessor minViewportHeight_: number = 0; protected title_: string = ''; private activeTabId_: number = -1; private apiProxy_: TabSearchApiProxy = TabSearchApiProxyImpl.getInstance(); @@ -88,6 +91,7 @@ this.scrollTarget_ = this.$.splitTabsList; this.apiProxy_.getProfileData().then(({profileData}) => { + this.updateViewportHeight_(profileData); this.onTabsChanged_(profileData); }); } @@ -208,6 +212,12 @@ private redirectToNtp_() { window.location.replace(loadTimeData.getString('newTabPageUrl')); } + + private updateViewportHeight_(profileData: ProfileData) { + const activeWindow = profileData.windows.find(({active}) => active)!; + this.minViewportHeight_ = + activeWindow ? activeWindow.height - this.$.header.offsetHeight : 0; + } } declare global {
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_browsertest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_browsertest.cc index 61d13d4..b0d7fe1 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_browsertest.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_browsertest.cc
@@ -8,8 +8,8 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/enterprise/connectors/analysis/content_analysis_features.h" #include "chrome/browser/enterprise/connectors/connectors_service.h" -#include "chrome/browser/enterprise/connectors/test/management_context_mixin.h" -#include "chrome/browser/enterprise/connectors/test/test_constants.h" +#include "chrome/browser/enterprise/test/management_context_mixin.h" +#include "chrome/browser/enterprise/test/test_constants.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" @@ -25,7 +25,7 @@ constexpr char kTestAccessToken[] = "test_access_token"; struct ManagementContextDeviceRequest { - enterprise_connectors::test::ManagementContext context; + enterprise::test::ManagementContext context; bool profile_request; }; @@ -71,7 +71,7 @@ TestCloudBinaryUploadService( scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, Profile* profile, - enterprise_connectors::test::ManagementContext management_context, + enterprise::test::ManagementContext management_context, enterprise_connectors::AnalysisConnector connector, bool profile_request) : CloudBinaryUploadService(url_loader_factory, profile), @@ -112,7 +112,7 @@ } private: - enterprise_connectors::test::ManagementContext management_context_; + enterprise::test::ManagementContext management_context_; bool profile_request_; }; @@ -138,13 +138,12 @@ public testing::WithParamInterface<ManagementContextDeviceRequest> { public: CloudBinaryUploadServiceRequestValidationBrowserTest() - : management_mixin_( - enterprise_connectors::test::ManagementContextMixin::Create( - &mixin_host_, - this, - management_context())) {} + : management_mixin_(enterprise::test::ManagementContextMixin::Create( + &mixin_host_, + this, + management_context())) {} - enterprise_connectors::test::ManagementContext management_context() const { + enterprise::test::ManagementContext management_context() const { return GetParam().context; } @@ -180,9 +179,9 @@ std::string dm_token() { if (profile_request()) { - return enterprise_connectors::test::kProfileDmToken; + return enterprise::test::kProfileDmToken; } else { - return enterprise_connectors::test::kDeviceDmToken; + return enterprise::test::kDeviceDmToken; } } @@ -193,8 +192,7 @@ protected: enterprise_connectors::AnalysisConnector connector_ = enterprise_connectors::AnalysisConnector::ANALYSIS_CONNECTOR_UNSPECIFIED; - std::unique_ptr<enterprise_connectors::test::ManagementContextMixin> - management_mixin_; + std::unique_ptr<enterprise::test::ManagementContextMixin> management_mixin_; base::test::ScopedFeatureList scoped_feature_list_; };
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc deleted file mode 100644 index 79c25b0..0000000 --- a/chrome/browser/send_tab_to_self/send_tab_to_self_util_unittest.cc +++ /dev/null
@@ -1,62 +0,0 @@ -// Copyright 2019 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h" - -#include <memory> - -#include "base/functional/bind.h" -#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/test/base/browser_with_test_window_test.h" -#include "components/send_tab_to_self/send_tab_to_self_sync_service.h" -#include "components/send_tab_to_self/test_send_tab_to_self_model.h" -#include "content/public/test/navigation_simulator.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -namespace send_tab_to_self { - -namespace { - -// A fake that's always ready to offer send-tab-to-self. -class FakeSendTabToSelfSyncService : public SendTabToSelfSyncService { - public: - FakeSendTabToSelfSyncService() = default; - ~FakeSendTabToSelfSyncService() override = default; - - std::optional<EntryPointDisplayReason> GetEntryPointDisplayReason( - const GURL&) override { - return EntryPointDisplayReason::kOfferFeature; - } -}; - -std::unique_ptr<KeyedService> BuildFakeSendTabToSelfSyncService( - content::BrowserContext*) { - return std::make_unique<FakeSendTabToSelfSyncService>(); -} - -class SendTabToSelfUtilTest : public BrowserWithTestWindowTest { - public: - void SetUp() override { - BrowserWithTestWindowTest::SetUp(); - - AddTab(browser(), GURL("about:blank")); - } - - TestingProfile::TestingFactories GetTestingFactories() override { - return {TestingProfile::TestingFactory{ - SendTabToSelfSyncServiceFactory::GetInstance(), - base::BindRepeating(&BuildFakeSendTabToSelfSyncService)}}; - } - - content::WebContents* web_contents() { - return browser()->tab_strip_model()->GetActiveWebContents(); - } -}; - -} // namespace - -} // namespace send_tab_to_self
diff --git a/chrome/browser/serial/BUILD.gn b/chrome/browser/serial/BUILD.gn index e122e51..3c57eee2 100644 --- a/chrome/browser/serial/BUILD.gn +++ b/chrome/browser/serial/BUILD.gn
@@ -3,11 +3,27 @@ # found in the LICENSE file. source_set("serial") { - sources = [ "serial_chooser_context.h" ] + sources = [ + "chrome_serial_delegate.h", + "serial_blocklist.h", + "serial_chooser_context.h", + "serial_chooser_context_factory.h", + "serial_chooser_histograms.h", + "serial_policy_allowed_ports.h", + "web_serial_chooser.h", + ] + + if (!is_android) { + sources += [ "web_serial_chooser_desktop.h" ] + } else { + sources += [ "android/web_serial_chooser_android.h" ] + } public_deps = [ "//base", + "//chrome/browser/profiles:profile", "//components/permissions", + "//components/prefs", "//content/public/browser", "//mojo/public/cpp/bindings", "//services/device/public/mojom",
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java index f6222f9..46ad4b4 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java
@@ -30,7 +30,8 @@ import org.chromium.chrome.browser.FileProviderHelper; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.components.browser_ui.share.ClipboardImageFileProvider; import org.chromium.ui.base.Clipboard; @@ -44,7 +45,8 @@ @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class ClipboardImageFileProviderTest { @Rule - public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); + public FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); private static final long WAIT_TIMEOUT_SECONDS = 30L; private static final String TEST_PNG_IMAGE_FILE_EXTENSION = ".png"; @@ -79,7 +81,7 @@ bitmap.compress(Bitmap.CompressFormat.PNG, /*quality = (0-100) */ 100, baos); mTestImageData = baos.toByteArray(); - mActivityTestRule.startMainActivityFromLauncher(); + mActivityTestRule.startFromLauncher(); FileProviderUtils.setFileProviderUtil(new FileProviderHelper()); }
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/BitmapGeneratorTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/BitmapGeneratorTest.java index 8aa6e07..1d2febdd 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/BitmapGeneratorTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/BitmapGeneratorTest.java
@@ -28,7 +28,9 @@ import org.chromium.chrome.browser.paint_preview.PaintPreviewCompositorUtils; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.page.WebPageStation; import org.chromium.components.paintpreview.player.CompositorStatus; import org.chromium.net.test.EmbeddedTestServer; @@ -37,21 +39,22 @@ @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class BitmapGeneratorTest { @Rule - public final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + public final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private Tab mTab; private BitmapGenerator mGenerator; private boolean mBitmapCreated; + private WebPageStation mInitialPage; @Before public void setUp() throws Exception { EmbeddedTestServer testServer = mActivityTestRule.getTestServer(); final String url = testServer.getURL("/chrome/test/data/android/about.html"); - mActivityTestRule.startMainActivityWithURL(url); - mTab = mActivityTestRule.getActivity().getActivityTab(); + mInitialPage = mActivityTestRule.startOnUrl(url); + mTab = mInitialPage.loadedTabElement.get(); } @After
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceTest.java index 7ad9509..45d8bee6 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceTest.java
@@ -24,18 +24,21 @@ import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.page.WebPageStation; /** Tests for the Paint Preview Tab Manager. */ @RunWith(ChromeJUnit4ClassRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class LongScreenshotsTabServiceTest { @Rule - public final ChromeTabbedActivityTestRule mActivityTestRule = - new ChromeTabbedActivityTestRule(); + public final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + private WebPageStation mInitialPage; private Tab mTab; private LongScreenshotsTabService mLongScreenshotsTabService; private TestCaptureProcessor mProcessor; @@ -67,9 +70,12 @@ @Before public void setUp() throws Exception { - mActivityTestRule.startMainActivityWithURL( - mActivityTestRule.getTestServer().getURL("/chrome/test/data/android/about.html")); - mTab = mActivityTestRule.getActivity().getActivityTab(); + mInitialPage = + mActivityTestRule.startOnUrl( + mActivityTestRule + .getTestServer() + .getURL("/chrome/test/data/android/about.html")); + mTab = mInitialPage.loadedTabElement.get(); mProcessor = new TestCaptureProcessor(); ThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetTest.java index 005018f..5842740 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetTest.java
@@ -24,12 +24,9 @@ import org.junit.Assert; import org.junit.Before; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; import org.chromium.base.ContextUtils; import org.chromium.base.ThreadUtils; @@ -43,8 +40,10 @@ import org.chromium.chrome.browser.profiles.ProfileManager; import org.chromium.chrome.browser.share.ShareHistoryBridge; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.R; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.page.WebPageStation; import org.chromium.chrome.test.util.MenuUtils; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport; @@ -64,13 +63,13 @@ @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE) @Batch(Batch.PER_CLASS) public class ShareSheetTest { - @ClassRule - public static final ChromeTabbedActivityTestRule sActivityTestRule = - new ChromeTabbedActivityTestRule(); + @Rule + public FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); private Profile mProfile; private List<ResolveInfo> mAvailableResolveInfos; + private WebPageStation mPage; // foo.bar.baz -> baz private String labelFromPackageName(String packageName) { @@ -152,7 +151,7 @@ ContextUtils.initApplicationContextForTests( new PackageManagerReplacingContext(ContextUtils.getApplicationContext(), this)); - sActivityTestRule.startMainActivityOnBlankPage(); + mPage = mActivityTestRule.startOnBlankPage(); ThreadUtils.runOnUiThreadBlocking( () -> { mProfile = ProfileManager.getLastUsedRegularProfile(); @@ -164,11 +163,11 @@ private void openShareSheet() { MenuUtils.invokeCustomMenuActionSync( InstrumentationRegistry.getInstrumentation(), - sActivityTestRule.getActivity(), + mActivityTestRule.getActivity(), R.id.share_menu_id); BottomSheetController controller = - sActivityTestRule + mActivityTestRule .getActivity() .getRootUiCoordinatorForTesting() .getBottomSheetController(); @@ -232,7 +231,7 @@ */ private List<String> getShown3PTargets() { BottomSheetController controller = - sActivityTestRule + mActivityTestRule .getActivity() .getRootUiCoordinatorForTesting() .getBottomSheetController();
diff --git a/chrome/browser/single_tab/android/java/res/layout/single_tab_module_layout.xml b/chrome/browser/single_tab/android/java/res/layout/single_tab_module_layout.xml index 829093e..527f56d4 100644 --- a/chrome/browser/single_tab/android/java/res/layout/single_tab_module_layout.xml +++ b/chrome/browser/single_tab/android/java/res/layout/single_tab_module_layout.xml
@@ -85,10 +85,12 @@ android:layout_marginStart="@dimen/single_tab_thumbnail_margin" android:layout_marginEnd="@dimen/single_tab_thumbnail_margin" android:layout_alignParentStart="true" + android:layout_alignParentBottom="true" + android:layout_alignParentTop="true" android:adjustViewBounds="false" android:gravity="center_horizontal" android:importantForAccessibility="no" - android:scaleType="fitCenter" + android:scaleType="matrix" app:cornerRadiusBottomEnd="@dimen/single_tab_thumbnail_radius" app:cornerRadiusBottomStart="@dimen/single_tab_thumbnail_radius" app:cornerRadiusTopEnd="@dimen/single_tab_thumbnail_radius" @@ -100,7 +102,7 @@ android:layout_height="24dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" - android:layout_alignBottom="@id/tab_thumbnail" + android:layout_alignParentBottom="true" android:layout_alignEnd="@id/tab_thumbnail" android:paddingTop="@dimen/favicon_padding" android:paddingBottom="@dimen/favicon_padding"
diff --git a/chrome/browser/single_tab/android/java/src/org/chromium/chrome/browser/single_tab/SingleTabView.java b/chrome/browser/single_tab/android/java/src/org/chromium/chrome/browser/single_tab/SingleTabView.java index e301a38..35dc7ff 100644 --- a/chrome/browser/single_tab/android/java/src/org/chromium/chrome/browser/single_tab/SingleTabView.java +++ b/chrome/browser/single_tab/android/java/src/org/chromium/chrome/browser/single_tab/SingleTabView.java
@@ -11,7 +11,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; -import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.TextView; @@ -68,7 +67,6 @@ resources.getQuantityString( R.plurals.home_modules_tab_resumption_title, 1)); } - mTabThumbnail.setScaleType(ScaleType.MATRIX); mTabThumbnail.updateThumbnailPlaceholder( /* isIncognito= */ false, /* isSelected= */ false); }
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.cc b/chrome/browser/speech/on_device_speech_recognition_impl.cc index 16077071..89625b7 100644 --- a/chrome/browser/speech/on_device_speech_recognition_impl.cc +++ b/chrome/browser/speech/on_device_speech_recognition_impl.cc
@@ -6,6 +6,7 @@ #include "base/rand_util.h" #include "base/strings/string_util.h" +#include "base/task/single_thread_task_runner.h" #include "base/task/task_runner.h" #include "base/time/time.h" #include "chrome/browser/browser_process.h" @@ -14,9 +15,6 @@ #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_types.h" -#include "components/language/core/browser/language_prefs.h" -#include "components/language/core/browser/pref_names.h" -#include "components/language/core/common/locale_util.h" #include "components/prefs/pref_service.h" #include "components/soda/constants.h" #include "components/soda/pref_names.h" @@ -28,8 +26,6 @@ #include "media/mojo/mojom/speech_recognizer.mojom.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" -#include "net/base/network_change_notifier.h" -#include "ui/base/l10n/l10n_util.h" #if !BUILDFLAG(IS_ANDROID) #include "components/soda/soda_util.h" @@ -118,14 +114,6 @@ return; } - if (!CanInstallWithoutUserConsent(language_config.value().language_name)) { - std::move(callback).Run(false); - - // TODO(crbug.com/40286514): Prompt the user for permission to download - // language pack. - return; - } - base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(&OnDeviceSpeechRecognitionImpl::InstallLanguageInternal, @@ -151,12 +139,7 @@ OnDeviceSpeechRecognitionImpl::OnDeviceSpeechRecognitionImpl( content::RenderFrameHost* frame_host) - : content::DocumentUserData<OnDeviceSpeechRecognitionImpl>(frame_host), - pref_service_(Profile::FromBrowserContext( - frame_host->GetProcess()->GetBrowserContext()) - ->GetPrefs()), - language_prefs_( - std::make_unique<language::LanguagePrefs>(pref_service_)) { + : content::DocumentUserData<OnDeviceSpeechRecognitionImpl>(frame_host) { #if !BUILDFLAG(IS_ANDROID) speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance(); if (soda_installer) { @@ -165,27 +148,6 @@ #endif // !BUILDFLAG(IS_ANDROID) } -bool OnDeviceSpeechRecognitionImpl::CanInstallWithoutUserConsent( - const std::string& language) { - net::NetworkChangeNotifier::ConnectionType connection_type = - net::NetworkChangeNotifier::GetConnectionType(); - if (connection_type != net::NetworkChangeNotifier::CONNECTION_ETHERNET && - connection_type != net::NetworkChangeNotifier::CONNECTION_WIFI) { - return false; - } - - std::vector<std::string> accept_languages; - language_prefs_->GetAcceptLanguagesList(&accept_languages); - for (const auto& accept_language : accept_languages) { - if (l10n_util::GetLanguage(base::ToLowerASCII(accept_language)) == - l10n_util::GetLanguage(base::ToLowerASCII(language))) { - return true; - } - } - - return false; -} - bool OnDeviceSpeechRecognitionImpl:: CanRenderFrameHostUseOnDeviceSpeechRecognition() { if (render_frame_host().GetStoragePartition() !=
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.h b/chrome/browser/speech/on_device_speech_recognition_impl.h index 1873067..a934a65 100644 --- a/chrome/browser/speech/on_device_speech_recognition_impl.h +++ b/chrome/browser/speech/on_device_speech_recognition_impl.h
@@ -19,16 +19,10 @@ #include "components/soda/soda_installer.h" #endif // !BUILDFLAG(IS_ANDROID) -class PrefService; - namespace content { class RenderFrameHost; } // namespace content -namespace language { -class LanguagePrefs; -} // namespace language - namespace speech { class OnDeviceSpeechRecognitionImpl @@ -70,10 +64,6 @@ friend class content::DocumentUserData<OnDeviceSpeechRecognitionImpl>; explicit OnDeviceSpeechRecognitionImpl(content::RenderFrameHost* frame_host); - // Returns whether or not a given language pack can be installed without - // explicit user consent. - bool CanInstallWithoutUserConsent(const std::string& language); - // Returns whether the render frame host can use on-device speech recognition. // HTTP(s) origins not scoped to the default storage partition may not use // on-device speech recognition. @@ -107,9 +97,6 @@ language_installation_callbacks_; #endif // !BUILDFLAG(IS_ANDROID) - raw_ptr<PrefService> pref_service_; - std::unique_ptr<language::LanguagePrefs> language_prefs_; - mojo::Receiver<media::mojom::OnDeviceSpeechRecognition> receiver_{this}; base::WeakPtrFactory<OnDeviceSpeechRecognitionImpl> weak_ptr_factory_{this};
diff --git a/chrome/browser/sync/test/integration/sync_service_impl_harness.cc b/chrome/browser/sync/test/integration/sync_service_impl_harness.cc index f532c28..6771a19b 100644 --- a/chrome/browser/sync/test/integration/sync_service_impl_harness.cc +++ b/chrome/browser/sync/test/integration/sync_service_impl_harness.cc
@@ -43,16 +43,12 @@ #include "services/network/public/mojom/fetch_api.mojom-shared.h" #include "third_party/zlib/google/compression_utils.h" -#if BUILDFLAG(IS_ANDROID) -#include "chrome/browser/sync/test/integration/sync_test_utils_android.h" -#endif // BUILDFLAG(IS_ANDROID) +namespace { using syncer::SyncCycleSnapshot; using syncer::SyncServiceImpl; -const char* kSyncUrlClearServerDataKey = "sync-url-clear-server-data"; - -namespace { +constexpr char kSyncUrlClearServerDataKey[] = "sync-url-clear-server-data"; bool HasAuthError(SyncServiceImpl* service) { return service->GetAuthError().state() == @@ -228,12 +224,8 @@ } GaiaId SyncServiceImplHarness::GetGaiaIdForDefaultTestAccount() const { -#if !BUILDFLAG(IS_ANDROID) + CHECK_EQ(signin_type_, SigninType::FAKE_SIGNIN); return signin::GetTestGaiaIdForEmail(username_); -#else // !BUILDFLAG(IS_ANDROID) - // TODO(crbug.com/40165479): pass `username_` once supported. - return sync_test_utils_android::GetGaiaIdForDefaultTestAccount(); -#endif // !BUILDFLAG(IS_ANDROID) } bool SyncServiceImplHarness::SignInPrimaryAccount(
diff --git a/chrome/browser/sync/test/integration/sync_signin_delegate_android.cc b/chrome/browser/sync/test/integration/sync_signin_delegate_android.cc index 0b44ca79..96c0643f 100644 --- a/chrome/browser/sync/test/integration/sync_signin_delegate_android.cc +++ b/chrome/browser/sync/test/integration/sync_signin_delegate_android.cc
@@ -4,23 +4,25 @@ #include "chrome/browser/sync/test/integration/sync_signin_delegate_android.h" +#include "base/notreached.h" #include "chrome/browser/sync/test/integration/sync_test_utils_android.h" void SyncSigninDelegateAndroid::SigninFake(Profile* profile, const std::string& username, signin::ConsentLevel consent_level) { - // TODO(crbug.com/1117345,crbug.com/1169662): Pass `username` to Java. switch (consent_level) { case signin::ConsentLevel::kSignin: - sync_test_utils_android::SetUpAccountAndSignInForTesting(); - break; + sync_test_utils_android::SetUpFakeAccountAndSignInForTesting(username); + return; case signin::ConsentLevel::kSync: - // TODO(crbug.com/1117345,crbug.com/1169662): Ideally (for consistency + // TODO(crbug.com/1117345,crbug.com/40165479): Ideally (for consistency // with desktop), this should sign in an account with ConsentLevel::kSync, // but *not* actually enable Sync-the-feature. - sync_test_utils_android::SetUpAccountAndSignInAndEnableSyncForTesting(); - break; + sync_test_utils_android::SetUpFakeAccountAndSignInAndEnableSyncForTesting( + username); + return; } + NOTREACHED(); } bool SyncSigninDelegateAndroid::SigninUI(Profile* profile, @@ -31,13 +33,13 @@ case signin::ConsentLevel::kSignin: sync_test_utils_android::SetUpLiveAccountAndSignInForTesting(username, password); - break; + return true; case signin::ConsentLevel::kSync: sync_test_utils_android::SetUpLiveAccountAndSignInAndEnableSyncForTesting( username, password); - break; + return true; } - return true; + NOTREACHED(); } bool SyncSigninDelegateAndroid::ConfirmSyncUI(Profile* profile) {
diff --git a/chrome/browser/sync/test/integration/sync_test_utils_android.cc b/chrome/browser/sync/test/integration/sync_test_utils_android.cc index 3ac0722..59cea923 100644 --- a/chrome/browser/sync/test/integration/sync_test_utils_android.cc +++ b/chrome/browser/sync/test/integration/sync_test_utils_android.cc
@@ -23,6 +23,8 @@ #include "components/saved_tab_groups/public/android/tab_group_sync_conversions_bridge.h" #include "components/saved_tab_groups/public/types.h" #include "components/signin/public/identity_manager/account_info.h" +#include "components/signin/public/identity_manager/identity_test_utils.h" +#include "google_apis/gaia/core_account_id.h" #include "google_apis/gaia/gaia_id.h" #include "url/android/gurl_android.h" #include "url/gurl.h" @@ -33,39 +35,36 @@ namespace sync_test_utils_android { -void SetUpAccountAndSignInForTesting() { +namespace { + +AccountInfo GetFakeAccountInfo(const std::string& username) { + AccountInfo account_info; + account_info.email = username; + account_info.gaia = signin::GetTestGaiaIdForEmail(username); + account_info.account_id = CoreAccountId::FromGaiaId(account_info.gaia); + return signin::WithGeneratedUserInfo(account_info, /*given_name=*/"Fake"); +} + +} // namespace + +void SetUpFakeAccountAndSignInForTesting(const std::string& username) { base::RunLoop run_loop; base::ThreadPool::PostTask( FROM_HERE, {base::MayBlock()}, base::BindLambdaForTesting([&]() { Java_SyncTestSigninUtils_setUpAccountAndSignInForTesting( - base::android::AttachCurrentThread()); + base::android::AttachCurrentThread(), GetFakeAccountInfo(username)); run_loop.Quit(); })); run_loop.Run(); } -GaiaId GetGaiaIdForDefaultTestAccount() { - base::RunLoop run_loop; - GaiaId result; - base::ThreadPool::PostTask( - FROM_HERE, {base::MayBlock()}, base::BindLambdaForTesting([&]() { - auto j_gaia_id = - Java_SyncTestSigninUtils_getGaiaIdForDefaultTestAccount( - base::android::AttachCurrentThread()); - result = ConvertFromJavaGaiaId(base::android::AttachCurrentThread(), - j_gaia_id); - run_loop.Quit(); - })); - run_loop.Run(); - return result; -} - -void SetUpAccountAndSignInAndEnableSyncForTesting() { +void SetUpFakeAccountAndSignInAndEnableSyncForTesting( + const std::string& username) { base::RunLoop run_loop; base::ThreadPool::PostTask( FROM_HERE, {base::MayBlock()}, base::BindLambdaForTesting([&]() { Java_SyncTestSigninUtils_setUpAccountAndSignInAndEnableSyncForTesting( - base::android::AttachCurrentThread()); + base::android::AttachCurrentThread(), GetFakeAccountInfo(username)); run_loop.Quit(); })); run_loop.Run();
diff --git a/chrome/browser/sync/test/integration/sync_test_utils_android.h b/chrome/browser/sync/test/integration/sync_test_utils_android.h index d4d7986a6..a6f62211 100644 --- a/chrome/browser/sync/test/integration/sync_test_utils_android.h +++ b/chrome/browser/sync/test/integration/sync_test_utils_android.h
@@ -19,14 +19,12 @@ namespace sync_test_utils_android { // Sets up the test account and signs in synchronously. -void SetUpAccountAndSignInForTesting(); - -// Returns GaiaId for the default test account on Android. -GaiaId GetGaiaIdForDefaultTestAccount(); +void SetUpFakeAccountAndSignInForTesting(const std::string& username); // Sets up the test account, signs in, and enables Sync-the-feature // synchronously. -void SetUpAccountAndSignInAndEnableSyncForTesting(); +void SetUpFakeAccountAndSignInAndEnableSyncForTesting( + const std::string& username); // Signs out and clears the primary account. void SignOutForTesting();
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalToRemoteTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalToRemoteTest.java index d8d3c06..849f529 100644 --- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalToRemoteTest.java +++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncLocalToRemoteTest.java
@@ -88,12 +88,13 @@ String secondTabUrl = ChromeTabUtils.getUrlStringOnUiThread(secondTab); RegularTabSwitcherStation tabSwitcher = secondPage.openRegularTabSwitcher(); - TabSwitcherListEditorFacility editor = tabSwitcher.openAppMenu().clickSelectTabs(); + TabSwitcherListEditorFacility<RegularTabSwitcherStation> editor = + tabSwitcher.openAppMenu().clickSelectTabs(); editor = editor.addTabToSelection(0, firstTabId); editor = editor.addTabToSelection(1, secondTabId); String title = "test_tab_group_name"; - NewTabGroupDialogFacility dialog = + NewTabGroupDialogFacility<RegularTabSwitcherStation> dialog = editor.openAppMenuWithEditor().groupTabs(); dialog = dialog.inputName(title); dialog = dialog.pickColor(TabGroupColorId.RED);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 5f24980..146ca15 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1785,7 +1785,6 @@ "//chrome/browser/ui/webui/top_chrome:impl", "//chrome/browser/ui/webui/util", "//chrome/browser/ui/webui/whats_new", - "//chrome/browser/ui/webui/whats_new", "//chrome/browser/ui/webui/whats_new:impl", "//chrome/browser/ui/window_sizer", "//chrome/browser/ui/window_sizer:impl",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/ChromeAutocompleteSchemeClassifier.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/ChromeAutocompleteSchemeClassifier.java index 2f022d7..7f0bc2f 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/ChromeAutocompleteSchemeClassifier.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/ChromeAutocompleteSchemeClassifier.java
@@ -32,7 +32,7 @@ // If mLifetimeAssert is GC'ed before this is called, it will throw an exception // with a stack trace showing the stack during LifetimeAssert.create(). - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } @NativeMethods
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java index 65f685d..c49cf5e 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -605,7 +605,9 @@ LoadUrlParams loadUrlParams = new LoadUrlParams(url); try (TimingMetric record = TimingMetric.shortUptime("Android.Omnibox.SetGeolocationHeadersTime")) { - loadUrlParams.setVerbatimHeaders(GeolocationHeader.getGeoHeader(url, currentTab)); + loadUrlParams.setVerbatimHeaders( + GeolocationHeader.getGeoHeader( + url, mProfileSupplier.get(), mTemplateUrlServiceSupplier.get())); } loadUrlParams.setTransitionType( omniboxLoadUrlParams.transitionType | PageTransition.FROM_ADDRESS_BAR);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java index 1095588..86cceb52 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java
@@ -30,7 +30,7 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.components.browser_ui.site_settings.PermissionInfo; import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge; import org.chromium.components.content_settings.ContentSettingValues; @@ -39,6 +39,7 @@ import org.chromium.components.embedder_support.util.UrlUtilitiesJni; import org.chromium.components.omnibox.OmniboxFeatures; import org.chromium.components.search_engines.TemplateUrlService; +import org.chromium.url.GURL; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -177,17 +178,28 @@ private static boolean isGeoHeaderEnabledForDse( Profile profile, TemplateUrlService templateService) { - return geoHeaderStateForUrl(profile, templateService.getUrlForSearchQuery(DUMMY_URL_QUERY)) + return geoHeaderStateForUrl( + profile, + templateService, + templateService.getUrlForSearchQuery(DUMMY_URL_QUERY)) == HeaderState.HEADER_ENABLED; } - private static @HeaderState int geoHeaderStateForUrl(Profile profile, String url) { + private static @HeaderState int geoHeaderStateForUrl( + Profile profile, @Nullable TemplateUrlService service, String url) { try (TraceEvent e = TraceEvent.scoped("GeolocationHeader.geoHeaderStateForUrl")) { + // Only send X-Geo to search engines associated with the current profile. + if (profile == null || service == null) return HeaderState.UNSUITABLE_URL; + // Only send X-Geo in normal mode. if (profile.isOffTheRecord()) return HeaderState.INCOGNITO; - // Only send X-Geo header to Google domains. - if (!UrlUtilitiesJni.get().isGoogleSearchUrl(url)) return HeaderState.UNSUITABLE_URL; + // Only send X-Geo header to Search Engines. + var isDseUrl = service.isSearchResultsPageFromDefaultSearchProvider(new GURL(url)); + var isGoogleDse = service.isDefaultSearchEngineGoogle(); + if (!(isDseUrl || (isGoogleDse && UrlUtilitiesJni.get().isGoogleSearchUrl(url)))) { + return HeaderState.UNSUITABLE_URL; + } Uri uri = Uri.parse(url); if (!UrlConstants.HTTPS_SCHEME.equals(uri.getScheme())) return HeaderState.NOT_HTTPS; @@ -205,24 +217,11 @@ } } - /** - * Returns an X-Geo HTTP header string if: - * - * <ul> - * <li>The current mode is not incognito, - * <li>The url is a google search URL (e.g. www.google.co.uk/search?q=cars), - * <li>The user has not disabled sharing location with this url, and - * <li>There is a valid and recent location available. - * </ul> - * - * <p>Returns null otherwise. - * - * @param url The URL of the request with which this header will be sent. - * @param tab The Tab currently being accessed. - * @return The X-Geo header string or null. - */ - public static @Nullable String getGeoHeader(String url, Tab tab) { - return getGeoHeader(url, tab.getProfile()); + @CalledByNative + private static @Nullable String getGeoHeader(String url, @Nullable Profile profile) { + if (profile == null) return null; + TemplateUrlService service = TemplateUrlServiceFactory.getForProfile(profile); + return getGeoHeader(url, profile, service); } /** @@ -239,15 +238,15 @@ * * @param url The URL of the request with which this header will be sent. * @param profile The user profile being accessed. + * @param service The TemplateUrlService representing default search engine. * @return The X-Geo header string or null. */ - @CalledByNative - private static @Nullable String getGeoHeader(String url, Profile profile) { - if (profile == null) return null; + public static @Nullable String getGeoHeader( + String url, Profile profile, @Nullable TemplateUrlService service) { try (TraceEvent e = TraceEvent.scoped("GeolocationHeader.getGeoHeader")) { Location locationToAttach = null; long locationAge = Long.MAX_VALUE; - @HeaderState int headerState = geoHeaderStateForUrl(profile, url); + @HeaderState int headerState = geoHeaderStateForUrl(profile, service, url); if (headerState == HeaderState.HEADER_ENABLED) { locationToAttach = getLastKnownLocation(); if (locationToAttach != null) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java index 2620f2e..e856a2c 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java
@@ -99,6 +99,7 @@ when(mProfileMock.isOffTheRecord()).thenReturn(false); when(mTemplateUrlServiceMock.getUrlForSearchQuery(anyString())) .thenReturn("https://example.com/"); + when(mTemplateUrlServiceMock.isDefaultSearchEngineGoogle()).thenReturn(true); sRefreshLastKnownLocation = 0; ShadowLocationServices.sFusedLocationProviderClient = mLocationProviderClient; } @@ -116,7 +117,8 @@ GeolocationTracker.setLocationForTesting(location, null); // 1 minute should be good enough and not require visible networks. GeolocationTracker.setLocationAgeForTesting(1 * 60 * 1000L); - String header = GeolocationHeader.getGeoHeader(SEARCH_URL, mTab); + String header = + GeolocationHeader.getGeoHeader(SEARCH_URL, mProfileMock, mTemplateUrlServiceMock); assertEquals("X-Geo: w " + ENCODED_PROTO_LOCATION, header); } @@ -203,7 +205,8 @@ GeolocationTracker.setLocationForTesting(location, null); // 6 minutes should hit the age limit, but the feature is off. GeolocationTracker.setLocationAgeForTesting(6 * 60 * 1000L); - String header = GeolocationHeader.getGeoHeader(SEARCH_URL, mTab); + String header = + GeolocationHeader.getGeoHeader(SEARCH_URL, mProfileMock, mTemplateUrlServiceMock); assertEquals(expectedHeader, header); }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java index 76765754..fdddbfe 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -112,8 +112,6 @@ private float mUrlFocusPercent; - private final int mPermissionIconDisplayTimeoutMs = PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS; - private @Nullable CookieControlsBridge mCookieControlsBridge; private boolean mCookieControlsVisible; private boolean mThirdPartyCookiesBlocked; @@ -674,7 +672,8 @@ mPermissionTaskHandler.removeCallbacksAndMessages(null); mModel.set(StatusProperties.STATUS_ICON_RESOURCE, permissionIconResource); Runnable finishIconAnimation = () -> updateLocationBarIcon(IconTransitionType.ROTATE); - mPermissionTaskHandler.postDelayed(finishIconAnimation, mPermissionIconDisplayTimeoutMs); + mPermissionTaskHandler.postDelayed( + finishIconAnimation, PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS); } // CookieControlsObserver interface @@ -737,7 +736,7 @@ mModel.set(StatusProperties.STATUS_ICON_RESOURCE, permissionIconResource); mPermissionTaskHandler.postDelayed( () -> updateLocationBarIcon(IconTransitionType.ROTATE), - mPermissionIconDisplayTimeoutMs); + PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS); } private void startIph() { @@ -781,7 +780,7 @@ () -> { updateLocationBarIcon(IconTransitionType.ROTATE); }, - mPermissionIconDisplayTimeoutMs); + PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS); mIsStoreIconShowing = true; } @@ -799,7 +798,7 @@ * finishes and should disappear when it animates out. */ private int getIphTimeout() { - return mPermissionIconDisplayTimeoutMs - (2 * StatusView.ICON_ROTATION_DURATION_MS); + return PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS - (2 * StatusView.ICON_ROTATION_DURATION_MS); } /** Notifies that the page info was opened. */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java index 70ab41d..b711a58 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
@@ -271,7 +271,9 @@ mLayoutScrollListener = new SuggestionLayoutScrollListener(context); setLayoutManager(mLayoutScrollListener); - mSelectionController = new RecyclerViewSelectionController(mLayoutScrollListener); + mSelectionController = + new RecyclerViewSelectionController( + mLayoutScrollListener, SelectionController.Mode.SATURATING_WITH_SENTINEL); addOnChildAttachStateChangeListener(mSelectionController); final Resources resources = context.getResources(); @@ -344,7 +346,7 @@ /** Resets selection typically in response to changes to the list. */ public void resetSelection() { - mSelectionController.resetSelection(); + mSelectionController.reset(); } /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionController.java index 17f5615..afe3cd8 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionController.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionController.java
@@ -13,153 +13,76 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import java.util.OptionalInt; + /** Selection manager for RecyclerViews. */ @NullMarked -public class RecyclerViewSelectionController +public class RecyclerViewSelectionController extends SelectionController implements RecyclerView.OnChildAttachStateChangeListener { private static final int ADVANCE_EXPOSE_VIEWS = 2; - private int mSelectedItemIndex = RecyclerView.NO_POSITION; private final LayoutManager mLayoutManager; + private int mLastSelectedItemIndex = RecyclerView.NO_POSITION; - /** When true, cycling to next/previous item will go through null selection. */ - private boolean mCycleThroughNoSelection; - - public RecyclerViewSelectionController(LayoutManager layoutManager) { + public RecyclerViewSelectionController( + LayoutManager layoutManager, @SelectionController.Mode int mode) { + super(mode); mLayoutManager = layoutManager; + reset(); } - /** - * Specifies whether advancing to previous/next element should go through no selection. - * - * <p>Note that advancing from no selection always proceeds to the next element: - * - * <p>- If the flag is set to `false`, once the last possible element is selected, the user - * cannot advance any further. - * - * <pre>[A] -> [B] -> [C] -> [C] -> [C] -> [C] ...</pre> - * - * <p>- if the flag is set to `true`, once the last possible element is selected and the user - * advances to the next item, selection will re-start from no selection, and advance to the - * first selectable item afterwards. - * - * <pre>[A] -> [B] -> [C] -> [∅] -> [A] -> [B] ...</pre> - */ - public void setCycleThroughNoSelection(boolean cycleThroughNoSelection) { - mCycleThroughNoSelection = cycleThroughNoSelection; + @Override + protected int getItemCount() { + return mLayoutManager.getItemCount(); + } + + @Override + public void reset() { + super.reset(); + mLastSelectedItemIndex = RecyclerView.NO_POSITION; } @Override public void onChildViewAttachedToWindow(View view) { // Force update selection of the view that might come from a recycle pool. - setSelectedItem(mSelectedItemIndex); + setItemState(mLastSelectedItemIndex, true); } @Override public void onChildViewDetachedFromWindow(View view) { - // Force move selection to the item that now occupies slot at mSelectedItemIndex. + // Force move selection to the item that now occupies slot at mLastSelectedItemIndex. // The explicit state set here is necessary, because the setSelectedItem call // does not iterate over all available views, so when this view is re-used, // we do not want it to show up as selected right away. view.setSelected(false); - setSelectedItem(mSelectedItemIndex); - } - - /** Reset the active selection. */ - public void resetSelection() { - setSelectedItem(RecyclerView.NO_POSITION); - } - - /** - * Move selection to the next element on the list. - * - * @return true, if change resulted in an item being highlighted - */ - public boolean selectNextItem() { - if (mLayoutManager == null || mLayoutManager.getItemCount() == 0) return false; - - // Note: this is also the index selected if there are no more selectable views after the - // current one. - int nextSelectableItemIndex = - mCycleThroughNoSelection ? RecyclerView.NO_POSITION : mSelectedItemIndex; - int currentIndex = - mSelectedItemIndex == RecyclerView.NO_POSITION ? 0 : mSelectedItemIndex + 1; - - while (currentIndex != mSelectedItemIndex) { - if (currentIndex >= mLayoutManager.getItemCount()) { - break; - } - - var view = mLayoutManager.findViewByPosition(currentIndex); - - if (view != null && view.isFocusable()) { - nextSelectableItemIndex = currentIndex; - break; - } - - currentIndex++; - } - - setSelectedItem(nextSelectableItemIndex); - return mSelectedItemIndex != RecyclerView.NO_POSITION; - } - - /** - * Move selection to the previous element on the list. - * - * @return true, if change resulted in an item being highlighted - */ - public boolean selectPreviousItem() { - if (mLayoutManager == null || mLayoutManager.getItemCount() == 0) return false; - - // Note: this is also the index selected if there are no more selectable views after the - // current one. - int nextSelectableItemIndex = - mCycleThroughNoSelection ? RecyclerView.NO_POSITION : mSelectedItemIndex; - int currentIndex = - mSelectedItemIndex == RecyclerView.NO_POSITION - ? mLayoutManager.getItemCount() - 1 - : mSelectedItemIndex - 1; - - while (currentIndex != mSelectedItemIndex) { - if (currentIndex == RecyclerView.NO_POSITION) { - break; - } - - var view = mLayoutManager.findViewByPosition(currentIndex); - - if (view != null && view.isFocusable()) { - nextSelectableItemIndex = currentIndex; - break; - } - - currentIndex--; - } - - setSelectedItem(nextSelectableItemIndex); - return mSelectedItemIndex != RecyclerView.NO_POSITION; + setItemState(mLastSelectedItemIndex, true); } /** Retrieve currently selected element. */ public @Nullable View getSelectedView() { - return mLayoutManager.findViewByPosition(mSelectedItemIndex); + OptionalInt selection = getPosition(); + if (selection.isEmpty()) return null; + return mLayoutManager.findViewByPosition(selection.getAsInt()); + } + + /** Returns whether item at specific position is focusable. */ + @Override + protected boolean isSelectableItem(int index) { + View view = mLayoutManager.findViewByPosition(index); + return view != null && view.isFocusable(); } /** - * Move focus to another view. + * Change the selected state of a view. * - * @param index Index of the child view to be selected. + * @param index index of the child view to be selected + * @param isSelected the desired selected state of the view */ @VisibleForTesting - public void setSelectedItem(int index) { - if (mLayoutManager == null) return; - if (index != RecyclerView.NO_POSITION - && (index < 0 || index >= mLayoutManager.getItemCount())) { - return; - } - - View previousSelectedView = mLayoutManager.findViewByPosition(mSelectedItemIndex); - if (previousSelectedView != null) { - previousSelectedView.setSelected(false); + @Override + public void setItemState(int index, boolean isSelected) { + View view = mLayoutManager.findViewByPosition(index); + if (view != null) { + view.setSelected(isSelected); } // Ensure additional views are exposed when tabbing through items. @@ -171,23 +94,14 @@ // If we don't expose additional views, the user may occasionally be unable to tab through // the list; the LayoutManager may report <null> when we request a view at a specific // position, because the view is not yet bound when we need it. - int exposeUntilViewIndex = - (mSelectedItemIndex < index) - ? Math.min(index + ADVANCE_EXPOSE_VIEWS, mLayoutManager.getItemCount() - 1) - : Math.max(index - ADVANCE_EXPOSE_VIEWS, 0); - mLayoutManager.scrollToPosition(exposeUntilViewIndex); - - mSelectedItemIndex = index; - - View currentSelectedView = mLayoutManager.findViewByPosition(index); - if (currentSelectedView != null) { - currentSelectedView.setSelected(true); + if (isSelected) { + int exposeUntilViewIndex = + (mLastSelectedItemIndex < index) + ? Math.min( + index + ADVANCE_EXPOSE_VIEWS, mLayoutManager.getItemCount() - 1) + : Math.max(index - ADVANCE_EXPOSE_VIEWS, 0); + mLayoutManager.scrollToPosition(exposeUntilViewIndex); + mLastSelectedItemIndex = index; } } - - /** Returns the selected item index. */ - @VisibleForTesting - int getSelectedItemForTest() { - return mSelectedItemIndex; - } }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionControllerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionControllerUnitTest.java index 74126db..24eff95 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionControllerUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/RecyclerViewSelectionControllerUnitTest.java
@@ -5,8 +5,10 @@ package org.chromium.chrome.browser.omnibox.suggestions; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -29,6 +31,8 @@ import org.chromium.base.test.BaseRobolectricTestRunner; +import java.util.OptionalInt; + /** Tests for {@link RecyclerViewSelectionController}. */ @RunWith(BaseRobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -43,115 +47,138 @@ private @Mock View mChildView4; private @Mock View mChildView5; RecyclerViewSelectionController mSelectionController; + RecyclerViewSelectionController mSelectionControllerWithSentinel; @Before public void setUp() { - when(mLayoutManager.getItemCount()).thenReturn(5); - when(mLayoutManager.findViewByPosition(0)).thenReturn(mChildView1); - when(mLayoutManager.findViewByPosition(1)).thenReturn(mChildView2); - when(mLayoutManager.findViewByPosition(2)).thenReturn(mChildView3); - when(mLayoutManager.findViewByPosition(3)).thenReturn(mChildView4); - when(mLayoutManager.findViewByPosition(4)).thenReturn(mChildView5); + lenient().when(mLayoutManager.getItemCount()).thenReturn(5); + lenient().when(mLayoutManager.findViewByPosition(0)).thenReturn(mChildView1); + lenient().when(mLayoutManager.findViewByPosition(1)).thenReturn(mChildView2); + lenient().when(mLayoutManager.findViewByPosition(2)).thenReturn(mChildView3); + lenient().when(mLayoutManager.findViewByPosition(3)).thenReturn(mChildView4); + lenient().when(mLayoutManager.findViewByPosition(4)).thenReturn(mChildView5); - doReturn(true).when(mChildView1).isFocusable(); - doReturn(true).when(mChildView2).isFocusable(); - doReturn(true).when(mChildView3).isFocusable(); - doReturn(true).when(mChildView4).isFocusable(); - doReturn(true).when(mChildView5).isFocusable(); + lenient().doReturn(true).when(mChildView1).isFocusable(); + lenient().doReturn(true).when(mChildView2).isFocusable(); + lenient().doReturn(true).when(mChildView3).isFocusable(); + lenient().doReturn(true).when(mChildView4).isFocusable(); + lenient().doReturn(true).when(mChildView5).isFocusable(); - mSelectionController = new RecyclerViewSelectionController(mLayoutManager); + mSelectionController = + new RecyclerViewSelectionController( + mLayoutManager, RecyclerViewSelectionController.Mode.SATURATING); + mSelectionControllerWithSentinel = + new RecyclerViewSelectionController( + mLayoutManager, + RecyclerViewSelectionController.Mode.SATURATING_WITH_SENTINEL); + + // Saturating controller will initialize selection, impacting tests. Reset this right away. + clearInvocations(mChildView1); } @Test public void selectNextItem_fromNone() { - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); mSelectionController.selectNextItem(); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); + Assert.assertEquals(OptionalInt.of(1), mSelectionController.getPosition()); + Assert.assertEquals(mChildView2, mSelectionController.getSelectedView()); + } + + @Test + public void selectNextItem_fromNone_withSentinel() { + Assert.assertTrue(mSelectionControllerWithSentinel.isParkedAtSentinel()); + mSelectionControllerWithSentinel.selectNextItem(); + Assert.assertEquals(OptionalInt.of(0), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(mChildView1, mSelectionControllerWithSentinel.getSelectedView()); } @Test public void selectNextItem_fromPrevious() { - mSelectionController.setSelectedItem(1); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(1); + Assert.assertEquals(OptionalInt.of(1), mSelectionController.getPosition()); Assert.assertEquals(mChildView2, mSelectionController.getSelectedView()); verify(mLayoutManager).scrollToPosition(3); + mSelectionController.selectNextItem(); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(2), mSelectionController.getPosition()); Assert.assertEquals(mChildView3, mSelectionController.getSelectedView()); verify(mLayoutManager).scrollToPosition(4); } @Test public void selectNextItem_fromLast() { - mSelectionController.setSelectedItem(4); + mSelectionController.setPosition(4); verify(mLayoutManager).scrollToPosition(4); - Assert.assertEquals(4, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(4), mSelectionController.getPosition()); Assert.assertEquals(mChildView5, mSelectionController.getSelectedView()); // Selecting next item should result in item being highlighted. - Assert.assertTrue(mSelectionController.selectNextItem()); - Assert.assertEquals(4, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView5, mSelectionController.getSelectedView()); - - // Permit cycling through to no selection. - mSelectionController.setCycleThroughNoSelection(true); - - // Cycling should result in no item being selected. Assert.assertFalse(mSelectionController.selectNextItem()); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(null, mSelectionController.getSelectedView()); + Assert.assertEquals(OptionalInt.of(4), mSelectionController.getPosition()); + Assert.assertEquals(mChildView5, mSelectionController.getSelectedView()); } @Test - public void selectPreviousItem_fromNone() { - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - mSelectionController.selectPreviousItem(); - // Jump to the last element on the list. - Assert.assertEquals(4, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView5, mSelectionController.getSelectedView()); + public void selectNextItem_fromLast_withSentinel() { + mSelectionControllerWithSentinel.setPosition(4); verify(mLayoutManager).scrollToPosition(4); + Assert.assertEquals(OptionalInt.of(4), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(mChildView5, mSelectionControllerWithSentinel.getSelectedView()); + + // Selecting next item should result in leaving valid range. + Assert.assertFalse(mSelectionControllerWithSentinel.selectNextItem()); + + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(null, mSelectionControllerWithSentinel.getSelectedView()); + } + + @Test + public void selectPreviousItem_fromNone_withSentinel() { + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + mSelectionControllerWithSentinel.selectPreviousItem(); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(null, mSelectionControllerWithSentinel.getSelectedView()); } @Test public void selectPreviousItem_fromPrevious() { - mSelectionController.setSelectedItem(1); + mSelectionController.setPosition(1); verify(mLayoutManager).scrollToPosition(3); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(1), mSelectionController.getPosition()); Assert.assertEquals(mChildView2, mSelectionController.getSelectedView()); + mSelectionController.selectPreviousItem(); verify(mLayoutManager).scrollToPosition(0); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); } @Test public void selectPreviousItem_fromFirst() { - mSelectionController.setSelectedItem(0); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(0); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); // Selecting previous item should result in item being highlighted. - Assert.assertTrue(mSelectionController.selectPreviousItem()); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); - - // Permit cycling through no selection. - mSelectionController.setCycleThroughNoSelection(true); - // Cycling should result in no item being selected. Assert.assertFalse(mSelectionController.selectPreviousItem()); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(null, mSelectionController.getSelectedView()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); + Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); + } + + @Test + public void selectPreviousItem_fromFirst_withSentinel() { + mSelectionControllerWithSentinel.setPosition(0); + Assert.assertEquals(OptionalInt.of(0), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(mChildView1, mSelectionControllerWithSentinel.getSelectedView()); + Assert.assertFalse(mSelectionControllerWithSentinel.selectPreviousItem()); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(null, mSelectionControllerWithSentinel.getSelectedView()); } @Test public void selectPreviousItem_skipNonFocusableItems_noCycling() { - mSelectionController.setSelectedItem(2); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(2); + Assert.assertEquals(OptionalInt.of(2), mSelectionController.getPosition()); Assert.assertEquals(mChildView3, mSelectionController.getSelectedView()); // View at position 1 is not focusable: @@ -159,14 +186,14 @@ // Focus skips position 1. mSelectionController.selectPreviousItem(); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); } @Test public void selectPreviousItem_ignoreHeadNonFocusableViews() { - mSelectionController.setSelectedItem(2); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(2); + Assert.assertEquals(OptionalInt.of(2), mSelectionController.getPosition()); Assert.assertEquals(mChildView3, mSelectionController.getSelectedView()); // Views at position 0 and 1 are not focusable: @@ -175,25 +202,23 @@ // Focus must not move. mSelectionController.selectPreviousItem(); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(2), mSelectionController.getPosition()); Assert.assertEquals(mChildView3, mSelectionController.getSelectedView()); } @Test - public void selectPreviousItem_skipNonFocusableItems_withCycling() { - mSelectionController.setCycleThroughNoSelection(true); - mSelectionController.setSelectedItem(1); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView2, mSelectionController.getSelectedView()); + public void selectPreviousItem_skipNonFocusableItems_withSentinel() { + mSelectionControllerWithSentinel.setPosition(1); + Assert.assertEquals(OptionalInt.of(1), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(mChildView2, mSelectionControllerWithSentinel.getSelectedView()); // View at position 0 is not focusable: doReturn(false).when(mChildView1).isFocusable(); // We wrap around ignoring view at position 2. - mSelectionController.selectPreviousItem(); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(null, mSelectionController.getSelectedView()); + mSelectionControllerWithSentinel.selectPreviousItem(); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(null, mSelectionControllerWithSentinel.getSelectedView()); } @Test @@ -204,24 +229,23 @@ doReturn(false).when(mChildView4).isFocusable(); doReturn(false).when(mChildView5).isFocusable(); - mSelectionController.selectPreviousItem(); - verify(mLayoutManager).scrollToPosition(0); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + // Re-create controller (because when it was created, all these views were focusable). + mSelectionController = + new RecyclerViewSelectionController( + mLayoutManager, RecyclerViewSelectionController.Mode.SATURATING); + + Assert.assertEquals(OptionalInt.empty(), mSelectionController.getPosition()); Assert.assertEquals(null, mSelectionController.getSelectedView()); - // Permit cycling through. - mSelectionController.setCycleThroughNoSelection(true); mSelectionController.selectPreviousItem(); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.empty(), mSelectionController.getPosition()); Assert.assertEquals(null, mSelectionController.getSelectedView()); } @Test public void selectNextItem_skipNonFocusableItems_noCycling() { - mSelectionController.setSelectedItem(0); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(0); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); // View at position 1 is not focusable: @@ -229,15 +253,15 @@ // Focus skips position 1. mSelectionController.selectNextItem(); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(2), mSelectionController.getPosition()); Assert.assertEquals(mChildView3, mSelectionController.getSelectedView()); } @Test public void selectNextItem_ignoreTailNonFocusableViews() { - mSelectionController.setSelectedItem(0); + // This controller initializes at 0th position by default. verify(mLayoutManager).scrollToPosition(2); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); // Views at positions 1-4 are not focusable: @@ -248,29 +272,25 @@ // Focus must not move. mSelectionController.selectNextItem(); - Assert.assertEquals(0, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); Assert.assertEquals(mChildView1, mSelectionController.getSelectedView()); } @Test - public void selectNextItem_skipNonFocusableItems_withCycling() { - mSelectionController.setCycleThroughNoSelection(true); - mSelectionController.setSelectedItem(1); + public void selectNextItem_skipNonFocusableItems_withSentinel() { + mSelectionControllerWithSentinel.setPosition(1); verify(mLayoutManager).scrollToPosition(3); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(mChildView2, mSelectionController.getSelectedView()); + Assert.assertEquals(OptionalInt.of(1), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(mChildView2, mSelectionControllerWithSentinel.getSelectedView()); // Views at positions 2-4 are not focusable: doReturn(false).when(mChildView3).isFocusable(); doReturn(false).when(mChildView4).isFocusable(); doReturn(false).when(mChildView5).isFocusable(); - // We wrap around ignoring view at position 2. - mSelectionController.selectNextItem(); - verify(mLayoutManager).scrollToPosition(0); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - Assert.assertEquals(null, mSelectionController.getSelectedView()); + mSelectionControllerWithSentinel.selectNextItem(); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + Assert.assertEquals(null, mSelectionControllerWithSentinel.getSelectedView()); } @Test @@ -281,50 +301,49 @@ doReturn(false).when(mChildView4).isFocusable(); doReturn(false).when(mChildView5).isFocusable(); - mSelectionController.selectNextItem(); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + // Re-create controller (because when it was created, all these views were focusable). + mSelectionController = + new RecyclerViewSelectionController( + mLayoutManager, RecyclerViewSelectionController.Mode.SATURATING); + + Assert.assertEquals(OptionalInt.empty(), mSelectionController.getPosition()); Assert.assertEquals(null, mSelectionController.getSelectedView()); - // Permit cycling through. - mSelectionController.setCycleThroughNoSelection(true); mSelectionController.selectNextItem(); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.empty(), mSelectionController.getPosition()); Assert.assertEquals(null, mSelectionController.getSelectedView()); } @Test - public void setSelectedItem_moveSelectionFromNone() { - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - mSelectionController.setSelectedItem(1); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); + public void setSelectedItem_moveSelectionFromNone_withSentinel() { + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + mSelectionControllerWithSentinel.setPosition(1); + Assert.assertEquals(OptionalInt.of(1), mSelectionControllerWithSentinel.getPosition()); + verify(mChildView2, atLeastOnce()).isFocusable(); verify(mChildView2, times(1)).setSelected(true); verify(mChildView2, times(1)).setSelected(anyBoolean()); verifyNoMoreInteractions(mChildView1, mChildView2, mChildView3); // Reset selection back to none. - mSelectionController.resetSelection(); + mSelectionControllerWithSentinel.reset(); + verify(mChildView2, atLeastOnce()).isFocusable(); verify(mChildView2, times(1)).setSelected(false); verify(mChildView2, times(2)).setSelected(anyBoolean()); verifyNoMoreInteractions(mChildView1, mChildView2, mChildView3); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); } @Test - public void setSelectedItem_moveSelectionFromAnotherItem() { - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - mSelectionController.setSelectedItem(1); - reset(mChildView2); + public void setSelectedItem_moveSelectionFromAnotherItem_withSentinel() { + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + mSelectionControllerWithSentinel.setPosition(1); + clearInvocations(mChildView2); - mSelectionController.setSelectedItem(2); - Assert.assertEquals(2, mSelectionController.getSelectedItemForTest()); + mSelectionControllerWithSentinel.setPosition(2); + Assert.assertEquals(OptionalInt.of(2), mSelectionControllerWithSentinel.getPosition()); verify(mChildView1, times(0)).setSelected(anyBoolean()); verify(mChildView2, times(0)).setSelected(true); @@ -334,15 +353,13 @@ } @Test - public void setSelectedItem_moveSelectionToNone() { - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); - mSelectionController.setSelectedItem(1); - reset(mChildView2); + public void setSelectedItem_moveSelectionToNone_withSentinel() { + Assert.assertTrue(mSelectionControllerWithSentinel.isParkedAtSentinel()); + mSelectionControllerWithSentinel.setPosition(1); + clearInvocations(mChildView2); - mSelectionController.setSelectedItem(RecyclerView.NO_POSITION); - Assert.assertEquals( - RecyclerView.NO_POSITION, mSelectionController.getSelectedItemForTest()); + mSelectionControllerWithSentinel.reset(); + Assert.assertTrue(mSelectionControllerWithSentinel.isParkedAtSentinel()); verify(mChildView1, times(0)).setSelected(anyBoolean()); verify(mChildView3, times(0)).setSelected(anyBoolean()); @@ -352,50 +369,74 @@ @Test public void setSelectedItem_indexNegative() { - mSelectionController.setSelectedItem(1); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); - // This call should be rejected, leaving selected item as it was. - // Note that (-1) is reserved value: RecyclerView.NO_POSITION. - mSelectionController.setSelectedItem(-2); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(1); + + Assert.assertEquals(OptionalInt.of(1), mSelectionController.getPosition()); + + // Clamped to valid range. + mSelectionController.setPosition(-2); + Assert.assertEquals(OptionalInt.of(0), mSelectionController.getPosition()); + } + + @Test + public void setSelectedItem_indexNegative_withSentinel() { + mSelectionControllerWithSentinel.setPosition(1); + + Assert.assertEquals(OptionalInt.of(1), mSelectionControllerWithSentinel.getPosition()); + + // Parked at sentinel. + mSelectionControllerWithSentinel.setPosition(-2); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); } @Test public void setSelectedItem_indexTooLarge() { - mSelectionController.setSelectedItem(1); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); - // This call should be rejected, leaving selected item as it was. - mSelectionController.setSelectedItem(30); - Assert.assertEquals(1, mSelectionController.getSelectedItemForTest()); + mSelectionController.setPosition(1); + Assert.assertEquals(OptionalInt.of(1), mSelectionController.getPosition()); + + // Clamped to valid range. + mSelectionController.setPosition(30); + Assert.assertEquals(OptionalInt.of(4), mSelectionController.getPosition()); } @Test - public void onChildViewAttached_viewIsReused() { + public void setSelectedItem_indexTooLarge_withSentinel() { + mSelectionControllerWithSentinel.setPosition(1); + Assert.assertEquals(OptionalInt.of(1), mSelectionControllerWithSentinel.getPosition()); + + // Parked at sentinel. + mSelectionControllerWithSentinel.setPosition(30); + Assert.assertEquals(OptionalInt.empty(), mSelectionControllerWithSentinel.getPosition()); + } + + @Test + public void onChildViewAttached_viewIsReused_withSentinel() { // Simulates the case where View at position 0 is used as a View at position 3. when(mLayoutManager.getItemCount()).thenReturn(4); // Select View at position 0. - mSelectionController.setSelectedItem(0); - verify(mChildView1, times(1)).setSelected(true); + mSelectionControllerWithSentinel.setPosition(0); + verify(mChildView1, atLeastOnce()).isFocusable(); + verify(mChildView1).setSelected(true); verifyNoMoreInteractions(mChildView1); verifyNoMoreInteractions(mChildView2); verifyNoMoreInteractions(mChildView3); - reset(mChildView1); + clearInvocations(mChildView1); // Pretend that the view is out of screen. // This should not result in view selection being cleared. when(mLayoutManager.findViewByPosition(0)).thenReturn(null); - mSelectionController.onChildViewDetachedFromWindow(mChildView1); - verify(mChildView1, times(1)).setSelected(false); + mSelectionControllerWithSentinel.onChildViewDetachedFromWindow(mChildView1); + verify(mChildView1).setSelected(false); verifyNoMoreInteractions(mChildView1); verifyNoMoreInteractions(mChildView2); verifyNoMoreInteractions(mChildView3); - reset(mChildView1); + clearInvocations(mChildView1); // Pretend that the View 0 is now re-used as View 3. // We should see that the Selected state is cleared. when(mLayoutManager.findViewByPosition(3)).thenReturn(mChildView1); - mSelectionController.onChildViewAttachedToWindow(mChildView1); + mSelectionControllerWithSentinel.onChildViewAttachedToWindow(mChildView1); verifyNoMoreInteractions(mChildView1); verifyNoMoreInteractions(mChildView2); verifyNoMoreInteractions(mChildView3); @@ -404,15 +445,14 @@ // This happens in 2 steps: // - 1. the view is removed from last position when(mLayoutManager.findViewByPosition(3)).thenReturn(null); - mSelectionController.onChildViewDetachedFromWindow(mChildView1); + mSelectionControllerWithSentinel.onChildViewDetachedFromWindow(mChildView1); // - 2. the view is inserted at first position. when(mLayoutManager.findViewByPosition(0)).thenReturn(mChildView1); - mSelectionController.onChildViewAttachedToWindow(mChildView1); - // This will result in the setSelected(false) being called 2 times: - // - once, when we signal the view is detached, and - // - once, when we force re-set the selected view state. - verify(mChildView1, times(2)).setSelected(false); - verify(mChildView1, times(1)).setSelected(true); + mSelectionControllerWithSentinel.onChildViewAttachedToWindow(mChildView1); + // This will result in the setSelected(false) being called once, when we signal the view + // is detached. + verify(mChildView1).setSelected(false); + verify(mChildView1).setSelected(true); verifyNoMoreInteractions(mChildView1); verifyNoMoreInteractions(mChildView2); verifyNoMoreInteractions(mChildView3);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java index 0568db3..8c06dad9 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionController.java
@@ -83,7 +83,7 @@ * * @return whether selection was applied to the new element. */ - public boolean advanceForward() { + public boolean selectNextItem() { // If parked at upper sentinel, bail. if (mPosition == Integer.MAX_VALUE) return false; @@ -110,7 +110,7 @@ * * @return whether selection was applied to the new element. */ - public boolean advanceBack() { + public boolean selectPreviousItem() { // If parked at lower sentinel, bail. if (mPosition == Integer.MIN_VALUE) return false;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java index 6d1ff55..2adf35d5 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SelectionControllerUnitTest.java
@@ -66,101 +66,101 @@ } @Test - public void advanceForward_saturating() { + public void selectNextItem_saturating() { var c = createTestController(Mode.SATURATING); c.reset(); verifyPositionSet(c, 0); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionChanged(c, 0, 1); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionChanged(c, 1, 2); // Cannot move any further. We've reached the limit. - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); assertEquals(OptionalInt.of(2), c.getPosition()); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); assertEquals(OptionalInt.of(2), c.getPosition()); } @Test - public void advanceForward_saturatingWithSentinel() { + public void selectNextItem_saturatingWithSentinel() { var c = createTestController(Mode.SATURATING_WITH_SENTINEL); c.reset(); assertTrue(c.isParkedAtSentinel()); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionSet(c, 0); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionChanged(c, 0, 1); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionChanged(c, 1, 2); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); verifyPositionReset(c, 2); - assertFalse(c.advanceForward()); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); + assertFalse(c.selectNextItem()); } @Test - public void advanceBack_saturating() { + public void selectPreviousItem_saturating() { var c = createTestController(Mode.SATURATING); c.reset(); c.setPosition(DEFAULT_NUM_ITEMS); verifyPositionChanged(c, 0, 2); - assertTrue(c.advanceBack()); + assertTrue(c.selectPreviousItem()); verifyPositionChanged(c, 2, 1); - assertTrue(c.advanceBack()); + assertTrue(c.selectPreviousItem()); verifyPositionChanged(c, 1, 0); // Cannot move any further. We've reached the limit. - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); assertEquals(OptionalInt.of(0), c.getPosition()); - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); assertEquals(OptionalInt.of(0), c.getPosition()); } @Test - public void advanceBack_saturatingWithSentinel() { + public void selectPreviousItem_saturatingWithSentinel() { var c = createTestController(Mode.SATURATING_WITH_SENTINEL); c.reset(); c.setPosition(DEFAULT_NUM_ITEMS - 1); verifyPositionSet(c, 2); - assertTrue(c.advanceBack()); + assertTrue(c.selectPreviousItem()); verifyPositionChanged(c, 2, 1); - assertTrue(c.advanceBack()); + assertTrue(c.selectPreviousItem()); verifyPositionChanged(c, 1, 0); - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); verifyPositionReset(c, 0); - assertFalse(c.advanceBack()); - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); + assertFalse(c.selectPreviousItem()); } @Test - public void advanceForward_skipMiddleItems_saturating() { + public void selectNextItem_skipMiddleItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(1)).thenReturn(false); c.reset(); verifyPositionSet(c, 0); - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verify(c, times(1)).setItemState(0, false); verify(c, times(1)).setItemState(2, true); @@ -170,14 +170,14 @@ } @Test - public void advanceBack_skipMiddleItems_saturating() { + public void selectPreviousItem_skipMiddleItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(1)).thenReturn(false); c.reset(); c.setPosition(2); verifyPositionChanged(c, 0, 2); - assertTrue(c.advanceBack()); + assertTrue(c.selectPreviousItem()); // This will try to move away from position 0 twice // - to advance to position 1, which will fail @@ -189,7 +189,7 @@ } @Test - public void advanceForward_skipTailItems_saturating() { + public void selectNextItem_skipTailItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(1)).thenReturn(false); when(c.isSelectableItem(2)).thenReturn(false); @@ -197,7 +197,7 @@ verifyPositionSet(c, 0); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); // Selection never moved. verify(c, times(0)).setItemState(anyInt(), anyBoolean()); @@ -207,14 +207,14 @@ } @Test - public void advanceBack_skipTailItems_saturating() { + public void selectPreviousItem_skipTailItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(1)).thenReturn(false); when(c.isSelectableItem(0)).thenReturn(false); c.setPosition(2); verifyPositionSet(c, 2); - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); // Selection never moved. verify(c, times(0)).setItemState(anyInt(), anyBoolean()); @@ -224,31 +224,31 @@ } @Test - public void advanceForward_skipTailItems_saturatingWithSentinel() { + public void selectNextItem_skipTailItems_saturatingWithSentinel() { var c = createTestController(Mode.SATURATING_WITH_SENTINEL); when(c.isSelectableItem(1)).thenReturn(false); when(c.isSelectableItem(2)).thenReturn(false); c.reset(); // Sentinel -> position 0: - assertTrue(c.advanceForward()); + assertTrue(c.selectNextItem()); verifyPositionSet(c, 0); // Position 0 -> (skipping 1 & 2) -> Sentinel - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); verifyPositionReset(c, 0); assertEquals(OptionalInt.empty(), c.getPosition()); } @Test - public void advanceBack_skipTailItems_saturatingWithSentinel() { + public void selectPreviousItem_skipTailItems_saturatingWithSentinel() { var c = createTestController(Mode.SATURATING_WITH_SENTINEL); when(c.isSelectableItem(1)).thenReturn(false); when(c.isSelectableItem(0)).thenReturn(false); c.setPosition(2); verifyPositionSet(c, 2); - assertFalse(c.advanceBack()); + assertFalse(c.selectPreviousItem()); // Selection reset. verifyPositionReset(c, 2); @@ -256,7 +256,7 @@ } @Test - public void advanceForward_noSelectableItems_saturating() { + public void selectNextItem_noSelectableItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(0)).thenReturn(false); when(c.isSelectableItem(1)).thenReturn(false); @@ -264,11 +264,11 @@ c.reset(); assertTrue(c.isParkedAtSentinel()); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); } @Test - public void advanceBack_noSelectableItems_saturating() { + public void selectPreviousItem_noSelectableItems_saturating() { var c = createTestController(Mode.SATURATING); when(c.isSelectableItem(0)).thenReturn(false); when(c.isSelectableItem(1)).thenReturn(false); @@ -276,7 +276,7 @@ c.reset(); assertTrue(c.isParkedAtSentinel()); - assertFalse(c.advanceForward()); + assertFalse(c.selectNextItem()); } @Test @@ -310,7 +310,7 @@ verifyPositionSet(c, 0); - c.advanceForward(); // 1 + c.selectNextItem(); // 1 verifyPositionChanged(c, 0, 1); c.reset(); // back to default (0) verifyPositionChanged(c, 1, 0);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java index 1f3cf714..498d3805a 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionController.java
@@ -11,7 +11,7 @@ */ @NullMarked public class SimpleSelectionController extends SelectionController { - private OnSelectionChangedListener mListener; + private final OnSelectionChangedListener mListener; private int mItemCount; @FunctionalInterface @@ -39,7 +39,8 @@ mListener = listener; mItemCount = itemCount; - reset(); + // Note: this will calculate correct position if there are no items. + setItemCount(itemCount); } /** Returns the number of items in the container controlled by the SelectionController. */
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java index 9f16494..b4ab502 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SimpleSelectionControllerUnitTest.java
@@ -52,13 +52,13 @@ c.setItemCount(5); verifyPositionSet(c, 0); - assertTrue(c.advanceForward()); // Should now reach index 4 without saturating + assertTrue(c.selectNextItem()); // Should now reach index 4 without saturating verifyPositionChanged(c, 0, 1); - assertTrue(c.advanceForward()); // 2 + assertTrue(c.selectNextItem()); // 2 verifyPositionChanged(c, 1, 2); - assertTrue(c.advanceForward()); // 3 + assertTrue(c.selectNextItem()); // 3 verifyPositionChanged(c, 2, 3); - assertTrue(c.advanceForward()); // 4 + assertTrue(c.selectNextItem()); // 4 verifyPositionChanged(c, 3, 4); // Shrink list of items
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayout.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayout.java index e35032a..1a8eda6 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayout.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayout.java
@@ -23,6 +23,8 @@ */ @NullMarked class CalculatorAnswerTextLayout implements AnswerText { + private static final int MAX_LINES = 1; + final Context mContext; private final boolean mIsAnswer; private final AnswerType mAnswerType; @@ -31,7 +33,6 @@ private final SpannableStringBuilder mText = new SpannableStringBuilder(); private @Nullable String mAccessibilityDescription; - private final int mMaxLines = 1; // AnswerText implementation. @Override @@ -46,7 +47,7 @@ @Override public int getMaxLines() { - return mMaxLines; + return MAX_LINES; } /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsView.java index a47088ad..f671066 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsView.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsView.java
@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.omnibox.R; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; import org.chromium.chrome.browser.omnibox.suggestions.RecyclerViewSelectionController; +import org.chromium.chrome.browser.omnibox.suggestions.SelectionController; import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.components.browser_ui.widget.chips.ChipView; @@ -41,8 +42,9 @@ var layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); setLayoutManager(layoutManager); - mSelectionController = new RecyclerViewSelectionController(layoutManager); - mSelectionController.setCycleThroughNoSelection(true); + mSelectionController = + new RecyclerViewSelectionController( + layoutManager, SelectionController.Mode.SATURATING_WITH_SENTINEL); addOnChildAttachStateChangeListener(mSelectionController); setMinimumHeight( @@ -90,10 +92,9 @@ @Override public void setSelected(boolean isSelected) { - mSelectionController.resetSelection(); + mSelectionController.reset(); } - @VisibleForTesting(otherwise = VisibleForTesting.NONE) void setSelectionControllerForTesting(RecyclerViewSelectionController controller) { mSelectionController = controller; }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsViewUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsViewUnitTest.java index 8eb75d1..699623b 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsViewUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionChipsViewUnitTest.java
@@ -8,7 +8,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -49,13 +48,13 @@ doReturn(true).when(mController).selectNextItem(); assertTrue(event.dispatch(mView)); - verify(mController, times(1)).selectNextItem(); + verify(mController).selectNextItem(); verifyNoMoreInteractions(mController); clearInvocations(mController); doReturn(false).when(mController).selectNextItem(); assertFalse(event.dispatch(mView)); - verify(mController, times(1)).selectNextItem(); + verify(mController).selectNextItem(); verifyNoMoreInteractions(mController); } @@ -74,14 +73,14 @@ doReturn(true).when(mController).selectPreviousItem(); assertTrue(event.dispatch(mView)); - verify(mController, times(1)).selectPreviousItem(); + verify(mController).selectPreviousItem(); verifyNoMoreInteractions(mController); clearInvocations(mController); doReturn(false).when(mController).selectPreviousItem(); assertFalse(event.dispatch(mView)); - verify(mController, times(1)).selectPreviousItem(); + verify(mController).selectPreviousItem(); verifyNoMoreInteractions(mController); } @@ -92,11 +91,11 @@ var event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); assertFalse(event.dispatch(mView)); - verify(mView, times(1)).onKeyDown(event.getKeyCode(), event); - verify(mView, times(1)).superOnKeyDown(event.getKeyCode(), event); + verify(mView).onKeyDown(event.getKeyCode(), event); + verify(mView).superOnKeyDown(event.getKeyCode(), event); verifyNoMoreInteractions(mView); - verify(mController, times(1)).getSelectedView(); + verify(mController).getSelectedView(); verifyNoMoreInteractions(mController); } @@ -111,11 +110,11 @@ var event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); assertTrue(event.dispatch(mView)); - verify(mChild, times(1)).performClick(); - verify(mView, times(1)).onKeyDown(event.getKeyCode(), event); + verify(mChild).performClick(); + verify(mView).onKeyDown(event.getKeyCode(), event); verifyNoMoreInteractions(mView); - verify(mController, times(1)).getSelectedView(); + verify(mController).getSelectedView(); verifyNoMoreInteractions(mController); } @@ -126,8 +125,8 @@ var event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_T); assertFalse(event.dispatch(mView)); - verify(mView, times(1)).onKeyDown(KeyEvent.KEYCODE_T, event); - verify(mView, times(1)).superOnKeyDown(KeyEvent.KEYCODE_T, event); + verify(mView).onKeyDown(KeyEvent.KEYCODE_T, event); + verify(mView).superOnKeyDown(KeyEvent.KEYCODE_T, event); verifyNoMoreInteractions(mView); verifyNoMoreInteractions(mController); @@ -147,7 +146,7 @@ installAdapter(); mView.setSelected(true); - verify(mController, times(1)).resetSelection(); + verify(mController).reset(); verifyNoMoreInteractions(mController); } @@ -156,7 +155,7 @@ installAdapter(); mView.setSelected(false); - verify(mController, times(1)).resetSelection(); + verify(mController).reset(); verifyNoMoreInteractions(mController); } }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java index 54925aa7..0174e2d8 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
@@ -181,11 +181,12 @@ if (keyCode == KeyEvent.KEYCODE_TAB) { if (!event.isShiftPressed()) { // Pass the TAB key to Action Buttons, then to Action Chips. - return mActionButtonsHighlighter.advanceForward() + return mActionButtonsHighlighter.selectNextItem() || super_onKeyDown(keyCode, event); } else { // Pass the TAB key to Action Chips, then to Action Buttons. - return super_onKeyDown(keyCode, event) || mActionButtonsHighlighter.advanceBack(); + return super_onKeyDown(keyCode, event) + || mActionButtonsHighlighter.selectPreviousItem(); } }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionView.java index 61d4658..01034f41e 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionView.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionView.java
@@ -16,6 +16,7 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.omnibox.suggestions.RecyclerViewSelectionController; +import org.chromium.chrome.browser.omnibox.suggestions.SelectionController; import org.chromium.chrome.browser.omnibox.suggestions.base.SpacingRecyclerViewItemDecoration; import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; @@ -42,8 +43,9 @@ new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); setLayoutManager(layoutManager); - mSelectionController = new RecyclerViewSelectionController(layoutManager); - mSelectionController.setCycleThroughNoSelection(true); + mSelectionController = + new RecyclerViewSelectionController( + layoutManager, SelectionController.Mode.SATURATING_WITH_SENTINEL); addOnChildAttachStateChangeListener(mSelectionController); setAdapter(adapter); @@ -73,16 +75,13 @@ } void resetSelection() { - mSelectionController.setSelectedItem(RecyclerView.NO_POSITION); + mSelectionController.reset(); } @Override public void setSelected(boolean isSelected) { - if (isSelected) { - mSelectionController.setSelectedItem(0); - } else { - resetSelection(); - } + resetSelection(); + if (isSelected) mSelectionController.selectNextItem(); } @Override
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionViewUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionViewUnitTest.java index a97f57b..2ec3a3f 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionViewUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/carousel/BaseCarouselSuggestionViewUnitTest.java
@@ -12,15 +12,12 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.view.KeyEvent; import android.view.View; -import androidx.recyclerview.widget.RecyclerView; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -140,14 +137,15 @@ @Test public void setSelected_resetsCarouselSelectionWhenSelected() { mView.setSelected(true); - verify(mController, times(1)).setSelectedItem(0); + verify(mController).reset(); + verify(mController).selectNextItem(); verifyNoMoreInteractions(mController); } @Test public void setSelected_resetsCarouselSelectionWhenDeselected() { mView.setSelected(false); - verify(mController, times(1)).setSelectedItem(RecyclerView.NO_POSITION); + verify(mController).reset(); verifyNoMoreInteractions(mController); }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java index 3089815..a6da50c8 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
@@ -24,6 +24,7 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.theme.ThemeUtils; import org.chromium.components.browser_ui.styles.SemanticColorUtils; import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar; @@ -174,12 +175,12 @@ // This tells accessibility services that progress bar changes are important enough to // announce to the user even when not focused. ViewCompat.setAccessibilityLiveRegion(this, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); - setForegroundOrThemeColor(); + setProgressBarColors(); } public void setAnimatingView(ToolbarProgressBarAnimatingView animatingView) { mAnimatingView = animatingView; - setForegroundOrThemeColor(); + setProgressBarColors(); } /** @@ -361,7 +362,7 @@ if (MathUtils.areFloatsEqual(progress, 1.0f) || progress > 1.0f) finish(true); } - private void setForegroundOrThemeColor() { + private void setProgressBarColors() { if (mThemeColor != 0) { setThemeColor(mThemeColor, false); } else { @@ -388,7 +389,11 @@ // The default toolbar has specific colors to use. if ((isDefaultTheme || ColorUtils.isThemeColorTooBright(color)) && !isIncognito) { setForegroundColor(SemanticColorUtils.getProgressBarForeground(getContext())); - setBackgroundColor(getContext().getColor(R.color.progress_bar_bg_color_list)); + if (ChromeFeatureList.sAndroidProgressBarVisualUpdate.isEnabled()) { + setBackgroundColor(SemanticColorUtils.getProgressBarTrackColor(getContext())); + } else { + setBackgroundColor(getContext().getColor(R.color.progress_bar_bg_color_list)); + } return; }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java index 64915563..dc5f065 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java
@@ -12,6 +12,7 @@ import android.animation.Animator; import android.content.res.Resources; +import android.graphics.Color; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -33,9 +34,12 @@ import org.chromium.base.MathUtils; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.util.Feature; +import org.chromium.base.test.util.Features; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.components.browser_ui.styles.SemanticColorUtils; import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.ProgressBarObserver; import org.chromium.ui.base.TestActivity; +import org.chromium.ui.util.ColorUtils; import java.util.concurrent.TimeoutException; @@ -48,6 +52,10 @@ private ToolbarProgressBarAnimatingView mProgressBarAnimatingView; private ShadowLooper mShadowLooper; private ActivityScenario<TestActivity> mActivityScenario; + private TestActivity mActivity; + private @ColorInt int mThemeColor; + + private static final float THEMED_BACKGROUND_WHITE_FRACTION = 0.2f; @Before public void setUp() { @@ -57,6 +65,7 @@ ActivityScenario.launch(TestActivity.class) .onActivity( activity -> { + mActivity = activity; activity.setTheme(R.style.Theme_BrowserUI_DayNight); ViewGroup view = new FrameLayout(activity); @@ -83,10 +92,9 @@ new ToolbarProgressBarAnimatingView(activity, null); mProgressBar = new ToolbarProgressBar(activity, null); mProgressBar.setAnimatingView(mProgressBarAnimatingView); - final @ColorInt int toolbarColor = - SemanticColorUtils.getToolbarBackgroundPrimary( - activity); - mProgressBar.setThemeColor(toolbarColor, false); + mThemeColor = SemanticColorUtils.getToolbarBackgroundPrimary( + mActivity); + mProgressBar.setThemeColor(mThemeColor, false); mProgressBar.setProgressBarObserver(mMockProgressBarObserver); view.addView( @@ -284,4 +292,85 @@ assertEquals(View.VISIBLE, mProgressBar.getVisibility()); assertEquals(View.VISIBLE, mProgressBarAnimatingView.getVisibility()); } + + @Test + @Feature({"Android-Progress-Bar"}) + public void testProgressBarColors_incognito() { + final boolean isIncognito = true; + mProgressBar.setThemeColor(mThemeColor, isIncognito); + + assertEquals("Foreground color does not match expected color.", + ColorUtils.getThemedAssetColor(mThemeColor, isIncognito), + mProgressBar.getForegroundColor()); + assertEquals("Background color does not match expected color.", + ColorUtils.getColorWithOverlay(mThemeColor, Color.WHITE, + THEMED_BACKGROUND_WHITE_FRACTION), mProgressBar.getBackgroundColor()); + } + + @Test + @Feature({"Android-Progress-Bar"}) + public void testProgressBarColors_TransparentTheme() { + mProgressBar.setThemeColor(Color.TRANSPARENT, /* isIncognito = */ false); + int foregroundColor = mProgressBar.getForegroundColor(); + int backgroundColor = mProgressBar.getBackgroundColor(); + mProgressBar.setAnimatingView(mProgressBarAnimatingView); + + assertEquals( + "Foreground color does not match color.", + foregroundColor, + mProgressBar.getForegroundColor()); + assertEquals( + "Background color does not match color.", + backgroundColor, + mProgressBar.getBackgroundColor()); + } + + @Test + @Feature({"Android-Progress-Bar"}) + public void testProgressBarColors_nonDefaultTheme() { + final int themeColor = Color.BLUE; + final boolean isIncognito = false; + mProgressBar.setThemeColor(themeColor, isIncognito); + + assertEquals( + "Foreground color does not match expected color.", + ColorUtils.getThemedAssetColor(themeColor, isIncognito), + mProgressBar.getForegroundColor()); + assertEquals( + "Background color does not match expected color.", + ColorUtils.getColorWithOverlay(themeColor, Color.WHITE, + THEMED_BACKGROUND_WHITE_FRACTION), mProgressBar.getBackgroundColor()); + } + + @Test + @Features.DisableFeatures(ChromeFeatureList.ANDROID_PROGRESS_BAR_VISUAL_UPDATE) + @Feature({"Android-Progress-Bar"}) + public void testProgressBarColors_defaultTheme() { + mProgressBar.setThemeColor(mThemeColor, /* isIncognito = */ false); + + assertEquals( + "Foreground color does not match expected color.", + SemanticColorUtils.getProgressBarForeground(mActivity), + mProgressBar.getForegroundColor()); + assertEquals( + "Background color does not match expected color.", + mActivity.getColor(R.color.progress_bar_bg_color_list), + mProgressBar.getBackgroundColor()); + } + + @Test + @Features.EnableFeatures(ChromeFeatureList.ANDROID_PROGRESS_BAR_VISUAL_UPDATE) + @Feature({"Android-Progress-Bar"}) + public void testProgressBarColors_defaultTheme_visualUpdate() { + mProgressBar.setThemeColor(mThemeColor, /* isIncognito = */ false); + + assertEquals( + "Foreground color does not match expected color.", + SemanticColorUtils.getProgressBarForeground(mActivity), + mProgressBar.getForegroundColor()); + assertEquals( + "Background color does not match expected color.", + SemanticColorUtils.getProgressBarTrackColor(mActivity), + mProgressBar.getBackgroundColor()); + } }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/home_page_button/HomePageButtonsMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/home_page_button/HomePageButtonsMediator.java index a0ab9e2..dca3d3d 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/home_page_button/HomePageButtonsMediator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/home_page_button/HomePageButtonsMediator.java
@@ -93,7 +93,10 @@ new HomePageButtonData( /* onClickListener= */ view -> new NtpCustomizationCoordinator( - mContext, mBottomSheetController, mProfileSupplier) + mContext, + mBottomSheetController, + mProfileSupplier, + NtpCustomizationCoordinator.BottomSheetType.MAIN) .showBottomSheet(), /* onLongClickListener= */ null); mModel.set(BUTTON_DATA, new Pair<>(1, mNtpCustomizationButtonData));
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc index 328bdc2..451d2c6 100644 --- a/chrome/browser/ui/lens/lens_overlay_controller.cc +++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -330,9 +330,6 @@ theme_service_(theme_service), gen204_controller_( std::make_unique<lens::LensOverlayGen204Controller>()) { - lens_overlay_event_handler_ = - std::make_unique<lens::LensOverlayEventHandler>(this); - InitializeTutorialIPHUrlMatcher(); // Listen to WebContents events @@ -1120,6 +1117,9 @@ } void LensOverlayController::ClearTextSelection() { + if(!IsOverlayShowing()) { + return; + } if (initialization_data_->selected_text_.has_value()) { initialization_data_->selected_text_.reset(); page_->ClearTextSelection(); @@ -1127,6 +1127,9 @@ } void LensOverlayController::ClearRegionSelection() { + if(!IsOverlayShowing()) { + return; + } GetLensSearchboxController()->SetSearchboxThumbnail(""); lens_selection_type_ = lens::UNKNOWN_SELECTION_TYPE; initialization_data_->selected_region_.reset(); @@ -1591,73 +1594,6 @@ RecordDocumentMetrics(page_count); } -#if BUILDFLAG(ENABLE_PDF) -void LensOverlayController::FetchVisiblePageIndexAndGetPartialPdfText( - uint32_t page_count) { - pdf::PDFDocumentHelper* pdf_helper = - pdf::PDFDocumentHelper::MaybeGetForWebContents(tab_->GetContents()); - if (!pdf_helper || - lens::features::GetLensOverlayPdfSuggestCharacterTarget() == 0 || - page_count == 0) { - return; - } - - // TODO(387306854): Add logic to grab page text form the visible page index. - - // Fetch the first page of text which will be then recursively fetch following - // pages. - initialization_data_->pdf_pages_text_.clear(); - pdf_helper->GetPageText( - /*page_index=*/0, - base::BindOnce(&LensOverlayController::GetPartialPdfTextCallback, - weak_factory_.GetWeakPtr(), /*page_index=*/0, page_count, - /*total_characters_retrieved=*/0)); -} - -void LensOverlayController::GetPartialPdfTextCallback( - uint32_t page_index, - uint32_t total_page_count, - uint32_t total_characters_retrieved, - const std::u16string& page_text) { - // Sanity checks that the input is expected. - CHECK_GE(total_page_count, 1u); - CHECK_LT(page_index, total_page_count); - CHECK_EQ(initialization_data_->pdf_pages_text_.size(), page_index); - - // Add the page text to the list of pages and update the total characters - // retrieved count. - initialization_data_->pdf_pages_text_.push_back(page_text); - - // Ensure no integer overflow. If overflow, set the total characters retrieved - // to the max value so the loop will exit. - base::CheckedNumeric<uint32_t> total_characters_retrieved_check = - total_characters_retrieved; - total_characters_retrieved_check += page_text.size(); - total_characters_retrieved = total_characters_retrieved_check.ValueOrDefault( - std::numeric_limits<uint32_t>::max()); - - pdf::PDFDocumentHelper* pdf_helper = - pdf::PDFDocumentHelper::MaybeGetForWebContents(tab_->GetContents()); - - // Stop the loop if the character limit is reached or if the page index is - // out of bounds or the PDF helper no longer exists. - if (!pdf_helper || - total_characters_retrieved >= - lens::features::GetLensOverlayPdfSuggestCharacterTarget() || - page_index + 1 >= total_page_count) { - lens_overlay_query_controller_->SendPartialPageContentRequest( - initialization_data_->pdf_pages_text_); - return; - } - - pdf_helper->GetPageText( - page_index + 1, - base::BindOnce(&LensOverlayController::GetPartialPdfTextCallback, - weak_factory_.GetWeakPtr(), page_index + 1, - total_page_count, total_characters_retrieved)); -} -#endif // BUILDFLAG(ENABLE_PDF) - std::vector<lens::mojom::CenterRotatedBoxPtr> LensOverlayController::ConvertSignificantRegionBoxes( const std::vector<gfx::Rect>& all_bounds) { @@ -1854,7 +1790,7 @@ initialization_data_->primary_content_type_ = primary_content_type; // If no bytes were retrieved from the page, the query won't be able to be - // contexualized. Notify the side panel so the ghost loader isn't shown. No + // contextualized. Notify the side panel so the ghost loader isn't shown. No // need to update update the overlay as this update only happens on navigation // where the side panel will already be open. if (!new_page_content || new_page_content->bytes_.empty()) { @@ -1866,7 +1802,10 @@ // suggest signals. if (new_page_content && new_page_content->content_type_ == lens::MimeType::kPdf) { - FetchVisiblePageIndexAndGetPartialPdfText(page_count.value_or(0)); + GetContextualizationController()->FetchVisiblePageIndexAndGetPartialPdfText( + lens_overlay_query_controller_, page_count.value_or(0), + base::BindOnce(&LensOverlayController::OnPdfPartialPageTextRetrieved, + weak_factory_.GetWeakPtr())); } #endif @@ -2055,8 +1994,11 @@ initialization_data_->page_contents_.front().content_type_ == lens::MimeType::kPdf) { CHECK(initialization_data_->pdf_page_count_.has_value()); - FetchVisiblePageIndexAndGetPartialPdfText( - initialization_data_->pdf_page_count_.value()); + GetContextualizationController()->FetchVisiblePageIndexAndGetPartialPdfText( + lens_overlay_query_controller_, + initialization_data_->pdf_page_count_.value(), + base::BindOnce(&LensOverlayController::OnPdfPartialPageTextRetrieved, + weak_factory_.GetWeakPtr())); } #endif @@ -2254,8 +2196,9 @@ if (!overlay_web_view_ || !overlay_web_view_->GetFocusManager()) { return false; } - return lens_overlay_event_handler_->HandleKeyboardEvent( - source, event, overlay_web_view_->GetFocusManager()); + return lens_search_controller_->lens_overlay_event_handler() + ->HandleKeyboardEvent(source, event, + overlay_web_view_->GetFocusManager()); } void LensOverlayController::OnFullscreenStateChanged() { @@ -3422,6 +3365,11 @@ /*hide_toolbar_entrypoint=*/false); } +void LensOverlayController::OnPdfPartialPageTextRetrieved( + std::vector<std::u16string> pdf_pages_text) { + initialization_data_->pdf_pages_text_ = std::move(pdf_pages_text); +} + lens::LensSearchboxController* LensOverlayController::GetLensSearchboxController() { return lens_search_controller_->lens_searchbox_controller();
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h index 60e7c72..a3ba461 100644 --- a/chrome/browser/ui/lens/lens_overlay_controller.h +++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -79,7 +79,6 @@ } // namespace content namespace lens { -class LensOverlayEventHandler; class LensOverlayQueryController; class LensOverlaySidePanelCoordinator; class LensPermissionBubbleController; @@ -258,11 +257,6 @@ // WebContents. const content::WebContents* tab_contents() { return tab_->GetContents(); } - // Returns the event handler for this instance of the Lens Overlay. - lens::LensOverlayEventHandler* lens_overlay_event_handler() { - return lens_overlay_event_handler_.get(); - } - // Returns invocation time since epoch. Used to set up html source for metric // logging. uint64_t GetInvocationTimeSinceEpoch(); @@ -773,21 +767,6 @@ lens::MimeType primary_content_type, std::optional<uint32_t> page_count); -#if BUILDFLAG(ENABLE_PDF) - // Fetches the visible page index from the PDF renderer and then starts the - // process of fetching the text from the PDF to be used for suggest signals. - void FetchVisiblePageIndexAndGetPartialPdfText(uint32_t page_count); - - // Gets the partial text from the PDF to be used for suggest. Schedules for - // the next page of text to be fetched, from the PDF in page order until - // either 1) all the text is received or 2) the character limit is reached. - // This method should only be called by GetPartialPdfText. - void GetPartialPdfTextCallback(uint32_t page_index, - uint32_t total_page_count, - uint32_t total_characters_retrieved, - const std::u16string& page_text); -#endif // BUILDFLAG(ENABLE_PDF) - // Creates the mojo bounding boxes for the significant regions. std::vector<lens::mojom::CenterRotatedBoxPtr> ConvertSignificantRegionBoxes( const std::vector<gfx::Rect>& all_bounds); @@ -1064,6 +1043,10 @@ // points since the state of the overlay has changed. void UpdateEntryPointsState(); + // Callback to run when the partial page text is retrieved from the PDF. + void OnPdfPartialPageTextRetrieved( + std::vector<std::u16string> pdf_pages_text); + // Shorthand to grab the LensSearchboxController for this instance of Lens. lens::LensSearchboxController* GetLensSearchboxController(); @@ -1333,9 +1316,6 @@ raw_ptr<lens::LensOverlaySidePanelCoordinator> results_side_panel_coordinator_; - // Class for handling key events from the renderer that were not handled. - std::unique_ptr<lens::LensOverlayEventHandler> lens_overlay_event_handler_; - // Layer delegate that handles blurring the background behind the WebUI. std::unique_ptr<lens::LensOverlayBlurLayerDelegate> lens_overlay_blur_layer_delegate_;
diff --git a/chrome/browser/ui/lens/lens_overlay_event_handler.cc b/chrome/browser/ui/lens/lens_overlay_event_handler.cc index fc9728e..fa3ff3d 100644 --- a/chrome/browser/ui/lens/lens_overlay_event_handler.cc +++ b/chrome/browser/ui/lens/lens_overlay_event_handler.cc
@@ -27,26 +27,20 @@ } // namespace LensOverlayEventHandler::LensOverlayEventHandler( - LensOverlayController* lens_overlay_controller) - : lens_overlay_controller_(lens_overlay_controller) {} + LensSearchController* lens_search_controller) + : lens_search_controller_(lens_search_controller) {} bool LensOverlayEventHandler::HandleKeyboardEvent( content::WebContents* source, const input::NativeWebKeyboardEvent& event, views::FocusManager* focus_manager) { - if (!focus_manager || !lens_overlay_controller_->IsOverlayActive()) { + if (!focus_manager || !lens_search_controller_->IsActive()) { return false; } if (IsEscapeEvent(event)) { - // TODO(crbug.com/404941800): This is a roundabout way to close the overlay, - // but this class isn't yet owned by the search controller, so it can't - // directly call it. This should be cleaned up once this class is owned by - // the search controller. - lens_overlay_controller_->GetTabInterface() - ->GetTabFeatures() - ->lens_search_controller() - ->CloseLensAsync(lens::LensOverlayDismissalSource::kEscapeKeyPress); + lens_search_controller_->CloseLensAsync( + lens::LensOverlayDismissalSource::kEscapeKeyPress); return true; } // We only want to copy if the user is not currently making a native text @@ -55,7 +49,7 @@ const bool is_making_selection = source->GetFocusedFrame() && source->GetFocusedFrame()->HasSelection(); if (IsCopyEvent(event) && !is_making_selection) { - lens_overlay_controller_->TriggerCopy(); + lens_search_controller_->lens_overlay_controller()->TriggerCopy(); return true; } return unhandled_keyboard_event_handler_.HandleKeyboardEvent(event,
diff --git a/chrome/browser/ui/lens/lens_overlay_event_handler.h b/chrome/browser/ui/lens/lens_overlay_event_handler.h index 164e0436..3a2a96c 100644 --- a/chrome/browser/ui/lens/lens_overlay_event_handler.h +++ b/chrome/browser/ui/lens/lens_overlay_event_handler.h
@@ -10,14 +10,14 @@ #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h" #include "ui/views/focus/focus_manager.h" -class LensOverlayController; +class LensSearchController; namespace lens { class LensOverlayEventHandler { public: explicit LensOverlayEventHandler( - LensOverlayController* lens_overlay_controller); + LensSearchController* lens_search_controller); bool HandleKeyboardEvent(content::WebContents* source, const input::NativeWebKeyboardEvent& event, @@ -28,8 +28,9 @@ // renderer process. views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_; - // The Lens Overlay controller to direct actions to. - const raw_ptr<LensOverlayController> lens_overlay_controller_; + // The Lens Search controller that owns this class and owns the overlay to + // direct actions to. + const raw_ptr<LensSearchController> lens_search_controller_; }; } // namespace lens
diff --git a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc index 67033e34..b9078ee 100644 --- a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc +++ b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
@@ -511,7 +511,13 @@ void LensOverlaySidePanelCoordinator::BindSidePanel( mojo::PendingReceiver<lens::mojom::LensSidePanelPageHandler> receiver, mojo::PendingRemote<lens::mojom::LensSidePanelPage> page) { - CHECK(state_ == State::kOpeningSidePanel); + // Ideally, this should be a CHECK, but if the user just navigates to the + // WebUI link directly, then this will be called without + // RegisterEntryAndShow() being called. If that is the case, ignore this call. + // More info at crbug.com/417119042. + if (state_ != State::kOpeningSidePanel) { + return; + } side_panel_receiver_.Bind(std::move(receiver)); side_panel_page_.Bind(std::move(page));
diff --git a/chrome/browser/ui/lens/lens_overlay_side_panel_web_view.cc b/chrome/browser/ui/lens/lens_overlay_side_panel_web_view.cc index 10fbd90..9f3324d 100644 --- a/chrome/browser/ui/lens/lens_overlay_side_panel_web_view.cc +++ b/chrome/browser/ui/lens/lens_overlay_side_panel_web_view.cc
@@ -87,7 +87,6 @@ return false; } return coordinator_->GetLensSearchController() - ->lens_overlay_controller() ->lens_overlay_event_handler() ->HandleKeyboardEvent(source, event, GetFocusManager()); }
diff --git a/chrome/browser/ui/lens/lens_search_contextualization_controller.cc b/chrome/browser/ui/lens/lens_search_contextualization_controller.cc index 24943644..cf1f870 100644 --- a/chrome/browser/ui/lens/lens_search_contextualization_controller.cc +++ b/chrome/browser/ui/lens/lens_search_contextualization_controller.cc
@@ -99,6 +99,40 @@ MaybeGetInnerHtml(page_contents, render_frame_host, std::move(callback)); } +#if BUILDFLAG(ENABLE_PDF) +void LensSearchContextualizationController:: + FetchVisiblePageIndexAndGetPartialPdfText( + lens::LensOverlayQueryController* lens_overlay_query_controller, + uint32_t page_count, + PdfPartialPageTextRetrievedCallback callback) { + pdf::PDFDocumentHelper* pdf_helper = + pdf::PDFDocumentHelper::MaybeGetForWebContents( + lens_search_controller_->GetTabInterface()->GetContents()); + if (!pdf_helper || + lens::features::GetLensOverlayPdfSuggestCharacterTarget() == 0 || + page_count == 0) { + return; + } + + CHECK(lens_overlay_query_controller); + CHECK(callback); + lens_overlay_query_controller_ = lens_overlay_query_controller; + pdf_partial_page_text_retrieved_callback_ = std::move(callback); + + // TODO(387306854): Add logic to grab page text form the visible page index. + + // Fetch the first page of text which will be then recursively fetch following + // pages. + pdf_pages_text_.clear(); + pdf_helper->GetPageText( + /*page_index=*/0, + base::BindOnce( + &LensSearchContextualizationController::GetPartialPdfTextCallback, + weak_ptr_factory_.GetWeakPtr(), /*page_index=*/0, page_count, + /*total_characters_retrieved=*/0)); +} +#endif // BUILDFLAG(ENABLE_PDF) + void LensSearchContextualizationController::MaybeGetInnerHtml( std::vector<lens::PageContent> page_contents, content::RenderFrameHost* render_frame_host, @@ -267,6 +301,55 @@ std::move(callback).Run({lens::PageContent(bytes, lens::MimeType::kPdf)}, lens::MimeType::kPdf, page_count); } + +void LensSearchContextualizationController::GetPartialPdfTextCallback( + uint32_t page_index, + uint32_t total_page_count, + uint32_t total_characters_retrieved, + const std::u16string& page_text) { + // Sanity checks that the input is expected. + CHECK_GE(total_page_count, 1u); + CHECK_LT(page_index, total_page_count); + CHECK_EQ(pdf_pages_text_.size(), page_index); + CHECK(lens_overlay_query_controller_); + + // Add the page text to the list of pages and update the total characters + // retrieved count. + pdf_pages_text_.push_back(page_text); + + // Ensure no integer overflow. If overflow, set the total characters retrieved + // to the max value so the loop will exit. + base::CheckedNumeric<uint32_t> total_characters_retrieved_check = + total_characters_retrieved; + total_characters_retrieved_check += page_text.size(); + total_characters_retrieved = total_characters_retrieved_check.ValueOrDefault( + std::numeric_limits<uint32_t>::max()); + + pdf::PDFDocumentHelper* pdf_helper = + pdf::PDFDocumentHelper::MaybeGetForWebContents( + lens_search_controller_->GetTabInterface()->GetContents()); + + // Stop the loop if the character limit is reached or if the page index is + // out of bounds or the PDF helper no longer exists. + if (!pdf_helper || + total_characters_retrieved >= + lens::features::GetLensOverlayPdfSuggestCharacterTarget() || + page_index + 1 >= total_page_count) { + std::move(pdf_partial_page_text_retrieved_callback_).Run(pdf_pages_text_); + lens_overlay_query_controller_->SendPartialPageContentRequest( + pdf_pages_text_); + // Reset the query controller to prevent dangling pointer. + lens_overlay_query_controller_ = nullptr; + return; + } + + pdf_helper->GetPageText( + page_index + 1, + base::BindOnce( + &LensSearchContextualizationController::GetPartialPdfTextCallback, + weak_ptr_factory_.GetWeakPtr(), page_index + 1, total_page_count, + total_characters_retrieved)); +} #endif // BUILDFLAG(ENABLE_PDF) } // namespace lens
diff --git a/chrome/browser/ui/lens/lens_search_contextualization_controller.h b/chrome/browser/ui/lens/lens_search_contextualization_controller.h index 14ceed442..1f0d7e8 100644 --- a/chrome/browser/ui/lens/lens_search_contextualization_controller.h +++ b/chrome/browser/ui/lens/lens_search_contextualization_controller.h
@@ -49,6 +49,10 @@ lens::MimeType primary_content_type, std::optional<uint32_t> pdf_page_count)>; +// Callback type alias for retrieving the text from the PDF pages one by one. +using PdfPartialPageTextRetrievedCallback = + base::OnceCallback<void(std::vector<std::u16string> pdf_pages_text)>; + // Controller responsible for handling contextualization logic for Lens flows. // This includes grabbing content related to the page and issuing Lens requests // so searchbox requests are contextualized. @@ -80,6 +84,17 @@ // be run with no bytes. void GetPageContextualization(PageContentRetrievedCallback callback); +#if BUILDFLAG(ENABLE_PDF) + // Fetches the visible page index from the PDF renderer and then starts the + // process of fetching the text from the PDF to be used for suggest signals. + // This is a no-op if the tab is not a PDF. Once the partial text is + // retrieved, the text is sent to the server via the query controller. + void FetchVisiblePageIndexAndGetPartialPdfText( + lens::LensOverlayQueryController* lens_overlay_query_controller, + uint32_t page_count, + PdfPartialPageTextRetrievedCallback callback); +#endif // BUILDFLAG(ENABLE_PDF) + private: // Gets the inner HTML for contextualization if flag enabled. Otherwise skip // to MaybeGetInnerText(). @@ -135,6 +150,15 @@ pdf::mojom::PdfListener::GetPdfBytesStatus status, const std::vector<uint8_t>& bytes, uint32_t pdf_page_count); + + // Gets the partial text from the PDF to be used for suggest. Schedules for + // the next page of text to be fetched, from the PDF in page order until + // either 1) all the text is received or 2) the character limit is reached. + // This method should only be called by GetPartialPdfText. + void GetPartialPdfTextCallback(uint32_t page_index, + uint32_t total_page_count, + uint32_t total_characters_retrieved, + const std::u16string& page_text); #endif // BUILDFLAG(ENABLE_PDF) // The current state of the contextualization flow. @@ -143,6 +167,18 @@ // Indicates whether the user is currently on a context eligible page. bool is_page_context_eligible_ = true; + // The callback to run when the partial page text is retrieved. This is + // populated when FetchVisiblePageIndexAndGetPartialPdfText is called. + PdfPartialPageTextRetrievedCallback pdf_partial_page_text_retrieved_callback_; + + // The partial representation of a PDF document. The element at a given + // index holds the text of the PDF page at the same index. + std::vector<std::u16string> pdf_pages_text_; + + // The query controller to use for sending partial page content requests. + raw_ptr<lens::LensOverlayQueryController> lens_overlay_query_controller_ = + nullptr; + // Owns this. const raw_ptr<LensSearchController> lens_search_controller_;
diff --git a/chrome/browser/ui/lens/lens_search_controller.cc b/chrome/browser/ui/lens/lens_search_controller.cc index bbbdf501..250bf94 100644 --- a/chrome/browser/ui/lens/lens_search_controller.cc +++ b/chrome/browser/ui/lens/lens_search_controller.cc
@@ -12,6 +12,7 @@ #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/lens/lens_overlay_controller.h" +#include "chrome/browser/ui/lens/lens_overlay_event_handler.h" #include "chrome/browser/ui/lens/lens_overlay_image_helper.h" #include "chrome/browser/ui/lens/lens_overlay_query_controller.h" #include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h" @@ -89,6 +90,9 @@ lens_contextualization_controller_ = CreateLensSearchContextualizationController(); + lens_overlay_event_handler_ = + std::make_unique<lens::LensOverlayEventHandler>(this); + CreatePageContextEligibilityAPI(); } @@ -265,6 +269,14 @@ CloseLensPart2(dismissal_source); } +bool LensSearchController::IsActive() { + return state_ == State::kActive; +} + +bool LensSearchController::IsClosing() { + return state_ == State::kClosing || state_ == State::kClosingSidePanel; +} + tabs::TabInterface* LensSearchController::GetTabInterface() { return tab_; } @@ -304,6 +316,12 @@ return lens_searchbox_controller_.get(); } +lens::LensOverlayEventHandler* +LensSearchController::lens_overlay_event_handler() { + CheckInitialized(initialized_); + return lens_overlay_event_handler_.get(); +} + optimization_guide::PageContextEligibility* LensSearchController::page_context_eligibility() { CheckInitialized(initialized_); @@ -579,7 +597,3 @@ return; } } - -bool LensSearchController::IsClosing() { - return state_ == State::kClosing || state_ == State::kClosingSidePanel; -}
diff --git a/chrome/browser/ui/lens/lens_search_controller.h b/chrome/browser/ui/lens/lens_search_controller.h index 6707f26..f9a4f86 100644 --- a/chrome/browser/ui/lens/lens_search_controller.h +++ b/chrome/browser/ui/lens/lens_search_controller.h
@@ -24,6 +24,7 @@ class GURL; namespace lens { +class LensOverlayEventHandler; class LensOverlayGen204Controller; class LensOverlaySidePanelCoordinator; class LensPermissionBubbleController; @@ -128,6 +129,13 @@ // nice if the overlay is visible when this is called. virtual void CloseLensSync(lens::LensOverlayDismissalSource dismissal_source); + // Returns true if Lens is currently active on this tab. + bool IsActive(); + + // Returns true if the overlay is in the process of closing. If true, Lens on + // this tab will soon be off. + bool IsClosing(); + // Returns the tab interface that owns this controller. tabs::TabInterface* GetTabInterface(); @@ -150,6 +158,9 @@ // Returns the LensSearchboxController. lens::LensSearchboxController* lens_searchbox_controller(); + // Returns the event handler for this instance of the Lens Overlay. + lens::LensOverlayEventHandler* lens_overlay_event_handler(); + optimization_guide::PageContextEligibility* page_context_eligibility(); // Returns the LensSearchContextualizationController. @@ -329,10 +340,6 @@ void WillDetach(tabs::TabInterface* tab, tabs::TabInterface::DetachReason reason); - // Returns true if the overlay is in the process of closing. If true, Lens on - // this tab will soon be off. - bool IsClosing(); - // Whether the LensSearchController has been initialized. Meaning, all the // dependencies have been initialized and the controller is ready to use. bool initialized_ = false; @@ -374,6 +381,11 @@ std::unique_ptr<lens::LensSearchContextualizationController> lens_contextualization_controller_; + // Class for handling key events from the renderer that were not handled. This + // is used by both the overlay and the WebUI to share common event handling + // logic. + std::unique_ptr<lens::LensOverlayEventHandler> lens_overlay_event_handler_; + // Holds subscriptions for TabInterface callbacks. std::vector<base::CallbackListSubscription> tab_subscriptions_;
diff --git a/chrome/browser/ui/lens/lens_searchbox_controller.cc b/chrome/browser/ui/lens/lens_searchbox_controller.cc index b59c6ee27..d07f7d56 100644 --- a/chrome/browser/ui/lens/lens_searchbox_controller.cc +++ b/chrome/browser/ui/lens/lens_searchbox_controller.cc
@@ -134,14 +134,10 @@ } void LensSearchboxController::OnTextModified() { - // TOOD(crbug.com/404941800): Verify this doesn't break if the overlay is - // off. lens_search_controller_->lens_overlay_controller()->ClearTextSelection(); } void LensSearchboxController::OnThumbnailRemoved() { - // TOOD(crbug.com/404941800): Verify this doesn't break if the overlay is - // off. lens_search_controller_->lens_overlay_controller()->ClearRegionSelection(); }
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc index 483c3833..d6806e80 100644 --- a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc +++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
@@ -267,6 +267,11 @@ chrome::ShowSettingsSubPage(browser, chrome::kCookieSettingsSubPage); } +void ChromePageInfoDelegate::ShowIncognitoSettings() { + Browser* browser = chrome::FindBrowserWithTab(web_contents_); + chrome::ShowSettingsSubPage(browser, chrome::kIncognitoSettingsSubPage); +} + void ChromePageInfoDelegate::ShowAllSitesSettingsFilteredByRwsOwner( const std::u16string& rws_owner) { Browser* browser = chrome::FindBrowserWithTab(web_contents_);
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_delegate.h index 3664471..066a2644 100644 --- a/chrome/browser/ui/page_info/chrome_page_info_delegate.h +++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
@@ -63,6 +63,7 @@ // page, depending on context. void ShowSiteSettings(const GURL& site_url) override; void ShowCookiesSettings() override; + void ShowIncognitoSettings() override; void ShowAllSitesSettingsFilteredByRwsOwner( const std::u16string& rws_owner) override; void ShowSyncSettings() override;
diff --git a/chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.cc b/chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.cc index 36a395a..8a62a87e 100644 --- a/chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.cc +++ b/chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.cc
@@ -154,14 +154,30 @@ return; } - tabs::TabFeatures* tab_features = - tabs::TabInterface::GetFromContents(&GetWebContents())->GetTabFeatures(); + // TODO(crbug.com/401033983): This code should only be running in a tab, + // so TabInterface::GetFromContents() should be reliable. However, some tests + // appear to end up with this tab helper failing to extract a TabInterface, so + // MaybeGetFromContents() is used as a test-only check. See + // crbug.com/417176824 for those tests. This should disappear when moving + // MemorySaver to run as a TabFeature. + tabs::TabInterface* tab_interface = + tabs::TabInterface::MaybeGetFromContents(web_contents()); + if (tab_interface == nullptr) { + CHECK_IS_TEST(); + return; + } + tabs::TabFeatures* tab_features = tab_interface->GetTabFeatures(); if (!tab_features) { // Tab features may not be present at shutdown. return; } memory_saver::MemorySaverChipController* controller = tab_features->memory_saver_chip_controller(); + if (!controller) { + // Tab features can be faked in tests. + CHECK_IS_TEST(); + return; + } switch (chip_state_) { case memory_saver::ChipState::HIDDEN:
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_collector.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_collector.cc index 0d3cf68..1f229eb 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_collector.cc +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_collector.cc
@@ -9,6 +9,7 @@ #include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" #include "components/performance_manager/public/resource_attribution/page_context.h" #include "components/performance_manager/public/resource_attribution/resource_types.h" +#include "components/tabs/public/tab_interface.h" #include "content/public/browser/web_contents.h" namespace { @@ -80,12 +81,14 @@ resource_attribution::AsContext<PageContext>(page_context) .GetWebContents(); if (web_contents) { - auto* const tab_resource_usage_tab_helper = - TabResourceUsageTabHelper::FromWebContents(web_contents); - if (tab_resource_usage_tab_helper) { - tab_resource_usage_tab_helper->SetMemoryUsageInBytes( - memory_result->private_footprint_kb * 1024); - did_resource_update = true; + if (auto* tab = + tabs::TabInterface::MaybeGetFromContents(web_contents)) { + if (auto* const helper = + tab->GetTabFeatures()->resource_usage_helper()) { + helper->SetMemoryUsageInBytes(memory_result->private_footprint_kb * + 1024); + did_resource_update = true; + } } } }
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_collector_browsertest.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_collector_browsertest.cc index efa09d4..a236922e 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_collector_browsertest.cc +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_collector_browsertest.cc
@@ -48,10 +48,10 @@ TabStripModel* const model = GetTabStripModel(); uint64_t bytes_used = 100; TabResourceUsageTabHelper* const first_tab_helper = - TabResourceUsageTabHelper::FromWebContents(model->GetWebContentsAt(0)); + model->GetTabAtIndex(0)->GetTabFeatures()->resource_usage_helper(); first_tab_helper->SetMemoryUsageInBytes(bytes_used); TabResourceUsageTabHelper* const second_tab_helper = - TabResourceUsageTabHelper::FromWebContents(model->GetWebContentsAt(0)); + model->GetTabAtIndex(1)->GetTabFeatures()->resource_usage_helper(); second_tab_helper->SetMemoryUsageInBytes(bytes_used); // Collector refresh memory usage data for all tabs @@ -77,19 +77,18 @@ AddAndWaitForTabReady(); TabStripModel* const model = GetTabStripModel(); uint64_t bytes_used = 100; - content::WebContents* const first_tab_contents = model->GetWebContentsAt(0); TabResourceUsageTabHelper* const first_tab_helper = - TabResourceUsageTabHelper::FromWebContents(first_tab_contents); + model->GetTabAtIndex(0)->GetTabFeatures()->resource_usage_helper(); first_tab_helper->SetMemoryUsageInBytes(bytes_used); TabResourceUsageTabHelper* const second_tab_helper = - TabResourceUsageTabHelper::FromWebContents(model->GetWebContentsAt(1)); + model->GetTabAtIndex(1)->GetTabFeatures()->resource_usage_helper(); second_tab_helper->SetMemoryUsageInBytes(bytes_used); // Collector refresh memory usage data for the first web contents base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); ResourceUsageCollectorObserver observer(run_loop.QuitClosure()); TabResourceUsageCollector::Get()->ImmediatelyRefreshMetrics( - first_tab_contents); + model->GetWebContentsAt(0)); run_loop.Run(); EXPECT_NE(bytes_used, first_tab_helper->GetMemoryUsageInBytes());
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.cc index 1ec935b..49b1bf1 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.cc +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.cc
@@ -11,14 +11,10 @@ is_high_memory_usage_ = memory_usage_bytes_ > kHighMemoryUsageThresholdBytes; } -WEB_CONTENTS_USER_DATA_KEY_IMPL(TabResourceUsageTabHelper); - TabResourceUsageTabHelper::~TabResourceUsageTabHelper() = default; -TabResourceUsageTabHelper::TabResourceUsageTabHelper( - content::WebContents* contents) - : content::WebContentsObserver(contents), - content::WebContentsUserData<TabResourceUsageTabHelper>(*contents), +TabResourceUsageTabHelper::TabResourceUsageTabHelper(tabs::TabInterface& tab) + : ContentsObservingTabFeature(tab), resource_usage_(base::MakeRefCounted<TabResourceUsage>()) {} void TabResourceUsageTabHelper::PrimaryPageChanged(content::Page&) {
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h index 5488be07..22fe914 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h
@@ -5,9 +5,13 @@ #ifndef CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_TAB_RESOURCE_USAGE_TAB_HELPER_H_ #define CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_TAB_RESOURCE_USAGE_TAB_HELPER_H_ +#include "chrome/browser/ui/tabs/contents_observing_tab_feature.h" #include "components/performance_manager/public/features.h" -#include "content/public/browser/web_contents_observer.h" -#include "content/public/browser/web_contents_user_data.h" + +namespace tabs { +class TabFeatures; +class TabInterface; +} // namespace tabs class TabResourceUsage : public base::RefCounted<TabResourceUsage> { public: @@ -31,14 +35,9 @@ }; // Per-tab class to keep track of current memory usage for each tab. -class TabResourceUsageTabHelper - : public content::WebContentsObserver, - public content::WebContentsUserData<TabResourceUsageTabHelper> { +class TabResourceUsageTabHelper : public tabs::ContentsObservingTabFeature { public: - TabResourceUsageTabHelper(const TabResourceUsageTabHelper&) = delete; - TabResourceUsageTabHelper& operator=(const TabResourceUsageTabHelper&) = - delete; - + explicit TabResourceUsageTabHelper(tabs::TabInterface& contents); ~TabResourceUsageTabHelper() override; // content::WebContentsObserver @@ -50,9 +49,7 @@ scoped_refptr<const TabResourceUsage> resource_usage() const; private: - friend class content::WebContentsUserData<TabResourceUsageTabHelper>; - explicit TabResourceUsageTabHelper(content::WebContents* contents); - WEB_CONTENTS_USER_DATA_KEY_DECL(); + friend class tabs::TabFeatures; scoped_refptr<TabResourceUsage> resource_usage_; };
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_browsertest.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_browsertest.cc new file mode 100644 index 0000000..ae7dc28 --- /dev/null +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_browsertest.cc
@@ -0,0 +1,74 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" + +#include "base/functional/callback_forward.h" +#include "base/test/bind.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/interaction/interactive_browser_test.h" +#include "components/tabs/public/tab_interface.h" +#include "content/public/test/browser_test.h" +#include "ui/base/interaction/element_identifier.h" +#include "url/gurl.h" + +namespace { +constexpr char kTestDomain[] = "https://foo.bar"; +constexpr uint64_t kTestMemoryUsageBytes = 100000; +DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTabId); +} // namespace + +class TabResourceUsageTabHelperUiTest : public InteractiveBrowserTest { + protected: + using WithTabHelperCallback = + base::OnceCallback<void(TabResourceUsageTabHelper&)>; + auto WithTabHelper(ui::ElementIdentifier instrumented_tab_id, + WithTabHelperCallback callback) { + return WithElement( + instrumented_tab_id, + [callback = std::move(callback)](ui::TrackedElement* el) mutable { + auto* const tab = tabs::TabInterface::GetFromContents( + AsInstrumentedWebContents(el)->web_contents()); + CHECK(tab); + std::move(callback).Run( + *tab->GetTabFeatures()->resource_usage_helper()); + }); + } + + template <typename T> + using CheckTabHelperCallback = + base::OnceCallback<T(TabResourceUsageTabHelper&)>; + template <typename T, typename M> + auto CheckTabHelper(ui::ElementIdentifier instrumented_tab_id, + CheckTabHelperCallback<T> callback, + M&& matcher) { + return CheckElement( + instrumented_tab_id, + [callback = std::move(callback)](ui::TrackedElement* el) mutable -> T { + auto* const tab = tabs::TabInterface::GetFromContents( + AsInstrumentedWebContents(el)->web_contents()); + CHECK(tab); + return std::move(callback).Run( + *tab->GetTabFeatures()->resource_usage_helper()); + }, + std::forward<M>(matcher)); + } +}; + +// Clears memory usage on navigate. +IN_PROC_BROWSER_TEST_F(TabResourceUsageTabHelperUiTest, + ClearsMemoryUsageOnNavigate) { + RunTestSequence( + InstrumentTab(kTabId), + WithTabHelper(kTabId, + base::BindOnce([](TabResourceUsageTabHelper& tab_helper) { + tab_helper.SetMemoryUsageInBytes(kTestMemoryUsageBytes); + })), + NavigateWebContents(kTabId, GURL(kTestDomain)), + CheckTabHelper(kTabId, + base::BindOnce([](TabResourceUsageTabHelper& tab_helper) { + return tab_helper.GetMemoryUsageInBytes(); + }), + 0u)); +}
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_interactive_uitest.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_interactive_uitest.cc index 978fd73f..008f866 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_interactive_uitest.cc +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_interactive_uitest.cc
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/functional/bind.h" +#include "base/test/bind.h" +#include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" #include "chrome/browser/ui/performance_controls/test_support/memory_metrics_refresh_waiter.h" #include "chrome/browser/ui/performance_controls/test_support/resource_usage_collector_observer.h" @@ -12,9 +15,16 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "ui/base/interaction/element_identifier.h" +#include "ui/base/interaction/element_tracker.h" #include "url/gurl.h" -class TabResourceUsageTabHelperTest : public InteractiveBrowserTest { +namespace { +DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTabContents); +DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kUpdatedEvent); +constexpr uint64_t kMaxByteUsed = std::numeric_limits<int64_t>::max(); +} // namespace + +class TabResourceUsageTabHelperUiTest : public InteractiveBrowserTest { public: void SetUpOnMainThread() override { InteractiveBrowserTest::SetUpOnMainThread(); @@ -32,38 +42,77 @@ waiter.Wait(); }); } + + using WithTabHelperCallback = + base::OnceCallback<void(TabResourceUsageTabHelper&)>; + auto WithTabHelper(ui::ElementIdentifier instrumented_tab_id, + WithTabHelperCallback callback) { + return WithElement( + instrumented_tab_id, + [callback = std::move(callback)](ui::TrackedElement* el) mutable { + auto* const tab = tabs::TabInterface::GetFromContents( + AsInstrumentedWebContents(el)->web_contents()); + CHECK(tab); + std::move(callback).Run( + *tab->GetTabFeatures()->resource_usage_helper()); + }); + } + + template <typename T> + using CheckTabHelperCallback = + base::OnceCallback<T(TabResourceUsageTabHelper&)>; + template <typename T, typename M> + auto CheckTabHelper(ui::ElementIdentifier instrumented_tab_id, + CheckTabHelperCallback<T> callback, + M&& matcher) { + return CheckElement( + instrumented_tab_id, + [callback = std::move(callback)](ui::TrackedElement* el) mutable -> T { + auto* const tab = tabs::TabInterface::GetFromContents( + AsInstrumentedWebContents(el)->web_contents()); + CHECK(tab); + return std::move(callback).Run( + *tab->GetTabFeatures()->resource_usage_helper()); + }, + std::forward<M>(matcher)); + } }; -IN_PROC_BROWSER_TEST_F(TabResourceUsageTabHelperTest, MemoryUsagePopulated) { - DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTabContents); +IN_PROC_BROWSER_TEST_F(TabResourceUsageTabHelperUiTest, MemoryUsagePopulated) { RunTestSequence( - InstrumentTab(kFirstTabContents, 0), + InstrumentTab(kFirstTabContents), NavigateWebContents(kFirstTabContents, GetURL()), - ForceRefreshMemoryMetrics(), Check([=, this]() { - content::WebContents* const web_contents = - browser()->tab_strip_model()->GetWebContentsAt(0); - auto* const resource_usage = - TabResourceUsageTabHelper::FromWebContents(web_contents); - return resource_usage && resource_usage->GetMemoryUsageInBytes() != 0; - })); + ForceRefreshMemoryMetrics(), + CheckTabHelper(kFirstTabContents, + base::BindOnce([](TabResourceUsageTabHelper& helper) { + return helper.GetMemoryUsageInBytes(); + }), + testing::Ne(0))); } -IN_PROC_BROWSER_TEST_F(TabResourceUsageTabHelperTest, +IN_PROC_BROWSER_TEST_F(TabResourceUsageTabHelperUiTest, MemoryUsageUpdatesAfterNavigation) { - ui_test_utils::NavigateToURLWithDisposition( - browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::CURRENT_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); - content::WebContents* const web_contents = - browser()->tab_strip_model()->GetWebContentsAt(0); - auto* const resource_usage = - TabResourceUsageTabHelper::FromWebContents(web_contents); - const uint64_t bytes_used = std::numeric_limits<int64_t>::max(); - resource_usage->SetMemoryUsageInBytes(bytes_used); - base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); - ResourceUsageCollectorObserver observer(run_loop.QuitClosure()); - ui_test_utils::NavigateToURLWithDisposition( - browser(), GetURL(), WindowOpenDisposition::CURRENT_TAB, - ui_test_utils::BROWSER_TEST_NO_WAIT); - run_loop.Run(); - EXPECT_NE(bytes_used, resource_usage->GetMemoryUsageInBytes()); + std::unique_ptr<ResourceUsageCollectorObserver> observer; + RunTestSequence( + InstrumentTab(kFirstTabContents), + WithTabHelper(kFirstTabContents, + base::BindOnce([](TabResourceUsageTabHelper& helper) { + helper.SetMemoryUsageInBytes(kMaxByteUsed); + })), + WithElement( + kBrowserViewElementId, + [&observer](ui::TrackedElement* el) { + observer = std::make_unique<ResourceUsageCollectorObserver>( + base::BindLambdaForTesting([el]() { + ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent( + el, kUpdatedEvent); + })); + }), + NavigateWebContents(kFirstTabContents, GetURL()), + WaitForEvent(kBrowserViewElementId, kUpdatedEvent), + CheckTabHelper(kFirstTabContents, + base::BindOnce([](TabResourceUsageTabHelper& helper) { + return helper.GetMemoryUsageInBytes(); + }), + testing::Ne(kMaxByteUsed))); }
diff --git a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_unittest.cc b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_unittest.cc index 3a36f6e..42ff3b1e 100644 --- a/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_unittest.cc +++ b/chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper_unittest.cc
@@ -4,44 +4,60 @@ #include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" +#include "chrome/browser/ui/tabs/test_util.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/browser_task_environment.h" +#include "content/public/test/test_renderer_host.h" +#include "content/public/test/web_contents_tester.h" #include "url/gurl.h" namespace { -constexpr char kTestDomain[] = "http://foo.bar"; constexpr uint64_t kTestMemoryUsageBytes = 100000; } // namespace -class TabResourceUsageTabHelperTest : public ChromeRenderViewHostTestHarness { - protected: - TabResourceUsageTabHelper* InitializeTabHelper() { - TabResourceUsageTabHelper::CreateForWebContents(web_contents()); - return TabResourceUsageTabHelper::FromWebContents(web_contents()); +class TabResourceUsageTabHelperUiTest : public testing::Test { + public: + TabResourceUsageTabHelperUiTest() = default; + ~TabResourceUsageTabHelperUiTest() override = default; + + void SetUp() override { + auto web_contents = + content::WebContentsTester::CreateTestWebContents(&profile_, nullptr); + tab_strip_model_.AppendWebContents(std::move(web_contents), true); + const int index = tab_strip_model_.count() - 1; + auto* tab = tab_strip_model_.GetTabAtIndex(index); + helper_ = tab->GetTabFeatures()->SetResourceUsageHelperForTesting( + std::make_unique<TabResourceUsageTabHelper>(*tab)); } + + void TearDown() override { helper_ = nullptr; } + + protected: + content::BrowserTaskEnvironment task_environment_; + content::RenderViewHostTestEnabler rvh_test_enabler_; + TestTabStripModelDelegate delegate_; + TestingProfile profile_; + TabStripModel tab_strip_model_{&delegate_, &profile_}; + tabs::PreventTabFeatureInitialization prevent_; + raw_ptr<TabResourceUsageTabHelper> helper_; }; // Return memory usage that was set on the tab helper. -TEST_F(TabResourceUsageTabHelperTest, ReturnsMemoryUsageInBytes) { - auto* const tab_helper = InitializeTabHelper(); - tab_helper->SetMemoryUsageInBytes(kTestMemoryUsageBytes); - EXPECT_EQ(tab_helper->GetMemoryUsageInBytes(), kTestMemoryUsageBytes); -} - -// Clears memory usage on navigate. -TEST_F(TabResourceUsageTabHelperTest, ClearsMemoryUsageOnNavigate) { - auto* const tab_helper = InitializeTabHelper(); - tab_helper->SetMemoryUsageInBytes(kTestMemoryUsageBytes); - NavigateAndCommit(GURL(kTestDomain)); - EXPECT_EQ(tab_helper->GetMemoryUsageInBytes(), 0u); +TEST_F(TabResourceUsageTabHelperUiTest, ReturnsMemoryUsageInBytes) { + helper_->SetMemoryUsageInBytes(kTestMemoryUsageBytes); + EXPECT_EQ(helper_->GetMemoryUsageInBytes(), kTestMemoryUsageBytes); } // Correctly reports whether memory usage is high after memory usage is set. -TEST_F(TabResourceUsageTabHelperTest, HighMemoryUsage) { - auto* const tab_helper = InitializeTabHelper(); +TEST_F(TabResourceUsageTabHelperUiTest, HighMemoryUsage) { uint64_t const high_memory_usage_threshold = TabResourceUsage::kHighMemoryUsageThresholdBytes; - tab_helper->SetMemoryUsageInBytes(high_memory_usage_threshold); - EXPECT_FALSE(tab_helper->resource_usage()->is_high_memory_usage()); - tab_helper->SetMemoryUsageInBytes(high_memory_usage_threshold + 1); - EXPECT_TRUE(tab_helper->resource_usage()->is_high_memory_usage()); + helper_->SetMemoryUsageInBytes(high_memory_usage_threshold); + EXPECT_FALSE(helper_->resource_usage()->is_high_memory_usage()); + helper_->SetMemoryUsageInBytes(high_memory_usage_threshold + 1); + EXPECT_TRUE(helper_->resource_usage()->is_high_memory_usage()); }
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc index 96d7d60..dcb5019 100644 --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc
@@ -87,7 +87,6 @@ #include "chrome/browser/ui/focus_tab_after_navigation_helper.h" #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h" #include "chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.h" -#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" #include "chrome/browser/ui/prefs/prefs_tab_helper.h" #include "chrome/browser/ui/privacy_sandbox/privacy_sandbox_prompt_helper.h" #include "chrome/browser/ui/recently_audible_helper.h" @@ -659,7 +658,6 @@ SearchTabHelper::CreateForWebContents(web_contents); TabDialogs::CreateForWebContents(web_contents); MemorySaverChipTabHelper::CreateForWebContents(web_contents); - TabResourceUsageTabHelper::CreateForWebContents(web_contents); if (base::FeatureList::IsEnabled(features::kTabHoverCardImages) || base::FeatureList::IsEnabled(features::kWebUITabStrip)) { ThumbnailTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/tabs/public/tab_features.h b/chrome/browser/ui/tabs/public/tab_features.h index 364ae0fe..5249d4fc 100644 --- a/chrome/browser/ui/tabs/public/tab_features.h +++ b/chrome/browser/ui/tabs/public/tab_features.h
@@ -14,17 +14,18 @@ #include "chrome/common/buildflags.h" class ChromeAutofillAiClient; +class FileSystemAccessPageActionController; class FromGWSNavigationAndKeepAliveRequestObserver; +class IntentPickerViewPageActionController; class LensOverlayController; class LensSearchController; class PinnedTranslateActionListener; class Profile; +class PwaInstallPageActionController; class ReadAnythingSidePanelController; class SidePanelRegistry; +class TabResourceUsageTabHelper; class TranslatePageActionController; -class IntentPickerViewPageActionController; -class FileSystemAccessPageActionController; -class PwaInstallPageActionController; namespace commerce { class CommerceUiTabHelper; @@ -226,6 +227,15 @@ return new_tab_footer_controller_.get(); } + TabResourceUsageTabHelper* resource_usage_helper() { + return resource_usage_helper_.get(); + } + + // Note: Temporary until there is a more uniform way to swap out features for + // testing. + TabResourceUsageTabHelper* SetResourceUsageHelperForTesting( + std::unique_ptr<TabResourceUsageTabHelper> resource_usage_helper); + #if BUILDFLAG(ENABLE_GLIC) glic::GlicPageContextEligibilityObserver* glic_page_context_eligibility_observer() { @@ -364,6 +374,8 @@ std::unique_ptr<new_tab_footer::NewTabFooterController> new_tab_footer_controller_; + std::unique_ptr<TabResourceUsageTabHelper> resource_usage_helper_; + // Must be the last member. base::WeakPtrFactory<TabFeatures> weak_factory_{this}; };
diff --git a/chrome/browser/ui/tabs/tab_features.cc b/chrome/browser/ui/tabs/tab_features.cc index a7b2a8dc..51777f8c 100644 --- a/chrome/browser/ui/tabs/tab_features.cc +++ b/chrome/browser/ui/tabs/tab_features.cc
@@ -37,6 +37,7 @@ #include "chrome/browser/ui/lens/lens_search_controller.h" #include "chrome/browser/ui/page_action/page_action_icon_type.h" #include "chrome/browser/ui/performance_controls/memory_saver_chip_controller.h" +#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" #include "chrome/browser/ui/tabs/inactive_window_mouse_event_controller.h" #include "chrome/browser/ui/tabs/public/tab_dialog_manager.h" #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data.h" @@ -322,6 +323,8 @@ FromGWSNavigationAndKeepAliveRequestObserver::MaybeCreateForWebContents( tab.GetContents()); + resource_usage_helper_ = std::make_unique<TabResourceUsageTabHelper>(tab); + task_manager::WebContentsTags::CreateForTabContents(tab.GetContents()); #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \ @@ -333,6 +336,12 @@ TabFeatures::TabFeatures() = default; +TabResourceUsageTabHelper* TabFeatures::SetResourceUsageHelperForTesting( + std::unique_ptr<TabResourceUsageTabHelper> resource_usage_helper) { + resource_usage_helper_ = std::move(resource_usage_helper); + return resource_usage_helper_.get(); +} + std::unique_ptr<LensSearchController> TabFeatures::CreateLensController( TabInterface* tab) { return std::make_unique<LensSearchController>(tab);
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.cc b/chrome/browser/ui/tabs/tab_renderer_data.cc index c6abafed..db8f64f 100644 --- a/chrome/browser/ui/tabs/tab_renderer_data.cc +++ b/chrome/browser/ui/tabs/tab_renderer_data.cc
@@ -165,9 +165,8 @@ memory_saver::GetDiscardedMemorySavingsInBytes(contents); } - const auto* const resource_tab_helper = - TabResourceUsageTabHelper::FromWebContents(contents); - if (resource_tab_helper) { + if (const auto* const resource_tab_helper = + tab->GetTabFeatures()->resource_usage_helper()) { data.tab_resource_usage = resource_tab_helper->resource_usage(); }
diff --git a/chrome/browser/ui/tabs/tab_renderer_data_unittest.cc b/chrome/browser/ui/tabs/tab_renderer_data_unittest.cc index 9226552..ae3753d 100644 --- a/chrome/browser/ui/tabs/tab_renderer_data_unittest.cc +++ b/chrome/browser/ui/tabs/tab_renderer_data_unittest.cc
@@ -335,7 +335,11 @@ TEST_F(TabRendererDataTest, TabLifecycleManagement) { int index = AddTab(); - content::WebContents* wc = tab_strip_model_.GetWebContentsAt(index); + + auto* tab = tab_strip_model_.GetTabAtIndex(index); + auto* features = tab->GetTabFeatures(); + auto* usage_helper = features->SetResourceUsageHelperForTesting( + std::make_unique<TabResourceUsageTabHelper>(*tab)); TabRendererData data_default = TabRendererData::FromTabInModel(&tab_strip_model_, index); @@ -344,9 +348,6 @@ EXPECT_EQ(data_default.discarded_memory_savings_in_bytes, 0); EXPECT_TRUE(data_default.tab_resource_usage); - TabResourceUsageTabHelper::CreateForWebContents(wc); - auto* usage_helper = TabResourceUsageTabHelper::FromWebContents(wc); - ASSERT_NE(nullptr, usage_helper); usage_helper->SetMemoryUsageInBytes(1234); TabRendererData data_usage = TabRendererData::FromTabInModel(&tab_strip_model_, index);
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc index e9796954..0b0a9558 100644 --- a/chrome/browser/ui/tabs/tab_strip_model.cc +++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -1620,12 +1620,13 @@ SplitTabChange::SplitTabRemoveReason::kSplitTabUpdated); if (update_type == SplitUpdateType::kReplace) { - int destination_index = - update_index < active_index() ? active_index() - 1 : active_index(); - MoveTabToIndexImpl(update_index, destination_index, - GetActiveTab()->GetGroup(), GetActiveTab()->IsPinned(), - true); - CloseWebContentsAt(active_index() + 1, TabCloseTypes::CLOSE_USER_GESTURE); + // The previous active tab to close is moved just before if the replacing + // tab is to the left and after if to the right. + int close_index = + update_index < active_index() ? active_index() - 1 : active_index() + 1; + MoveTabToIndexImpl(update_index, active_index(), GetActiveTab()->GetGroup(), + GetActiveTab()->IsPinned(), true); + CloseWebContentsAt(close_index, TabCloseTypes::CLOSE_USER_GESTURE); } else { int initial_active_index = active_index(); std::optional<tab_groups::TabGroupId> destination_group =
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc index 2f518c4..c743618 100644 --- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc +++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -2158,6 +2158,29 @@ EXPECT_TRUE(tabstrip()->empty()); } +TEST_F(TabStripModelTest, ReverseAndReplaceTabsInSplit) { + ASSERT_NO_FATAL_FAILURE( + PrepareTabstripForSelectionTest(tabstrip(), 3, 0, {1})); + + tabstrip()->ActivateTabAt( + 2, TabStripUserGestureDetails( + TabStripUserGestureDetails::GestureType::kOther)); + + split_tabs::SplitTabId split_tab_id = + tabstrip()->AddToNewSplit({1}, split_tabs::SplitTabLayout::kVertical); + EXPECT_EQ("0 1s 2s", GetTabStripStateString(tabstrip())); + + tabstrip()->ReverseTabsInSplit(split_tab_id); + EXPECT_EQ("0 2s 1s", GetTabStripStateString(tabstrip())); + + tabstrip()->UpdateActiveTabInSplit(split_tab_id, 0, + TabStripModel::SplitUpdateType::kReplace); + EXPECT_EQ("0s 1s", GetTabStripStateString(tabstrip())); + + tabstrip()->CloseAllTabs(); + EXPECT_TRUE(tabstrip()->empty()); +} + // Tests IsContextMenuCommandEnabled and ExecuteContextMenuCommand with // CommandToggleGrouped. TEST_F(TabStripModelTest, CommandToggleGrouped) {
diff --git a/chrome/browser/ui/views/new_tab_footer/footer_controller.cc b/chrome/browser/ui/views/new_tab_footer/footer_controller.cc index 4f7213a..8ae1c724 100644 --- a/chrome/browser/ui/views/new_tab_footer/footer_controller.cc +++ b/chrome/browser/ui/views/new_tab_footer/footer_controller.cc
@@ -7,22 +7,66 @@ #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h" +#include "chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h" +#include "content/public/browser/navigation_entry.h" namespace new_tab_footer { +// TODO (crbug.com/415116344) add unittest coverage. NewTabFooterController::NewTabFooterController(tabs::TabInterface* tab) : tab_(tab) { // TODO(crbug.com/4438803): Support SideBySide. - if (!features::IsNtpFooterEnabledWithoutSideBySide()) { + if (features::IsNtpFooterEnabledWithoutSideBySide()) { + footer_web_view_ = tab_->GetBrowserWindowInterface()->NewTabFooterWebView(); + } + content::WebContentsObserver::Observe(tab_->GetContents()); + tab_did_activate_callback_subscription_ = tab_->RegisterDidActivate( + base::BindRepeating(&NewTabFooterController::TabForegrounded, + weak_factory_.GetWeakPtr())); +} + +NewTabFooterController::~NewTabFooterController() = default; + +void NewTabFooterController::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (tab_->IsActivated()) { + UpdateFooterVisibility(); + } +} + +void NewTabFooterController::UpdateFooterVisibility() { + if (!footer_web_view_) { return; } - auto* footer_web_view = - tab_->GetBrowserWindowInterface()->NewTabFooterWebView(); - CHECK(footer_web_view); - // TODO(crbug.com/409056427): Show/hide the footer based on what tab is being - // used. - footer_web_view->ShowUI(); + GURL url = + tab_->GetContents()->GetController().GetLastCommittedEntry()->GetURL(); + if (url.is_empty()) { + url = tab_->GetContents()->GetController().GetVisibleEntry()->GetURL(); + } + + if (ntp_footer::IsExtensionNtp( + url, tab_->GetBrowserWindowInterface()->GetProfile())) { + ShowUI(); + } else { + CloseUI(); + } +} + +void NewTabFooterController::TabForegrounded(tabs::TabInterface* tab) { + UpdateFooterVisibility(); +} + +void NewTabFooterController::ShowUI() { + if (footer_web_view_) { + footer_web_view_->ShowUI(); + } +} + +void NewTabFooterController::CloseUI() { + if (footer_web_view_) { + footer_web_view_->CloseUI(); + } } } // namespace new_tab_footer
diff --git a/chrome/browser/ui/views/new_tab_footer/footer_controller.h b/chrome/browser/ui/views/new_tab_footer/footer_controller.h index c1195c6a..755a3df7 100644 --- a/chrome/browser/ui/views/new_tab_footer/footer_controller.h +++ b/chrome/browser/ui/views/new_tab_footer/footer_controller.h
@@ -5,19 +5,36 @@ #ifndef CHROME_BROWSER_UI_VIEWS_NEW_TAB_FOOTER_FOOTER_CONTROLLER_H_ #define CHROME_BROWSER_UI_VIEWS_NEW_TAB_FOOTER_FOOTER_CONTROLLER_H_ +#include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h" #include "components/tabs/public/tab_interface.h" +#include "content/public/browser/web_contents_observer.h" namespace new_tab_footer { // Class used to manage the state of the new tab footer. -class NewTabFooterController { +class NewTabFooterController : public content::WebContentsObserver { public: explicit NewTabFooterController(tabs::TabInterface* tab); NewTabFooterController(const NewTabFooterController&) = delete; NewTabFooterController& operator=(const NewTabFooterController&) = delete; + ~NewTabFooterController() override; private: + // content::WebContentsObserver: + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override; + + void UpdateFooterVisibility(); + // Called when the associated tab enters the foreground. + void TabForegrounded(tabs::TabInterface* tab); + void ShowUI(); + void CloseUI(); + const raw_ptr<tabs::TabInterface> tab_; + raw_ptr<new_tab_footer::NewTabFooterWebView> footer_web_view_; + base::CallbackListSubscription tab_did_activate_callback_subscription_; + + base::WeakPtrFactory<NewTabFooterController> weak_factory_{this}; }; } // namespace new_tab_footer
diff --git a/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc b/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc new file mode 100644 index 0000000..b329e865 --- /dev/null +++ b/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc
@@ -0,0 +1,112 @@ +// Copyright 2025 The Chromium Authors +// 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/chrome_test_extension_loader.h" +#include "chrome/browser/extensions/install_verifier.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/ui_features.h" +#include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h" +#include "chrome/browser/ui/webui/test_support/webui_interactive_test_mixin.h" +#include "chrome/test/base/ui_test_utils.h" +#include "chrome/test/interaction/interactive_browser_test.h" +#include "components/search/ntp_features.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/test_navigation_observer.h" +#include "extensions/test/test_extension_dir.h" + +namespace { +constexpr char kFooterViewName[] = "footer_view"; +} // namespace + +class FooterInteractiveTest + : public WebUiInteractiveTestMixin<InteractiveBrowserTest> { + public: + FooterInteractiveTest() { + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{ntp_features::kNtpFooter}, + /*disabled_features=*/{features::kSideBySide}); + } + + void LoadNtpOverridingExtension(Profile* profile) { + extensions::TestExtensionDir extension_dir; + extension_dir.WriteFile(FILE_PATH_LITERAL("ext.html"), + "<body>Extension-overridden NTP</body>"); + + const char extension_manifest[] = R"( + { + "chrome_url_overrides": { + "newtab": "ext.html" + }, + "name": "Extension-overridden NTP", + "manifest_version": 3, + "version": "0.1" + })"; + + extension_dir.WriteManifest(extension_manifest); + + extensions::ChromeTestExtensionLoader extension_loader(profile); + extension_loader.set_ignore_manifest_warnings(true); + const extensions::Extension* extension = + extension_loader.LoadExtension(extension_dir.Pack()).get(); + ASSERT_TRUE(extension); + } + + void OpenNewTabPage() { + chrome::NewTab(browser()); + + // Wait until navigation to chrome://newtab finishes. + content::TestNavigationObserver nav_observer( + browser()->tab_strip_model()->GetActiveWebContents()); + nav_observer.Wait(); + } + + void NavigateTo(const GURL& url) { + // Wait until navigation to `url` finishes. + content::TestNavigationObserver nav_observer( + browser()->tab_strip_model()->GetActiveWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + nav_observer.Wait(); + } + + new_tab_footer::NewTabFooterWebView* GetFooterView() { + return browser()->GetBrowserView().new_tab_footer_web_view(); + } + + protected: + base::test::ScopedFeatureList scoped_feature_list_; + extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass_; +}; + +IN_PROC_BROWSER_TEST_F(FooterInteractiveTest, FooterVisibleOnExtensionNtp) { + LoadNtpOverridingExtension(browser()->profile()); + RunTestSequence( + // Open extension NTP. + Do(base::BindLambdaForTesting([&, this]() { OpenNewTabPage(); })), + Steps(NameView(kFooterViewName, GetFooterView()), + // Ensure footer is visible. + CheckView(kFooterViewName, + [](new_tab_footer::NewTabFooterWebView* footer) { + return footer->GetVisible(); + }))); +} + +IN_PROC_BROWSER_TEST_F(FooterInteractiveTest, + FooterNotVisibleOnNonExtensionNtp) { + LoadNtpOverridingExtension(browser()->profile()); + RunTestSequence( + // Open extension NTP. + Do(base::BindLambdaForTesting([&, this]() { OpenNewTabPage(); })), + Steps(NameView(kFooterViewName, GetFooterView()), + // Ensure footer is visible. + CheckView(kFooterViewName, + [](new_tab_footer::NewTabFooterWebView* footer) { + return footer->GetVisible(); + })), + // Navigate to non-extension NTP and check that the footer isn't visible. + Do(base::BindLambdaForTesting( + [&, this]() { NavigateTo(GURL("https://google.com")); })), + WaitForHide(kFooterViewName)); +}
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc index 33b62d27e..a70e19a 100644 --- a/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc +++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc
@@ -112,8 +112,11 @@ namespace { using ::testing::IsFalse; using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::SizeIs; constexpr char kExpiredCertificateFile[] = "expired_cert.pem"; +constexpr char kUrl[] = "http://example/other/stuff.html"; void PerformMouseClickOnView(views::View* view) { ui::AXActionData data; @@ -1466,7 +1469,9 @@ std::vector<base::test::FeatureRef> enabled_features = {privacy_sandbox::kPrivacySandboxRelatedWebsiteSetsUi}, - disabled_features = {}; + disabled_features = {privacy_sandbox::kActUserBypassUx, + privacy_sandbox::kFingerprintingProtectionUx, + privacy_sandbox::kIpProtectionUx}; if (GetParam()) { enabled_features.push_back( content_settings::features::kTrackingProtection3pcd); @@ -1509,14 +1514,16 @@ #endif void OpenPageInfoAndGoToCookiesSubpage( - std::optional<std::u16string> rws_owner) { - EXPECT_FALSE(prefs_->GetBoolean(prefs::kInContextCookieControlsOpened)); + std::optional<std::u16string> rws_owner, + Browser* browser) { + EXPECT_FALSE(browser->profile()->GetPrefs()->GetBoolean( + prefs::kInContextCookieControlsOpened)); EXPECT_CALL(*mock_service(), GetRelatedWebsiteSetOwnerForDisplay(testing::_)) .WillRepeatedly(testing::Return(rws_owner)); base::RunLoop run_loop; GetPageInfoDialogCreatedCallbackForTesting() = run_loop.QuitClosure(); - OpenPageInfoBubble(browser()); + OpenPageInfoBubble(browser); run_loop.Run(); views::View* cookies_button = GetView( @@ -1541,8 +1548,10 @@ content_settings::features::kTrackingProtection3pcd) || prefs_->GetInteger(prefs::kCookieControlsMode) == static_cast<int>( - content_settings::CookieControlsMode::kBlockThirdParty); - EXPECT_EQ(prefs_->GetBoolean(prefs::kInContextCookieControlsOpened), + content_settings::CookieControlsMode::kBlockThirdParty) || + browser->profile()->IsIncognitoProfile(); + EXPECT_EQ(browser->profile()->GetPrefs()->GetBoolean( + prefs::kInContextCookieControlsOpened), block_third_party); } @@ -1562,7 +1571,7 @@ // checks if the metrics for opening cookies dialog work properly. IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestCookiesSubpage, ClickingCookieDialogButton) { - OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}); + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, browser()); // RWS blocked and 3pc allowed -> button for opening cookie dialog + // separator. @@ -1585,13 +1594,12 @@ // click on the rws button (result and user action). IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestCookiesSubpage, ClickingRwsButton) { - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUrl))); const std::u16string rws_owner = u"example"; SetCookieControlsMode(content_settings::CookieControlsMode::kBlockThirdParty); - OpenPageInfoAndGoToCookiesSubpage({rws_owner}); + OpenPageInfoAndGoToCookiesSubpage({rws_owner}, browser()); size_t kExpectedChildren = 3; auto* cookies_buttons_container = @@ -1634,12 +1642,11 @@ // toggle on blocking third party button. IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestCookiesSubpage, ToggleForBlockingThirdPartyCookies) { - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUrl))); SetCookieControlsMode(content_settings::CookieControlsMode::kBlockThirdParty); - OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}); + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, browser()); // RWS blocked and 3pc blocked -> buttons for cookie dialog and third party // cookies. @@ -1673,12 +1680,11 @@ // click on link in description of cookies subapge. IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestCookiesSubpage, LinkInDescriptionForCookiesSettings) { - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUrl))); std::u16string rws_owner = u"example"; - OpenPageInfoAndGoToCookiesSubpage({rws_owner}); + OpenPageInfoAndGoToCookiesSubpage({rws_owner}, browser()); // RWS allowed and 3pc allowed -> buttons for cookie dialog and rws button and // separator. @@ -1712,10 +1718,9 @@ IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestCookiesSubpage, LinkInCookieSyncDisclaimer) { EnableCookieSync(); - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUrl))); - OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}); + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, browser()); auto* cookie_sync_disclaimer = static_cast<views::StyledLabel*>( GetView(PageInfoViewFactory::VIEW_ID_PAGE_INFO_COOKIES_SYNC)); @@ -1744,10 +1749,9 @@ domain_blocklist.Append("example"); SetBlockedDomainsForCookieSync(std::move(domain_blocklist)); - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUrl))); - OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}); + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, browser()); views::Widget* page_info_bubble = PageInfoBubbleView::GetPageInfoBubbleForTesting()->GetWidget(); ASSERT_TRUE(page_info_bubble); @@ -1786,6 +1790,11 @@ feature_list_.InitWithFeatures(enabled_features, disabled_features); } + HostContentSettingsMap* host_content_settings_map() { + return HostContentSettingsMapFactory::GetForProfile( + CreateIncognitoBrowser()->profile()); + } + private: base::test::ScopedFeatureList feature_list_; }; @@ -1793,17 +1802,13 @@ IN_PROC_BROWSER_TEST_P( PageInfoBubbleViewBrowserTestTrackingProtectionSubpage, ButtonForPausingAndResumingProtectionsUpdatesTrackingProtectionException) { - profile_metrics::SetBrowserProfileType( - browser()->profile(), profile_metrics::BrowserProfileType::kIncognito); + auto* const incognito_browser = CreateIncognitoBrowser(); + incognito_browser->profile()->GetPrefs()->SetBoolean( + prefs::kIpProtectionEnabled, true); - GURL url_example = GURL("http://example/other/stuff.htm"); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_example)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, GURL(kUrl))); - SetCookieControlsMode(content_settings::CookieControlsMode::kBlockThirdParty); - browser()->profile()->GetPrefs()->SetBoolean(prefs::kIpProtectionEnabled, - true); - - OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}); + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, incognito_browser); auto* tracking_protections_button = static_cast<views::LabelButton*>( GetView(PageInfoViewFactory::VIEW_ID_PAGE_INFO_ACT_PROTECTIONS_BUTTON)); @@ -1817,7 +1822,7 @@ IDS_TRACKING_PROTECTION_BUBBLE_RESUME_PROTECTIONS_LABEL)); EXPECT_EQ( host_content_settings_map()->GetContentSetting( - GURL(), url_example, ContentSettingsType::TRACKING_PROTECTION, &info), + GURL(), GURL(kUrl), ContentSettingsType::TRACKING_PROTECTION, &info), CONTENT_SETTING_ALLOW); PerformMouseClickOnView(tracking_protections_button); @@ -1826,12 +1831,45 @@ IDS_TRACKING_PROTECTION_BUBBLE_PAUSE_PROTECTIONS_LABEL)); EXPECT_EQ( host_content_settings_map()->GetContentSetting( - GURL(), url_example, ContentSettingsType::TRACKING_PROTECTION, &info), + GURL(), GURL(kUrl), ContentSettingsType::TRACKING_PROTECTION, &info), CONTENT_SETTING_BLOCK); +} - // Reset browser profile before teardown to avoid profile_destroyer errors. - profile_metrics::SetBrowserProfileType( - browser()->profile(), profile_metrics::BrowserProfileType::kRegular); +IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewBrowserTestTrackingProtectionSubpage, + ClickingSettingsButtonOpensIncognitoSettingsPage) { + auto* const incognito_browser = CreateIncognitoBrowser(); + incognito_browser->profile()->GetPrefs()->SetBoolean( + prefs::kIpProtectionEnabled, true); + + ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, GURL(kUrl))); + + OpenPageInfoAndGoToCookiesSubpage(/*rws_owner =*/{}, incognito_browser); + + auto* cookies_buttons_container = + GetView(PageInfoViewFactory::VIEW_ID_PAGE_INFO_COOKIES_BUTTONS_CONTAINER); + ASSERT_THAT(cookies_buttons_container, NotNull()); + ASSERT_THAT(cookies_buttons_container->children(), SizeIs(3)); + + auto* settings_button_view = GetView( + PageInfoViewFactory:: + VIEW_ID_PAGE_INFO_BUTTON_INCOGNITO_TRACKING_PROTECTIONS_SETTINGS); + ASSERT_THAT(settings_button_view, NotNull()); + auto* settings_button = static_cast<RichHoverButton*>(settings_button_view); + + EXPECT_EQ( + settings_button->GetTitleText(), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_TITLE)); + EXPECT_EQ( + settings_button->GetSubtitleText(), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_SUBTITLE)); + + content::WebContentsAddedObserver new_tab_observer; + PerformMouseClickOnView(settings_button); + + EXPECT_EQ(new_tab_observer.GetWebContents()->GetVisibleURL(), + chrome::GetSettingsUrl(chrome::kIncognitoSettingsSubPage)); } INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/ui/views/page_info/page_info_cookies_content_view.cc b/chrome/browser/ui/views/page_info/page_info_cookies_content_view.cc index cc830d4..2e0edac 100644 --- a/chrome/browser/ui/views/page_info/page_info_cookies_content_view.cc +++ b/chrome/browser/ui/views/page_info/page_info_cookies_content_view.cc
@@ -49,6 +49,13 @@ : views::kEyeCrossedRefreshIcon); } +// TODO(crbug.com/388294499): Move this logic into the privacy_sandbox/ +// directory. +bool IsActUi(CookieControlsState controls_state) { + return controls_state == CookieControlsState::kTpActive || + controls_state == CookieControlsState::kTpPaused; +} + class ThirdPartyCookieLabelWrapper : public views::BoxLayoutView { METADATA_HEADER(ThirdPartyCookieLabelWrapper, views::BoxLayoutView) @@ -169,11 +176,6 @@ info.type = ContentSettingsType::COOKIES; info.setting = CONTENT_SETTING_ALLOW; - cookies_buttons_container_view_->AddChildView( - PageInfoViewFactory::CreateSeparator( - ChromeLayoutProvider::Get()->GetDistanceMetric( - DISTANCE_HORIZONTAL_SEPARATOR_PADDING_PAGE_INFO_VIEW))); - // Create the cookie button, with a temporary value for the subtitle text // since the site count is not yet known. cookies_dialog_button_ = cookies_buttons_container_view_->AddChildView( @@ -198,11 +200,45 @@ views::style::STYLE_BODY_4, kColorPageInfoSubtitleForeground); } +void PageInfoCookiesContentView:: + InitIncognitoTrackingProtectionSettingsButton() { + if (tp_settings_button_) { + return; + } + + tp_settings_button_ = cookies_buttons_container_view_->AddChildView( + std::make_unique<RichHoverButton>( + base::BindRepeating( + &PageInfoCookiesContentView:: + IncognitoTrackingProtectionSettingsLinkClicked, + base::Unretained(this)), + PageInfoViewFactory::GetImageModel(vector_icons::kSettingsIcon), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_TITLE), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_SUBTITLE), + PageInfoViewFactory::GetLaunchIcon())); + tp_settings_button_->SetID( + PageInfoViewFactory:: + VIEW_ID_PAGE_INFO_BUTTON_INCOGNITO_TRACKING_PROTECTIONS_SETTINGS); + tp_settings_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_SUBTITLE)); + tp_settings_button_->SetTitleTextStyleAndColor( + views::style::STYLE_BODY_3_MEDIUM, kColorPageInfoForeground); + tp_settings_button_->SetSubtitleTextStyleAndColor( + views::style::STYLE_BODY_4, kColorPageInfoSubtitleForeground); +} + void PageInfoCookiesContentView::CookiesSettingsLinkClicked( const ui::Event& event) { presenter_->OpenCookiesSettingsView(); } +void PageInfoCookiesContentView::IncognitoTrackingProtectionSettingsLinkClicked( + const ui::Event& event) { + presenter_->OpenIncognitoSettingsView(); +} + void PageInfoCookiesContentView::SyncSettingsLinkClicked( const ui::Event& event) { presenter_->OpenSyncSettingsView(); @@ -214,6 +250,17 @@ cookie_info.is_incognito); SetThirdPartyCookiesInfo(cookie_info.controls_state, cookie_info.enforcement, cookie_info.blocking_status, cookie_info.expiration); + + // Ensure the separator is only initialized once. + if (!tp_settings_button_ && !cookies_dialog_button_) { + cookies_buttons_container_view_->AddChildView( + PageInfoViewFactory::CreateSeparator( + ChromeLayoutProvider::Get()->GetDistanceMetric( + DISTANCE_HORIZONTAL_SEPARATOR_PADDING_PAGE_INFO_VIEW))); + } + if (IsActUi(cookie_info.controls_state)) { + InitIncognitoTrackingProtectionSettingsButton(); + } InitCookiesDialogButton(); // Update the text displaying the number of allowed sites. cookies_dialog_button_->SetSubtitleText(l10n_util::GetPluralStringFUTF16( @@ -372,15 +419,13 @@ tracking_protection_button_->SetID( PageInfoViewFactory::VIEW_ID_PAGE_INFO_ACT_PROTECTIONS_BUTTON); - // Show 3PC toggle in 3PC UI and button in TP UI. - bool tpcs_ui = controls_state == CookieControlsState::k3pcsAllowed || - controls_state == CookieControlsState::k3pcsBlocked; - third_party_cookies_row_->SetVisible(tpcs_ui); - tracking_protection_button_->SetVisible(!tpcs_ui); + bool act_ui = IsActUi(controls_state); + third_party_cookies_row_->SetVisible(!act_ui); + tracking_protection_button_->SetVisible(act_ui); third_party_cookies_container_->SetCrossAxisAlignment( - tpcs_ui ? views::BoxLayout::CrossAxisAlignment::kStretch - : views::BoxLayout::CrossAxisAlignment::kStart); + act_ui ? views::BoxLayout::CrossAxisAlignment::kStart + : views::BoxLayout::CrossAxisAlignment::kStretch); if (enforcement == CookieControlsEnforcement::kNoEnforcement) { third_party_cookies_label_wrapper_->SetVisible(true);
diff --git a/chrome/browser/ui/views/page_info/page_info_cookies_content_view.h b/chrome/browser/ui/views/page_info/page_info_cookies_content_view.h index 2d922e59..07969aa 100644 --- a/chrome/browser/ui/views/page_info/page_info_cookies_content_view.h +++ b/chrome/browser/ui/views/page_info/page_info_cookies_content_view.h
@@ -40,6 +40,8 @@ void CookiesSettingsLinkClicked(const ui::Event& event); + void IncognitoTrackingProtectionSettingsLinkClicked(const ui::Event& event); + void SyncSettingsLinkClicked(const ui::Event& event); void RwsSettingsButtonClicked(const ui::Event& event); @@ -100,7 +102,6 @@ // `blocking_status`: label for the status of the protection (e.g. allowed, // limited, blocked) // `expiration`: duration of site exception - // `feature: list of tracking protection features void SetThirdPartyCookiesInfo(CookieControlsState controls_state, CookieControlsEnforcement enforcement, CookieBlocking3pcdStatus blocking_status, @@ -117,6 +118,8 @@ // placeholder information if necessary. void InitRwsButton(bool is_managed); + void InitIncognitoTrackingProtectionSettingsButton(); + // Initializes the new third-party cookies section. The section starts out // hidden and is only shown when third-party cookies are blocked or there is // an active exception. @@ -164,6 +167,9 @@ // status changed. bool rws_histogram_recorded_ = false; + // The button that links to the Incognito tracking protection settings page. + raw_ptr<RichHoverButton> tp_settings_button_ = nullptr; + // Third-party cookies section which contains a title, a description and a // toggle row view. raw_ptr<views::BoxLayoutView> third_party_cookies_container_ = nullptr;
diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.h b/chrome/browser/ui/views/page_info/page_info_view_factory.h index 286c0c0..ce259009 100644 --- a/chrome/browser/ui/views/page_info/page_info_view_factory.h +++ b/chrome/browser/ui/views/page_info/page_info_view_factory.h
@@ -75,6 +75,7 @@ VIEW_ID_PAGE_INFO_EXTENDED_SITE_INFO_SECTION, VIEW_ID_PAGE_INFO_COOKIES_SYNC, VIEW_ID_PAGE_INFO_ACT_PROTECTIONS_BUTTON, + VIEW_ID_PAGE_INFO_BUTTON_INCOGNITO_TRACKING_PROTECTIONS_SETTINGS, }; // Creates a separator view with padding on top and bottom. Use with flex
diff --git a/chrome/browser/ui/views/performance_controls/memory_saver_chip_view_browsertest.cc b/chrome/browser/ui/views/performance_controls/memory_saver_chip_view_browsertest.cc index 256792b..141a0fc 100644 --- a/chrome/browser/ui/views/performance_controls/memory_saver_chip_view_browsertest.cc +++ b/chrome/browser/ui/views/performance_controls/memory_saver_chip_view_browsertest.cc
@@ -16,6 +16,7 @@ #include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/performance_controls/test_support/memory_saver_browser_test_mixin.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h" #include "chrome/browser/ui/views/page_action/page_action_icon_view.h" @@ -48,6 +49,11 @@ class MemorySaverChipViewBrowserTest : public MemorySaverBrowserTestMixin<InProcessBrowserTest> { public: + MemorySaverChipViewBrowserTest() { + scoped_feature_list_.InitWithFeatureState(features::kPageActionsMigration, + false); + } + void SetUpOnMainThread() override { MemorySaverBrowserTestMixin::SetUpOnMainThread(); @@ -74,11 +80,14 @@ ->GetInkDrop() ->GetTargetInkDropState(); } + + private: + base::test::ScopedFeatureList scoped_feature_list_; }; -// TODO(crbug.com/376283619): Remove this test after the page action is -// migrated to the new framework, and sufficient ink-drop test coverage is -// present in the framework itself. +// TODO(crbug.com/376283619): Remove this test (and entire file) after the page +// action is migrated to the new framework, and sufficient ink-drop test +// coverage is present in the framework itself. IN_PROC_BROWSER_TEST_F(MemorySaverChipViewBrowserTest, ShowAndHideInkDropOnDialog) { PageActionIconView* chip = GetMemorySaverChipView();
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc index b58c85eb..3fdef326 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc
@@ -329,17 +329,30 @@ return account_info; } - // Sign in with the full account information that triggers the name greeting, - // but force timing it out right away to clear the animation. - AccountInfo SigninWithImageAndClearGreeting( + // Sign in with the full account information that triggers the name greeting + // followed by the history sync opt-in promo (if enabled and not syncing), but + // force timing both out right away to clear the animation. + AccountInfo SigninWithImageAndClearGreetingAndSyncPromo( AvatarToolbarButton* avatar, const std::u16string& email, const std::u16string& name = u"account_name") { AccountInfo account_info = SigninWithImage(email, name); avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + ClearHistorySyncOptinPromoIfEnabled(avatar); return account_info; } + // Clears the history sync optin promo if it is enabled. This is a no-op if + // the promo is disabled. + void ClearHistorySyncOptinPromoIfEnabled(AvatarToolbarButton* avatar) { +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + if (base::FeatureList::IsEnabled( + switches::kEnableHistorySyncOptinExpansionPill)) { + avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + } +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + } + #if !BUILDFLAG(IS_CHROMEOS) void Signout() { ASSERT_TRUE( @@ -653,6 +666,7 @@ l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, name)); avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + ClearHistorySyncOptinPromoIfEnabled(avatar); // Once the name is not shown anymore, we expect no text. EXPECT_EQ(avatar->GetText(), std::u16string()); @@ -736,7 +750,8 @@ MAYBE_ShowNameDoesNotAppearOnNewBrowserIfNotShowing) { AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); // Name is shown and force clearing. - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com", u"account_name"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com", + u"account_name"); ASSERT_EQ(avatar->GetText(), std::u16string()); Browser* new_browser = CreateBrowser(browser()->profile()); @@ -2369,7 +2384,7 @@ IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonBrowserTest, MAYBE_SigninPausedFromExternalErrorThenReauth) { AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com"); ASSERT_EQ(avatar->GetText(), std::u16string()); // Browser opened before the error. @@ -2407,7 +2422,7 @@ MAYBE_SigninPausedFromWebSignout) { AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com"); ASSERT_EQ(avatar->GetText(), std::u16string()); // Browser opened before the error. @@ -2462,7 +2477,7 @@ ScopedKeepAlive keep_alive(KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED); AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com"); SimulateSigninError(/*web_sign_out=*/true); ASSERT_EQ(avatar->GetText(), std::u16string()); @@ -2488,7 +2503,8 @@ ASSERT_EQ(1u, chrome::GetTotalBrowserCount()); AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com", u"TestName"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com", + u"TestName"); SimulateSigninError(/*web_sign_out=*/true); ASSERT_TRUE(avatar->GetText().empty()); Profile* profile = browser()->profile(); @@ -2518,7 +2534,7 @@ IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonBrowserTest, MAYBE_SigninPausedThenSignout) { AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com"); ASSERT_EQ(avatar->GetText(), std::u16string()); SimulateSigninError(/*web_sign_out=*/false); @@ -2544,7 +2560,8 @@ EXPECT_EQ(accessibility.GetCachedDescription(), std::u16string()); const std::u16string account_name(u"Test Name"); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com", account_name); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com", + account_name); const std::u16string expected_profile_name_with_account = account_name + u" (" + profile_name + u")"; @@ -2617,7 +2634,7 @@ IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonBrowserTest, PassphraseErrorSignedIn) { AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser()); - SigninWithImageAndClearGreeting(avatar, u"test@gmail.com"); + SigninWithImageAndClearGreetingAndSyncPromo(avatar, u"test@gmail.com"); ASSERT_EQ(avatar->GetText(), std::u16string()); SimulatePassphraseError(); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_ui_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_ui_browsertest.cc index 6cc8601..fd5a555 100644 --- a/chrome/browser/ui/views/profiles/profile_menu_view_ui_browsertest.cc +++ b/chrome/browser/ui/views/profiles/profile_menu_view_ui_browsertest.cc
@@ -219,6 +219,12 @@ .extra_features_state_ = {{switches::kEnableImprovedGuestProfileMenu, true}}, }, + { + .pixel_test_param = {.test_suffix = "HistorySyncOptinExperiment"}, + .signin_status = SigninStatusPixelTestParam::kSignedInNoSync, + .extra_features_state_ = + {{switches::kEnableHistorySyncOptinExpansionPill, true}}, + }, }; } // namespace @@ -233,7 +239,8 @@ {features::kEnterpriseProfileBadgingForMenu, true}, {features::kEnterpriseProfileBadgingPolicies, true}, // False by default but may be overridden by `extra_features_state_`. - {switches::kEnableImprovedGuestProfileMenu, false}}; + {switches::kEnableImprovedGuestProfileMenu, false}, + {switches::kEnableHistorySyncOptinExpansionPill, false}}; for (const auto& [feature, state] : GetParam().extra_features_state_) { features_state[feature] = state; }
diff --git a/chrome/browser/ui/views/side_panel/BUILD.gn b/chrome/browser/ui/views/side_panel/BUILD.gn index 2af616e..49c4e87 100644 --- a/chrome/browser/ui/views/side_panel/BUILD.gn +++ b/chrome/browser/ui/views/side_panel/BUILD.gn
@@ -100,6 +100,7 @@ "//chrome/browser/ui/color:color_headers", "//chrome/browser/ui/customize_chrome", "//chrome/browser/ui/tabs:tab_model", + "//chrome/browser/ui/webui/new_tab_footer:new_tab_footer", "//chrome/browser/ui/webui/side_panel/customize_chrome", "//chrome/common", "//chrome/common/read_anything:mojo_bindings",
diff --git a/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.cc b/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.cc index d2d2759..8114cdf 100644 --- a/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.cc +++ b/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.cc
@@ -56,19 +56,4 @@ {extensions::disable_reason::DISABLE_USER_ACTION}); } -bool IsExtensionNtp(const GURL& url, Profile* profile) { - if (!url.SchemeIs(extensions::kExtensionScheme)) { - return false; - } - - const extensions::Extension* extension_managing_ntp = - extensions::GetExtensionOverridingNewTabPage(profile); - - if (!extension_managing_ntp) { - return false; - } - - return extension_managing_ntp->id() == url.host(); -} - } // namespace customize_chrome
diff --git a/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h b/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h index 2dcdde48..0087876 100644 --- a/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h +++ b/chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h
@@ -22,9 +22,6 @@ void MaybeDisableExtensionOverridingNtp( content::BrowserContext* browser_context); -// Returns whether `url` belongs to an extension NTP. -bool IsExtensionNtp(const GURL& url, Profile* profile); - } // namespace customize_chrome #endif // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_CUSTOMIZE_CHROME_CUSTOMIZE_CHROME_UTILS_H_
diff --git a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc index b7fa7a5..dc26434 100644 --- a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc +++ b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc
@@ -18,6 +18,7 @@ #include "chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h" #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h" +#include "chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h" #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h" #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h" #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h" @@ -102,7 +103,7 @@ Profile::FromBrowserContext(tab_->GetContents()->GetBrowserContext()); return NewTabPageUI::IsNewTabPageOrigin(url) || (base::FeatureList::IsEnabled(ntp_features::kNtpFooter) && - customize_chrome::IsExtensionNtp(url, profile)); + ntp_footer::IsExtensionNtp(url, profile)); } void SidePanelControllerViews::DidFinishNavigation(
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc index c7e1f81..6be75e6 100644 --- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc +++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -42,6 +42,7 @@ #include "chrome/browser/ui/tabs/tab_renderer_data.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" +#include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" #include "chrome/browser/ui/tabs/tab_utils.h" #include "chrome/browser/ui/ui_features.h" @@ -994,6 +995,14 @@ SplitTabChange::SplitTabRemoveReason::kSplitTabRemoved) { tabstrip_->StopAnimating(true); } + } else if (change.type == SplitTabChange::Type::kContentsChanged) { + std::vector<int> split_indices; + std::transform( + change.GetContentsChange()->new_tabs().begin(), + change.GetContentsChange()->new_tabs().end(), + std::back_inserter(split_indices), + [](const std::pair<tabs::TabInterface*, int>& p) { return p.second; }); + tabstrip_->OnSplitContentsChanged(split_indices); } }
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.cc b/chrome/browser/ui/views/tabs/compound_tab_container.cc index 54318ba..89c707db 100644 --- a/chrome/browser/ui/views/tabs/compound_tab_container.cc +++ b/chrome/browser/ui/views/tabs/compound_tab_container.cc
@@ -460,6 +460,11 @@ unpinned_tab_container_->OnSplitRemoved(indices); } +void CompoundTabContainer::OnSplitContentsChanged( + const std::vector<int>& indices) { + unpinned_tab_container_->OnSplitContentsChanged(indices); +} + std::optional<int> CompoundTabContainer::GetModelIndexOf( const TabSlotView* slot_view) const { const std::optional<int> unpinned_index =
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.h b/chrome/browser/ui/views/tabs/compound_tab_container.h index c492e37..579f102 100644 --- a/chrome/browser/ui/views/tabs/compound_tab_container.h +++ b/chrome/browser/ui/views/tabs/compound_tab_container.h
@@ -65,6 +65,7 @@ void NotifyTabstripBubbleClosed() override; void OnSplitCreated(const std::vector<int>& indices) override; void OnSplitRemoved(const std::vector<int>& indices) override; + void OnSplitContentsChanged(const std::vector<int>& indices) override; std::optional<int> GetModelIndexOf( const TabSlotView* slot_view) const override; Tab* GetTabAtModelIndex(int index) const override;
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h index 0333193..196fff1 100644 --- a/chrome/browser/ui/views/tabs/tab_container.h +++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -108,6 +108,7 @@ virtual void OnSplitCreated(const std::vector<int>& indices) = 0; virtual void OnSplitRemoved(const std::vector<int>& indices) = 0; + virtual void OnSplitContentsChanged(const std::vector<int>& indices) = 0; virtual std::optional<int> GetModelIndexOf( const TabSlotView* slot_view) const = 0;
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.cc b/chrome/browser/ui/views/tabs/tab_container_impl.cc index 308ac12..3eaed70e 100644 --- a/chrome/browser/ui/views/tabs/tab_container_impl.cc +++ b/chrome/browser/ui/views/tabs/tab_container_impl.cc
@@ -511,6 +511,14 @@ AnimateToIdealBounds(); } +void TabContainerImpl::OnSplitContentsChanged(const std::vector<int>& indices) { + for (const int index : indices) { + Tab* const tab = GetTabAtModelIndex(index); + CHECK(tab->split().has_value()); + tab->UpdateInsets(); + } +} + std::optional<int> TabContainerImpl::GetModelIndexOf( const TabSlotView* slot_view) const { return tabs_view_model_.GetIndexOfView(slot_view);
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.h b/chrome/browser/ui/views/tabs/tab_container_impl.h index 0df13fa4..5a346a5d 100644 --- a/chrome/browser/ui/views/tabs/tab_container_impl.h +++ b/chrome/browser/ui/views/tabs/tab_container_impl.h
@@ -85,6 +85,7 @@ void OnSplitCreated(const std::vector<int>& indices) override; void OnSplitRemoved(const std::vector<int>& indices) override; + void OnSplitContentsChanged(const std::vector<int>& indices) override; std::optional<int> GetModelIndexOf( const TabSlotView* slot_view) const override;
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc index ea652ef..d9b3a563 100644 --- a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc +++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
@@ -153,6 +153,14 @@ return WaitForHide(TabHoverCardBubbleView::kHoverCardBubbleElementId); } + TabResourceUsageTabHelper* GetResourceUsageAt(int index) { + return browser() + ->tab_strip_model() + ->GetTabAtIndex(index) + ->GetTabFeatures() + ->resource_usage_helper(); + } + private: base::test::ScopedFeatureList scoped_feature_list_; }; @@ -576,8 +584,7 @@ AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED)); uint64_t bytes_used = 1000; - auto* const tab_resource_usage_tab_helper = - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(1)); + auto* const tab_resource_usage_tab_helper = GetResourceUsageAt(1); tab_resource_usage_tab_helper->SetMemoryUsageInBytes(bytes_used); // Show memory usage without savings @@ -613,8 +620,7 @@ AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED)); uint64_t bytes_used = 1000; - auto* const tab_resource_usage_tab_helper = - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(1)); + auto* const tab_resource_usage_tab_helper = GetResourceUsageAt(1); tab_resource_usage_tab_helper->SetMemoryUsageInBytes(bytes_used); // Don't show memory usage @@ -638,8 +644,7 @@ IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest, ActiveMemoryUsageHidesOnDiscard) { const uint64_t bytes_used = 1; - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(0)) - ->SetMemoryUsageInBytes(bytes_used); + GetResourceUsageAt(0)->SetMemoryUsageInBytes(bytes_used); RunTestSequence(InstrumentTab(kFirstTabContents, 0), NavigateWebContents(kFirstTabContents, GetURL("a.com")), @@ -690,8 +695,7 @@ IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest, MemoryUpdatesOnNavigation) { const uint64_t bytes_used = 1; - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(0)) - ->SetMemoryUsageInBytes(bytes_used); + GetResourceUsageAt(0)->SetMemoryUsageInBytes(bytes_used); RunTestSequence( InstrumentTab(kFirstTabContents, 0), UnhoverTab(), HoverTabAt(0), @@ -720,8 +724,7 @@ ASSERT_TRUE( AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED)); - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(0)) - ->SetMemoryUsageInBytes(1000); + GetResourceUsageAt(0)->SetMemoryUsageInBytes(1000); // Footer should show when hovering over tab with memory usage views::View* const footer_view = @@ -729,8 +732,7 @@ EXPECT_TRUE(footer_view->GetVisible()); // Hover over a tab without memory usage - TabResourceUsageTabHelper::FromWebContents(GetWebContentsAt(1)) - ->SetMemoryUsageInBytes(0); + GetResourceUsageAt(1)->SetMemoryUsageInBytes(0); SimulateHoverTab(browser(), 1); // Footer should no longer be visible because there is no memory data
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc index 64597aee..0d8bfbe 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.cc +++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1094,7 +1094,8 @@ } bool TabStrip::IsTabStripEditable() const { - return !drag_context_->IsDragSessionActive() && + return !tab_strip_not_editable_for_testing_ && + !drag_context_->IsDragSessionActive() && !drag_context_->IsActiveDropTarget(); } @@ -1334,6 +1335,10 @@ tab_container_->OnSplitRemoved(split_indices); } +void TabStrip::OnSplitContentsChanged(const std::vector<int>& split_indices) { + tab_container_->OnSplitContentsChanged(split_indices); +} + bool TabStrip::ShouldDrawStrokes() const { #if BUILDFLAG(IS_CHROMEOS) return false; @@ -2168,6 +2173,10 @@ NOTREACHED(); } +void TabStrip::SetTabStripNotEditableForTesting() { + tab_strip_not_editable_for_testing_ = true; +} + /////////////////////////////////////////////////////////////////////////////// // TabStrip, private:
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h index 5d6403a..b3249f63 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.h +++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -183,9 +183,12 @@ void OnSplitCreated(const std::vector<int>& split_indices, split_tabs::SplitTabId split_id); - // Updates the tab slot view split state and animates to bounds. + // Updates the tab slot view split state and animates to bounds. void OnSplitRemoved(const std::vector<int>& split_indices); + // Updates the tab slot view split state and animates to bounds. + void OnSplitContentsChanged(const std::vector<int>& split_indices); + // Returns whether or not strokes should be drawn around and under the tabs. bool ShouldDrawStrokes() const; @@ -358,6 +361,7 @@ gfx::Point loc_in_local_coords) override; views::View* GetViewForDrop() override; + void SetTabStripNotEditableForTesting(); TabHoverCardController* hover_card_controller_for_testing() { return hover_card_controller_.get(); } @@ -513,6 +517,9 @@ SkColor separator_color_ = gfx::kPlaceholderColor; + // If true simulates a non-editable tab strip for testing. + bool tab_strip_not_editable_for_testing_ = false; + base::CallbackListSubscription paint_as_active_subscription_; const base::CallbackListSubscription subscription_ =
diff --git a/chrome/browser/ui/webui/new_tab_footer/BUILD.gn b/chrome/browser/ui/webui/new_tab_footer/BUILD.gn index c509cd82..bd6eb54 100644 --- a/chrome/browser/ui/webui/new_tab_footer/BUILD.gn +++ b/chrome/browser/ui/webui/new_tab_footer/BUILD.gn
@@ -9,6 +9,7 @@ source_set("new_tab_footer") { sources = [ "new_tab_footer_handler.h", + "new_tab_footer_helper.h", "new_tab_footer_ui.h", ] public_deps = [ @@ -21,6 +22,7 @@ source_set("impl") { sources = [ "new_tab_footer_handler.cc", + "new_tab_footer_helper.cc", "new_tab_footer_ui.cc", ]
diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.cc b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.cc new file mode 100644 index 0000000..e9b6320 --- /dev/null +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.cc
@@ -0,0 +1,27 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h" + +#include "chrome/browser/extensions/settings_api_helpers.h" +#include "extensions/common/constants.h" + +namespace ntp_footer { + +bool IsExtensionNtp(const GURL& url, Profile* profile) { + if (!url.SchemeIs(extensions::kExtensionScheme)) { + return false; + } + + const extensions::Extension* extension_managing_ntp = + extensions::GetExtensionOverridingNewTabPage(profile); + + if (!extension_managing_ntp) { + return false; + } + + return extension_managing_ntp->id() == url.host(); +} + +} // namespace ntp_footer
diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h new file mode 100644 index 0000000..2a16ae7 --- /dev/null +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h
@@ -0,0 +1,18 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_WEBUI_NEW_TAB_FOOTER_NEW_TAB_FOOTER_HELPER_H_ +#define CHROME_BROWSER_UI_WEBUI_NEW_TAB_FOOTER_NEW_TAB_FOOTER_HELPER_H_ + +#include "chrome/browser/profiles/profile.h" +#include "url/gurl.h" + +class Profile; + +namespace ntp_footer { +// Returns whether `url` belongs to an extension NTP. +bool IsExtensionNtp(const GURL& url, Profile* profile); +} // namespace ntp_footer + +#endif // CHROME_BROWSER_UI_WEBUI_NEW_TAB_FOOTER_NEW_TAB_FOOTER_HELPER_H_
diff --git a/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn b/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn index 80aa948..629c07f 100644 --- a/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn +++ b/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn
@@ -7,7 +7,10 @@ "//mojo/public/mojom/base", ] - deps = [ "//components/content_settings/core/common:content_settings_types" ] + deps = [ + "//chrome/browser/privacy_sandbox/notice:notice_mojom", + "//components/content_settings/core/common:content_settings_types", + ] sources = [ "base_dialog.mojom", @@ -33,6 +36,8 @@ deps = [ "//base", "//chrome/app:generated_resources", + "//chrome/browser/privacy_sandbox/notice:desktop_view_manager", + "//chrome/browser/privacy_sandbox/notice:factory", "//chrome/browser/profiles:profile", "//chrome/browser/resources/privacy_sandbox:resources", "//chrome/browser/ui/views/privacy_sandbox", @@ -52,6 +57,8 @@ ":mojo_bindings", ":privacy_sandbox", "//base/test:test_support", + "//chrome/browser/privacy_sandbox/notice:test_support", + "//chrome/test:test_support", "//testing/gmock", "//testing/gtest", ]
diff --git a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.cc b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.cc index 787d096..d901f0e 100644 --- a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.cc +++ b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.cc
@@ -9,11 +9,16 @@ namespace privacy_sandbox { using dialog::mojom::BaseDialogPageHandler; +using privacy_sandbox::notice::mojom::PrivacySandboxNotice; BaseDialogHandler::BaseDialogHandler( mojo::PendingReceiver<BaseDialogPageHandler> receiver, + DesktopViewManagerInterface* view_manager, BaseDialogUIDelegate* delegate) - : receiver_(this, std::move(receiver)), delegate_(delegate) {} + : receiver_(this, std::move(receiver)), delegate_(delegate) { + CHECK(view_manager); + desktop_view_manager_observation_.Observe(view_manager); +} BaseDialogHandler::~BaseDialogHandler() = default; @@ -40,4 +45,9 @@ delegate_->CloseNativeView(); } +void BaseDialogHandler::MaybeNavigateToNextStep( + std::optional<PrivacySandboxNotice> next_id) { + // TODO(crbug.com/408016824): implement and add tests. +} + } // namespace privacy_sandbox
diff --git a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.h b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.h index b8f4ecf..45169cf 100644 --- a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.h +++ b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.h
@@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_UI_WEBUI_PRIVACY_SANDBOX_BASE_DIALOG_HANDLER_H_ #define CHROME_BROWSER_UI_WEBUI_PRIVACY_SANDBOX_BASE_DIALOG_HANDLER_H_ +#include "base/scoped_observation.h" +#include "chrome/browser/privacy_sandbox/notice/desktop_view_manager.h" #include "chrome/browser/ui/webui/privacy_sandbox/base_dialog.mojom.h" #include "mojo/public/cpp/bindings/receiver.h" @@ -12,10 +14,13 @@ class BaseDialogUIDelegate; -class BaseDialogHandler : public dialog::mojom::BaseDialogPageHandler { +class BaseDialogHandler + : public dialog::mojom::BaseDialogPageHandler, + public privacy_sandbox::DesktopViewManagerInterface::Observer { public: BaseDialogHandler( mojo::PendingReceiver<dialog::mojom::BaseDialogPageHandler> receiver, + DesktopViewManagerInterface* view_manager, BaseDialogUIDelegate* delegate); BaseDialogHandler(const BaseDialogHandler&) = delete; @@ -23,12 +28,20 @@ ~BaseDialogHandler() override; + // DesktopViewManagerInterface::Observer: + void MaybeNavigateToNextStep( + std::optional<privacy_sandbox::notice::mojom::PrivacySandboxNotice> + next_id) override; + // privacy_sandbox::dialog::mojom::BaseDialogPageHandler void ResizeDialog(uint32_t height) override; void ShowDialog() override; void CloseDialog() override; private: + base::ScopedObservation<DesktopViewManagerInterface, + DesktopViewManagerInterface::Observer> + desktop_view_manager_observation_{this}; mojo::Receiver<dialog::mojom::BaseDialogPageHandler> receiver_; raw_ptr<BaseDialogUIDelegate> delegate_; bool has_resized = false;
diff --git a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler_unittest.cc b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler_unittest.cc index beb0500fb..8faee09 100644 --- a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler_unittest.cc +++ b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler_unittest.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/ui/webui/privacy_sandbox/base_dialog_handler.h" +#include "chrome/browser/privacy_sandbox/notice/mocks/mock_desktop_view_manager.h" #include "chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -27,7 +28,9 @@ protected: MockBaseDialogUIDelegate mock_delegate_; - BaseDialogHandler handler_{mojo::NullReceiver(), &mock_delegate_}; + MockDesktopViewManager view_manager_; + BaseDialogHandler handler_{mojo::NullReceiver(), &view_manager_, + &mock_delegate_}; }; TEST_F(PrivacySandboxBaseDialogHandlerTest, ShowDialog) { @@ -62,7 +65,8 @@ PrivacySandboxBaseDialogHandlerNullDelegateTest() = default; protected: - BaseDialogHandler handler_{mojo::NullReceiver(), nullptr}; + MockDesktopViewManager view_manager_; + BaseDialogHandler handler_{mojo::NullReceiver(), &view_manager_, nullptr}; }; TEST_F(PrivacySandboxBaseDialogHandlerNullDelegateTest, ShowDialog) {
diff --git a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.cc b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.cc index 7027814..896b650 100644 --- a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.cc +++ b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.cc
@@ -4,6 +4,8 @@ #include "chrome/browser/ui/webui/privacy_sandbox/base_dialog_ui.h" +#include "chrome/browser/privacy_sandbox/notice/notice.mojom.h" +#include "chrome/browser/privacy_sandbox/notice/notice_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/views/privacy_sandbox/dialog_view_context.h" #include "chrome/grit/generated_resources.h" @@ -17,6 +19,7 @@ namespace privacy_sandbox { using dialog::mojom::BaseDialogPageHandler; +using notice::mojom::PrivacySandboxNotice; BaseDialogUI::BaseDialogUI(content::WebUI* web_ui) : ui::MojoWebUIController(web_ui) { @@ -36,6 +39,11 @@ if (view_context) { delegate_ = &view_context->GetDelegate(); } + // TODO(crbug.com/398005782): Replace hard coded value once notice is passed + // in from constructor. + source->AddInteger( + "noticeIdToShow", + static_cast<int32_t>(PrivacySandboxNotice::kTopicsConsentNotice)); } WEB_UI_CONTROLLER_TYPE_IMPL(BaseDialogUI) @@ -44,8 +52,13 @@ void BaseDialogUI::BindInterface( mojo::PendingReceiver<BaseDialogPageHandler> receiver) { - page_handler_ = - std::make_unique<BaseDialogHandler>(std::move(receiver), delegate_); + if (auto* privacy_sandbox_notice_service = + PrivacySandboxNoticeServiceFactory::GetForProfile( + Profile::FromWebUI(web_ui()))) { + page_handler_ = std::make_unique<BaseDialogHandler>( + std::move(receiver), + privacy_sandbox_notice_service->GetDesktopViewManager(), delegate_); + } } } // namespace privacy_sandbox
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc index 981d88d2..5a9d48b 100644 --- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc +++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -29,6 +29,7 @@ #include "chrome/browser/ui/color/chrome_color_id.h" #include "chrome/browser/ui/search/ntp_user_data_types.h" #include "chrome/browser/ui/views/side_panel/customize_chrome/customize_chrome_utils.h" +#include "chrome/browser/ui/webui/new_tab_footer/new_tab_footer_helper.h" #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h" #include "chrome/browser/ui/webui/new_tab_page/ntp_pref_names.h" #include "chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.h" @@ -733,7 +734,7 @@ const GURL& url) { if (NewTabPageUI::IsNewTabPageOrigin(url)) { return side_panel::mojom::NewTabPageType::kFirstPartyWebUI; - } else if (customize_chrome::IsExtensionNtp(url, profile_)) { + } else if (ntp_footer::IsExtensionNtp(url, profile_)) { return side_panel::mojom::NewTabPageType::kExtension; } else if (NewTabPageThirdPartyUI::IsNewTabPageOrigin(url)) { return side_panel::mojom::NewTabPageType::kThirdPartyWebUI;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc index f0b26493..c9f6bc19 100644 --- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc +++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -820,7 +820,7 @@ std::vector<std::pair<side_panel::mojom::NewTabPageType, GURL>> ntp_types_and_urls = { {side_panel::mojom::NewTabPageType::kNone, - GURL("https://www.google.com/")}, + GURL("chrome-extension://someinvaldextension/index.html")}, {side_panel::mojom::NewTabPageType::kFirstPartyWebUI, GURL(chrome::kChromeUINewTabPageURL)}, {side_panel::mojom::NewTabPageType::kThirdPartyWebUI,
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn index d897592..3d6c999 100644 --- a/chrome/browser/web_applications/BUILD.gn +++ b/chrome/browser/web_applications/BUILD.gn
@@ -545,7 +545,6 @@ "//components/webapps/browser", "//components/webapps/common:common", "//components/webapps/common:mojo_bindings", - "//components/webapps/isolated_web_apps", "//components/webapps/isolated_web_apps:isolated_web_apps", "//components/webapps/services/web_app_origin_association:lib", "//components/webapps/services/web_app_origin_association:service",
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsing_data_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsing_data_browsertest.cc index b4b4e80..f864e5d 100644 --- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsing_data_browsertest.cc +++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsing_data_browsertest.cc
@@ -20,6 +20,7 @@ #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h" @@ -38,6 +39,7 @@ #include "components/browsing_data/core/browsing_data_utils.h" #include "components/browsing_data/core/pref_names.h" #include "components/content_settings/core/browser/cookie_settings.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/content_settings.h" #include "components/services/storage/public/mojom/local_storage_control.mojom.h" #include "components/webapps/common/web_app_id.h" @@ -93,6 +95,20 @@ return app->InstallChecked(profile()); } + IsolatedWebAppUrlInfo ForceInstallIsolatedWebApp() { + std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app = + web_app::IsolatedWebAppBuilder( + web_app::ManifestBuilder().AddPermissionsPolicyWildcard( + network::mojom::PermissionsPolicyFeature::kControlledFrame)) + .BuildBundle(); + app->TrustSigningKey(); + return app + ->InstallWithSource( + profile(), + &web_app::IsolatedWebAppInstallSource::FromExternalPolicy) + .value(); + } + WebAppProvider& web_app_provider() { return CHECK_DEREF(WebAppProvider::GetForTest(profile())); } @@ -313,6 +329,27 @@ run_loop.Run(); } + void ForceUninstall(const IsolatedWebAppUrlInfo& url_info) { + base::RunLoop run_loop; + auto* browsing_data_remover = profile()->GetBrowsingDataRemover(); + browsing_data_remover->SetWouldCompleteCallbackForTesting( + base::BindLambdaForTesting([&](base::OnceClosure callback) { + if (browsing_data_remover->GetPendingTaskCountForTesting() == 1) { + run_loop.Quit(); + } + std::move(callback).Run(); + })); + + base::test::TestFuture<webapps::UninstallResultCode> future; + provider().scheduler().RemoveInstallManagementMaybeUninstall( + url_info.app_id(), WebAppManagement::Type::kIwaPolicy, + webapps::WebappUninstallSource::kIwaEnterprisePolicy, + future.GetCallback()); + auto code = future.Get(); + ASSERT_TRUE(code == webapps::UninstallResultCode::kAppRemoved); + run_loop.Run(); + } + int64_t GetCacheSize(content::StoragePartition* storage_partition) { base::test::TestFuture<bool, int64_t> future; @@ -509,6 +546,32 @@ } IN_PROC_BROWSER_TEST_F(IsolatedWebAppBrowsingDataClearingTest, + PopupsContentSettingClearedOnUninstall) { + IsolatedWebAppUrlInfo url_info = ForceInstallIsolatedWebApp(); + HostContentSettingsMap* settings_map = + HostContentSettingsMapFactory::GetForProfile(profile()); + + const GURL app_scope = web_app_provider() + .registrar_unsafe() + .GetAppById(url_info.app_id()) + ->scope(); + + ContentSetting default_popup_setting = + settings_map->GetDefaultContentSetting(ContentSettingsType::POPUPS, + /*provider_id=*/nullptr); + + EXPECT_EQ(settings_map->GetContentSetting(app_scope, GURL(), + ContentSettingsType::POPUPS), + CONTENT_SETTING_ALLOW); + + ForceUninstall(url_info); + + EXPECT_EQ(settings_map->GetContentSetting(app_scope, GURL(), + ContentSettingsType::POPUPS), + default_popup_setting); +} + +IN_PROC_BROWSER_TEST_F(IsolatedWebAppBrowsingDataClearingTest, DataClearedOnUninstall) { IsolatedWebAppUrlInfo url_info = InstallIsolatedWebApp(); Browser* browser = LaunchWebAppBrowserAndWait(url_info.app_id()); @@ -799,8 +862,8 @@ ASSERT_TRUE( content::NavigateToURLFromRenderer(iframe_rfh, clear_site_data_url)); - // Not all cache on the StoragePartition is deleted. But it should be smaller - // than previous value. + // Not all cache on the StoragePartition is deleted. But it should be + // smaller than previous value. EXPECT_LT(GetCacheSize(iwa_main_storage_partition), old_cache_size); // Verify cookie cleared. cookie_list = GetAllCookies(iwa_main_storage_partition);
diff --git a/chrome/browser/web_applications/isolated_web_apps/key_distribution/iwa_key_distribution_component_installer_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/key_distribution/iwa_key_distribution_component_installer_browsertest.cc index d343f38..24bce67 100644 --- a/chrome/browser/web_applications/isolated_web_apps/key_distribution/iwa_key_distribution_component_installer_browsertest.cc +++ b/chrome/browser/web_applications/isolated_web_apps/key_distribution/iwa_key_distribution_component_installer_browsertest.cc
@@ -46,6 +46,13 @@ std::move(kr_info)); *key_distribution.mutable_key_rotation_data() = std::move(key_rotations); + IwaSpecialAppPermissions special_app_permissions; + IwaSpecialAppPermissions::SpecialAppPermissions special_app_permissions_info; + special_app_permissions_info.mutable_multi_screen_capture() + ->set_skip_capture_started_notification(true); + special_app_permissions.mutable_special_app_permissions()->emplace( + kWebBundleId, std::move(special_app_permissions_info)); + return key_distribution; }
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_unittest.cc index fc8d467f5..643d86b1 100644 --- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_unittest.cc +++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager_unittest.cc
@@ -455,6 +455,7 @@ .SetComponentDataForTesting(IwaKeyDistributionInfoProvider::ComponentData( /*version=*/base::Version("1.0.0"), /*key_rotations=*/{}, + /*special_app_permissions=*/{}, /*managed_allowlist=*/{web_bundle_id_1().id()}, /*is_preloaded=*/false));
diff --git a/chrome/browser/web_applications/isolated_web_apps/remove_isolated_web_app_data.cc b/chrome/browser/web_applications/isolated_web_apps/remove_isolated_web_app_data.cc index fac9d22c..55f3f39 100644 --- a/chrome/browser/web_applications/isolated_web_apps/remove_isolated_web_app_data.cc +++ b/chrome/browser/web_applications/isolated_web_apps/remove_isolated_web_app_data.cc
@@ -12,13 +12,17 @@ #include "base/memory/raw_ptr.h" #include "base/time/time.h" #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper.h" #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_reader_registry.h" #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_reader_registry_factory.h" #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_source.h" #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h" +#include "chrome/browser/web_applications/web_app_utils.h" #include "chrome/common/url_constants.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings.h" #include "content/public/browser/browsing_data_filter_builder.h" #include "content/public/browser/browsing_data_remover.h" #include "url/origin.h" @@ -76,6 +80,13 @@ base::OnceClosure callback) { CHECK(iwa_origin.scheme() == chrome::kIsolatedAppScheme); + // Content settings for this Isolated Web App (IWA) are reset to default + // because `BrowseDataRemover` does not clear them. This prevents stale + // entries for the uninstalled IWA from appearing in Chrome's site settings + // page (e.g., showing granted permissions for an app that's no longer + // present). + ResetAllContentSettingsForWebApp(profile, iwa_origin.GetURL()); + auto filter = content::BrowsingDataFilterBuilder::Create( content::BrowsingDataFilterBuilder::Mode::kDelete); filter->AddOrigin(iwa_origin);
diff --git a/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.mm b/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.mm index c3c1e54..fbbb2395 100644 --- a/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.mm +++ b/chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.mm
@@ -79,6 +79,15 @@ return false; } + // An explicitly enabled (via command line or chrome://flags) feature flag + // also takes precedence over any enterprise policy, to allow testing the + // behavior even if the enterprise policy is set to disabled. + if (base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine( + features::kUseAdHocSigningForWebAppShims.name, + base::FeatureList::OVERRIDE_ENABLE_FEATURE)) { + return true; + } + // The browser's local_state can be null in tests. In that case there is no // enterprise policy to consider. if (PrefService* local_state = g_browser_process->local_state()) {
diff --git a/chrome/browser/web_applications/web_app_utils.cc b/chrome/browser/web_applications/web_app_utils.cc index c8c17126..6640077 100644 --- a/chrome/browser/web_applications/web_app_utils.cc +++ b/chrome/browser/web_applications/web_app_utils.cc
@@ -33,6 +33,7 @@ #include "base/values.h" #include "build/build_config.h" #include "build/buildflag.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h" #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" @@ -46,6 +47,10 @@ #include "chrome/common/chrome_features.h" #include "chrome/common/chrome_isolated_world_ids.h" #include "chrome/grit/generated_resources.h" +#include "components/content_settings/core/browser/content_settings_info.h" +#include "components/content_settings/core/browser/content_settings_registry.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings.h" #include "components/crx_file/id_util.h" #include "components/grit/components_resources.h" #include "components/services/app_service/public/cpp/app_launch_util.h" @@ -833,12 +838,41 @@ return scope.is_valid() && scope.has_scheme() && scope.SchemeIsHTTPOrHTTPS(); } -// TODO(http://b/331208955): Remove after migration. +void ResetAllContentSettingsForWebApp(Profile* profile, const GURL& app_scope) { + HostContentSettingsMap* host_content_settings_map = + HostContentSettingsMapFactory::GetForProfile(profile); + for (int i = static_cast<int>(ContentSettingsType::kMinValue); + i <= static_cast<int>(ContentSettingsType::kMaxValue); ++i) { + ContentSettingsType content_type = static_cast<ContentSettingsType>(i); + + if (content_type == ContentSettingsType::MIXEDSCRIPT || + content_type == ContentSettingsType::PROTOCOL_HANDLERS) { + // These types are excluded because one can't call + // GetDefaultContentSetting() for them. + continue; + } + + // ContentSettingsType enum values may include deprecated types or other + // that are not registered in the ContentSettingsRegistry. + // `Get()` returns nullptr for unregistered types. Skip these, as they + // cannot be managed or reset via HostContentSettingsMap. + if (!content_settings::ContentSettingsRegistry::GetInstance()->Get( + content_type)) { + continue; + } + + host_content_settings_map->SetContentSettingDefaultScope( + app_scope, app_scope, content_type, CONTENT_SETTING_DEFAULT); + } +} + +// TODO(crbug.com/331208955): Remove after migration. bool WillBeSystemWebApp(const webapps::AppId& app_id, WebAppManagementTypes sources) { #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS) return app_id == ash::kGeminiAppId && sources.Has(WebAppManagement::kDefault); -#else // BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS) +#else // BUILDFLAG(GOOGLE_CHROME_BRANDING) + // && BUILDFLAG(IS_CHROMEOS) return false; #endif }
diff --git a/chrome/browser/web_applications/web_app_utils.h b/chrome/browser/web_applications/web_app_utils.h index 6d397d5..fe1b3f1 100644 --- a/chrome/browser/web_applications/web_app_utils.h +++ b/chrome/browser/web_applications/web_app_utils.h
@@ -238,6 +238,10 @@ bool IsValidScopeForLinkCapturing(const GURL& scope); +// Resets all content settings for the given `app_scope` to their default +// values. +void ResetAllContentSettingsForWebApp(Profile* profile, const GURL& app_scope); + // TODO(http://b/331208955): Remove after migration. // Returns whether |app_id| will soon refer to a system web app given |sources|. bool WillBeSystemWebApp(const webapps::AppId& app_id,
diff --git a/chrome/browser/webauthn/gpm_enclave_controller.h b/chrome/browser/webauthn/gpm_enclave_controller.h index 0d6d934..95f793e 100644 --- a/chrome/browser/webauthn/gpm_enclave_controller.h +++ b/chrome/browser/webauthn/gpm_enclave_controller.h
@@ -237,16 +237,6 @@ // Starts a create() or get() action with the enclave. void StartTransaction(); - // Called when the UI has reached a state where it needs to do an enclave - // operation, and an OAuth token for the enclave has been fetched. - void MaybeHashPinAndStartEnclaveTransaction(std::optional<std::string> token); - - // Called when the UI has reached a state where it needs to do an enclave - // operation, an OAuth token for the enclave has been fetched, and any PIN - // hashing has been completed. - void StartEnclaveTransaction(std::optional<std::string> token, - std::unique_ptr<device::enclave::ClaimedPIN>); - // Accessors for the profile pref that counts the number of consecutive failed // PIN attempts to know when a lockout will happen. int GetFailedPINAttemptCount();
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt index dd60c13..2a8dea09 100644 --- a/chrome/build/android-arm32.pgo.txt +++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@ -chrome-android32-main-1747137314-3709e2060febeef396fac45d2da47dfc464f0491-a072337580a64495dd16caf299d4415ae81ce129.profdata +chrome-android32-main-1747159012-e7bc216f029e55f2ae250e3e2d53ee45af96b64c-73f6bd2d9c91d78e0435c846427b7b49515c67ed.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index 8336df0..e33a424 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1747146606-cd10b5c1613ec9d85773d998f591ddc403679da2-b82599d9138b47be6aedefbfd70971d605a45879.profdata +chrome-android64-main-1747163744-f93a621812295c9d1a16ed00765fc001c4eb506b-92ca499a47722b517f22f4e084b4b7c18e7074b8.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 4435b6b..9e392a5 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1747151806-70bc2ce0db0c84b9e5b87db4a09f3708c7a63c42-e91bac9b6103a18fa18449251fe932a6b4ad5c2e.profdata +chrome-mac-arm-main-1747166387-f40b376f33e5d0c19378c6edfa1c4f48673ac228-191461041094bd005eab65ff040e82aaab0692df.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 2835709..fda4740a 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1747137314-7c3d3889d66eaf27e94abe7f50339fcf332f2f95-a072337580a64495dd16caf299d4415ae81ce129.profdata +chrome-mac-main-1747159012-e014e9e662d760dd10a1971bd4dc70d10bca5594-73f6bd2d9c91d78e0435c846427b7b49515c67ed.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt index 58c4e6b..9b191ee1 100644 --- a/chrome/build/win-arm64.pgo.txt +++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@ -chrome-win-arm64-main-1747137314-f4142c33322ff97645cee2f12a60bf6806cb8222-a072337580a64495dd16caf299d4415ae81ce129.profdata +chrome-win-arm64-main-1747159012-2ed4dd242357c1aa5e8a2e66fdc1dbae106154b8-73f6bd2d9c91d78e0435c846427b7b49515c67ed.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index dc284d8..43b7e7b 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1747126696-6988a9ddeeca7755128316c355855311ed54318c-36293ee11a943f79049f098c597f9d89aa10191b.profdata +chrome-win32-main-1747137314-fe6b7fb6c267e2c132eb8b1465b799462e5de4ee-a072337580a64495dd16caf299d4415ae81ce129.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn index d45e0f4..8729c5b 100644 --- a/chrome/common/BUILD.gn +++ b/chrome/common/BUILD.gn
@@ -7,6 +7,7 @@ import("//build/util/process_version.gni") import("//chrome/browser/downgrade/buildflags.gni") import("//chrome/common/features.gni") +import("//components/cdm/common/playready.gni") import("//components/nacl/features.gni") import("//components/offline_pages/buildflags/features.gni") import("//components/signin/features.gni") @@ -259,6 +260,7 @@ ] deps = [ + "//components/cdm/common:buildflags", "//components/crash/core/app", "//components/google/core/common", "//components/heap_profiling/in_process", @@ -272,7 +274,7 @@ "//components/pdf/common:util", ] - if (is_android || enable_library_cdms) { + if (is_android || enable_library_cdms || enable_playready) { public_deps += [ "//components/cdm/common" ] }
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index 9506f44b..6c3b7722 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -533,6 +533,9 @@ &kGlicPageContextEligibility, "glic-page-context-eligibility-allow-no-metadata", true}; +BASE_FEATURE(kGlicUnloadOnClose, + "GlicUnloadOnClose", + base::FEATURE_DISABLED_BY_DEFAULT); #endif // BUILDFLAG(ENABLE_GLIC) BASE_FEATURE(kTabstripComboButton,
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h index 1fa276d..a178e48 100644 --- a/chrome/common/chrome_features.h +++ b/chrome/common/chrome_features.h
@@ -306,6 +306,9 @@ COMPONENT_EXPORT(CHROME_FEATURES) extern const base::FeatureParam<bool> kGlicPageContextEligibilityAllowNoMetadata; + +COMPONENT_EXPORT(CHROME_FEATURES) +BASE_DECLARE_FEATURE(kGlicUnloadOnClose); #endif // BUILDFLAG(ENABLE_GLIC) COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kTabstripComboButton);
diff --git a/chrome/common/media/cdm_registration.cc b/chrome/common/media/cdm_registration.cc index 358e51d..dbf8612 100644 --- a/chrome/common/media/cdm_registration.cc +++ b/chrome/common/media/cdm_registration.cc
@@ -17,6 +17,7 @@ #include "base/path_service.h" #include "base/version.h" #include "build/build_config.h" +#include "components/cdm/common/buildflags.h" #include "content/public/common/cdm_info.h" #include "media/base/cdm_capability.h" #include "media/base/media_switches.h" @@ -44,6 +45,12 @@ #include "components/cdm/common/android_cdm_registration.h" #endif // BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_PLAYREADY) +#include "base/file_version_info_win.h" +#include "components/cdm/common/playready_cdm_common.h" +#include "media/base/win/mf_feature_checks.h" +#endif // BUILDFLAG(ENABLE_PLAYREADY) + namespace { using Robustness = content::CdmInfo::Robustness; @@ -332,6 +339,40 @@ #endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) #if BUILDFLAG(IS_WIN) +#if BUILDFLAG(ENABLE_PLAYREADY) +void AddPlayReady(std::vector<content::CdmInfo>* cdms) { + DVLOG(1) << __func__; + if (!media::SupportMediaFoundationEncryptedPlayback()) { + DVLOG(1) << __func__ << ": Not adding PlayReady CdmInfo"; + return; + } + + std::unique_ptr<FileVersionInfoWin> playready_version_info = + FileVersionInfoWin::CreateFileVersionInfoWin(base::FilePath( + FILE_PATH_LITERAL("Windows.Media.Protection.PlayReady.dll"))); + if (!playready_version_info) { + DVLOG(1) << __func__ << ": Failed to get PlayReady version info. " + << "Not adding PlayReady CdmInfo"; + return; + } + + DVLOG(1) << __func__ << ":" + << " CdmType=" << kPlayReadyCdmType.ToString() + << " Version=" << playready_version_info->GetFileVersion(); + + // Add PlayReady hardware secure CdmInfo - its capability will be + // filled by `CdmRegistryImpl::LazyInitializeHardwareSecureCapability()`. + // Path is empty since the CDM is not in a separate library. + cdms->emplace_back(kPlayReadyKeySystemRecommendationDefault, + content::CdmInfo::Robustness::kHardwareSecure, + /*capability=*/std::nullopt, + /*supports_sub_key_systems=*/true, + kPlayReadyCdmDisplayName, kPlayReadyCdmType, + playready_version_info->GetFileVersion(), + /*path=*/base::FilePath()); +} +#endif // BUILDFLAG(ENABLE_PLAYREADY) + void AddMediaFoundationClearKey(std::vector<content::CdmInfo>* cdms) { if (!base::FeatureList::IsEnabled(media::kExternalClearKeyForTesting)) { return; @@ -374,6 +415,9 @@ #endif #if BUILDFLAG(IS_WIN) +#if BUILDFLAG(ENABLE_PLAYREADY) + AddPlayReady(cdms); +#endif AddMediaFoundationClearKey(cdms); #endif
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h index d2e895d..1826ce2 100644 --- a/chrome/common/webui_url_constants.h +++ b/chrome/common/webui_url_constants.h
@@ -623,6 +623,7 @@ inline constexpr char kHandlerSettingsSubPage[] = "handlers"; inline constexpr char kHistorySearchSubpage[] = "historySearch"; inline constexpr char kImportDataSubPage[] = "importData"; +inline constexpr char kIncognitoSettingsSubPage[] = "incognito"; inline constexpr char kLanguageOptionsSubPage[] = "languages"; inline constexpr char kLanguagesSubPage[] = "languages/details"; inline constexpr char kManageProfileSubPage[] = "manageProfile";
diff --git a/chrome/installer/mac/pkg-dmg b/chrome/installer/mac/pkg-dmg index 6807e3c..4f9d0363 100755 --- a/chrome/installer/mac/pkg-dmg +++ b/chrome/installer/mac/pkg-dmg
@@ -198,6 +198,8 @@ 0 - Only error messages are displayed. 1 - Print error messages and command invocations. 2 - Print everything, including command output. + 3 - Additionally, configure some external commands to be more verbose. + 4 - Additionally, configure some external commands to be extremely verbose. The default I<level> is 2. @@ -295,7 +297,8 @@ sub usage(); # Variables used as globals -my(@gCleanup, %gConfig, $gDryRun, $gVerbosity); +my(@gCleanup, %gConfig, $gDryRun, @gHdiutilVerbosityFlags, @gRmVerbosityFlags, + $gVerbosity); # Use the commands by name if they're expected to be in the user's # $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute @@ -380,11 +383,21 @@ exit(1); } -if($gVerbosity<0 || $gVerbosity>2) { +if($gVerbosity<0 || $gVerbosity>4) { usage(); exit(1); } +if ($gVerbosity == 3) { + @gHdiutilVerbosityFlags = ('-verbose'); + @gRmVerbosityFlags = ('-v'); +} +if ($gVerbosity == 4) { + # -debug automatically activates -verbose. + @gHdiutilVerbosityFlags = ('-debug'); + @gRmVerbosityFlags = ('-v'); +} + if(!defined($sourceFolder) || $sourceFolder eq '' || !defined($targetImage) || $targetImage eq '') { # --source and --target are required arguments @@ -544,7 +557,8 @@ # Only unflatten read-only and compressed images. It's not supported # on other image times. if($unflattenable && - (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) { + (command($gConfig{'cmd_hdiutil'}, 'unflatten', @gHdiutilVerbosityFlags, + $targetImage)) != 0) { cleanupDie('hdiutil unflatten failed'); } # Don't push flatten onto the cleanup stack. If we fail now, we'll be @@ -559,7 +573,8 @@ # Flatten. This merges the resource fork into the data fork, so no # special encoding is needed to transfer the file. if($unflattenable && - (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) { + (command($gConfig{'cmd_hdiutil'}, 'flatten', @gHdiutilVerbosityFlags, + $targetImage)) != 0) { cleanupDie('hdiutil flatten failed'); } } @@ -569,13 +584,13 @@ # pop-save, pop, push-save. splice(@gCleanup, -2, 1); # No need to remove licenseResource separately, it's in tempSubdir. -if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) { +if(command($gConfig{'cmd_rm'}, @gRmVerbosityFlags, '-rf', $tempSubdir) != 0) { cleanupDie('rm -rf tempSubdir failed'); } if($idme) { - if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes', - $targetImage) != 0) { + if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', + @gHdiutilVerbosityFlags, '-yes', $targetImage) != 0) { cleanupDie('hdiutil internet-enable failed'); } } @@ -802,7 +817,7 @@ $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg'); if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs', - '-hfs-volume-name', $name, + @gHdiutilVerbosityFlags, '-hfs-volume-name', $name, ($gConfig{'openfolder_bless'} ? ('-hfs-openfolder', $source) : ()), '-ov', $source, '-o', $hybridImage) != 0) { cleanupDie('hdiutil makehybrid failed'); @@ -815,7 +830,7 @@ splice(@gCleanup, -1, 1, sub {commandInternalVerbosity(0, 'unlink', $hybridImage);}); - if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + if(command($gConfig{'cmd_rm'}, @gRmVerbosityFlags, '-rf', $source) != 0) { cleanupDie('rm -rf failed'); } @@ -824,7 +839,8 @@ ($format eq 'UDZO' ? ('-imagekey', 'zlib-level=9') : ()), ($format eq 'UDBZ' ? ('-imagekey', 'bzip2-level=9') : ()), ($format eq 'ULMO' ? ('-imagekey', 'udif-chunk-size=8192') : ()), - '-ov', $uncompressedImage, '-o', $destination) != 0) { + @gHdiutilVerbosityFlags, '-ov', $uncompressedImage, '-o', + $destination) != 0) { cleanupDie('hdiutil convert failed'); } @@ -842,11 +858,9 @@ # been removed, and its cleanup entry has been removed as well. } elsif($format eq 'UDRW' || $format eq 'UDSP') { - my(@extraArguments); - # Use -fs HFS+ to suppress the journal. if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format, - @extraArguments, '-fs', 'HFS+', '-volname', $name, + @gHdiutilVerbosityFlags, '-fs', 'HFS+', '-volname', $name, '-ov', '-srcfolder', $source, $destination) != 0) { cleanupDie('hdiutil create failed'); } @@ -856,7 +870,7 @@ splice(@gCleanup, -1, 1, sub {commandInternalVerbosity(0, 'unlink', $destination);}); - if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + if(command($gConfig{'cmd_rm'}, @gRmVerbosityFlags, '-rf', $source) != 0) { cleanupDie('rm -rf failed'); } @@ -879,8 +893,13 @@ } if($gConfig{'openfolder_bless'}) { - # "bless -openfolder" is no longer documented. It appears to still work. - # It specifies what folder the Finder should open when a dmg is mounted. + # "bless -openfolder" is no longer functional as of macOS 13.0. + # Historically, on x86_64, it configured a folder Finder should open + # as soon as the file system was mounted; it was never supported on arm64. + # + # Since we care about controlling the auto-open behavior, we use + # "bless -openfolder" anyway, allowing its unsuccessful exit code to + # make the entire pkg-dmg script fail. if(command($gConfig{'cmd_bless'}, '-openfolder', $partitionMountPoint) != 0) { cleanupDie('bless failed'); @@ -1009,8 +1028,15 @@ # Returns the return code from rsync. Thus, this is falsey on success. sub invokeRsync($$) { my($src, $dest) = @_; - return command($gConfig{'cmd_rsync'}, '--archive', '--copy-unsafe-links', - '--cvs-exclude', '--include=*.so', $src, $dest); + my @verbosity; + if($gVerbosity == 3) { + @verbosity = ('--verbose'); + } + if ($gVerbosity == 4) { + @verbosity = ('--verbose', '--verbose'); + } + return command($gConfig{'cmd_rsync'}, @verbosity, '--archive', + '--copy-unsafe-links', '--cvs-exclude', '--include=*.so', $src, $dest); } # isFormatReadOnly($format)
diff --git a/chrome/services/sharing/nearby/test_support/mock_webrtc_dependencies.h b/chrome/services/sharing/nearby/test_support/mock_webrtc_dependencies.h index 38b72f69..71717d9 100644 --- a/chrome/services/sharing/nearby/test_support/mock_webrtc_dependencies.h +++ b/chrome/services/sharing/nearby/test_support/mock_webrtc_dependencies.h
@@ -36,20 +36,12 @@ void, GetHostAddress, (const std::string& host_name, + std::optional<net::AddressFamily> address_family, bool enable_mdns, network::mojom::P2PSocketManager::GetHostAddressCallback callback), (override)); MOCK_METHOD( void, - GetHostAddressWithFamily, - (const std::string& host_name, - int address_family, - bool enable_mdns, - network::mojom::P2PSocketManager::GetHostAddressWithFamilyCallback - callback), - (override)); - MOCK_METHOD( - void, CreateSocket, (network::P2PSocketType type, const net::IPEndPoint& local_address,
diff --git a/chrome/services/sharing/webrtc/p2p_async_address_resolver.cc b/chrome/services/sharing/webrtc/p2p_async_address_resolver.cc index dbc0d94a..5e578932 100644 --- a/chrome/services/sharing/webrtc/p2p_async_address_resolver.cc +++ b/chrome/services/sharing/webrtc/p2p_async_address_resolver.cc
@@ -10,6 +10,7 @@ #include "base/functional/bind.h" #include "base/location.h" #include "components/webrtc/net_address_utils.h" +#include "net/base/address_family.h" namespace sharing { @@ -31,17 +32,16 @@ state_ = STATE_SENT; done_callback_ = std::move(done_callback); + + std::optional<net::AddressFamily> family = std::nullopt; if (address_family.has_value()) { - socket_manager_->GetHostAddressWithFamily( - host_name.hostname(), address_family.value(), /*enable_mdns=*/true, - base::BindOnce(&P2PAsyncAddressResolver::OnResponse, - base::Unretained(this))); - } else { - socket_manager_->GetHostAddress( - host_name.hostname(), /*enable_mdns=*/true, - base::BindOnce(&P2PAsyncAddressResolver::OnResponse, - base::Unretained(this))); + family = net::ToAddressFamily(*address_family); } + + socket_manager_->GetHostAddress( + host_name.hostname(), family, /*enable_mdns=*/true, + base::BindOnce(&P2PAsyncAddressResolver::OnResponse, + base::Unretained(this))); } void P2PAsyncAddressResolver::Cancel() {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 4a299a91..130d628b 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -495,6 +495,7 @@ "//chrome/browser/profiles:profiles_extra_parts", "//chrome/browser/reading_list", "//chrome/browser/search_engines", + "//chrome/browser/serial", "//chrome/browser/sync", "//chrome/browser/ui:browser_element_identifiers", "//chrome/browser/ui/color:color_headers", @@ -2389,6 +2390,7 @@ "//components/captive_portal/core:buildflags", "//components/captive_portal/core:test_support", "//components/cbor", + "//components/cdm/common:buildflags", "//components/certificate_transparency", "//components/certificate_transparency:proto", "//components/collaboration/public:empty_messaging_backend_service", @@ -3398,6 +3400,7 @@ "../browser/ui/passwords/password_generation_popup_view_browsertest.cc", "../browser/ui/passwords/well_known_change_password_navigation_throttle_browsertest.cc", "../browser/ui/performance_controls/tab_resource_usage_collector_browsertest.cc", + "../browser/ui/performance_controls/tab_resource_usage_tab_helper_browsertest.cc", "../browser/ui/profiles/profile_error_browsertest.cc", "../browser/ui/promos/ios_promos_utils_browsertest.cc", "../browser/ui/renderer_event_injection_browsertest.cc", @@ -7799,7 +7802,6 @@ "../browser/search/search_unittest.cc", "../browser/send_tab_to_self/desktop_notification_handler_unittest.cc", "../browser/send_tab_to_self/send_tab_to_self_client_service_unittest.cc", - "../browser/send_tab_to_self/send_tab_to_self_util_unittest.cc", "../browser/sessions/restore_on_startup_policy_handler_unittest.cc", "../browser/sessions/tab_restore_service_unittest.cc", "../browser/sharing/shared_clipboard/remote_copy_message_handler_unittest.cc", @@ -10936,6 +10938,7 @@ "../browser/ui/views/location_bar/content_setting_image_view_interactive_uitest.cc", "../browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_pixel_test.cc", "../browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc", + "../browser/ui/views/new_tab_footer/footer_interactive_uitest.cc", "../browser/ui/views/optimization_guide/optimization_guide_icon_view_interactive_uitest.cc", "../browser/ui/views/page_info/page_info_bubble_view_interactive_uitest.cc", "../browser/ui/views/permissions/midi_permissions_flow_interactive_uitest.cc", @@ -11034,6 +11037,7 @@ "//chrome/browser/prefs", "//chrome/browser/prefs:util", "//chrome/browser/privacy_sandbox:interactive_ui_tests", + "//chrome/browser/ui/views/new_tab_footer:new_tab_footer", "//chrome/browser/ui/webui/access_code_cast:interactive_ui_tests", # TODO(413315837): Remove this dependency when
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/edge_to_edge/ViewportFitCoverPageStation.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/edge_to_edge/ViewportFitCoverPageStation.java index 49a075bf..9434a889 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/edge_to_edge/ViewportFitCoverPageStation.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/edge_to_edge/ViewportFitCoverPageStation.java
@@ -35,8 +35,8 @@ public static final HtmlElementSpec FULLSCREEN_MAIN_BUTTON = new HtmlElementSpec("fullscreen-main"); - private HtmlElement mAvoidBottomElement; - private HtmlElement mFullScreenButtonElement; + private final HtmlElement mAvoidBottomElement; + private final HtmlElement mFullScreenButtonElement; protected <T extends ViewportFitCoverPageStation> ViewportFitCoverPageStation( Builder<T> builder) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/NewTabGroupDialogFacility.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/NewTabGroupDialogFacility.java index bd85084..6b8f6a00 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/NewTabGroupDialogFacility.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/NewTabGroupDialogFacility.java
@@ -25,9 +25,11 @@ import org.hamcrest.Matcher; import org.chromium.base.test.transit.Facility; +import org.chromium.base.test.transit.Station; import org.chromium.base.test.transit.Transition; import org.chromium.base.test.transit.ViewElement; import org.chromium.base.test.transit.ViewSpec; +import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.tabmodel.TabGroupColorUtils; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tasks.tab_management.ColorPickerUtils; @@ -39,9 +41,13 @@ import java.util.List; -/** Dialog that appears when a new tab group is created to name the group and pick a color. */ -// TODO(crbug.com/374366760): Change to generic Facility<HostStationT>. -public class NewTabGroupDialogFacility extends Facility<TabSwitcherStation> { +/** + * Dialog that appears when a new tab group is created to name the group and pick a color. + * + * @param <HostStationT> the type of station this is scoped to. + */ +public class NewTabGroupDialogFacility<HostStationT extends Station<ChromeTabbedActivity>> + extends Facility<HostStationT> { private final List<Integer> mTabIdsToGroup; private final String mTitle; private final @Nullable @TabGroupColorId Integer mSelectedColor; @@ -132,7 +138,7 @@ } /** Input a new tab group name. */ - public NewTabGroupDialogFacility inputName(String newTabGroupName) { + public NewTabGroupDialogFacility<HostStationT> inputName(String newTabGroupName) { // An empty name causes warning text to show up which could push the color picker container // out of view for small screen devices, so dismiss the keyboard. if (newTabGroupName.isEmpty()) { @@ -141,16 +147,16 @@ return mHostStation.swapFacilitySync( this, - new NewTabGroupDialogFacility( + new NewTabGroupDialogFacility<>( mTabIdsToGroup, newTabGroupName, mSelectedColor, mSoftKeyboard), titleInputElement.getPerformTrigger(replaceText(newTabGroupName))); } /** Select a color. */ - public NewTabGroupDialogFacility pickColor(@TabGroupColorId int newColor) { + public NewTabGroupDialogFacility<HostStationT> pickColor(@TabGroupColorId int newColor) { return mHostStation.swapFacilitySync( this, - new NewTabGroupDialogFacility(mTabIdsToGroup, mTitle, newColor, mSoftKeyboard), + new NewTabGroupDialogFacility<>(mTabIdsToGroup, mTitle, newColor, mSoftKeyboard), colorElements[newColor].getClickTrigger()); } @@ -160,7 +166,7 @@ // The reason we can pass an expected card index is because the tab group has already been // created. - TabModel currentModel = mHostStation.tabModelSelectorElement.get().getCurrentModel(); + TabModel currentModel = mHostStation.getActivity().getCurrentTabModel(); int expectedCardIndex = TabBinningUtil.getBinIndex(currentModel, mTabIdsToGroup); return mHostStation.swapFacilitySync( this, @@ -169,12 +175,12 @@ } /** Press "Done" to confirm the tab group name and color, but no-op from an invalid title. */ - public NewTabGroupDialogFacility pressDoneWithInvalidTitle() { + public NewTabGroupDialogFacility<HostStationT> pressDoneWithInvalidTitle() { ensureSoftKeyboardClosed(); return mHostStation.swapFacilitySync( this, - new NewTabGroupDialogFacility( + new NewTabGroupDialogFacility<>( mTabIdsToGroup, mTitle, mSelectedColor, mSoftKeyboard), Transition.possiblyAlreadyFulfilledOption(), doneButtonElement.getClickTrigger()); @@ -186,7 +192,7 @@ // The reason we can pass an expected card index is because the tab group has already been // created. - TabModel currentModel = mHostStation.tabModelSelectorElement.get().getCurrentModel(); + TabModel currentModel = mHostStation.getActivity().getCurrentTabModel(); int expectedCardIndex = TabBinningUtil.getBinIndex(currentModel, mTabIdsToGroup); return mHostStation.swapFacilitySync( this,
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabListEditorAppMenu.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabListEditorAppMenu.java index 58f072c..a586b62a 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabListEditorAppMenu.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabListEditorAppMenu.java
@@ -10,6 +10,7 @@ import org.chromium.base.test.transit.Condition; import org.chromium.base.test.transit.ScrollableFacility; +import org.chromium.base.test.transit.Station; import org.chromium.base.test.transit.Transition; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.test.R; @@ -25,21 +26,24 @@ * * <p>Differs significantly from the app menu normally shown; the options are operations to change * the tab selection or to do something with the selected tabs. + * + * @param <HostStationT> the type of host {@link Station} this is scoped to. */ -public class TabListEditorAppMenu extends CtaAppMenuFacility<TabSwitcherStation> { +public class TabListEditorAppMenu<HostStationT extends TabSwitcherStation> + extends CtaAppMenuFacility<HostStationT> { - private final TabSwitcherListEditorFacility mListEditor; + private final TabSwitcherListEditorFacility<HostStationT> mListEditor; private Item<Void> mCloseMenuItem; - private Item<NewTabGroupDialogFacility> mGroupWithDialogMenuItem; - private Item<Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility>> + private Item<NewTabGroupDialogFacility<HostStationT>> mGroupWithDialogMenuItem; + private Item<Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility<HostStationT>>> mGroupWithoutDialogMenuItem; - public TabListEditorAppMenu(TabSwitcherListEditorFacility listEditor) { + public TabListEditorAppMenu(TabSwitcherListEditorFacility<HostStationT> listEditor) { mListEditor = listEditor; } @Override - protected void declareItems(ScrollableFacility<TabSwitcherStation>.ItemsBuilder items) { + protected void declareItems(ScrollableFacility<HostStationT>.ItemsBuilder items) { String tabOrTabs = mListEditor.getNumTabsSelected() > 1 ? "tabs" : "tab"; // "Select all" usually, or "Deselect all" if all tabs are selected. @@ -79,16 +83,16 @@ * * @return the "New tab group" dialog as a Facility. */ - public NewTabGroupDialogFacility groupTabs() { + public NewTabGroupDialogFacility<HostStationT> groupTabs() { return mGroupWithDialogMenuItem.scrollToAndSelect(); } /** Factory for the result of {@link #groupTabs()}. */ - private NewTabGroupDialogFacility doGroupTabs( - ItemOnScreenFacility<NewTabGroupDialogFacility> itemOnScreen) { + private NewTabGroupDialogFacility<HostStationT> doGroupTabs( + ItemOnScreenFacility<NewTabGroupDialogFacility<HostStationT>> itemOnScreen) { SoftKeyboardFacility softKeyboard = new SoftKeyboardFacility(); - NewTabGroupDialogFacility dialog = - new NewTabGroupDialogFacility(mListEditor.getAllTabIdsSelected(), softKeyboard); + NewTabGroupDialogFacility<HostStationT> dialog = + new NewTabGroupDialogFacility<>(mListEditor.getAllTabIdsSelected(), softKeyboard); mHostStation.swapFacilitiesSync( List.of(this, mListEditor, itemOnScreen), List.of(dialog, softKeyboard), @@ -104,21 +108,27 @@ * * @return the new group card and the undo snackbar expected to be shown. */ - public Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility> groupTabsWithoutDialog() { + public Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility<HostStationT>> + groupTabsWithoutDialog() { assert mListEditor.isAnyGroupSelected(); return mGroupWithoutDialogMenuItem.scrollToAndSelect(); } /** Factory for the result of {@link #groupTabsWithoutDialog()}. */ - private Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility> doGroupTabsWithoutDialog( - ItemOnScreenFacility<Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility>> - itemOnScreen) { + private Pair<TabSwitcherGroupCardFacility, UndoSnackbarFacility<HostStationT>> + doGroupTabsWithoutDialog( + ItemOnScreenFacility< + Pair< + TabSwitcherGroupCardFacility, + UndoSnackbarFacility<HostStationT>>> + itemOnScreen) { List<Integer> tabIdsSelected = mListEditor.getAllTabIdsSelected(); String title = TabGroupUtil.getNumberOfTabsString(tabIdsSelected.size()); String snackbarMessage = TabGroupUtil.getUndoGroupTabsSnackbarMessageString(tabIdsSelected.size()); var card = new TabSwitcherGroupCardFacility(/* cardIndex= */ null, tabIdsSelected, title); - var undoSnackbar = new UndoSnackbarFacility(snackbarMessage); + UndoSnackbarFacility<HostStationT> undoSnackbar = + new UndoSnackbarFacility<>(snackbarMessage); mHostStation.swapFacilitiesSync( List.of(this, mListEditor, itemOnScreen), List.of(card, undoSnackbar),
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherAppMenuFacility.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherAppMenuFacility.java index 832f823cc..1cb64001 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherAppMenuFacility.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherAppMenuFacility.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.test.transit.hub; import org.chromium.base.ThreadUtils; +import org.chromium.base.test.transit.Station; import org.chromium.chrome.R; import org.chromium.chrome.test.transit.CtaAppMenuFacility; import org.chromium.chrome.test.transit.ntp.IncognitoNewTabPageStation; @@ -14,8 +15,13 @@ import java.util.Collections; -/** The app menu shown when pressing ("...") in the Hub on a tab switcher pane. */ -public class TabSwitcherAppMenuFacility extends CtaAppMenuFacility<TabSwitcherStation> { +/** + * The app menu shown when pressing ("...") in the Hub on a tab switcher pane. + * + * @param <HostStationT> the type of host {@link Station} this is scoped to. + */ +public class TabSwitcherAppMenuFacility<HostStationT extends TabSwitcherStation> + extends CtaAppMenuFacility<HostStationT> { public static final int CLOSE_ALL_TABS_ID = R.id.close_all_tabs_menu_id; public static final int CLOSE_INCOGNITO_TABS_ID = R.id.close_all_incognito_tabs_menu_id; public static final int SELECT_TABS_ID = R.id.menu_select_tabs; @@ -25,7 +31,7 @@ private Item<IncognitoNewTabPageStation> mNewIncognitoTab; private Item<Void> mCloseAllTabs; private Item<Void> mCloseIncognitoTabs; - private Item<TabSwitcherListEditorFacility> mSelectTabs; + private Item<TabSwitcherListEditorFacility<HostStationT>> mSelectTabs; private Item<QuickDeleteDialogFacility> mQuickDelete; private Item<SettingsStation> mSettings; @@ -100,12 +106,13 @@ } /** Select "Select tabs" from the app menu. */ - public TabSwitcherListEditorFacility clickSelectTabs() { + public TabSwitcherListEditorFacility<HostStationT> clickSelectTabs() { return mSelectTabs.scrollToAndSelect(); } - private TabSwitcherListEditorFacility createListEditorFacility() { - return new TabSwitcherListEditorFacility(Collections.emptyList(), Collections.emptyList()); + private TabSwitcherListEditorFacility<HostStationT> createListEditorFacility() { + return new TabSwitcherListEditorFacility<>( + Collections.emptyList(), Collections.emptyList()); } /** Select "Delete browsing data" from the app menu. */
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherListEditorFacility.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherListEditorFacility.java index 442a59f..f3b04ae 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherListEditorFacility.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherListEditorFacility.java
@@ -17,6 +17,7 @@ import androidx.test.espresso.Espresso; import org.chromium.base.test.transit.Facility; +import org.chromium.base.test.transit.Station; import org.chromium.base.test.transit.ViewElement; import org.chromium.base.test.transit.ViewSpec; import org.chromium.base.test.util.ViewActionOnDescendant; @@ -26,9 +27,14 @@ import java.util.ArrayList; import java.util.List; -/** The 3-dot menu "Select Tabs" UI for the {@link TabSwitcherStation} panes. */ +/** + * The 3-dot menu "Select Tabs" UI for the {@link TabSwitcherStation} panes. + * + * @param <HostStationT> the type of host {@link Station} this is scoped to. + */ // TODO(crbug/324919909): Migrate TabListEditorTestingRobot to here. -public class TabSwitcherListEditorFacility extends Facility<TabSwitcherStation> { +public class TabSwitcherListEditorFacility<HostStationT extends TabSwitcherStation> + extends Facility<HostStationT> { private final List<Integer> mTabIdsSelected; private final List<List<Integer>> mTabGroupsSelected; public final ViewElement<RecyclerView> tabListRecyclerViewElement; @@ -83,12 +89,12 @@ } /** Add a tab in the grid to the selection. */ - public TabSwitcherListEditorFacility addTabToSelection(int index, int tabId) { + public TabSwitcherListEditorFacility<HostStationT> addTabToSelection(int index, int tabId) { List<Integer> newTabIdsSelected = new ArrayList<>(mTabIdsSelected); newTabIdsSelected.add(tabId); return mHostStation.swapFacilitySync( this, - new TabSwitcherListEditorFacility(newTabIdsSelected, mTabGroupsSelected), + new TabSwitcherListEditorFacility<>(newTabIdsSelected, mTabGroupsSelected), () -> ViewActionOnDescendant.performOnRecyclerViewNthItem( tabListRecyclerViewElement.getViewSpec().getViewMatcher(), @@ -97,12 +103,13 @@ } /** Add a tab group in the grid to the selection. */ - public TabSwitcherListEditorFacility addTabGroupToSelection(int index, List<Integer> tabIds) { + public TabSwitcherListEditorFacility<HostStationT> addTabGroupToSelection( + int index, List<Integer> tabIds) { List<List<Integer>> newTabGroupsSelected = new ArrayList<>(mTabGroupsSelected); newTabGroupsSelected.add(tabIds); return mHostStation.swapFacilitySync( this, - new TabSwitcherListEditorFacility(mTabIdsSelected, newTabGroupsSelected), + new TabSwitcherListEditorFacility<>(mTabIdsSelected, newTabGroupsSelected), () -> ViewActionOnDescendant.performOnRecyclerViewNthItem( tabListRecyclerViewElement.getViewSpec().getViewMatcher(), @@ -111,9 +118,9 @@ } /** Open the app menu, which looks different while selecting tabs. */ - public TabListEditorAppMenu openAppMenuWithEditor() { + public TabListEditorAppMenu<HostStationT> openAppMenuWithEditor() { return mHostStation.enterFacilitySync( - new TabListEditorAppMenu(this), mHostStation.menuButtonElement.getClickTrigger()); + new TabListEditorAppMenu<>(this), mHostStation.menuButtonElement.getClickTrigger()); } /**
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherStation.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherStation.java index 5edc3ee..41972c2 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherStation.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherStation.java
@@ -109,7 +109,8 @@ recheckActiveConditions(); return enterFacilitySync( - new TabSwitcherAppMenuFacility(mIsIncognito), menuButtonElement.getClickTrigger()); + new TabSwitcherAppMenuFacility<>(mIsIncognito), + menuButtonElement.getClickTrigger()); } /**
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/UndoSnackbarFacility.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/UndoSnackbarFacility.java index c98ed4a..f289d6a6 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/UndoSnackbarFacility.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/UndoSnackbarFacility.java
@@ -4,10 +4,16 @@ package org.chromium.chrome.test.transit.hub; +import org.chromium.base.test.transit.Station; import org.chromium.chrome.test.transit.SnackbarFacility; -/** The snackbar that lets the user undo a group or close operation for a short time. */ -public class UndoSnackbarFacility extends SnackbarFacility<TabSwitcherStation> { +/** + * The snackbar that lets the user undo a group or close operation for a short time. + * + * @param <HostStationT> the type of host {@link Station} this is scoped to. + */ +public class UndoSnackbarFacility<HostStationT extends TabSwitcherStation> + extends SnackbarFacility<HostStationT> { public UndoSnackbarFacility(String message) { super(message, "Undo");
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestRule.java index 22f63665..ef6e328 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestRule.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestRule.java
@@ -88,6 +88,16 @@ mIsSignedIn = true; } + /** Adds and signs in with the provided account and enables sync. */ + // TODO(crbug.com/40066949): Remove once Sync-the-feature is fully removed. + public void addAccountThenSigninAndEnableSync(AccountInfo accountInfo) { + assert !mIsSignedIn : "An account is already signed in!"; + addAccount(accountInfo); + SigninTestUtil.signinAndEnableSync( + accountInfo, SyncTestUtil.getSyncServiceForLastUsedProfile()); + mIsSignedIn = true; + } + /** Adds and signs in with the provided account and opts into history sync. */ public void addAccountThenSigninAndEnableHistorySync(AccountInfo accountInfo) { assert !mIsSignedIn : "An account is already signed in!"; @@ -96,17 +106,12 @@ mIsSignedIn = true; } - /** Adds and signs in an account with the default name and enables sync. */ - public CoreAccountInfo addTestAccountThenSigninAndEnableSync() { - return addTestAccountThenSigninAndEnableSync( - SyncTestUtil.getSyncServiceForLastUsedProfile()); - } - /** * Adds and signs in an account with the default name and enables sync. * * @param syncService SyncService object to set up sync. */ + // TODO(crbug.com/40066949): Remove once Sync-the-feature is fully removed. public CoreAccountInfo addTestAccountThenSigninAndEnableSync(SyncService syncService) { assert !mIsSignedIn : "An account is already signed in!"; assert syncService != null : "SyncService must not be null";
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java index 870db24..dc9e2c08 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
@@ -137,6 +137,7 @@ * * @param syncService Enable the sync with it if it is not null. */ + // TODO(crbug.com/40066949): Remove once Sync-the-feature is fully removed. @WorkerThread static void signinAndEnableSync(CoreAccountInfo coreAccountInfo, SyncService syncService) { assert syncService != null : "SyncService must not be null";
diff --git a/chrome/test/base/scoped_testing_local_state.cc b/chrome/test/base/scoped_testing_local_state.cc index 05b376f0..9731da0 100644 --- a/chrome/test/base/scoped_testing_local_state.cc +++ b/chrome/test/base/scoped_testing_local_state.cc
@@ -11,9 +11,10 @@ ScopedTestingLocalState::ScopedTestingLocalState( TestingBrowserProcess* browser_process) : browser_process_(browser_process) { + CHECK(browser_process_); RegisterLocalState(local_state_.registry()); - EXPECT_FALSE(browser_process->local_state()); - browser_process->SetLocalState(&local_state_); + EXPECT_FALSE(browser_process_->local_state()); + browser_process_->SetLocalState(&local_state_); } ScopedTestingLocalState::~ScopedTestingLocalState() {
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn index 1ae23de..0db4f7e 100644 --- a/chrome/test/data/webui/BUILD.gn +++ b/chrome/test/data/webui/BUILD.gn
@@ -63,6 +63,7 @@ "optimization_guide_internals/optimization_guide_internals_browsertest.cc", "password_manager/password_manager_browsertest.cc", "password_manager_internals/password_manager_internals_browsertest.cc", + "privacy_sandbox/base_dialog_browsertest.cc", "privacy_sandbox/internals/privacy_sandbox_internals_browsertest.cc", "privacy_sandbox/internals/private_state_tokens/private_state_tokens_browsertest.cc", "privacy_sandbox/internals/related_website_sets/related_website_sets_browsertest.cc",
diff --git a/chrome/test/data/webui/new_tab_page/app_test.ts b/chrome/test/data/webui/new_tab_page/app_test.ts index af6b19a..97d01a3 100644 --- a/chrome/test/data/webui/new_tab_page/app_test.ts +++ b/chrome/test/data/webui/new_tab_page/app_test.ts
@@ -297,7 +297,7 @@ assertStyle($$(app, '#backgroundImageAttribution2')!, 'display', 'none'); assertFalse(app.$.logo.singleColored); assertFalse(app.$.logo.dark); - assertEquals(0xffff0000, app.$.logo.backgroundColor.value); + assertEquals(0xffff0000, app.$.logo.backgroundColor?.value); }); test('setting 3p theme shows attribution', async () => {
diff --git a/chrome/test/data/webui/new_tab_page/test_support.ts b/chrome/test/data/webui/new_tab_page/test_support.ts index 5b9ac77..d1fd8acf 100644 --- a/chrome/test/data/webui/new_tab_page/test_support.ts +++ b/chrome/test/data/webui/new_tab_page/test_support.ts
@@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import type {DomIf} from 'chrome://new-tab-page/new_tab_page.js'; import type {BackgroundImage, Theme} from 'chrome://new-tab-page/new_tab_page.mojom-webui.js'; import {NtpBackgroundImageSource} from 'chrome://new-tab-page/new_tab_page.mojom-webui.js'; import {getDeepActiveElement} from 'chrome://resources/js/util.js'; @@ -98,11 +97,6 @@ return document.createElement('div'); } -export function render(element: HTMLElement) { - element.shadowRoot!.querySelectorAll<DomIf>('dom-if').forEach( - tmpl => tmpl.render()); -} - export function capture( target: HTMLElement, event: string): {received: boolean} { const capture = {received: false};
diff --git a/chrome/test/data/webui/privacy_sandbox/BUILD.gn b/chrome/test/data/webui/privacy_sandbox/BUILD.gn index d90cef1..878de3807d 100644 --- a/chrome/test/data/webui/privacy_sandbox/BUILD.gn +++ b/chrome/test/data/webui/privacy_sandbox/BUILD.gn
@@ -5,12 +5,21 @@ import("../build_webui_tests.gni") build_webui_tests("build") { - files = [ "privacy_sandbox_dialog_test.ts" ] - ts_path_mappings = - [ "chrome://privacy-sandbox-dialog/*|" + + files = [ + "privacy_sandbox_dialog_test.ts", + "base_dialog_test.ts", + "test_base_dialog_browser_proxy.ts", + ] + ts_path_mappings = [ + "chrome://privacy-sandbox-dialog/*|" + rebase_path( "$root_gen_dir/chrome/browser/resources/privacy_sandbox/tsc/*", - target_gen_dir) ] + target_gen_dir), + "chrome://privacy-sandbox-base-dialog/*|" + + rebase_path( + "$root_gen_dir/chrome/browser/resources/privacy_sandbox/tsc/*", + target_gen_dir), + ] ts_deps = [ "//chrome/browser/resources/privacy_sandbox:build_ts", "//third_party/polymer/v3_0:library",
diff --git a/chrome/test/data/webui/privacy_sandbox/base_dialog_browsertest.cc b/chrome/test/data/webui/privacy_sandbox/base_dialog_browsertest.cc new file mode 100644 index 0000000..ecd6c99 --- /dev/null +++ b/chrome/test/data/webui/privacy_sandbox/base_dialog_browsertest.cc
@@ -0,0 +1,54 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/test/base/web_ui_mocha_browser_test.h" +#include "content/public/test/browser_test.h" + +namespace { + +enum class WindowSize { kSmall, kBig }; + +class PrivacySandboxBaseDialogMochaTest + : public WebUIMochaBrowserTest, + public testing::WithParamInterface<WindowSize> { + protected: + PrivacySandboxBaseDialogMochaTest() { + set_test_loader_host(chrome::kChromeUIPrivacySandboxBaseDialogHost); + } + + void RunTestSuite(const std::string& testCase) { + if (GetParam() == WindowSize::kSmall) { + ForceWindowSizeSmall(); + } else { + ForceWindowSizeBig(); + } + WebUIMochaBrowserTest::RunTest( + "privacy_sandbox/base_dialog_test.js", + base::StringPrintf("runMochaSuite('%s');", testCase.c_str())); + } + + private: + void ForceWindowSizeSmall() { + BrowserView::GetBrowserViewForBrowser(browser())->GetWidget()->SetBounds( + {0, 0, 620, 600}); + } + + void ForceWindowSizeBig() { + BrowserView::GetBrowserViewForBrowser(browser())->GetWidget()->SetBounds( + {0, 0, 620, 900}); + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + PrivacySandboxBaseDialogMochaTest, + testing::Values(WindowSize::kSmall, WindowSize::kBig)); + +IN_PROC_BROWSER_TEST_P(PrivacySandboxBaseDialogMochaTest, BaseDialogTest) { + RunTestSuite("BaseDialogTest"); +} + +} // namespace
diff --git a/chrome/test/data/webui/privacy_sandbox/base_dialog_test.ts b/chrome/test/data/webui/privacy_sandbox/base_dialog_test.ts new file mode 100644 index 0000000..4b2edc92 --- /dev/null +++ b/chrome/test/data/webui/privacy_sandbox/base_dialog_test.ts
@@ -0,0 +1,64 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://privacy-sandbox-base-dialog/base_dialog_app.js'; +import 'chrome://privacy-sandbox-base-dialog/topics_consent.js'; + +import type {BaseDialogApp} from 'chrome://privacy-sandbox-base-dialog/base_dialog_app.js'; +import {BaseDialogBrowserProxy} from 'chrome://privacy-sandbox-base-dialog/base_dialog_browser_proxy.js'; +import {PrivacySandboxNotice} from 'chrome://privacy-sandbox-base-dialog/notice.mojom-webui.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js'; + +import type {TestBaseDialogPageHandler} from './test_base_dialog_browser_proxy.js'; +import {TestBaseDialogBrowserProxy} from './test_base_dialog_browser_proxy.js'; + +suite('BaseDialogTest', function() { + let page: BaseDialogApp; + let testHandler: TestBaseDialogPageHandler; + + // TODO(crbug.com/398005782): Since this loadTimeData value is hard coded in + // BaseDialogUI, we set that here. Once that is removed, we can refactor this + // logic to test all of the other notices. + suiteSetup(function() { + loadTimeData.overrideValues({ + noticeIdToShow: PrivacySandboxNotice.kTopicsConsentNotice, + }); + }); + + setup(async function() { + const testBrowserProxy = new TestBaseDialogBrowserProxy(); + BaseDialogBrowserProxy.setInstance(testBrowserProxy); + testHandler = testBrowserProxy.handler; + + document.body.innerHTML = window.trustedTypes!.emptyHTML; + page = document.createElement('base-dialog-app'); + document.body.appendChild(page); + + await testHandler.whenCalled('resizeDialog'); + await testHandler.whenCalled('showDialog'); + await page.updateComplete; + }); + + test('ShowsConsentView', function() { + const viewManager = page.shadowRoot.querySelector('cr-view-manager'); + assertTrue(!!viewManager); + const activeView = viewManager.querySelector('[slot="view"].active'); + assertTrue(!!activeView); + assertEquals( + PrivacySandboxNotice[PrivacySandboxNotice.kTopicsConsentNotice], + activeView.id); + }); + + test('Consent', async function() { + const topicsConsent = page.shadowRoot.querySelector('topics-consent'); + assertTrue(!!topicsConsent); + assertTrue(!!topicsConsent.shadowRoot); + const consentButton = + topicsConsent.shadowRoot.querySelector<HTMLElement>('#consentButton'); + assertTrue(!!consentButton); + consentButton.click(); + await testHandler.whenCalled('closeDialog'); + }); +});
diff --git a/chrome/test/data/webui/privacy_sandbox/test_base_dialog_browser_proxy.ts b/chrome/test/data/webui/privacy_sandbox/test_base_dialog_browser_proxy.ts new file mode 100644 index 0000000..55a508f7c --- /dev/null +++ b/chrome/test/data/webui/privacy_sandbox/test_base_dialog_browser_proxy.ts
@@ -0,0 +1,37 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import type {BaseDialogPageHandlerInterface} from 'chrome://privacy-sandbox-base-dialog/base_dialog.mojom-webui.js'; +import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js'; + +export class TestBaseDialogBrowserProxy { + handler: TestBaseDialogPageHandler; + + constructor() { + this.handler = new TestBaseDialogPageHandler(); + } +} + +export class TestBaseDialogPageHandler extends TestBrowserProxy implements + BaseDialogPageHandlerInterface { + constructor() { + super([ + 'resizeDialog', + 'showDialog', + 'closeDialog', + ]); + } + + resizeDialog(height: number) { + this.methodCalled('resizeDialog', height); + } + + showDialog() { + this.methodCalled('showDialog'); + } + + closeDialog() { + this.methodCalled('closeDialog'); + } +}
diff --git a/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts b/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts index 6820576..17b1b8e 100644 --- a/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts
@@ -75,6 +75,11 @@ emitEvent(app, ToolbarEvent.THEME); } + function emitPlayPause(): Promise<void> { + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + return microtasksFinished(); + } + setup(async () => { // Clearing the DOM should always be done first. document.body.innerHTML = window.trustedTypes!.emptyHTML; @@ -199,10 +204,10 @@ }); }); - test('on speech rate change speech rate updated', () => { + test('on speech rate change speech rate updated', async () => { setupBasicSpeech(speech); app.updateContent(); - app.playSpeech(); + await emitPlayPause(); const speechRate1 = 2; chrome.readingMode.speechRate = speechRate1; @@ -297,7 +302,7 @@ setup(() => { emitColorTheme(chrome.readingMode.defaultTheme); app.updateContent(); - app.playSpeech(); + emitPlayPause(); }); test('on hide, uses transparent highlight', () => { @@ -343,19 +348,18 @@ test('updates highlight', () => { emitHighlight(chrome.readingMode.wordHighlighting); - app.playSpeech(); + emitPlayPause(); + assertEquals( chrome.readingMode.wordHighlighting, chrome.readingMode.highlightGranularity); emitHighlight(chrome.readingMode.phraseHighlighting); - app.playSpeech(); assertEquals( chrome.readingMode.phraseHighlighting, chrome.readingMode.highlightGranularity); emitHighlight(chrome.readingMode.noHighlighting); - app.playSpeech(); assertEquals( chrome.readingMode.noHighlighting, chrome.readingMode.highlightGranularity);
diff --git a/chrome/test/data/webui/side_panel/read_anything/common.ts b/chrome/test/data/webui/side_panel/read_anything/common.ts index a775176..59c6e8d 100644 --- a/chrome/test/data/webui/side_panel/read_anything/common.ts +++ b/chrome/test/data/webui/side_panel/read_anything/common.ts
@@ -4,7 +4,7 @@ import type {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js'; import type {CrLazyRenderLitElement} from '//resources/cr_elements/cr_lazy_render/cr_lazy_render_lit.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {MetricsBrowserProxyImpl, NodeStore, playFromSelectionTimeout, ReadAnythingLogger, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {MetricsBrowserProxyImpl, NodeStore, playFromSelectionTimeout, ReadAnythingLogger, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; @@ -40,7 +40,7 @@ export function playFromSelectionWithMockTimer(app: AppElement): void { const mockTimer = new MockTimer(); mockTimer.install(); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); mockTimer.tick(playFromSelectionTimeout); mockTimer.uninstall(); }
diff --git a/chrome/test/data/webui/side_panel/read_anything/image_test.ts b/chrome/test/data/webui/side_panel/read_anything/image_test.ts index 157c986..9a04d88 100644 --- a/chrome/test/data/webui/side_panel/read_anything/image_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/image_test.ts
@@ -5,11 +5,11 @@ import type {CrIconButtonElement} from '//resources/cr_elements/cr_icon_button/cr_icon_button.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {IMAGES_TOGGLE_BUTTON_ID, SpeechBrowserProxyImpl, SpeechController, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {IMAGES_TOGGLE_BUTTON_ID, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; -import {createApp, setupBasicSpeech} from './common.js'; +import {createApp, emitEvent, setupBasicSpeech} from './common.js'; import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js'; suite('Images', () => { @@ -174,7 +174,7 @@ ]; setTree([2], nodes); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); assertEquals(1, speech.getCallCount('speak')); assertEquals(figcaption, speech.getArgs('speak')[0].text); @@ -219,7 +219,7 @@ assertFalse(chrome.readingMode.imagesEnabled); await microtasksFinished(); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); assertEquals(0, speech.getCallCount('speak')); });
diff --git a/chrome/test/data/webui/side_panel/read_anything/links_toggled_integration_test.ts b/chrome/test/data/webui/side_panel/read_anything/links_toggled_integration_test.ts index bee1035..9950a4a 100644 --- a/chrome/test/data/webui/side_panel/read_anything/links_toggled_integration_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/links_toggled_integration_test.ts
@@ -5,7 +5,7 @@ import type {CrIconButtonElement} from '//resources/cr_elements/cr_icon_button/cr_icon_button.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {LINK_TOGGLE_BUTTON_ID, PauseActionSource, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {LINK_TOGGLE_BUTTON_ID, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; @@ -109,7 +109,7 @@ suite('after speech starts', () => { setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); }); test('container does not have links', () => { @@ -134,8 +134,8 @@ suite('after speech pauses', () => { setup(() => { - app.playSpeech(); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); }); test('container has links again', () => { @@ -164,7 +164,7 @@ suite('after speech starts', () => { setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); }); test('container does not have links', () => { @@ -180,8 +180,8 @@ suite('after speech pauses', () => { setup(() => { - app.playSpeech(); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); }); test('container does not have links', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/phrase_highlighting_test.ts b/chrome/test/data/webui/side_panel/read_anything/phrase_highlighting_test.ts index c4a622a..6c16232 100644 --- a/chrome/test/data/webui/side_panel/read_anything/phrase_highlighting_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/phrase_highlighting_test.ts
@@ -3,10 +3,10 @@ // found in the LICENSE file. import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {ReadAloudHighlighter, SpeechController, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {ReadAloudHighlighter, SpeechController, ToolbarEvent, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; -import {createApp} from './common.js'; +import {createApp, emitEvent} from './common.js'; suite('PhraseHighlighting', () => { let app: AppElement; @@ -79,7 +79,7 @@ chrome.readingMode.wordHighlighting); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); assertTrue(currentHighlight !== undefined); @@ -91,7 +91,7 @@ chrome.readingMode.phraseHighlighting); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -104,7 +104,7 @@ chrome.readingMode.sentenceHighlighting); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -117,7 +117,7 @@ chrome.readingMode.noHighlighting); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -133,7 +133,7 @@ test('initially, phrase is highlighted', () => { chrome.readingMode.onHighlightGranularityChanged( chrome.readingMode.phraseHighlighting); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); assertTrue(currentHighlight !== undefined); @@ -144,7 +144,7 @@ chrome.readingMode.onHighlightGranularityChanged( chrome.readingMode.phraseHighlighting); wordBoundaries.updateBoundary(5); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); assertTrue(currentHighlight !== undefined); @@ -155,7 +155,7 @@ chrome.readingMode.onHighlightGranularityChanged( chrome.readingMode.phraseHighlighting); wordBoundaries.updateBoundary(10); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); assertTrue(currentHighlight !== undefined);
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlighting_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlighting_test.ts index 083eb8b..f2ff228 100644 --- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlighting_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlighting_test.ts
@@ -4,7 +4,7 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {PauseActionSource, playFromSelectionTimeout, SpeechController, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {playFromSelectionTimeout, SpeechController, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse} from 'chrome-untrusted://webui-test/chai_assert.js'; import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js'; @@ -74,7 +74,7 @@ }); test('on speak first sentence highlights are correct', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); const previousHighlight = @@ -89,7 +89,7 @@ let previousHighlights: NodeListOf<Element>; setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); emitNextGranularity(); }); @@ -124,7 +124,7 @@ }); test('on speak next sentence highlights are correct', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -136,9 +136,9 @@ }); test('on update content after pause, keeps reading position', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); app.updateContent(); const currentHighlight = @@ -167,9 +167,9 @@ }, ], }; - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); chrome.readingMode.setContentForTesting(newTree, [2]); const currentHighlight = @@ -185,7 +185,7 @@ let previousHighlights: NodeListOf<Element>; setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); emitNextGranularity(); emitNextGranularity(); @@ -211,7 +211,7 @@ let previousHighlights: NodeListOf<Element>; setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); emitNextGranularity(); emitPreviousGranularity(); @@ -288,7 +288,7 @@ axTree); chrome.readingMode.setContentForTesting(selectedTree, leafIds); app.updateSelection(); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); mockTimer.tick(playFromSelectionTimeout); mockTimer.uninstall(); }
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf_test.ts index a6b00ed2..45da99f3 100644 --- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_pdf_test.ts
@@ -4,11 +4,11 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {PauseActionSource, SpeechController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {SpeechController, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; -import {createApp, playFromSelectionWithMockTimer} from './common.js'; +import {createApp, emitEvent, playFromSelectionWithMockTimer} from './common.js'; suite('ReadAloud_UpdateContentSelectionPDF', () => { let app: AppElement; @@ -161,7 +161,7 @@ suite('While Read Aloud paused', () => { setup(() => { playFromSelectionWithMockTimer(app); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); return microtasksFinished(); }); test('inner html of container matches expected html', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_test.ts index 88f8bc6..16bf673 100644 --- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_update_content_selection_test.ts
@@ -4,11 +4,11 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {PauseActionSource, SpeechController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {SpeechController, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; -import {createApp, playFromSelectionWithMockTimer} from './common.js'; +import {createApp, emitEvent, playFromSelectionWithMockTimer} from './common.js'; suite('ReadAloud_UpdateContentSelection', () => { let app: AppElement; @@ -160,7 +160,7 @@ suite('While Read Aloud paused', () => { setup(() => { playFromSelectionWithMockTimer(app); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); }); test('inner html of container matches expected html', () => { assertFalse(speechController.isSpeechActive());
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts index 8a326ed..84da242 100644 --- a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts
@@ -30,8 +30,8 @@ const readingMode = new FakeReadingMode(); chrome.readingMode = readingMode as unknown as typeof chrome.readingMode; speech = new TestSpeechBrowserProxy(); - metrics = mockMetrics(); SpeechBrowserProxyImpl.setInstance(speech); + metrics = mockMetrics(); isSpeechActiveChanged = false; isAudioCurrentlyPlayingChanged = false; onPreviewVoicePlaying = false; @@ -52,7 +52,6 @@ onPreviewVoicePlaying() { onPreviewVoicePlaying = true; }, - onSpeechRateChange() {}, }; voicePackController = new VoicePackController(); @@ -67,6 +66,7 @@ ReadAloudHighlighter.setInstance(highlighter); speechController = new SpeechController(); speechController.addListener(speechListener); + speech.reset(); }); test('setState', () => { @@ -118,18 +118,14 @@ }); test('isPausedFromButton', () => { - const pauseSource1 = PauseActionSource.ENGINE_INTERRUPT; - const pauseSource2 = PauseActionSource.DEFAULT; - const pauseSource3 = PauseActionSource.BUTTON_CLICK; - - speechController.stopSpeech(pauseSource1); assertFalse(speechController.isPausedFromButton()); - speechController.stopSpeech(pauseSource2); - assertFalse(speechController.isPausedFromButton()); - - speechController.stopSpeech(pauseSource3); + speechController.setIsSpeechActive(true); + speechController.onPlayPauseToggle(null, 'No matter how many times'); assertTrue(speechController.isPausedFromButton()); + + speechController.previewVoice(null); + assertFalse(speechController.isPausedFromButton()); }); test('setIsSpeechActive notifies listeners if value changes', () => { @@ -266,22 +262,42 @@ }); }); - test('onSpeechSettingsChange cancels speech', () => { + test('onSpeechSettingsChange cancels and resumes speech if playing', () => { speechController.initializeSpeechTree(1); speechController.setIsSpeechActive(true); speechController.setHasSpeechBeenTriggered(true); speechController.setIsAudioCurrentlyPlaying(true); + setSimpleNodeStoreWithText('In all the time I\'ve been by your side'); speechController.onSpeechSettingsChange(); assertTrue(isSpeechActiveChanged); + assertEquals( + PauseActionSource.VOICE_SETTINGS_CHANGE, + speechController.getPauseSource()); + assertEquals(0, speech.getCallCount('pause')); + assertEquals(2, speech.getCallCount('cancel')); + assertEquals(1, speech.getCallCount('speak')); + assertEquals(0, metrics.getCallCount('recordSpeechPlaybackLength')); + }); + + test('onSpeechSettingsChange does not resume speech if not playing', () => { + speechController.initializeSpeechTree(1); + speechController.setHasSpeechBeenTriggered(true); + speechController.setIsSpeechActive(false); + speechController.setIsAudioCurrentlyPlaying(false); + setSimpleNodeStoreWithText('I\'ve never lost control'); + + speechController.onSpeechSettingsChange(); + + assertFalse(isSpeechActiveChanged); assertFalse(speechController.isSpeechActive()); - assertFalse(speechController.isAudioCurrentlyPlaying()); assertEquals( PauseActionSource.VOICE_SETTINGS_CHANGE, speechController.getPauseSource()); assertEquals(0, speech.getCallCount('pause')); assertEquals(1, speech.getCallCount('cancel')); + assertEquals(0, speech.getCallCount('speak')); assertEquals(0, metrics.getCallCount('recordSpeechPlaybackLength')); }); @@ -510,9 +526,9 @@ utterance.onerror(createSpeechErrorEvent(utterance, 'invalid-argument')); assertEquals(1, chrome.readingMode.speechRate); - assertEquals(0, speech.getCallCount('cancel')); + assertEquals(2, speech.getCallCount('cancel')); assertEquals(0, speech.getCallCount('pause')); - assertEquals(0, speech.getCallCount('speak')); + assertEquals(1, speech.getCallCount('speak')); assertEquals(1, metrics.getCallCount('recordSpeechError')); assertEquals(0, metrics.getCallCount('recordSpeechStopSource')); }); @@ -667,4 +683,58 @@ assertEquals(0, speech.getCallCount('pause')); assertEquals(0, speech.getCallCount('speak')); }); + + test('onVoiceMenuClose resumes speech only if it was active before', () => { + setSimpleNodeStoreWithText('You must agree that baby'); + speechController.setIsSpeechActive(false); + speechController.onVoiceMenuOpen(); + + speechController.onVoiceMenuClose(); + + assertEquals(0, speech.getCallCount('cancel')); + assertEquals(0, speech.getCallCount('pause')); + assertEquals(0, speech.getCallCount('speak')); + + speechController.setIsSpeechActive(true); + speechController.onVoiceMenuOpen(); + speechController.setIsSpeechActive(false); + + speechController.onVoiceMenuClose(); + + assertEquals(1, speech.getCallCount('cancel'), 'cancel'); + assertEquals(0, speech.getCallCount('pause')); + assertEquals(1, speech.getCallCount('speak'), 'speak'); + }); + + test('onVoiceSelected sets current voice', () => { + const voice1 = createSpeechSynthesisVoice({lang: 'pt-pt', name: 'Donkey'}); + const voice2 = createSpeechSynthesisVoice({lang: 'pt-br', name: 'Corgi'}); + voicePackController.setCurrentVoice(voice1); + let sentName = ''; + let sentLang = ''; + chrome.readingMode.onVoiceChange = (name, lang) => { + sentName = name; + sentLang = lang; + }; + + speechController.onVoiceSelected(voice2); + + assertEquals(voice2, voicePackController.getCurrentVoice()); + assertEquals(voice2.name, sentName); + assertEquals(voice2.lang, sentLang); + }); + + test('onVoiceSelected resets word boundaries on different locale', () => { + const voice1 = createSpeechSynthesisVoice({lang: 'pt-pt', name: 'Tabby'}); + const voice2 = createSpeechSynthesisVoice({lang: 'pt-PT', name: 'Cheetah'}); + const voice3 = createSpeechSynthesisVoice({lang: 'pt-br', name: 'Leopard'}); + voicePackController.setCurrentVoice(voice1); + wordBoundaries.updateBoundary(10); + + speechController.onVoiceSelected(voice2); + assertTrue(wordBoundaries.hasBoundaries()); + + speechController.onVoiceSelected(voice3); + assertFalse(wordBoundaries.hasBoundaries()); + }); });
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_test.ts index 7c4000f76..5431699 100644 --- a/chrome/test/data/webui/side_panel/read_anything/speech_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/speech_test.ts
@@ -4,12 +4,12 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {playFromSelectionTimeout, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {playFromSelectionTimeout, SpeechBrowserProxyImpl, SpeechController, SpeechEngineState, ToolbarEvent, VoicePackController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js'; import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js'; -import {createAndSetVoices, createSpeechSynthesisVoice, emitEvent, mockMetrics, setupBasicSpeech} from './common.js'; +import {createAndSetVoices, emitEvent, mockMetrics, setupBasicSpeech} from './common.js'; import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js'; suite('Speech', () => { @@ -98,76 +98,59 @@ speech.reset(); }); - suite('on play', () => { - setup(() => { - app.playSpeech(); - }); + test('speaks all text by sentences', () => { + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + assertEquals(1, speech.getCallCount('speak')); + const spoken1 = speech.getArgs('speak')[0]; + assertEquals(paragraph1[0], spoken1.text.trim()); - test('speaks all text by sentences', () => { - assertEquals(1, speech.getCallCount('speak')); - const spoken1 = speech.getArgs('speak')[0]; - assertEquals(paragraph1[0], spoken1.text.trim()); + spoken1.onend(); + assertEquals(2, speech.getCallCount('speak')); + const spoken2 = speech.getArgs('speak')[1]; + assertEquals(paragraph1[1], spoken2.text.trim()); - spoken1.onend(); - assertEquals(2, speech.getCallCount('speak')); - const spoken2 = speech.getArgs('speak')[1]; - assertEquals(paragraph1[1], spoken2.text.trim()); + spoken2.onend(); + assertEquals(3, speech.getCallCount('speak')); + const spoken3 = speech.getArgs('speak')[2]; + assertEquals(paragraph1[2], spoken3.text.trim()); - spoken2.onend(); - assertEquals(3, speech.getCallCount('speak')); - const spoken3 = speech.getArgs('speak')[2]; - assertEquals(paragraph1[2], spoken3.text.trim()); + spoken3.onend(); + assertEquals(4, speech.getCallCount('speak')); + const spoken4 = speech.getArgs('speak')[3]; + assertEquals(paragraph1[3], spoken4.text.trim()); - spoken3.onend(); - assertEquals(4, speech.getCallCount('speak')); - const spoken4 = speech.getArgs('speak')[3]; - assertEquals(paragraph1[3], spoken4.text.trim()); + spoken4.onend(); + assertEquals(5, speech.getCallCount('speak')); + const spoken5 = speech.getArgs('speak')[4]; + assertEquals(paragraph2[0], spoken5.text.trim()); - spoken4.onend(); - assertEquals(5, speech.getCallCount('speak')); - const spoken5 = speech.getArgs('speak')[4]; - assertEquals(paragraph2[0], spoken5.text.trim()); + spoken5.onend(); + assertEquals(6, speech.getCallCount('speak')); + const spoken6 = speech.getArgs('speak')[5]; + assertEquals(paragraph2[1], spoken6.text.trim()); - spoken5.onend(); - assertEquals(6, speech.getCallCount('speak')); - const spoken6 = speech.getArgs('speak')[5]; - assertEquals(paragraph2[1], spoken6.text.trim()); + spoken6.onend(); + assertEquals(7, speech.getCallCount('speak')); + const spoken7 = speech.getArgs('speak')[6]; + assertEquals(paragraph2[2], spoken7.text.trim()); - spoken6.onend(); - assertEquals(7, speech.getCallCount('speak')); - const spoken7 = speech.getArgs('speak')[6]; - assertEquals(paragraph2[2], spoken7.text.trim()); + spoken7.onend(); + assertEquals(8, speech.getCallCount('speak')); + const spoken8 = speech.getArgs('speak')[7]; + assertEquals(paragraph2[3], spoken8.text.trim()); - spoken7.onend(); - assertEquals(8, speech.getCallCount('speak')); - const spoken8 = speech.getArgs('speak')[7]; - assertEquals(paragraph2[3], spoken8.text.trim()); + spoken8.onend(); + assertEquals(8, speech.getCallCount('speak')); + }); - spoken8.onend(); - assertEquals(8, speech.getCallCount('speak')); - }); + test('uses set language', () => { + const expectedLang = 'fr'; + chrome.readingMode.setLanguageForTesting(expectedLang); - test('uses set language', () => { - let expectedLang = 'en'; - assertEquals(1, speech.getCallCount('speak')); - assertEquals(expectedLang, speech.getArgs('speak')[0].lang); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); - expectedLang = 'fr'; - chrome.readingMode.setLanguageForTesting(expectedLang); - speech.reset(); - app.playSpeech(); - - assertEquals(1, speech.getCallCount('speak')); - assertEquals(expectedLang, speech.getArgs('speak')[0].lang); - - expectedLang = 'zh'; - chrome.readingMode.setLanguageForTesting(expectedLang); - speech.reset(); - app.playSpeech(); - - assertEquals(1, speech.getCallCount('speak')); - assertEquals(expectedLang, speech.getArgs('speak')[0].lang); - }); + assertEquals(1, speech.getCallCount('speak')); + assertEquals(expectedLang, speech.getArgs('speak')[0].lang); }); suite('with text selected', () => { @@ -201,7 +184,7 @@ } function playFromSelection() { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); mockTimer.tick(playFromSelectionTimeout); mockTimer.uninstall(); } @@ -304,7 +287,7 @@ test('previous granularity plays from there', () => { chrome.readingMode.initAxPositionWithNode(2); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); speech.reset(); emitEvent(app, ToolbarEvent.PREVIOUS_GRANULARITY); @@ -348,49 +331,16 @@ assertTrue(app.$.toolbar.isReadAloudPlayable); }); - test('before utterance.onStarted is not playable', async () => { - app.playSpeech(); + test('before speech engine is loaded is not playable', async () => { + speechController.setEngineState(SpeechEngineState.LOADING); await microtasksFinished(); - assertFalse(app.$.toolbar.isReadAloudPlayable); }); - test('after utterance.onStarted is playable', async () => { - app.playSpeech(); - assertEquals(1, speech.getCallCount('speak')); - speech.getArgs('speak')[0].onstart(); + test('after speech engine is loaded is playable', async () => { + speechController.setEngineState(SpeechEngineState.LOADED); await microtasksFinished(); - assertTrue(app.$.toolbar.isReadAloudPlayable); }); - - suite('and voice preview is played', () => { - setup(() => { - const voice = - createSpeechSynthesisVoice({lang: 'en', name: 'December'}); - emitEvent(app, 'preview-voice', { - detail: { - previewVoice: voice, - }, - }); - }); - - test('cancels speech and plays preview', () => { - assertEquals(1, speech.getCallCount('cancel')); - assertEquals(0, speech.getCallCount('pause')); - assertEquals(1, speech.getCallCount('speak')); - }); - - test('then resumes speech after voice menu is closed', () => { - speech.reset(); - emitEvent( - app, 'voice-menu-close', - {detail: {voicePlayingWhenMenuOpened: true}}); - - assertEquals(1, speech.getCallCount('cancel')); - assertEquals(0, speech.getCallCount('pause')); - assertEquals(1, speech.getCallCount('speak')); - }); - }); }); });
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_uses_max_text_length_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_uses_max_text_length_test.ts index 149e6c43..3855abf6 100644 --- a/chrome/test/data/webui/side_panel/read_anything/speech_uses_max_text_length_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/speech_uses_max_text_length_test.ts
@@ -4,10 +4,10 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {MAX_SPEECH_LENGTH, SpeechBrowserProxyImpl, SpeechController} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {MAX_SPEECH_LENGTH, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertGT, assertLT, assertNotEquals} from 'chrome-untrusted://webui-test/chai_assert.js'; -import {createApp} from './common.js'; +import {createApp, emitEvent} from './common.js'; import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js'; suite('SpeechUsesMaxTextLength', () => { @@ -113,7 +113,7 @@ test('highlights full sentence', () => { chrome.readingMode.setContentForTesting(axTree, [2, 3]); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); assertEquals( app.$.container.querySelector('.current-read-highlight')!.textContent,
diff --git a/chrome/test/data/webui/side_panel/read_anything/word_boundaries_speech_test.ts b/chrome/test/data/webui/side_panel/read_anything/word_boundaries_speech_test.ts index 93969a4..6c20e50 100644 --- a/chrome/test/data/webui/side_panel/read_anything/word_boundaries_speech_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/word_boundaries_speech_test.ts
@@ -4,7 +4,7 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import type {AppElement, WordBoundaryState} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {PauseActionSource, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {createApp, createSpeechSynthesisVoice, emitEvent, setupBasicSpeech} from './common.js'; @@ -80,7 +80,7 @@ test( 'during speech with no boundaries wordBoundaryState in default state', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const state: WordBoundaryState = wordBoundaries.state; assertTrue(wordBoundaries.notSupported()); assertEquals(0, state.previouslySpokenIndex); @@ -90,7 +90,7 @@ suite('during speech with one initial word boundary ', () => { setup(() => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(10, 5); }); @@ -103,8 +103,8 @@ }); test('pause / play toggle maintains word boundary state', () => { - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const state: WordBoundaryState = wordBoundaries.state; assertTrue(wordBoundaries.hasBoundaries()); @@ -115,8 +115,8 @@ }); test('word boundaries update after pause / play toggle', () => { - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(3, 9); const state: WordBoundaryState = wordBoundaries.state; @@ -127,14 +127,14 @@ }); test('word boundaries correct after multiple pause / play toggles', () => { - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(3, 9); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(7); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(1, 15); const state: WordBoundaryState = wordBoundaries.state; @@ -148,7 +148,7 @@ test( 'with multiple word boundaries wordBoundaryState in default state during speech', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(10); wordBoundaries.updateBoundary(15, 5); wordBoundaries.updateBoundary(25, 10); @@ -162,7 +162,7 @@ }); test('after voice change resets to unsupported boundary mode', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(10); assertTrue(wordBoundaries.hasBoundaries()); @@ -186,7 +186,7 @@ test( 'after voice change to same language does not reset word boundary mode', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(10); assertTrue(wordBoundaries.hasBoundaries());
diff --git a/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts b/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts index 8369c1f1..45216b39 100644 --- a/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/word_highlighting_test.ts
@@ -3,7 +3,7 @@ // found in the LICENSE file. import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {PauseActionSource, ReadAloudHighlighter, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {ReadAloudHighlighter, SpeechBrowserProxyImpl, SpeechController, ToolbarEvent, VoicePackController, WordBoundaries} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {createApp, createSpeechSynthesisVoice, emitEvent, playFromSelectionWithMockTimer, setSimpleAxTreeWithText} from './common.js'; @@ -79,7 +79,7 @@ test('word highlight used', () => { wordBoundaries.updateBoundary(10); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -96,7 +96,7 @@ test('with rate over 1 sentence highlight used', () => { wordBoundaries.updateBoundary(10); chrome.readingMode.onSpeechRateChange(2); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -105,7 +105,7 @@ }); test('with no word boundary sentence highlight used', () => { - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -116,7 +116,7 @@ test('word highlighting with only punctuation skips highlight', () => { setSimpleAxTreeWithText('.?!\'\",(){}[]'); wordBoundaries.updateBoundary(10); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -127,7 +127,7 @@ const text = '4:00pm'; setSimpleAxTreeWithText(text); wordBoundaries.updateBoundary(0, text.length); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -139,7 +139,7 @@ const text = '4:00pm'; setSimpleAxTreeWithText(text); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -188,7 +188,7 @@ }; chrome.readingMode.setContentForTesting(axTree, [3, 4, 6]); wordBoundaries.updateBoundary(0, 14); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -237,7 +237,7 @@ }; chrome.readingMode.setContentForTesting(axTree, [3, 4, 6]); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -248,7 +248,7 @@ test('word highlighting with single alphabet character has highlight', () => { setSimpleAxTreeWithText('a'); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); @@ -263,7 +263,7 @@ for (const char of toTest) { setSimpleAxTreeWithText(char); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight'); assertFalse(!!currentHighlight); @@ -275,9 +275,9 @@ const focusIndex = 2; const anchorOffset = 0; const focusOffset = 1; - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); wordBoundaries.updateBoundary(2); - speechController.stopSpeech(PauseActionSource.BUTTON_CLICK); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); // Update the selection directly on the document. const spans = app.$.container.querySelectorAll('span'); @@ -312,7 +312,7 @@ const sentence = 'Hello, how are you!'; setSimpleAxTreeWithText(sentence); wordBoundaries.updateBoundary(0); - app.playSpeech(); + emitEvent(app, ToolbarEvent.PLAY_PAUSE); const currentHighlight = app.$.container.querySelector('.current-read-highlight');
diff --git a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts index d028bbf6..c5ad1a39 100644 --- a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts +++ b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
@@ -80,10 +80,6 @@ splitNewTabPage = document.createElement('split-new-tab-page-app'); document.body.appendChild(splitNewTabPage); - // TODO(crbug.com/412693981): Figure out why this is needed only in tests. - splitNewTabPage.shadowRoot.querySelector<HTMLElement>( - '.tab-list')!.style.flexGrow = '1'; - await eventToPromise('viewport-filled', splitNewTabPage.$.splitTabsList); }
diff --git a/chromecast/base/java/src/org/chromium/chromecast/base/CastSettingsManager.java b/chromecast/base/java/src/org/chromium/chromecast/base/CastSettingsManager.java index 6ad76aa..67291af3 100644 --- a/chromecast/base/java/src/org/chromium/chromecast/base/CastSettingsManager.java +++ b/chromecast/base/java/src/org/chromium/chromecast/base/CastSettingsManager.java
@@ -41,7 +41,7 @@ public void onDeviceNameChanged(String deviceName) {} } - private OnSettingChangedListener mListener; + private final OnSettingChangedListener mListener; /** * Creates a fully-featured CastSettingsManager instance. Will fail if called from a
diff --git a/chromecast/base/java/src/org/chromium/chromecast/base/DumpstateWriter.java b/chromecast/base/java/src/org/chromium/chromecast/base/DumpstateWriter.java index 8cc147b..cd8f14d 100644 --- a/chromecast/base/java/src/org/chromium/chromecast/base/DumpstateWriter.java +++ b/chromecast/base/java/src/org/chromium/chromecast/base/DumpstateWriter.java
@@ -22,11 +22,10 @@ private static DumpstateWriter sDumpstateWriter; - private Map<String, String> mDumpValues; + private final Map<String, String> mDumpValues = new HashMap<>(); public DumpstateWriter() { sDumpstateWriter = this; - mDumpValues = new HashMap<>(); } public void writeDumpValues(PrintWriter writer) {
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java index 4202c59..fb3c9f1 100644 --- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java +++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
@@ -27,7 +27,7 @@ // ref should be checked that it is not zero before it is used. private long mNativeCastContentWindowAndroid; private final String mSessionId; - private CastWebContentsComponent mComponent; + private final CastWebContentsComponent mComponent; private boolean mScreenAccess; private CastWebContentsComponent.StartParams mStartParams;
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/WebContentsRegistry.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/WebContentsRegistry.java index ea2819f6..e372aa6 100644 --- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/WebContentsRegistry.java +++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/WebContentsRegistry.java
@@ -18,12 +18,11 @@ */ public class WebContentsRegistry { private static class WebContentsHolder { - public WebContents webContents; + public final WebContents webContents; public boolean initialized; public WebContentsHolder(WebContents webContents) { this.webContents = webContents; - this.initialized = false; } }
diff --git a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsSurfaceHelperTest.java b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsSurfaceHelperTest.java index 497e0bc..4a2e82d 100644 --- a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsSurfaceHelperTest.java +++ b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsSurfaceHelperTest.java
@@ -51,7 +51,7 @@ private CastWebContentsSurfaceHelper mSurfaceHelper; private @Mock MediaSessionGetter mMediaSessionGetter; private @Mock MediaSession mMediaSession; - private Controller<Unit> mSurfaceAvailable = new Controller<>(); + private final Controller<Unit> mSurfaceAvailable = new Controller<>(); private static class StartParamsBuilder { private String mId = "0";
diff --git a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/VolumeControl.java b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/VolumeControl.java index e46e047..1cf2dcd 100644 --- a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/VolumeControl.java +++ b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/VolumeControl.java
@@ -109,7 +109,7 @@ private final float mMaxVolumeIndexAsFloat; // Cached minimum volume index. - private int mMinVolumeIndex; + private final int mMinVolumeIndex; // Current volume index. Stored as float for easier calculations. float mVolumeIndexAsFloat; @@ -140,14 +140,14 @@ private final long mNativeVolumeControl; - private Context mContext; + private final Context mContext; - private AudioManager mAudioManager; + private final AudioManager mAudioManager; private BroadcastReceiver mMediaEventIntentListener; // Mapping from Cast's AudioContentType to their respective Settings instance. - private SparseArray<Settings> mSettings; + private final SparseArray<Settings> mSettings; @CalledByNative private static boolean isSingleVolumeDevice() {
diff --git a/chromecast/media/common/media_pipeline_backend_wrapper.cc b/chromecast/media/common/media_pipeline_backend_wrapper.cc index 5c3535d..a8de168 100644 --- a/chromecast/media/common/media_pipeline_backend_wrapper.cc +++ b/chromecast/media/common/media_pipeline_backend_wrapper.cc
@@ -125,6 +125,12 @@ media::MediaPipelineDeviceParams::kAudioStreamSoundEffects; } + // Acquire the media resource at construction. The resource will be released + // when this class is destructed. This should be destructed AFTER the + // MediaPipelineBackend is destructed, or it can lead to race conditions at + // shutdown. + MediaResourceTracker::ScopedUsage media_resource_usage_; + AudioDecoderWrapper* audio_decoder_ptr_; bool video_decoder_created_; const std::unique_ptr<MediaPipelineBackend> backend_; @@ -133,10 +139,6 @@ const MediaPipelineDeviceParams::AudioStreamType audio_stream_type_; const AudioContentType content_type_; - // Acquire the media resource at construction. The resource will be released - // when this class is destructed. - MediaResourceTracker::ScopedUsage media_resource_usage_; - bool playing_; }; @@ -145,14 +147,14 @@ MediaPipelineBackendWrapper* wrapping_backend, base::WeakPtr<MediaPipelineBackendManager> backend_manager, MediaResourceTracker* media_resource_tracker) - : audio_decoder_ptr_(nullptr), + : media_resource_usage_(media_resource_tracker), + audio_decoder_ptr_(nullptr), video_decoder_created_(false), backend_(CreateMediaPipelineBackend(params)), wrapping_backend_(wrapping_backend), backend_manager_(backend_manager), audio_stream_type_(params.audio_type), content_type_(params.content_type), - media_resource_usage_(media_resource_tracker), playing_(false) { DCHECK(backend_); DCHECK(backend_manager_);
diff --git a/chromecast/starboard/media/media/BUILD.gn b/chromecast/starboard/media/media/BUILD.gn index 3e3eee9..fa9574e 100644 --- a/chromecast/starboard/media/media/BUILD.gn +++ b/chromecast/starboard/media/media/BUILD.gn
@@ -112,6 +112,7 @@ "//base/test:test_support", "//chromecast/public", "//chromecast/public/media", + "//chromecast/starboard/media/cdm:starboard_drm_wrapper", "//testing/gmock", "//testing/gtest", ]
diff --git a/chromecast/starboard/media/media/media_pipeline_backend_starboard.cc b/chromecast/starboard/media/media/media_pipeline_backend_starboard.cc index 5bfee13..66a8d01 100644 --- a/chromecast/starboard/media/media/media_pipeline_backend_starboard.cc +++ b/chromecast/starboard/media/media/media_pipeline_backend_starboard.cc
@@ -221,20 +221,12 @@ void MediaPipelineBackendStarboard::CreatePlayer() { DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); - bool has_drm = false; StarboardPlayerCreationParam params = {}; if (audio_decoder_) { const std::optional<StarboardAudioSampleInfo>& audio_info = audio_decoder_->GetAudioSampleInfo(); CHECK(audio_info); params.audio_sample_info = *audio_info; - - std::optional<EncryptionScheme> encryption_scheme = - audio_decoder_->GetEncryptionScheme(); - if (encryption_scheme.has_value() && - *encryption_scheme != EncryptionScheme::kUnencrypted) { - has_drm = true; - } } if (video_decoder_) { const std::optional<StarboardVideoSampleInfo>& video_info = @@ -247,21 +239,9 @@ // prioritize minimizing latency (render the frames as soon as possible). params.video_sample_info.max_video_capabilities = "streaming=1"; } - - std::optional<EncryptionScheme> encryption_scheme = - video_decoder_->GetEncryptionScheme(); - if (encryption_scheme.has_value() && - *encryption_scheme != EncryptionScheme::kUnencrypted) { - has_drm = true; - } } - if (has_drm) { - params.drm_system = StarboardDrmWrapper::GetInstance().GetDrmSystem(); - } else { - params.drm_system = nullptr; - } - + params.drm_system = StarboardDrmWrapper::GetInstance().GetDrmSystem(); params.output_mode = kStarboardPlayerOutputModePunchOut; player_ = starboard_->CreatePlayer(¶ms,
diff --git a/chromecast/starboard/media/media/media_pipeline_backend_starboard_test.cc b/chromecast/starboard/media/media/media_pipeline_backend_starboard_test.cc index e9cb93e..0c1f483 100644 --- a/chromecast/starboard/media/media/media_pipeline_backend_starboard_test.cc +++ b/chromecast/starboard/media/media/media_pipeline_backend_starboard_test.cc
@@ -8,6 +8,7 @@ #include "chromecast/public/graphics_types.h" #include "chromecast/public/media/media_pipeline_device_params.h" #include "chromecast/public/volume_control.h" +#include "chromecast/starboard/media/cdm/starboard_drm_wrapper.h" #include "chromecast/starboard/media/media/mock_starboard_api_wrapper.h" #include "chromecast/starboard/media/media/starboard_api_wrapper.h" #include "testing/gmock/include/gmock/gmock.h" @@ -99,8 +100,12 @@ // Sets up default behavior for the mock functions that return values, so // that tests that do not care about this functionality can ignore them. ON_CALL(*starboard_, CreatePlayer).WillByDefault(Return(&fake_player_)); + ON_CALL(*starboard_, CreateDrmSystem) + .WillByDefault(Return(&fake_drm_system_)); ON_CALL(*starboard_, EnsureInitialized).WillByDefault(Return(true)); ON_CALL(*starboard_, SetPlaybackRate).WillByDefault(Return(true)); + + StarboardDrmWrapper::SetSingletonForTesting(starboard_.get()); } ~MediaPipelineBackendStarboardTest() override = default; @@ -110,9 +115,12 @@ // This will be passed to the MediaPipelineBackendStarboard, and all calls to // Starboard will go through it. Thus, we can mock out those calls. std::unique_ptr<MockStarboardApiWrapper> starboard_; - // Since SbPlayer is just an opaque blob to the MPB, we will simply use an int - // to represent it. + // Since SbPlayer is just an opaque blob to cast code, we will simply use an + // int to represent it. int fake_player_ = 1; + // Since SbDrmSystem is just an opaque blob to cast code, we will simply use + // an int to represent it. + int fake_drm_system_ = 2; StarboardVideoPlane video_plane_; MediaPipelineDeviceParams device_params_; };
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index 875bf0c..42aac806 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -16281.0.0-1068809 \ No newline at end of file +16283.0.0-1068845 \ No newline at end of file
diff --git a/clank b/clank index 3a123c3..055a0b8a 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit 3a123c3809dc9420b94f4a54d3beae45eb56567b +Subproject commit 055a0b8adbd2767a69d3a83093a386fcdbe41228
diff --git a/components/browser_ui/strings/android/browser_ui_strings.grd b/components/browser_ui/strings/android/browser_ui_strings.grd index 07f6a97..ef341bf 100644 --- a/components/browser_ui/strings/android/browser_ui_strings.grd +++ b/components/browser_ui/strings/android/browser_ui_strings.grd
@@ -1128,8 +1128,8 @@ </message> <!-- Magic stack strings --> - <message name="IDS_HOME_MODULES_CONTEXT_MENU_CUSTOMIZE" desc="The context menu item to show the customize homepage settings."> - Customize + <message name="IDS_HOME_MODULES_CONTEXT_MENU_MORE_SETTINGS" desc="The context menu item to show more settings to customize the new tab page."> + More settings </message> <message name="IDS_HOME_MODULES_CONFIGURATION" desc="The title for the configuration page which customizes the home modules settings."> New tab page cards
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_CUSTOMIZE.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_CUSTOMIZE.png.sha1 deleted file mode 100644 index 86d96fd1..0000000 --- a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_CUSTOMIZE.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -bb460ee33f968cf6b86e4f62f9c0b2cbb9cb992c \ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_MORE_SETTINGS.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_MORE_SETTINGS.png.sha1 new file mode 100644 index 0000000..3a0ea50 --- /dev/null +++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_HOME_MODULES_CONTEXT_MENU_MORE_SETTINGS.png.sha1
@@ -0,0 +1 @@ +7c670ae684c95d38116d6190a0d83b4d7ef08ce1 \ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/SemanticColorUtils.java b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/SemanticColorUtils.java index 6501f21..cfd7862 100644 --- a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/SemanticColorUtils.java +++ b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/SemanticColorUtils.java
@@ -125,6 +125,11 @@ return getDefaultControlColorActive(context); } + /** Returns the semantic color value that corresponds to progress_bar_track_color. */ + public static @ColorInt int getProgressBarTrackColor(Context context) { + return resolve(R.attr.colorSecondaryContainer, context); + } + /** Returns the surface color value of the conceptual toolbar_background_primary. */ public static @ColorInt int getToolbarBackgroundPrimary(Context context) { return getDefaultBgColor(context);
diff --git a/components/cdm/renderer/key_system_support_update.cc b/components/cdm/renderer/key_system_support_update.cc index 1a1ddc5..41cb0cc 100644 --- a/components/cdm/renderer/key_system_support_update.cc +++ b/components/cdm/renderer/key_system_support_update.cc
@@ -17,6 +17,7 @@ #include "base/feature_list.h" #include "base/logging.h" #include "build/build_config.h" +#include "components/cdm/common/buildflags.h" #include "components/cdm/renderer/external_clear_key_key_system_info.h" #include "content/public/renderer/key_system_support.h" #include "content/public/renderer/render_frame.h" @@ -41,6 +42,13 @@ #include "components/cdm/renderer/android_key_system_info.h" #endif // BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_PLAYREADY) +#include "components/cdm/common/playready_cdm_common.h" +#include "components/cdm/renderer/playready_key_system_info.h" +#include "media/base/supported_types.h" +#include "media/base/win/mf_feature_checks.h" +#endif // BUILDFLAG(ENABLE_PLAYREADY) + using media::CdmSessionType; using media::EmeFeatureSupport; using media::KeySystemInfo; @@ -51,7 +59,8 @@ namespace { -#if BUILDFLAG(ENABLE_WIDEVINE) || BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_WIDEVINE) || BUILDFLAG(IS_ANDROID) || \ + BUILDFLAG(ENABLE_PLAYREADY) SupportedCodecs GetVP9Codecs( const base::flat_set<media::VideoCodecProfile>& profiles) { if (profiles.empty()) { @@ -241,7 +250,8 @@ return supported_codecs; } -#endif // BUILDFLAG(ENABLE_WIDEVINE) || BUILDFLAG(IS_ANDROID) +#endif // BUILDFLAG(ENABLE_WIDEVINE) || BUILDFLAG(IS_ANDROID) || + // BUILDFLAG(ENABLE_PLAYREADY) #if BUILDFLAG(ENABLE_WIDEVINE) @@ -446,6 +456,60 @@ } #if BUILDFLAG(IS_WIN) +#if BUILDFLAG(ENABLE_PLAYREADY) +void AddPlayReady(const media::KeySystemCapability& capability, + bool can_persist_data, + KeySystemInfos* key_systems) { + DVLOG(1) << __func__; + + // When using MediaFoundation, it is assumed that it will try to persist some + // data. If incognito mode is enabled and MediaFoundation were to persist data + // this would violate the incognito assumption. + if (!can_persist_data) { + DVLOG(2) << __func__ << ": Persistent data not supported."; + return; + } + + if (!media::SupportMediaFoundationEncryptedPlayback()) { + DLOG(ERROR) << __func__ + << ": Media Foundation encrypted playback not supported."; + return; + } + + if (capability.sw_cdm_capability_or_status.has_value()) { + DVLOG(2) << "Software secure PlayReady supported but not expected"; + } + + // Codecs and encryption schemes. + SupportedCodecs hw_secure_codecs = media::EME_CODEC_NONE; + base::flat_set<::media::EncryptionScheme> hw_secure_encryption_schemes; + if (!capability.hw_cdm_capability_or_status.has_value()) { + DVLOG(2) << __func__ << ": Hardware secure PlayReady NOT supported"; + return; + } + + const auto& hw_secure_capability = + capability.hw_cdm_capability_or_status.value(); + // For the default PlayReady key system, we support a codec only when it + // supports clear lead, unless `force_support_clear_lead` is set to true. + hw_secure_codecs = GetSupportedCodecs( + hw_secure_capability, + !media::kHardwareSecureDecryptionForceSupportClearLead.Get()); + hw_secure_encryption_schemes = + capability.hw_cdm_capability_or_status->encryption_schemes; + if (!base::Contains(capability.hw_cdm_capability_or_status->session_types, + CdmSessionType::kTemporary)) { + DVLOG(1) << "Temporary sessions must be supported for hardware secure " + "PlayReady"; + return; + } + DVLOG(2) << __func__ << ": Hardware secure PlayReady supported"; + + key_systems->emplace_back(new PlayReadyKeySystemInfo( + hw_secure_codecs, hw_secure_encryption_schemes)); +} +#endif // BUILDFLAG(ENABLE_PLAYREADY) + void AddMediaFoundationClearKey( const media::KeySystemCapability& /*capability*/, KeySystemInfos* key_systems) { @@ -542,6 +606,13 @@ } #if BUILDFLAG(IS_WIN) +#if BUILDFLAG(ENABLE_PLAYREADY) + if (key_system == kPlayReadyKeySystemRecommendationDefault) { + AddPlayReady(capability, can_persist_data, &key_systems); + continue; + } +#endif // BUILDFLAG(ENABLE_PLAYREADY) + if (key_system == media::kMediaFoundationClearKeyKeySystem) { AddMediaFoundationClearKey(capability, &key_systems); continue;
diff --git a/components/collaboration/internal/collaboration_service_impl_unittest.cc b/components/collaboration/internal/collaboration_service_impl_unittest.cc index d835b6f..119f8cb5 100644 --- a/components/collaboration/internal/collaboration_service_impl_unittest.cc +++ b/components/collaboration/internal/collaboration_service_impl_unittest.cc
@@ -173,6 +173,8 @@ SigninStatus::kSigninDisabled); EXPECT_EQ(service_->GetServiceStatus().collaboration_status, CollaborationStatus::kEnabledCreateAndJoin); + EXPECT_EQ(service_->GetServiceStatus().IsAllowedToJoin(), true); + EXPECT_EQ(service_->GetServiceStatus().IsAllowedToCreate(), false); pref_service_.SetManagedPref(prefs::kSigninAllowed, base::Value(false)); EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
diff --git a/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java b/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java index 74e2a25..34ecc76 100644 --- a/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java +++ b/components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java
@@ -57,6 +57,10 @@ * @return Whether the user is allowed to create a collaboration group. */ public boolean isAllowedToCreate() { + if (signinStatus == SigninStatus.SIGNIN_DISABLED) { + return false; + } + switch (collaborationStatus) { case CollaborationStatus.DISABLED: case CollaborationStatus.DISABLED_PENDING:
diff --git a/components/collaboration/public/service_status.cc b/components/collaboration/public/service_status.cc index 4506378..a0a6eeb 100644 --- a/components/collaboration/public/service_status.cc +++ b/components/collaboration/public/service_status.cc
@@ -25,6 +25,11 @@ bool ServiceStatus::IsAllowedToCreate() { // Please keep logic consistent with // //components/collaboration/public/android/java/src/org/chromium/components/collaboration/ServiceStatus.java. + + if (signin_status == SigninStatus::kSigninDisabled) { + return false; + } + switch (collaboration_status) { case CollaborationStatus::kDisabled: case CollaborationStatus::kDisabledPending:
diff --git a/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java b/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java index 270c2ef..e3c9477 100644 --- a/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java +++ b/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java
@@ -63,7 +63,7 @@ // If mLifetimeAssert is GC'ed before this is called, it will throw an exception // with a stack trace showing the stack during LifetimeAssert.create(). - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } /** @@ -85,7 +85,7 @@ // If mLifetimeAssert is GC'ed before this is called, it will throw an exception // with a stack trace showing the stack during LifetimeAssert.create(). - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); } /**
diff --git a/components/crash/core/app/crashpad_android.cc b/components/crash/core/app/crashpad_android.cc index 0392a2b..9a4c7c1 100644 --- a/components/crash/core/app/crashpad_android.cc +++ b/components/crash/core/app/crashpad_android.cc
@@ -165,8 +165,8 @@ restore_previous_handler_ = build_info->sdk_int() < base::android::SDK_VERSION_JELLY_BEAN_MR2 || build_info->sdk_int() >= base::android::SDK_VERSION_OREO || - strcmp(build_info->build_type(), "eng") == 0 || - strcmp(build_info->build_type(), "userdebug") == 0; + build_info->build_type() == "eng" || + build_info->build_type() == "userdebug"; bool signal_stack_initialized = CrashpadClient::InitializeSignalStackForThread();
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java index e1159770..d12c36b 100644 --- a/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java +++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java
@@ -137,12 +137,12 @@ @GuardedBy("mNativeStreamLock") // Pending write data. - private LinkedList<ByteBuffer> mPendingData; + private final LinkedList<ByteBuffer> mPendingData; @GuardedBy("mNativeStreamLock") // Flush data queue that should be pushed to the native stack when the previous // CronetBidirectionalStreamJni.get().writevData completes. - private LinkedList<ByteBuffer> mFlushData; + private final LinkedList<ByteBuffer> mFlushData; @GuardedBy("mNativeStreamLock") // Whether an end-of-stream flag is passed in through write().
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java index 6ae2111..eead1f4 100644 --- a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java +++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java
@@ -103,8 +103,8 @@ private final int mTrafficStatsUid; private final VersionSafeCallbacks.RequestFinishedInfoListener mRequestFinishedListener; // See {@link org.chromium.net.UrlRequest.Builder#setRawCompressionDictionary}. - private byte[] mDictionarySha256Hash; - private ByteBuffer mDictionary; + private final byte[] mDictionarySha256Hash; + private final ByteBuffer mDictionary; private final String mDictionaryId; private final long mNetworkHandle; private final CronetLogger mLogger;
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java index cabb622c..7e00f3f 100644 --- a/components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java +++ b/components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java
@@ -91,8 +91,8 @@ /* These don't change with redirects */ private final String mInitialMethod; - private VersionSafeCallbacks.UploadDataProviderWrapper mUploadDataProvider; - private Executor mUploadExecutor; + private final VersionSafeCallbacks.UploadDataProviderWrapper mUploadDataProvider; + private final Executor mUploadExecutor; /** * Holds a subset of StatusValues - {@link State#STARTED} can represent {@link
diff --git a/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java index 923fc55..171bd43 100644 --- a/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java +++ b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java
@@ -50,7 +50,7 @@ private boolean mTrafficStatsUidSet; private int mTrafficStatsUid; - private CronetInputStream mInputStream; + private final CronetInputStream mInputStream; private CronetOutputStream mOutputStream; private UrlResponseInfo mResponseInfo; private IOException mException;
diff --git a/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/MainFragment.java b/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/MainFragment.java index ab01c15..d1fbd2b0 100644 --- a/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/MainFragment.java +++ b/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/MainFragment.java
@@ -81,8 +81,8 @@ } class SimpleUrlRequestCallback extends UrlRequest.Callback { - private ByteArrayOutputStream mBytesReceived = new ByteArrayOutputStream(); - private WritableByteChannel mReceiveChannel = Channels.newChannel(mBytesReceived); + private final ByteArrayOutputStream mBytesReceived = new ByteArrayOutputStream(); + private final WritableByteChannel mReceiveChannel = Channels.newChannel(mBytesReceived); @Override public void onRedirectReceived(
diff --git a/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/SampleActivityViewModel.java b/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/SampleActivityViewModel.java index f97d129..003b43ee 100644 --- a/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/SampleActivityViewModel.java +++ b/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/SampleActivityViewModel.java
@@ -11,7 +11,7 @@ import java.util.Map; public class SampleActivityViewModel extends ViewModel { - private Map<Integer, Fragment> mFragmentMap = new HashMap<>(); + private final Map<Integer, Fragment> mFragmentMap = new HashMap<>(); public static final int FRAGMENT_ID_HOME = 0; public static final int FRAGMENT_ID_FLAGS = 1;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java index a9fba71..2e20ecb 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java
@@ -21,7 +21,7 @@ * @param <T> The actual type of the class extending CronetLogger. */ public class CronetLoggerTestRule<T extends CronetLogger> implements TestRule { - private Class<T> mTestLoggerClazz; + private final Class<T> mTestLoggerClazz; // Expose the fake logger to the test. public T mTestLogger;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestTest.java index f3f4adb..6ea9cf8 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestTest.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestTest.java
@@ -2634,7 +2634,7 @@ class HangingUploadDataProvider extends UploadDataProvider { UploadDataSink mUploadDataSink; ByteBuffer mByteBuffer; - ConditionVariable mReadCalled = new ConditionVariable(false); + final ConditionVariable mReadCalled = new ConditionVariable(false); @Override public long getLength() {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java index 11b8217..dea3935 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
@@ -80,7 +80,7 @@ } static class DirectExecutor implements Executor { - private ConditionVariable mBlock = new ConditionVariable(); + private final ConditionVariable mBlock = new ConditionVariable(); @Override public void execute(Runnable task) { @@ -94,7 +94,7 @@ } static class ThreadExecutor implements Executor { - private List<Thread> mThreads = new ArrayList<Thread>(); + private final List<Thread> mThreads = new ArrayList<Thread>(); @Override public void execute(Runnable task) {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/TestBidirectionalStreamCallback.java b/components/cronet/android/test/javatests/src/org/chromium/net/TestBidirectionalStreamCallback.java index 6176922..66e93d96 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/TestBidirectionalStreamCallback.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/TestBidirectionalStreamCallback.java
@@ -45,7 +45,7 @@ // The executor thread will block on this after reaching a terminal method. // Terminal methods are (onSucceeded, onFailed or onCancelled) - private ConditionVariable mBlockOnTerminalState = new ConditionVariable(true); + private final ConditionVariable mBlockOnTerminalState = new ConditionVariable(true); // Conditionally fail on certain steps. private FailureType mFailureType = FailureType.NONE;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/TestNetworkQualityRttListener.java b/components/cronet/android/test/javatests/src/org/chromium/net/TestNetworkQualityRttListener.java index 404fd26..9481b5ea 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/TestNetworkQualityRttListener.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/TestNetworkQualityRttListener.java
@@ -21,7 +21,7 @@ private int mRttObservationCount; // Holds the RTT observations counts indexed by source. - private SparseIntArray mRttObservationCountBySource = new SparseIntArray(); + private final SparseIntArray mRttObservationCountBySource = new SparseIntArray(); private Thread mExecutorThread;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/TestUploadDataProvider.java b/components/cronet/android/test/javatests/src/org/chromium/net/TestUploadDataProvider.java index 38f8926..718a329a 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/TestUploadDataProvider.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/TestUploadDataProvider.java
@@ -31,7 +31,7 @@ CALLBACK_ASYNC } - private ArrayList<byte[]> mReads = new ArrayList<byte[]>(); + private final ArrayList<byte[]> mReads = new ArrayList<byte[]>(); private final SuccessCallbackMode mSuccessCallbackMode; private final Executor mExecutor; @@ -56,8 +56,8 @@ // Used to ensure there are no read/rewind requests after a failure. private boolean mFailed; - private AtomicBoolean mClosed = new AtomicBoolean(false); - private ConditionVariable mAwaitingClose = new ConditionVariable(false); + private final AtomicBoolean mClosed = new AtomicBoolean(false); + private final ConditionVariable mAwaitingClose = new ConditionVariable(false); public TestUploadDataProvider( SuccessCallbackMode successCallbackMode, final Executor executor) {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/TestUrlRequestCallback.java b/components/cronet/android/test/javatests/src/org/chromium/net/TestUrlRequestCallback.java index 11e51d43..8ebadd3 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/TestUrlRequestCallback.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/TestUrlRequestCallback.java
@@ -52,7 +52,7 @@ // The executor thread will block on this after reaching a terminal method. // Terminal methods are (onSucceeded, onFailed or onCancelled) - private ConditionVariable mBlockOnTerminalState = new ConditionVariable(true); + private final ConditionVariable mBlockOnTerminalState = new ConditionVariable(true); // Conditionally fail on certain steps. private FailureType mFailureType = FailureType.NONE;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/impl/TestLogger.java b/components/cronet/android/test/javatests/src/org/chromium/net/impl/TestLogger.java index 500c0ee..b800e8f 100644 --- a/components/cronet/android/test/javatests/src/org/chromium/net/impl/TestLogger.java +++ b/components/cronet/android/test/javatests/src/org/chromium/net/impl/TestLogger.java
@@ -20,21 +20,21 @@ /** Records the last engine creation (and traffic info) call it has received. */ public final class TestLogger extends CronetLogger { - private AtomicInteger mNextId = new AtomicInteger(); + private final AtomicInteger mNextId = new AtomicInteger(); private final AtomicInteger mCallsToLogCronetEngineBuilderInitializedInfo = new AtomicInteger(); private final AtomicInteger mCallsToCronetInitializedInfo = new AtomicInteger(); - private AtomicInteger mCallsToLogCronetEngineCreation = new AtomicInteger(); - private AtomicInteger mCallsToLogCronetTrafficInfo = new AtomicInteger(); - private AtomicLong mCronetEngineId = new AtomicLong(); - private AtomicLong mCronetRequestId = new AtomicLong(); + private final AtomicInteger mCallsToLogCronetEngineCreation = new AtomicInteger(); + private final AtomicInteger mCallsToLogCronetTrafficInfo = new AtomicInteger(); + private final AtomicLong mCronetEngineId = new AtomicLong(); + private final AtomicLong mCronetRequestId = new AtomicLong(); private final AtomicReference<CronetEngineBuilderInitializedInfo> mCronetEngineBuilderInitializedInfo = new AtomicReference<>(); private final AtomicReference<CronetInitializedInfo> mCronetInitializedInfo = new AtomicReference<>(); - private AtomicReference<CronetTrafficInfo> mTrafficInfo = new AtomicReference<>(); - private AtomicReference<CronetEngineBuilderInfo> mBuilderInfo = new AtomicReference<>(); - private AtomicReference<CronetVersion> mVersion = new AtomicReference<>(); - private AtomicReference<CronetSource> mSource = new AtomicReference<>(); + private final AtomicReference<CronetTrafficInfo> mTrafficInfo = new AtomicReference<>(); + private final AtomicReference<CronetEngineBuilderInfo> mBuilderInfo = new AtomicReference<>(); + private final AtomicReference<CronetVersion> mVersion = new AtomicReference<>(); + private final AtomicReference<CronetSource> mSource = new AtomicReference<>(); private final ConditionVariable mCronetInitializedInfoCalled = new ConditionVariable(); private final ConditionVariable mBlock = new ConditionVariable();
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestRule.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestRule.java index 30173316..b490f1ea 100644 --- a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestRule.java +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestRule.java
@@ -22,7 +22,7 @@ public abstract class CronetSmokeTestRule implements TestRule { public ExperimentalCronetEngine.Builder mCronetEngineBuilder; public CronetEngine mCronetEngine; - private TestSupport mTestSupport = initTestSupport(); + private final TestSupport mTestSupport = initTestSupport(); @Override public Statement apply(final Statement base, Description desc) {
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java index a7dcbc7..bc72961a 100644 --- a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java
@@ -37,8 +37,8 @@ private static final int PORT = 8080; private Channel mServerChannel; - private ConditionVariable mStartBlock = new ConditionVariable(); - private ConditionVariable mShutdownBlock = new ConditionVariable(); + private final ConditionVariable mStartBlock = new ConditionVariable(); + private final ConditionVariable mShutdownBlock = new ConditionVariable(); @Override public boolean start() {
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java index 9357c37..2c79bf5 100644 --- a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java
@@ -22,7 +22,7 @@ private static final int READ_BUFFER_SIZE = 10000; // An executor that is used to execute {@link UrlRequest.Callback UrlRequest callbacks}. - private ExecutorService mExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); // Signals when the request is done either successfully or not. private final ConditionVariable mDone = new ConditionVariable();
diff --git a/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java b/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java index f13b0e3..74a70bc 100644 --- a/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java +++ b/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java
@@ -58,11 +58,11 @@ private static final ByteBuf RESPONSE_BYTES = unreleasableBuffer(copiedBuffer("HTTP/2 Test Server", CharsetUtil.UTF_8)); - private HashMap<Integer, RequestResponder> mResponderMap = new HashMap<>(); + private final HashMap<Integer, RequestResponder> mResponderMap = new HashMap<>(); - private ReportingCollector mReportingCollector; - private String mServerUrl; - private CountDownLatch mHangingUrlLatch; + private final ReportingCollector mReportingCollector; + private final String mServerUrl; + private final CountDownLatch mHangingUrlLatch; /** Builder for HTTP/2 test handler. */ public static final class Builder @@ -507,7 +507,7 @@ // A RequestResponder that implements a Reporting collector. private class ReportingCollectorResponder extends RequestResponder { - private ByteArrayOutputStream mPartialPayload = new ByteArrayOutputStream(); + private final ByteArrayOutputStream mPartialPayload = new ByteArrayOutputStream(); @Override void onHeadersRead(
diff --git a/components/cronet/android/test/src/org/chromium/net/TestUploadDataStreamHandler.java b/components/cronet/android/test/src/org/chromium/net/TestUploadDataStreamHandler.java index d8219192..083baab 100644 --- a/components/cronet/android/test/src/org/chromium/net/TestUploadDataStreamHandler.java +++ b/components/cronet/android/test/src/org/chromium/net/TestUploadDataStreamHandler.java
@@ -24,14 +24,14 @@ public final class TestUploadDataStreamHandler { private final CronetEngine mCronetEngine; private long mTestUploadDataStreamHandler; - private ConditionVariable mWaitInitCalled = new ConditionVariable(); - private ConditionVariable mWaitInitComplete = new ConditionVariable(); - private ConditionVariable mWaitReadComplete = new ConditionVariable(); - private ConditionVariable mWaitResetComplete = new ConditionVariable(); + private final ConditionVariable mWaitInitCalled = new ConditionVariable(); + private final ConditionVariable mWaitInitComplete = new ConditionVariable(); + private final ConditionVariable mWaitReadComplete = new ConditionVariable(); + private final ConditionVariable mWaitResetComplete = new ConditionVariable(); // Waits for checkIfInitCallbackInvoked() returns result asynchronously. - private ConditionVariable mWaitCheckInit = new ConditionVariable(); + private final ConditionVariable mWaitCheckInit = new ConditionVariable(); // Waits for checkIfReadCallbackInvoked() returns result asynchronously. - private ConditionVariable mWaitCheckRead = new ConditionVariable(); + private final ConditionVariable mWaitCheckRead = new ConditionVariable(); // If true, init completes synchronously. private boolean mInitCompletedSynchronously; private String mData = "";
diff --git a/components/enterprise/browser/reporting/chrome_profile_request_generator_unittest.cc b/components/enterprise/browser/reporting/chrome_profile_request_generator_unittest.cc index 1287b08d..19fcc81 100644 --- a/components/enterprise/browser/reporting/chrome_profile_request_generator_unittest.cc +++ b/components/enterprise/browser/reporting/chrome_profile_request_generator_unittest.cc
@@ -246,7 +246,8 @@ TEST_F(ChromeProfileRequestGeneratorTest, GenerateFullReportNoSecuritySignals) { EXPECT_CALL(mock_aggregator_, GetSignals(_, _)).Times(0); base::test::TestFuture<ReportRequestQueue> test_future; - generator_.Generate(ReportGenerationConfig(ReportType::kProfileReport, + generator_.Generate(ReportGenerationConfig(ReportTrigger::kTriggerTimer, + ReportType::kProfileReport, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false), test_future.GetCallback()); @@ -267,7 +268,8 @@ base::test::TestFuture<ReportRequestQueue> test_future; generator_.Generate( - ReportGenerationConfig(ReportType::kProfileReport, + ReportGenerationConfig(ReportTrigger::kTriggerTimer, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsAttached, /*use_cookies=*/false), test_future.GetCallback()); @@ -286,7 +288,8 @@ std::move(callback).Run(CreateFilledResponse()); })); base::test::TestFuture<ReportRequestQueue> test_future; - generator_.Generate(ReportGenerationConfig(ReportType::kProfileReport, + generator_.Generate(ReportGenerationConfig(ReportTrigger::kTriggerNone, + ReportType::kProfileReport, SecuritySignalsMode::kSignalsOnly, /*use_cookies=*/false), test_future.GetCallback()); @@ -297,10 +300,7 @@ TEST_F(ChromeProfileRequestGeneratorTest, IncorrectReportType) { EXPECT_CALL(mock_aggregator_, GetSignals(_, _)).Times(0); base::test::TestFuture<ReportRequestQueue> test_future; - generator_.Generate( - ReportGenerationConfig(ReportType::kFull, SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - test_future.GetCallback()); + generator_.Generate(ReportGenerationConfig(), test_future.GetCallback()); const ReportRequestQueue& requests = test_future.Get();
diff --git a/components/enterprise/browser/reporting/report_generation_config.cc b/components/enterprise/browser/reporting/report_generation_config.cc index 76032f53..11a2ed5 100644 --- a/components/enterprise/browser/reporting/report_generation_config.cc +++ b/components/enterprise/browser/reporting/report_generation_config.cc
@@ -9,9 +9,29 @@ namespace { constexpr char kReportGenerationConfigTemplate[] = - R"(Report Type: %s, Security Signals Mode: %s, Using Cookies: %s)"; + R"(Trigger: %s, Report Type: %s, Security Signals Mode: %s," + " Using Cookies: %s)"; -std::string TranslateReportType(enterprise_reporting::ReportType report_type) { +std::string_view ReportTriggerToString( + enterprise_reporting::ReportTrigger report_trigger) { + switch (report_trigger) { + case enterprise_reporting::ReportTrigger::kTriggerNone: + return "No trigger"; + case enterprise_reporting::ReportTrigger::kTriggerTimer: + return "Periodic timer expired"; + case enterprise_reporting::ReportTrigger::kTriggerUpdate: + return "An update was detected"; + case enterprise_reporting::ReportTrigger::kTriggerNewVersion: + return "A new version is running"; + case enterprise_reporting::ReportTrigger::kTriggerManual: + return "Trigger manually"; + case enterprise_reporting::ReportTrigger::kTriggerSecurity: + return "Trigger for security signals"; + } +} + +std::string_view TranslateReportType( + enterprise_reporting::ReportType report_type) { switch (report_type) { case enterprise_reporting::ReportType::kFull: return "Full/Browser Report"; @@ -22,7 +42,7 @@ } } -std::string TranslateSecuritySignalsMode( +std::string_view TranslateSecuritySignalsMode( SecuritySignalsMode security_signals_mode) { switch (security_signals_mode) { case SecuritySignalsMode::kNoSignals: @@ -39,10 +59,12 @@ namespace enterprise_reporting { ReportGenerationConfig::ReportGenerationConfig( + ReportTrigger report_trigger, ReportType report_type, SecuritySignalsMode security_signals_mode, bool use_cookies) - : report_type(report_type), + : report_trigger(report_trigger), + report_type(report_type), security_signals_mode(security_signals_mode), use_cookies(use_cookies) { // Currently security signals are only being reported in profile level @@ -52,11 +74,15 @@ } } -ReportGenerationConfig::ReportGenerationConfig() - : ReportGenerationConfig(ReportType::kFull, +ReportGenerationConfig::ReportGenerationConfig(ReportTrigger report_trigger) + : ReportGenerationConfig(report_trigger, + ReportType::kFull, SecuritySignalsMode::kNoSignals, /*use_cookies=*/false) {} +ReportGenerationConfig::ReportGenerationConfig() + : ReportGenerationConfig(ReportTrigger::kTriggerNone) {} + ReportGenerationConfig::~ReportGenerationConfig() = default; bool ReportGenerationConfig::operator==(const ReportGenerationConfig&) const = @@ -64,6 +90,7 @@ std::string ReportGenerationConfig::ToString() const { return base::StringPrintf(kReportGenerationConfigTemplate, + ReportTriggerToString(report_trigger), TranslateReportType(report_type), TranslateSecuritySignalsMode(security_signals_mode), use_cookies ? "Yes" : "No");
diff --git a/components/enterprise/browser/reporting/report_generation_config.h b/components/enterprise/browser/reporting/report_generation_config.h index 5b02928..8aaf740 100644 --- a/components/enterprise/browser/reporting/report_generation_config.h +++ b/components/enterprise/browser/reporting/report_generation_config.h
@@ -25,12 +25,25 @@ namespace enterprise_reporting { +// The trigger leading to report generation. Values are bitmasks in the +// |pending_triggers_| bitfield. +enum ReportTrigger : uint32_t { + kTriggerNone = 0, // No trigger. + kTriggerTimer = 1U << 0, // The periodic timer expired. + kTriggerUpdate = 1U << 1, // An update was detected. + kTriggerNewVersion = 1U << 2, // A new version is running. + kTriggerManual = 1U << 3, // Trigger manually. + kTriggerSecurity = 1U << 4, // Triggered by a security trigger. +}; + // Struct that includes various configuration of report generation and upload // process. Only used by profile-level reporting for now. struct ReportGenerationConfig { - ReportGenerationConfig(ReportType report_type, + ReportGenerationConfig(ReportTrigger report_trigger, + ReportType report_type, SecuritySignalsMode security_signals_mode, bool use_cookies); + explicit ReportGenerationConfig(ReportTrigger report_trigger); ReportGenerationConfig(); ~ReportGenerationConfig(); @@ -40,9 +53,10 @@ // logging and debugging purposes. std::string ToString() const; - ReportType report_type{}; - SecuritySignalsMode security_signals_mode{SecuritySignalsMode::kNoSignals}; - bool use_cookies = false; + ReportTrigger report_trigger; + ReportType report_type; + SecuritySignalsMode security_signals_mode; + bool use_cookies; }; } // namespace enterprise_reporting
diff --git a/components/enterprise/browser/reporting/report_scheduler.cc b/components/enterprise/browser/reporting/report_scheduler.cc index 55fb4f4..cd7e5ce 100644 --- a/components/enterprise/browser/reporting/report_scheduler.cc +++ b/components/enterprise/browser/reporting/report_scheduler.cc
@@ -31,15 +31,15 @@ const int kMaximumRetry = 10; // Retry 10 times takes about 15 to 19 hours. -bool IsBrowserVersionUploaded(ReportScheduler::ReportTrigger trigger) { +bool IsBrowserVersionUploaded(ReportTrigger trigger) { switch (trigger) { - case ReportScheduler::kTriggerTimer: - case ReportScheduler::kTriggerManual: - case ReportScheduler::kTriggerUpdate: - case ReportScheduler::kTriggerNewVersion: - case ReportScheduler::kTriggerSecurity: + case ReportTrigger::kTriggerTimer: + case ReportTrigger::kTriggerManual: + case ReportTrigger::kTriggerUpdate: + case ReportTrigger::kTriggerNewVersion: + case ReportTrigger::kTriggerSecurity: return true; - case ReportScheduler::kTriggerNone: + case ReportTrigger::kTriggerNone: return false; } } @@ -102,9 +102,8 @@ return request_timer_.IsRunning(); } -ReportScheduler::ReportTrigger ReportScheduler::GetActiveTriggerForTesting() - const { - return active_trigger_; +ReportTrigger ReportScheduler::GetActiveTriggerForTesting() const { + return active_report_generation_config_.report_trigger; } ReportGenerationConfig ReportScheduler::GetActiveGenerationConfigForTesting() @@ -262,26 +261,25 @@ SetupBrowserPolicyClientRegistration(); } - if (active_trigger_ != kTriggerNone) { + if (active_report_generation_config_.report_trigger != kTriggerNone) { // A report is already being generated. Remember this trigger to be handled // once the current report completes. pending_triggers_ |= trigger; return; } - active_trigger_ = trigger; - ReportType report_type = TriggerToReportType(active_trigger_); + ReportType report_type = TriggerToReportType(trigger); SecuritySignalsMode signals_mode = SecuritySignalsMode::kNoSignals; if (report_type == ReportType::kProfileReport) { signals_mode = delegate_->AreSecurityReportsEnabled() - ? (active_trigger_ == ReportScheduler::kTriggerSecurity + ? (trigger == ReportTrigger::kTriggerSecurity ? SecuritySignalsMode::kSignalsOnly : SecuritySignalsMode::kSignalsAttached) : SecuritySignalsMode::kNoSignals; } active_report_generation_config_ = ReportGenerationConfig( - report_type, signals_mode, delegate_->UseCookiesInUploads()); + trigger, report_type, signals_mode, delegate_->UseCookiesInUploads()); VLOG_POLICY(1, REPORTING) << "Starting report generation with the following configuration: " @@ -303,13 +301,15 @@ } void ReportScheduler::OnReportGenerated(ReportRequestQueue requests) { - DCHECK_NE(active_trigger_, kTriggerNone); + DCHECK_NE(active_report_generation_config_.report_trigger, + ReportTrigger::kTriggerNone); if (requests.empty()) { SYSLOG(ERROR) << "No cloud report can be generated. Likely the report is too large."; // Do not restart the periodic report timer, as it's likely that subsequent // attempts to generate full reports would also fail. - active_trigger_ = kTriggerNone; + active_report_generation_config_ = + ReportGenerationConfig(ReportTrigger::kTriggerNone); RunPendingTriggers(); return; } @@ -336,19 +336,23 @@ } void ReportScheduler::OnReportUploaded(ReportUploader::ReportStatus status) { - DCHECK_NE(active_trigger_, kTriggerNone); + DCHECK_NE(active_report_generation_config_.report_trigger, + ReportTrigger::kTriggerNone); VLOG(1) << "The enterprise report upload result " << status << "."; switch (status) { case ReportUploader::kSuccess: // Schedule the next report for success. Reset uploader to reset failure // count. report_uploader_.reset(); - if (IsBrowserVersionUploaded(active_trigger_)) + if (IsBrowserVersionUploaded( + active_report_generation_config_.report_trigger)) { delegate_->OnBrowserVersionUploaded(); + } // Signals-only report does not contain most content of a status report // and should not update this timestamp. - if (active_trigger_ != ReportScheduler::kTriggerSecurity) { + if (active_report_generation_config_.report_trigger != + ReportTrigger::kTriggerSecurity) { delegate_->GetPrefService()->SetTime(kLastUploadSucceededTimestamp, base::Time::Now()); } @@ -365,8 +369,10 @@ case ReportUploader::kTransientError: // Stop retrying and schedule the next report to avoid stale report. // Failure count is not reset so retry delay remains. - if (active_trigger_ == kTriggerTimer || - active_trigger_ == kTriggerManual) { + if (active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerTimer || + active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerManual) { const base::Time now = base::Time::Now(); delegate_->GetPrefService()->SetTime(kLastUploadTimestamp, now); if (IsReportingEnabled()) @@ -379,17 +385,26 @@ break; } - if ((active_trigger_ == kTriggerManual || active_trigger_ == kTriggerTimer)) { + if ((active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerManual || + active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerTimer)) { // Timer and Manual report are exactly same. If we just uploaded one, skip // the other. - if (pending_triggers_ & kTriggerTimer) - pending_triggers_ -= kTriggerTimer; - if (pending_triggers_ & kTriggerManual) - pending_triggers_ -= kTriggerManual; + if (pending_triggers_ & ReportTrigger::kTriggerTimer) { + pending_triggers_ -= ReportTrigger::kTriggerTimer; + } + if (pending_triggers_ & ReportTrigger::kTriggerManual) { + pending_triggers_ -= ReportTrigger::kTriggerManual; + } } - if (active_trigger_ == kTriggerManual || active_trigger_ == kTriggerTimer || - active_trigger_ == kTriggerSecurity) { + if (active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerManual || + active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerTimer || + active_report_generation_config_.report_trigger == + ReportTrigger::kTriggerSecurity) { if (on_manual_report_uploaded_) { std::move(on_manual_report_uploaded_).Run(); } @@ -400,18 +415,20 @@ // A full report includes security signals already, we don't need another // security signals only report until the timer runs out again. - if (pending_triggers_ & kTriggerSecurity) { - pending_triggers_ -= kTriggerSecurity; + if (pending_triggers_ & ReportTrigger::kTriggerSecurity) { + pending_triggers_ -= ReportTrigger::kTriggerSecurity; } } } - active_trigger_ = kTriggerNone; + active_report_generation_config_ = + ReportGenerationConfig(ReportTrigger::kTriggerNone); RunPendingTriggers(); } void ReportScheduler::RunPendingTriggers() { - DCHECK_EQ(active_trigger_, kTriggerNone); + DCHECK_EQ(active_report_generation_config_.report_trigger, + ReportTrigger::kTriggerNone); if (!pending_triggers_) return; @@ -419,28 +436,28 @@ // new version, so favor them and consider that they serve all purposes. ReportTrigger trigger; - if ((pending_triggers_ & kTriggerTimer) != 0) { + if ((pending_triggers_ & ReportTrigger::kTriggerTimer) != 0) { // Timer-triggered reports contain data of all other report types. - trigger = kTriggerTimer; + trigger = ReportTrigger::kTriggerTimer; pending_triggers_ = 0; - } else if ((pending_triggers_ & kTriggerManual) != 0) { + } else if ((pending_triggers_ & ReportTrigger::kTriggerManual) != 0) { // Manual-triggered reports also contains all data. trigger = kTriggerManual; pending_triggers_ = 0; - } else if ((pending_triggers_ & kTriggerSecurity) != 0) { + } else if ((pending_triggers_ & ReportTrigger::kTriggerSecurity) != 0) { trigger = kTriggerSecurity; - pending_triggers_ -= kTriggerSecurity; + pending_triggers_ -= ReportTrigger::kTriggerSecurity; } else { // Update and NewVersion triggers lead to the same report content being // uploaded. - if ((pending_triggers_ & kTriggerUpdate) != 0) { - trigger = kTriggerUpdate; - pending_triggers_ -= kTriggerUpdate; + if ((pending_triggers_ & ReportTrigger::kTriggerUpdate) != 0) { + trigger = ReportTrigger::kTriggerUpdate; + pending_triggers_ -= ReportTrigger::kTriggerUpdate; } - if ((pending_triggers_ & kTriggerNewVersion) != 0) { - trigger = kTriggerNewVersion; - pending_triggers_ -= kTriggerNewVersion; + if ((pending_triggers_ & ReportTrigger::kTriggerNewVersion) != 0) { + trigger = ReportTrigger::kTriggerNewVersion; + pending_triggers_ -= ReportTrigger::kTriggerNewVersion; } } @@ -461,22 +478,22 @@ kSecurity = 7, kMaxValue = kSecurity } sample = Sample::kNone; - switch (active_trigger_) { - case kTriggerNone: + switch (active_report_generation_config_.report_trigger) { + case ReportTrigger::kTriggerNone: break; - case kTriggerTimer: + case ReportTrigger::kTriggerTimer: sample = Sample::kTimer; break; - case kTriggerManual: + case ReportTrigger::kTriggerManual: sample = Sample::kManual; break; - case kTriggerUpdate: + case ReportTrigger::kTriggerUpdate: sample = Sample::kUpdate; break; - case kTriggerNewVersion: + case ReportTrigger::kTriggerNewVersion: sample = Sample::kNewVersion; break; - case kTriggerSecurity: + case ReportTrigger::kTriggerSecurity: sample = Sample::kSecurity; break; } @@ -491,19 +508,18 @@ } } -ReportType ReportScheduler::TriggerToReportType( - ReportScheduler::ReportTrigger trigger) { +ReportType ReportScheduler::TriggerToReportType(ReportTrigger trigger) { switch (trigger) { - case ReportScheduler::kTriggerNone: + case ReportTrigger::kTriggerNone: NOTREACHED(); - case ReportScheduler::kTriggerTimer: - case ReportScheduler::kTriggerManual: + case ReportTrigger::kTriggerTimer: + case ReportTrigger::kTriggerManual: return full_report_type_; - case ReportScheduler::kTriggerUpdate: + case ReportTrigger::kTriggerUpdate: return ReportType::kBrowserVersion; - case ReportScheduler::kTriggerNewVersion: + case ReportTrigger::kTriggerNewVersion: return ReportType::kBrowserVersion; - case ReportScheduler::kTriggerSecurity: + case ReportTrigger::kTriggerSecurity: // Security triggers are not supported at the browser-level yet. return ReportType::kProfileReport; }
diff --git a/components/enterprise/browser/reporting/report_scheduler.h b/components/enterprise/browser/reporting/report_scheduler.h index 365375b..6b89fe3 100644 --- a/components/enterprise/browser/reporting/report_scheduler.h +++ b/components/enterprise/browser/reporting/report_scheduler.h
@@ -36,17 +36,6 @@ // completes. class ReportScheduler { public: - // The trigger leading to report generation. Values are bitmasks in the - // |pending_triggers_| bitfield. - enum ReportTrigger : uint32_t { - kTriggerNone = 0, // No trigger. - kTriggerTimer = 1U << 0, // The periodic timer expired. - kTriggerUpdate = 1U << 1, // An update was detected. - kTriggerNewVersion = 1U << 2, // A new version is running. - kTriggerManual = 1U << 3, // Trigger manually. - kTriggerSecurity = 1U << 4, // Triggered by a security trigger. - }; - using ReportTriggerCallback = base::RepeatingCallback<void(ReportTrigger)>; class Delegate { @@ -186,10 +175,11 @@ std::unique_ptr<ChromeProfileRequestGenerator> profile_request_generator_; std::unique_ptr<RealTimeReportController> real_time_report_controller_; - // The trigger responsible for initiating active report generation. - ReportTrigger active_trigger_ = ReportTrigger::kTriggerNone; // The configuration for active report generation. - ReportGenerationConfig active_report_generation_config_; + // If the configuration has `kTriggerNone` as its trigger, it means there is + // no active report generation/upload in progress. + ReportGenerationConfig active_report_generation_config_ = + ReportGenerationConfig(ReportTrigger::kTriggerNone); // The set of triggers that have fired while processing a report (a bitfield // of ReportTrigger values). They will be handled following completion of the
diff --git a/components/enterprise/browser/reporting/report_uploader_unittest.cc b/components/enterprise/browser/reporting/report_uploader_unittest.cc index 9befc5e..08f232e 100644 --- a/components/enterprise/browser/reporting/report_uploader_unittest.cc +++ b/components/enterprise/browser/reporting/report_uploader_unittest.cc
@@ -99,8 +99,8 @@ } has_responded_ = false; uploader_->SetRequestAndUpload( - ReportGenerationConfig(GetReportType(), SecuritySignalsMode::kNoSignals, - use_cookies_), + ReportGenerationConfig(ReportTrigger::kTriggerNone, GetReportType(), + SecuritySignalsMode::kNoSignals, use_cookies_), std::move(requests), base::BindOnce(&ReportUploaderTest::OnReportUploaded, base::Unretained(this), expected_status)); @@ -173,8 +173,8 @@ requests.push(std::make_unique<ReportRequest>(GetReportType())); base::test::TestFuture<ReportUploader::ReportStatus> future; uploader_->SetRequestAndUpload( - ReportGenerationConfig(GetReportType(), SecuritySignalsMode::kNoSignals, - use_cookies_), + ReportGenerationConfig(ReportTrigger::kTriggerNone, GetReportType(), + SecuritySignalsMode::kNoSignals, use_cookies_), std::move(requests), future.GetCallback()); ASSERT_DEATH(std::ignore = future.Get(), ""); }
diff --git a/components/exo/buffer.cc b/components/exo/buffer.cc index 7dfd6c1..2b2fd8e 100644 --- a/components/exo/buffer.cc +++ b/components/exo/buffer.cc
@@ -98,12 +98,6 @@ return viz::SinglePlaneFormat::kBGRA_8888; case gfx::BufferFormat::R_8: return viz::SinglePlaneFormat::kR_8; - case gfx::BufferFormat::R_16: - return viz::SinglePlaneFormat::kR_16; - case gfx::BufferFormat::RG_1616: - return viz::SinglePlaneFormat::kRG_1616; - case gfx::BufferFormat::RGBA_4444: - return viz::SinglePlaneFormat::kRGBA_4444; case gfx::BufferFormat::RGBA_8888: return viz::SinglePlaneFormat::kRGBA_8888; case gfx::BufferFormat::RGBA_F16: @@ -126,12 +120,14 @@ case gfx::BufferFormat::YUV_420_BIPLANAR: format = viz::MultiPlaneFormat::kNV12; break; - case gfx::BufferFormat::YUVA_420_TRIPLANAR: - format = viz::MultiPlaneFormat::kNV12A; - break; case gfx::BufferFormat::P010: format = viz::MultiPlaneFormat::kP010; break; + case gfx::BufferFormat::R_16: + case gfx::BufferFormat::RG_1616: + case gfx::BufferFormat::RGBA_4444: + case gfx::BufferFormat::YUVA_420_TRIPLANAR: + NOTREACHED(); } #if BUILDFLAG(IS_CHROMEOS) // If format is true multiplanar format, we prefer external sampler on
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc index 2b2b348b..3e62a41 100644 --- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc +++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -330,7 +330,7 @@ session->ExecuteModel(PageUrlRequest("foo"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); - const std::string expected_response = "Context: execute:foo max:1024\n"; + const std::string expected_response = "execute:foo max:1024"; EXPECT_EQ(*response_.value(), expected_response); EXPECT_TRUE(*response_.provided_by_on_device()); EXPECT_THAT(response_.partials(), ElementsAre(expected_response)); @@ -453,8 +453,7 @@ session->ExecuteModel(PageUrlRequest("foo"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); - EXPECT_EQ(*response_.value(), - "Cache weight: 1015\nContext: execute:foo max:1024\n"); + EXPECT_EQ(*response_.value(), "Cache weight: 1015execute:foo max:1024"); // If we destroy all sessions and wait long enough, everything should idle out // and the service should get terminated. @@ -481,8 +480,7 @@ session->ExecuteModel(PageUrlRequest("foo"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); - EXPECT_EQ(*response_.value(), - "Adaptation model: 1015\nContext: execute:foo max:1024\n"); + EXPECT_EQ(*response_.value(), "Adaptation model: 1015execute:foo max:1024"); // If we destroy all sessions and wait long enough, everything should idle out // and the service should get terminated. @@ -535,11 +533,11 @@ ASSERT_TRUE(compose_response.GetFinalStatus()); EXPECT_EQ(*compose_response.value(), - "Adaptation model: 1015\nContext: execute:foo max:1024\n"); + "Adaptation model: 1015execute:foo max:1024"); EXPECT_TRUE(*compose_response.provided_by_on_device()); ASSERT_TRUE(test_response.GetFinalStatus()); EXPECT_EQ(*test_response.value(), - "Adaptation model: 2024\nContext: execute:bar max:1024\n"); + "Adaptation model: 2024execute:bar max:1024"); EXPECT_TRUE(*test_response.provided_by_on_device()); session_compose.reset(); @@ -594,10 +592,10 @@ ASSERT_TRUE(compose_response.GetFinalStatus()); EXPECT_EQ(*compose_response.value(), - "Adaptation model: 1015\nContext: execute:foo max:1024\n"); + "Adaptation model: 1015execute:foo max:1024"); EXPECT_TRUE(*compose_response.provided_by_on_device()); ASSERT_TRUE(test_response.GetFinalStatus()); - EXPECT_EQ(*test_response.value(), "Context: execute:bar max:1024\n"); + EXPECT_EQ(*test_response.value(), "execute:bar max:1024"); EXPECT_TRUE(*test_response.provided_by_on_device()); session_compose.reset(); @@ -719,8 +717,7 @@ session2->ExecuteModel(PageUrlRequest("foo"), response2.GetStreamingCallback()); ASSERT_TRUE(response2.GetFinalStatus()); - EXPECT_EQ(*response2.value(), - "Base model: 2\nContext: execute:foo max:1024\n"); + EXPECT_EQ(*response2.value(), "Base model: 2execute:foo max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, SessionFailsForInvalidFeature) { @@ -1636,7 +1633,7 @@ EXPECT_EQ( *resp1.error(), OptimizationGuideModelExecutionError::ModelExecutionError::kCancelled); - EXPECT_EQ(*resp2.value(), "Context: execute:bar max:1024\n"); + EXPECT_EQ(*resp2.value(), "execute:bar max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, WontStartSessionAfterGpuBlocked) { @@ -1822,8 +1819,8 @@ "OptimizationGuide.ModelExecution.OnDeviceExecuteModelResult.Compose", ExecuteModelResult::kUsedOnDevice, 1); std::string expected_response = - ("Context: ctx:foo max:8192\n" - "Context: execute:foobaz max:1024\n"); + ("ctx:foo max:8192" + "execute:foobaz max:1024"); EXPECT_EQ(*response_.value(), expected_response); } @@ -1861,16 +1858,16 @@ session2->ExecuteModel(PageUrlRequest("2"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); std::string expected_response1 = - ("Context: ctx:bar max:8192\n" - "Context: execute:bar2 max:1024\n"); + ("ctx:bar max:8192" + "execute:bar2 max:1024"); EXPECT_EQ(*response_.value(), expected_response1); ResponseHolder response2; session1->ExecuteModel(PageUrlRequest("1"), response2.GetStreamingCallback()); ASSERT_TRUE(response2.GetFinalStatus()); std::string expected_response2 = - ("Context: ctx:foo max:8192\n" - "Context: execute:foo1 max:1024\n"); + ("ctx:foo max:8192" + "execute:foo1 max:1024"); EXPECT_EQ(*response2.value(), expected_response2); } @@ -2067,7 +2064,7 @@ session1->ExecuteModel(UserInputRequest("foo"), response_.GetStreamingCallback()); task_environment_.RunUntilIdle(); - const std::string expected_response1 = "Context: execute:foo max:1024\n"; + const std::string expected_response1 = "execute:foo max:1024"; EXPECT_EQ(*response_.value(), expected_response1); EXPECT_THAT(response_.partials(), IsEmpty()); @@ -2078,19 +2075,19 @@ session2->ExecuteModel(UserInputRequest("abarx"), response2.GetStreamingCallback()); task_environment_.RunUntilIdle(); - const std::string expected_response2 = "Context: execute:abarx max:1024\n"; + const std::string expected_response2 = "execute:abarx max:1024"; EXPECT_EQ(*response2.value(), expected_response2); EXPECT_THAT(response2.partials(), IsEmpty()); // Output contains redacted text (and input doesn't), so redact. - fake_settings_.set_execute_result({"Context: abarx max:1024\n"}); + fake_settings_.set_execute_result({"abarx max:1024"}); auto session3 = CreateSession(); ASSERT_TRUE(session3); ResponseHolder response3; session3->ExecuteModel(UserInputRequest("foo"), response3.GetStreamingCallback()); task_environment_.RunUntilIdle(); - const std::string expected_response3 = "Context: a[###]x max:1024\n"; + const std::string expected_response3 = "a[###]x max:1024"; EXPECT_EQ(*response3.value(), expected_response3); EXPECT_THAT(response3.partials(), IsEmpty()); } @@ -2151,7 +2148,7 @@ }); // Force 'bar' to be returned from model. - fake_settings_.set_execute_result({"Context: bar max:1024\n"}); + fake_settings_.set_execute_result({"bar max:1024"}); auto session = CreateSession(); ASSERT_TRUE(session); @@ -2160,7 +2157,7 @@ response_.GetStreamingCallback()); task_environment_.RunUntilIdle(); // `bar` shouldn't be rewritten as it's in the input. - const std::string expected_response = "Context: bar max:1024\n"; + const std::string expected_response = "bar max:1024"; EXPECT_EQ(*response_.value(), expected_response); EXPECT_THAT(response_.partials(), IsEmpty()); } @@ -2177,13 +2174,13 @@ }); // Output contains redacted text (and input doesn't), so redact. - fake_settings_.set_execute_result({"Context: abarx max:1024\n"}); + fake_settings_.set_execute_result({"abarx max:1024"}); auto session = CreateSession(); ASSERT_TRUE(session); session->ExecuteModel(UserInputRequest("foo"), response_.GetStreamingCallback()); task_environment_.RunUntilIdle(); - const std::string expected_response = "Context: a[redacted]x max:1024\n"; + const std::string expected_response = "a[redacted]x max:1024"; EXPECT_EQ(*response_.value(), expected_response); EXPECT_THAT(response_.partials(), IsEmpty()); } @@ -2435,8 +2432,8 @@ task_environment_.RunUntilIdle(); EXPECT_TRUE(response_.value()); const std::vector<std::string> partial_responses = { - "Context: execute:foo max:1024\n", - "TopK: 3, Temp: 2\n", + "execute:foo max:1024", + "TopK: 3, Temp: 2", }; EXPECT_EQ(*response_.value(), ConcatResponses(partial_responses)); EXPECT_THAT(response_.partials(), ElementsAreArray(partial_responses)); @@ -3238,8 +3235,7 @@ session->ExecuteModel(PageUrlRequest("foo"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); - EXPECT_EQ(*response_.value(), - "Fastest inference\nContext: execute:foo max:1024\n"); + EXPECT_EQ(*response_.value(), "Fastest inferenceexecute:foo max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, ImageExecutionSuccess) { @@ -3296,8 +3292,7 @@ session->ExecuteModel(proto::ExampleForTestingRequest(), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); - EXPECT_EQ(*response.value(), - "Context: <image> max:22\nContext: <image> max:1024\n"); + EXPECT_EQ(*response.value(), "<image> max:22<image> max:1024"); } // Session without capabilities should not allow images. @@ -3310,8 +3305,8 @@ response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); EXPECT_EQ(*response.value(), - "Context: <unsupported> max:22\nContext: <unsupported> " - "max:1024\n"); + "<unsupported> max:22<unsupported> " + "max:1024"); } } @@ -3403,8 +3398,7 @@ altered_clone->ExecuteModel(proto::ExampleForTestingRequest(), altered_response.GetStreamingCallback()); ASSERT_TRUE(altered_response.GetFinalStatus()); - EXPECT_EQ(*altered_response.value(), - "Context: v1<image><audio>v2v3v4 max:22\n"); + EXPECT_EQ(*altered_response.value(), "v1<image><audio>v2v3v4 max:22"); // The clone that only extended should have sent input in separate chunks. ResponseHolder extended_response; @@ -3412,19 +3406,19 @@ extended_response.GetStreamingCallback()); ASSERT_TRUE(extended_response.GetFinalStatus()); EXPECT_EQ(*extended_response.value(), - "Context: v1<image><audio> max:22\n" - "Context: v2 max:22\n" - "Context: v3 max:4\n" - "Context: v4 max:4\n"); + "v1<image><audio> max:22" + "v2 max:22" + "v3 max:4" + "v4 max:4"); // The original should have input in separate chunks. session->ExecuteModel(proto::ExampleForTestingRequest(), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(*response_.value(), - "Context: v1<image><audio> max:22\n" - "Context: v2 max:22\n" - "Context: v3 max:4\n"); + "v1<image><audio> max:22" + "v2 max:22" + "v3 max:4"); } TEST_F(OnDeviceModelServiceControllerTest, OmitEmptyInputs) { @@ -3495,8 +3489,8 @@ task_environment_.RunUntilIdle(); EXPECT_TRUE(response_.value()); const std::vector<std::string> partial_responses = { - "Context: execute:foo max:1024\n", - "TopK: 3, Temp: 2\n", + "execute:foo max:1024", + "TopK: 3, Temp: 2", }; EXPECT_EQ(*response_.value(), ConcatResponses(partial_responses)); EXPECT_THAT(response_.partials(), ElementsAreArray(partial_responses)); @@ -3586,8 +3580,8 @@ clone->ExecuteModel(PageUrlRequest("bar"), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); std::string expected_response = - ("Context: ctx:foo max:8192\n" - "Context: execute:foobar max:1024\n"); + ("ctx:foo max:8192" + "execute:foobar max:1024"); EXPECT_EQ(*response.value(), expected_response); } @@ -3598,8 +3592,8 @@ response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); std::string expected_response = - ("Context: ctx:foo max:8192\n" - "Context: execute:fooblah max:1024\n"); + ("ctx:foo max:8192" + "execute:fooblah max:1024"); EXPECT_EQ(*response.value(), expected_response); } } @@ -3618,7 +3612,7 @@ ResponseHolder response; clone->ExecuteModel(PageUrlRequest("bar"), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); - EXPECT_EQ(*response.value(), "Context: execute:bar max:1024\n"); + EXPECT_EQ(*response.value(), "execute:bar max:1024"); } // Original session should execute with context @@ -3628,8 +3622,8 @@ response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); std::string expected_response = - ("Context: ctx:foo max:8192\n" - "Context: execute:fooblah max:1024\n"); + ("ctx:foo max:8192" + "execute:fooblah max:1024"); EXPECT_EQ(*response.value(), expected_response); } } @@ -3647,7 +3641,7 @@ ResponseHolder response; clone->ExecuteModel(PageUrlRequest("bar"), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); - EXPECT_EQ(*response.value(), "Context: execute:foobar max:1024\n"); + EXPECT_EQ(*response.value(), "execute:foobar max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, CloneAddContextDisconnectExecute) { @@ -3666,8 +3660,8 @@ clone->ExecuteModel(PageUrlRequest("bar"), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); std::string expected_response = - ("Context: ctx:foo max:8192\n" - "Context: execute:foobar max:1024\n"); + ("ctx:foo max:8192" + "execute:foobar max:1024"); EXPECT_EQ(*response.value(), expected_response); } @@ -3692,7 +3686,7 @@ ResponseHolder response; session->ExecuteModel(PageUrlRequest("bar"), response.GetStreamingCallback()); ASSERT_TRUE(response.GetFinalStatus()); - EXPECT_EQ(*response.value(), "Context: execute:bar max:1024\n"); + EXPECT_EQ(*response.value(), "execute:bar max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, @@ -3724,16 +3718,16 @@ auto session = CreateSession(); EXPECT_TRUE(session); - EXPECT_EQ(GetResponse(*session, "foo"), "Context: execute:foo max:1024\n"); + EXPECT_EQ(GetResponse(*session, "foo"), "execute:foo max:1024"); session->SetPriority(on_device_model::mojom::Priority::kBackground); EXPECT_EQ(GetResponse(*session, "foo"), - "Priority: background\nContext: execute:foo max:1024\n"); + "Priority: backgroundexecute:foo max:1024"); EXPECT_EQ(GetResponse(*session, "foo"), - "Priority: background\nContext: execute:foo max:1024\n"); + "Priority: backgroundexecute:foo max:1024"); session->SetPriority(on_device_model::mojom::Priority::kForeground); - EXPECT_EQ(GetResponse(*session, "foo"), "Context: execute:foo max:1024\n"); + EXPECT_EQ(GetResponse(*session, "foo"), "execute:foo max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, PriorityClone) { @@ -3742,17 +3736,17 @@ auto session = CreateSession(); EXPECT_TRUE(session); - EXPECT_EQ(GetResponse(*session, "foo"), "Context: execute:foo max:1024\n"); + EXPECT_EQ(GetResponse(*session, "foo"), "execute:foo max:1024"); session->SetPriority(on_device_model::mojom::Priority::kBackground); EXPECT_EQ(GetResponse(*session, "foo"), - "Priority: background\nContext: execute:foo max:1024\n"); + "Priority: backgroundexecute:foo max:1024"); auto clone = session->Clone(); EXPECT_EQ(GetResponse(*clone, "foo"), - "Priority: background\nContext: execute:foo max:1024\n"); + "Priority: backgroundexecute:foo max:1024"); EXPECT_EQ(GetResponse(*clone, "foo"), - "Priority: background\nContext: execute:foo max:1024\n"); + "Priority: backgroundexecute:foo max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, SetInputCallback) { @@ -3772,8 +3766,8 @@ response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(response_.value(), - "Context: ctx:foo max:8192\nContext: execute:foobar " - "max:1024\n"); + "ctx:foo max:8192execute:foobar " + "max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, SetInputCallbackCancelled) { @@ -3802,8 +3796,8 @@ response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(response_.value(), - "Context: ctx:foo max:8192\nContext: execute:foobar " - "max:1024\n"); + "ctx:foo max:8192execute:foobar " + "max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, SetInputCallbackError) { @@ -3831,7 +3825,7 @@ session->ExecuteModel(PageUrlRequest("foo"), response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); - EXPECT_EQ(response_.value(), "Context: execute:foo max:1024\n"); + EXPECT_EQ(response_.value(), "execute:foo max:1024"); EXPECT_EQ(response_.input_token_count(), strlen("execute:foo")); EXPECT_EQ(response_.output_token_count(), strlen("execute:foo max:1024")); } @@ -3848,8 +3842,8 @@ response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(response_.value(), - "Constraint: regex [A-Z]*\n" - "Context: execute:input max:1024\n"); + "Constraint: regex [A-Z]*" + "execute:input max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, ResponseConstraintConfigJson) { @@ -3880,8 +3874,8 @@ response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(response_.value(), - "Constraint: json { type: \"object\"}\n" - "Context: execute:input max:1024\n"); + "Constraint: json { type: \"object\"}" + "execute:input max:1024"); } TEST_F(OnDeviceModelServiceControllerTest, ResponseConstraintConfigRegex) { @@ -3912,8 +3906,8 @@ response_.GetStreamingCallback()); ASSERT_TRUE(response_.GetFinalStatus()); EXPECT_EQ(response_.value(), - "Constraint: regex [A-Z]*\n" - "Context: execute:input max:1024\n"); + "Constraint: regex [A-Z]*" + "execute:input max:1024"); } } // namespace optimization_guide
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal index c69f1d7..9b0ca6aa 160000 --- a/components/optimization_guide/internal +++ b/components/optimization_guide/internal
@@ -1 +1 @@ -Subproject commit c69f1d7a8ad0ed4c22595719e1d78b7bb8754516 +Subproject commit 9b0ca6aa4bd15459531457f03ecd60090cd69ed6
diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc index 9bb58ce..86c90bb 100644 --- a/components/page_info/page_info.cc +++ b/components/page_info/page_info.cc
@@ -814,6 +814,15 @@ #endif } +void PageInfo::OpenIncognitoSettingsView() { +#if BUILDFLAG(IS_ANDROID) + NOTREACHED(); +#else + // TODO(crbug.com/388294499): Add metrics for recording settings clicks. + delegate_->ShowIncognitoSettings(); +#endif +} + void PageInfo::OpenAllSitesViewFilteredToRws() { #if BUILDFLAG(IS_ANDROID) NOTREACHED();
diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h index c4cb8593..2dfcff8 100644 --- a/components/page_info/page_info.h +++ b/components/page_info/page_info.h
@@ -230,6 +230,9 @@ // Handles opening the link to show cookies settings and records the event. void OpenCookiesSettingsView(); + // Handles opening the link to show Incognito tracking protection settings. + void OpenIncognitoSettingsView(); + // Handles opening the link to show all sites settings with a filter for // current site's fps and records the event. void OpenAllSitesViewFilteredToRws();
diff --git a/components/page_info/page_info_delegate.h b/components/page_info/page_info_delegate.h index 035e131..5af907e3 100644 --- a/components/page_info/page_info_delegate.h +++ b/components/page_info/page_info_delegate.h
@@ -84,6 +84,7 @@ virtual bool IsIsolatedWebApp() = 0; virtual void ShowSiteSettings(const GURL& site_url) = 0; virtual void ShowCookiesSettings() = 0; + virtual void ShowIncognitoSettings() = 0; virtual void ShowAllSitesSettingsFilteredByRwsOwner( const std::u16string& rws_owner) = 0; virtual void ShowSyncSettings() = 0;
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc index 688a789..de36ad88 100644 --- a/components/payments/content/payment_request_state.cc +++ b/components/payments/content/payment_request_state.cc
@@ -587,10 +587,14 @@ // Remove home and work profiles since they are non-editable. std::erase_if(profiles, [](const autofill::AutofillProfile* profile) { - return profile->record_type() == - autofill::AutofillProfile::RecordType::kAccountHome || - profile->record_type() == - autofill::AutofillProfile::RecordType::kAccountWork; + switch (profile->record_type()) { + case autofill::AutofillProfile::RecordType::kLocalOrSyncable: + case autofill::AutofillProfile::RecordType::kAccount: + return false; + case autofill::AutofillProfile::RecordType::kAccountHome: + case autofill::AutofillProfile::RecordType::kAccountWork: + return true; + } }); std::vector<raw_ptr<autofill::AutofillProfile, VectorExperimental>>
diff --git a/components/privacy_sandbox_strings.grd b/components/privacy_sandbox_strings.grd index 8dcd5bae..ec040ba 100644 --- a/components/privacy_sandbox_strings.grd +++ b/components/privacy_sandbox_strings.grd
@@ -332,6 +332,13 @@ <message name="IDS_TRACKING_PROTECTION_BUBBLE_PAUSED_PROTECTIONS_TITLE" desc="The title label in the Tracking Protection bubble when ACT protections are paused." translateable="false"> Tracking protections are paused on this site </message> + <!-- Page Info --> + <message name="IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_TITLE" desc="" translateable="false"> + Settings + </message> + <message name="IDS_PAGE_INFO_INCOGNITO_TRACKING_PROTECTION_SETTINGS_BUTTON_SUBTITLE" desc="" translateable="false"> + Manage Incognito tracking protections + </message> <!-- Privacy UX WebUI --> <!-- Privacy Sandbox Ads Notice --> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_LEARN_MORE_V2_CLANK" desc="A sentence that appears alone at the bottom of the learn more page. It offers the user a path towards additional information about how Google protects their data." formatter_data="android_java">
diff --git a/components/signin/public/identity_manager/account_info.h b/components/signin/public/identity_manager/account_info.h index f8979c2..ccd0488 100644 --- a/components/signin/public/identity_manager/account_info.h +++ b/components/signin/public/identity_manager/account_info.h
@@ -188,6 +188,19 @@ const CoreAccountInfo& core_account_info) { return ConvertToJavaCoreAccountInfo(env, core_account_info); } + +template <> +inline AccountInfo FromJniType<AccountInfo>( + JNIEnv* env, + const JavaRef<jobject>& j_account_info) { + return ConvertFromJavaAccountInfo(env, j_account_info); +} + +template <> +inline ScopedJavaLocalRef<jobject> ToJniType(JNIEnv* env, + const AccountInfo& account_info) { + return ConvertToJavaAccountInfo(env, account_info); +} } // namespace jni_zero #endif
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc index 5187ada..e608e1b8 100644 --- a/components/viz/common/viz_utils.cc +++ b/components/viz/common/viz_utils.cc
@@ -51,13 +51,14 @@ // As it takes some work to compute this, cache the result. static bool is_always_use_wide_color_gamut_enabled = [] { - const char* current_model = + const std::string& current_model = base::android::BuildInfo::GetInstance()->model(); const std::array<std::string, 2> enabled_models = { std::string{"Pixel 4"}, std::string{"Pixel 4 XL"}}; for (const std::string& model : enabled_models) { - if (model == current_model) + if (model == current_model) { return true; + } } return false;
diff --git a/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.cc b/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.cc index 4613f34..50a0584 100644 --- a/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.cc +++ b/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.cc
@@ -44,7 +44,19 @@ return *dev_mode_kr_data; } -base::expected<IwaKeyDistributionInfoProvider::KeyRotations, +bool GetSkipCaptureStartedNotification( + const IwaSpecialAppPermissions::SpecialAppPermissions& special_permission) { + if (!special_permission.has_multi_screen_capture()) { + return false; + } + const auto& multi_screen_capture = special_permission.multi_screen_capture(); + if (!multi_screen_capture.has_skip_capture_started_notification()) { + return false; + } + return multi_screen_capture.skip_capture_started_notification(); +} + +base::expected<IwaKeyDistributionInfoProvider::KeyDistributionData, IwaComponentUpdateError> LoadKeyDistributionDataImpl(const base::FilePath& file_path) { std::string key_distribution_data; @@ -78,7 +90,20 @@ } } - return key_rotations; + IwaKeyDistributionInfoProvider::SpecialAppPermissions special_app_permissions; + if (key_distribution.has_special_app_permissions_data()) { + for (const auto& [web_bundle_id, special_app_permission_data] : + key_distribution.special_app_permissions_data() + .special_app_permissions()) { + special_app_permissions.emplace( + web_bundle_id, + IwaKeyDistributionInfoProvider::SpecialAppPermissionsInfo{ + GetSkipCaptureStartedNotification(special_app_permission_data)}); + } + } + + return std::make_tuple(std::move(key_rotations), + std::move(special_app_permissions)); } std::unique_ptr<IwaKeyDistributionInfoProvider>& @@ -103,7 +128,6 @@ return base::TaskPriority::BEST_EFFORT; #endif } - } // namespace IwaKeyDistributionInfoProvider::KeyRotationInfo::KeyRotationInfo( @@ -121,6 +145,13 @@ "public_key", public_key ? base::Base64Encode(*public_key) : "null")); } +base::Value +IwaKeyDistributionInfoProvider::SpecialAppPermissionsInfo::AsDebugValue() + const { + return base::Value(base::Value::Dict().Set( + "skip_capture_started_notification", skip_capture_started_notification)); +} + IwaKeyDistributionInfoProvider* IwaKeyDistributionInfoProvider::GetInstance() { auto& instance = GetGlobalIwaKeyDistributionInfoProviderInstance(); if (!instance) { @@ -186,13 +217,39 @@ base::Unretained(this), component_version, is_preloaded)); } +const IwaKeyDistributionInfoProvider::SpecialAppPermissionsInfo* +IwaKeyDistributionInfoProvider::GetSpecialAppPermissionsInfo( + const std::string& web_bundle_id) const { + if (data_) { + return base::FindOrNull(data_->special_app_permissions, web_bundle_id); + } + return nullptr; +} + +std::vector<std::string> +IwaKeyDistributionInfoProvider::GetSkipMultiCaptureNotificationBundleIds() + const { + if (!data_) { + return {}; + } + + std::vector<std::string> skip_multi_capture_notification_bundle_ids; + for (const auto& [bundle_id, special_app_permissions] : + data_->special_app_permissions) { + if (special_app_permissions.skip_capture_started_notification) { + skip_multi_capture_notification_bundle_ids.push_back(bundle_id); + } + } + return skip_multi_capture_notification_bundle_ids; +} + IwaKeyDistributionInfoProvider::IwaKeyDistributionInfoProvider() = default; IwaKeyDistributionInfoProvider::~IwaKeyDistributionInfoProvider() = default; void IwaKeyDistributionInfoProvider::OnKeyDistributionDataLoaded( const base::Version& component_version, bool is_preloaded, - base::expected<KeyRotations, IwaComponentUpdateError> result) { + base::expected<KeyDistributionData, IwaComponentUpdateError> result) { if (data_ && data_->version > component_version) { // This might happen if two tasks with different versions have been posted // to the task runner in `LoadKeyDistributionData()`. @@ -201,13 +258,14 @@ return; } - ASSIGN_OR_RETURN(auto key_rotations, std::move(result), - [&](IwaComponentUpdateError error) { + ASSIGN_OR_RETURN((auto [key_rotations, special_app_permissions]), + std::move(result), [&](IwaComponentUpdateError error) { DispatchComponentUpdateError(component_version, error); }); - // TODO(crbug.com/410532804): Add allowlist to the associated proto file. + // TODO(crbug.com/410532804): Add allowlist to the proto file. data_ = ComponentData(component_version, std::move(key_rotations), + std::move(special_app_permissions), /*managed_allowlist=*/{}, is_preloaded); SignalOnDataReady(is_preloaded); DispatchComponentUpdateSuccess(component_version, is_preloaded); @@ -270,6 +328,13 @@ for (const auto& [web_bundle_id, kr_info] : data_->key_rotations) { key_rotations->Set(web_bundle_id, kr_info.AsDebugValue()); } + + auto* app_permissions = debug_data.EnsureDict("special_app_permissions"); + for (const auto& [web_bundle_id, app_permissions_info] : + data_->special_app_permissions) { + app_permissions->Set(web_bundle_id, app_permissions_info.AsDebugValue()); + } + if (data_->is_preloaded) { debug_data.Set("is_preloaded", true); } @@ -361,10 +426,12 @@ IwaKeyDistributionInfoProvider::ComponentData::ComponentData( base::Version version, KeyRotations key_rotations, + SpecialAppPermissions special_app_permissions, ManagedAllowlist managed_allowlist, bool is_preloaded) : version(std::move(version)), key_rotations(std::move(key_rotations)), + special_app_permissions(std::move(special_app_permissions)), managed_allowlist(std::move(managed_allowlist)), is_preloaded(is_preloaded) {} IwaKeyDistributionInfoProvider::ComponentData::~ComponentData() = default;
diff --git a/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h b/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h index d87f52a..68e0549 100644 --- a/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h +++ b/components/webapps/isolated_web_apps/iwa_key_distribution_info_provider.h
@@ -49,10 +49,20 @@ std::optional<PublicKeyData> public_key; }; + struct SpecialAppPermissionsInfo { + base::Value AsDebugValue() const; + + bool skip_capture_started_notification; + }; + using KeyRotations = base::flat_map<std::string, KeyRotationInfo>; using ManagedAllowlist = base::flat_set<std::string>; using QueueOnDemandUpdateCallback = base::RepeatingCallback<bool( base::PassKey<IwaKeyDistributionInfoProvider>)>; + using SpecialAppPermissions = + base::flat_map<std::string, SpecialAppPermissionsInfo>; + using KeyDistributionData = std::tuple<KeyRotations, SpecialAppPermissions>; + class Observer : public base::CheckedObserver { public: virtual void OnComponentUpdateSuccess(const base::Version& version, @@ -64,6 +74,7 @@ struct ComponentData { ComponentData(base::Version version, KeyRotations key_rotations, + SpecialAppPermissions special_app_permissions, ManagedAllowlist managed_allowlist, bool is_preloaded); ~ComponentData(); @@ -71,6 +82,7 @@ base::Version version; KeyRotations key_rotations; + SpecialAppPermissions special_app_permissions; ManagedAllowlist managed_allowlist; bool is_preloaded = false; @@ -88,6 +100,9 @@ const KeyRotationInfo* GetKeyRotationInfo( const std::string& web_bundle_id) const; + const SpecialAppPermissionsInfo* GetSpecialAppPermissionsInfo( + const std::string& web_bundle_id) const; + std::vector<std::string> GetSkipMultiCaptureNotificationBundleIds() const; // Only bundles present in the managed allowlist can be installed and updated. bool IsManagedInstallPermitted(std::string_view web_bundle_id) const; @@ -151,7 +166,7 @@ void OnKeyDistributionDataLoaded( const base::Version& version, bool is_preloaded, - base::expected<KeyRotations, IwaComponentUpdateError>); + base::expected<KeyDistributionData, IwaComponentUpdateError>); void DispatchComponentUpdateSuccess(const base::Version& version, bool is_preloaded);
diff --git a/components/webapps/isolated_web_apps/preload/key_distribution.textproto b/components/webapps/isolated_web_apps/preload/key_distribution.textproto index 66af7ab..8657ce9 100644 --- a/components/webapps/isolated_web_apps/preload/key_distribution.textproto +++ b/components/webapps/isolated_web_apps/preload/key_distribution.textproto
@@ -9,3 +9,14 @@ } } } + +special_app_permissions_data { + special_app_permissions { + key: "aiv4bxauvcu3zvbu6r5yynoh4atkzqqaoeof5mwz54b4zfywcrjuoaacai" + value { + multi_screen_capture { + skip_capture_started_notification: true + } + } + } +}
diff --git a/components/webapps/isolated_web_apps/proto/key_distribution.proto b/components/webapps/isolated_web_apps/proto/key_distribution.proto index 0e742f8..a7afb800 100644 --- a/components/webapps/isolated_web_apps/proto/key_distribution.proto +++ b/components/webapps/isolated_web_apps/proto/key_distribution.proto
@@ -10,6 +10,10 @@ message IwaKeyDistribution { // Data about key rotations. optional IwaKeyRotations key_rotation_data = 1; + + // Data about special app permissions. These permissions provide certain + // trusted and allowlisted apps additional and more powerful permissions. + optional IwaSpecialAppPermissions special_app_permissions_data = 2; } message IwaKeyRotations { @@ -22,3 +26,18 @@ // A list of key rotations mapping web bundle IDs to expected keys. map<string, KeyRotationInfo> key_rotations = 1; } + +message IwaSpecialAppPermissions { + message SpecialAppPermissions { + message MultiScreenCapturePermissions { + // The permission to allow remove "Your screen is captured" notification + // for multi-screen capture. + optional bool skip_capture_started_notification = 2; + } + optional MultiScreenCapturePermissions multi_screen_capture = 1; + } + + // A list of special app permissions mapping web bundle IDs to additional + // capabilities. + map<string, SpecialAppPermissions> special_app_permissions = 1; +}
diff --git a/components/webauthn/ios/passkey_java_script_feature.mm b/components/webauthn/ios/passkey_java_script_feature.mm index e0deb21..415ed57 100644 --- a/components/webauthn/ios/passkey_java_script_feature.mm +++ b/components/webauthn/ios/passkey_java_script_feature.mm
@@ -71,7 +71,8 @@ CHECK(passkey_tab_helper); // For those events there are no more expected arguments. - if (*event == "getRequested" || *event == "createRequested") { + if (*event == "getRequested" || *event == "createRequested" || + *event == "createResolvedGpm" || *event == "createResolvedNonGpm") { passkey_tab_helper->LogEventFromString(*event); return; }
diff --git a/components/webauthn/ios/passkey_tab_helper.mm b/components/webauthn/ios/passkey_tab_helper.mm index 270b3303..74416b49 100644 --- a/components/webauthn/ios/passkey_tab_helper.mm +++ b/components/webauthn/ios/passkey_tab_helper.mm
@@ -23,7 +23,9 @@ kCreateRequested, kGetResolvedGpm, kGetResolvedNonGpm, - kMaxValue = kGetResolvedNonGpm, + kCreateResolvedGpm, + kCreateResolvedNonGpm, + kMaxValue = kCreateResolvedNonGpm, }; // LINT.ThenChange(//tools/metrics/histograms/metadata/webauthn/enums.xml) @@ -42,6 +44,10 @@ LogEvent(WebAuthenticationIOSContentAreaEvent::kGetRequested); } else if (event == "createRequested") { LogEvent(WebAuthenticationIOSContentAreaEvent::kCreateRequested); + } else if (event == "createResolvedGpm") { + LogEvent(WebAuthenticationIOSContentAreaEvent::kCreateResolvedGpm); + } else if (event == "createResolvedNonGpm") { + LogEvent(WebAuthenticationIOSContentAreaEvent::kCreateResolvedNonGpm); } else { NOTREACHED(); }
diff --git a/components/webauthn/ios/passkey_tab_helper_unittest.mm b/components/webauthn/ios/passkey_tab_helper_unittest.mm index 33621ac..da223e6 100644 --- a/components/webauthn/ios/passkey_tab_helper_unittest.mm +++ b/components/webauthn/ios/passkey_tab_helper_unittest.mm
@@ -90,3 +90,21 @@ kWebAuthenticationIOSContentAreaEventHistogram, kGetResolvedNonGpmBucket, /*count=*/1); } + +TEST_F(PasskeyTabHelperTest, LogsEventFromCreateResolvedGpmString) { + passkey_tab_helper()->LogEventFromString("createResolvedGpm"); + + constexpr int kCreateRequestedBucket = 4; + histogram_tester_.ExpectUniqueSample( + kWebAuthenticationIOSContentAreaEventHistogram, kCreateRequestedBucket, + /*count=*/1); +} + +TEST_F(PasskeyTabHelperTest, LogsEventFromCreateResolvedNonGpmString) { + passkey_tab_helper()->LogEventFromString("createResolvedNonGpm"); + + constexpr int kCreateRequestedBucket = 5; + histogram_tester_.ExpectUniqueSample( + kWebAuthenticationIOSContentAreaEventHistogram, kCreateRequestedBucket, + /*count=*/1); +}
diff --git a/components/webauthn/ios/resources/passkey_controller.ts b/components/webauthn/ios/resources/passkey_controller.ts index 70312da33..4cf51b6 100644 --- a/components/webauthn/ios/resources/passkey_controller.ts +++ b/components/webauthn/ios/resources/passkey_controller.ts
@@ -12,6 +12,27 @@ // Must be kept in sync with passkey_java_script_feature.mm. const HANDLER_NAME = 'PasskeyInteractionHandler'; +// AAGUID value of Google Password Manager. +const GPM_AAGUID = new Uint8Array([ + // clang-format off + 0xea, 0x9b, 0x8d, 0x66, 0x4d, 0x01, 0x1d, 0x21, + 0x3c, 0xe4, 0xb6, 0xb4, 0x8c, 0xb5, 0x75, 0xd4, + // clang-format on +]); + +// Checks whether provided aaguid is equal to Google Password Manager's aaguid. +function isGpmAaguid(aaguid: Uint8Array): boolean { + if (aaguid.byteLength !== GPM_AAGUID.byteLength) { + return false; + } + for (let i = 0; i < aaguid.byteLength; i++) { + if (aaguid[i] !== GPM_AAGUID[i]) { + return false; + } + } + return true; +} + /** * Caches the existing value of WebKit's navigator.credentials, so that the * calls can be forwarded to it, if needed. @@ -45,14 +66,30 @@ return credential; }); }, - create: function(options?: CredentialCreationOptions|undefined): - Promise<Credential|null> { - // Only process WebAuthn requests. - if (options?.publicKey) { - sendWebKitMessage(HANDLER_NAME, {'event': 'createRequested'}); - } - return cachedNavigatorCredentials.create(options); - }, + create: function( + options?: CredentialCreationOptions|undefined): Promise<Credential|null> { + // Only process WebAuthn requests. + if (!options?.publicKey) { + return cachedNavigatorCredentials.create(options); + } + + sendWebKitMessage(HANDLER_NAME, {'event': 'createRequested'}); + + return cachedNavigatorCredentials.create(options).then((credential) => { + if (credential && credential instanceof PublicKeyCredential && + credential.response instanceof AuthenticatorAttestationResponse) { + // Parse the aaguid from authenticator data according to + // https://w3c.github.io/webauthn/#sctn-authenticator-data. + const aaguid = new Uint8Array( + credential.response.getAuthenticatorData().slice(37).slice(0, 16)); + sendWebKitMessage(HANDLER_NAME, { + 'event': isGpmAaguid(aaguid) ? 'createResolvedGpm' : + 'createResolvedNonGpm', + }); + } + return credential; + }); + }, preventSilentAccess: function(): Promise<any> { return cachedNavigatorCredentials.preventSilentAccess(); },
diff --git a/content/browser/btm/btm_helper_browsertest.cc b/content/browser/btm/btm_helper_browsertest.cc index 6a9f647b..ed3a5dad 100644 --- a/content/browser/btm/btm_helper_browsertest.cc +++ b/content/browser/btm/btm_helper_browsertest.cc
@@ -442,6 +442,7 @@ web_contents, embedded_test_server()->GetURL("a.test", "/title1.html"))); UserActivationObserver observer(web_contents, web_contents->GetPrimaryMainFrame()); + SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents); SimulateMouseClick(web_contents, 0, blink::WebMouseEvent::Button::kLeft); observer.Wait();
diff --git a/content/browser/indexed_db/instance/leveldb/backing_store.cc b/content/browser/indexed_db/instance/leveldb/backing_store.cc index 1b3037f..552483d 100644 --- a/content/browser/indexed_db/instance/leveldb/backing_store.cc +++ b/content/browser/indexed_db/instance/leveldb/backing_store.cc
@@ -1231,56 +1231,6 @@ active_blob_registry()->ForceShutdown(); } -Status BackingStore::AnyDatabaseContainsBlobs(bool* blobs_exist) { - std::vector<std::u16string> names; - Status status = GetDatabaseNames(&names); - if (!status.ok()) { - return status; - } - - *blobs_exist = false; - for (const std::u16string& name : names) { - DatabaseMetadata metadata(name); - status = ReadMetadataForDatabaseName(metadata); - if (!metadata.id) { - return Status::NotFound("Metadata not found for \"%s\".", - base::UTF16ToUTF8(name)); - } - for (const auto& store_id_metadata_pair : metadata.object_stores) { - leveldb::ReadOptions options; - // Since this is a scan, don't fill up the cache, as it's not likely these - // blocks will be reloaded. - options.fill_cache = false; - options.verify_checksums = true; - std::unique_ptr<TransactionalLevelDBIterator> iterator = - db_->CreateIterator(options); - std::string min_key = BlobEntryKey::EncodeMinKeyForObjectStore( - *metadata.id, store_id_metadata_pair.first); - std::string max_key = BlobEntryKey::EncodeStopKeyForObjectStore( - *metadata.id, store_id_metadata_pair.first); - status = iterator->Seek(std::string_view(min_key)); - if (status.IsNotFound()) { - status = Status::OK(); - continue; - } - if (!status.ok()) { - return status; - } - if (iterator->IsValid() && - db_->leveldb_state()->comparator()->Compare( - leveldb_env::MakeSlice(iterator->Key()), max_key) < 0) { - *blobs_exist = true; - return Status::OK(); - } - } - - if (!status.ok()) { - return status; - } - } - return Status::OK(); -} - Status BackingStore::UpgradeBlobEntriesToV4( LevelDBWriteBatch* write_batch, std::vector<base::FilePath>* empty_blobs_to_delete) {
diff --git a/content/browser/indexed_db/instance/leveldb/backing_store.h b/content/browser/indexed_db/instance/leveldb/backing_store.h index d16e455..725572a 100644 --- a/content/browser/indexed_db/instance/leveldb/backing_store.h +++ b/content/browser/indexed_db/instance/leveldb/backing_store.h
@@ -582,8 +582,6 @@ std::vector<std::unique_ptr<blink::IndexedDBDatabaseMetadata>>* output) override; - Status AnyDatabaseContainsBlobs(bool* blobs_exist); - // A helper function for V4 schema migration. // It iterates through all blob files. It will add to the db entry both the // size and modified date for the blob based on the written file. If any blob
diff --git a/content/browser/speech/speech_synthesis_impl.cc b/content/browser/speech/speech_synthesis_impl.cc index 0f3f97bb..9567ac9 100644 --- a/content/browser/speech/speech_synthesis_impl.cc +++ b/content/browser/speech/speech_synthesis_impl.cc
@@ -4,20 +4,25 @@ #include "content/browser/speech/speech_synthesis_impl.h" +#include "content/browser/media/audio_stream_monitor.h" #include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/speech/tts_utterance_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/web_contents.h" namespace content { namespace { +using AudibleCB = base::RepeatingCallback< + std::unique_ptr<AudioStreamMonitor::AudibleClientRegistration>()>; + // The lifetime of instances of this class is manually bound to the lifetime of // the associated TtsUtterance. See OnTtsEvent. class EventThunk : public UtteranceEventDelegate { public: - explicit EventThunk( - mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client) - : client_(std::move(client)) {} + EventThunk(mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client, + AudibleCB audible_cb) + : client_(std::move(client)), audible_cb_(std::move(audible_cb)) {} ~EventThunk() override = default; // UtteranceEventDelegate methods: @@ -33,17 +38,21 @@ switch (event_type) { case TTS_EVENT_START: + audible_client_ = audible_cb_.Run(); client_->OnStartedSpeaking(); break; case TTS_EVENT_END: + audible_client_.reset(); client_->OnFinishedSpeaking( blink::mojom::SpeechSynthesisErrorCode::kNoError); break; case TTS_EVENT_INTERRUPTED: + audible_client_.reset(); client_->OnFinishedSpeaking( blink::mojom::SpeechSynthesisErrorCode::kInterrupted); break; case TTS_EVENT_CANCELLED: + audible_client_.reset(); client_->OnFinishedSpeaking( blink::mojom::SpeechSynthesisErrorCode::kCancelled); break; @@ -57,13 +66,16 @@ // The web platform API does not support this event. break; case TTS_EVENT_ERROR: + audible_client_.reset(); // The web platform API does not support error text. client_->OnEncounteredSpeakingError(); break; case TTS_EVENT_PAUSE: + audible_client_.reset(); client_->OnPausedSpeaking(); break; case TTS_EVENT_RESUME: + audible_client_ = audible_cb_.Run(); client_->OnResumedSpeaking(); break; } @@ -74,6 +86,9 @@ private: mojo::Remote<blink::mojom::SpeechSynthesisClient> client_; + AudibleCB audible_cb_; + std::unique_ptr<AudioStreamMonitor::AudibleClientRegistration> + audible_client_; }; void SendVoiceListToObserver( @@ -98,7 +113,8 @@ SpeechSynthesisImpl::SpeechSynthesisImpl(BrowserContext* browser_context, RenderFrameHostImpl* rfh) : browser_context_(browser_context), - web_contents_(WebContents::FromRenderFrameHost((rfh))) { + web_contents_(WebContents::FromRenderFrameHost((rfh))), + frame_id_(rfh->GetGlobalId()) { DCHECK(browser_context_); DCHECK(web_contents_); TtsController::GetInstance()->AddVoicesChangedDelegate(this); @@ -146,7 +162,13 @@ utterance->volume); // See comments on EventThunk about how lifetime of this instance is managed. - tts_utterance->SetEventDelegate(new EventThunk(std::move(client))); + tts_utterance->SetEventDelegate(new EventThunk( + std::move(client), + base::BindRepeating( + &AudioStreamMonitor::RegisterAudibleClient, + base::Unretained(static_cast<WebContentsImpl*>(web_contents_) + ->audio_stream_monitor()), + frame_id_))); TtsController::GetInstance()->SpeakOrEnqueue(std::move(tts_utterance)); }
diff --git a/content/browser/speech/speech_synthesis_impl.h b/content/browser/speech/speech_synthesis_impl.h index 963e8f6..3777555 100644 --- a/content/browser/speech/speech_synthesis_impl.h +++ b/content/browser/speech/speech_synthesis_impl.h
@@ -6,6 +6,7 @@ #define CONTENT_BROWSER_SPEECH_SPEECH_SYNTHESIS_IMPL_H_ #include "base/memory/raw_ptr.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/tts_controller.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote_set.h" @@ -48,6 +49,7 @@ private: raw_ptr<BrowserContext> browser_context_; raw_ptr<WebContents> web_contents_; + GlobalRenderFrameHostId frame_id_; mojo::ReceiverSet<blink::mojom::SpeechSynthesis> receiver_set_; mojo::RemoteSet<blink::mojom::SpeechSynthesisVoiceListObserver> observer_set_;
diff --git a/content/browser/webid/fedcm_mappers.cc b/content/browser/webid/fedcm_mappers.cc index be298fcf..39a37f4f 100644 --- a/content/browser/webid/fedcm_mappers.cc +++ b/content/browser/webid/fedcm_mappers.cc
@@ -7,12 +7,16 @@ #include <string> #include <vector> +#include "content/browser/renderer_host/render_frame_host_impl.h" +#include "content/browser/webid/fedcm_metrics.h" #include "content/public/browser/identity_request_dialog_controller.h" #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h" #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h" using blink::mojom::FederatedAuthRequestResult; using blink::mojom::RequestTokenStatus; +using ParseStatus = content::IdpNetworkRequestManager::ParseStatus; +using LifecycleStateImpl = content::RenderFrameHostImpl::LifecycleStateImpl; namespace content { @@ -173,4 +177,61 @@ } } +std::pair<FederatedAuthRequestResult, FedCmRequestIdTokenStatus> +AccountParseStatusToRequestResultAndTokenStatus(ParseStatus parse_status) { + switch (parse_status) { + case ParseStatus::kHttpNotFoundError: { + return {FederatedAuthRequestResult::kAccountsHttpNotFound, + FedCmRequestIdTokenStatus::kAccountsHttpNotFound}; + } + case ParseStatus::kNoResponseError: { + return {FederatedAuthRequestResult::kAccountsNoResponse, + FedCmRequestIdTokenStatus::kAccountsNoResponse}; + } + case ParseStatus::kInvalidResponseError: { + return {FederatedAuthRequestResult::kAccountsInvalidResponse, + FedCmRequestIdTokenStatus::kAccountsInvalidResponse}; + } + case ParseStatus::kEmptyListError: { + return {FederatedAuthRequestResult::kAccountsListEmpty, + FedCmRequestIdTokenStatus::kAccountsListEmpty}; + } + case ParseStatus::kInvalidContentTypeError: { + return {FederatedAuthRequestResult::kAccountsInvalidContentType, + FedCmRequestIdTokenStatus::kAccountsInvalidContentType}; + } + case ParseStatus::kSuccess: { + NOTREACHED() << "Should not be invoked on success"; + } + } +} + +FedCmLifecycleStateFailureReason +LifecycleStateImplLifecycleStateImplToFedCmLifecycleStateFailureReason( + LifecycleStateImpl lifecycle_state) { + switch (lifecycle_state) { + case LifecycleStateImpl::kSpeculative: { + return FedCmLifecycleStateFailureReason::kSpeculative; + } + case LifecycleStateImpl::kPendingCommit: { + return FedCmLifecycleStateFailureReason::kPendingCommit; + } + case LifecycleStateImpl::kPrerendering: { + return FedCmLifecycleStateFailureReason::kPrerendering; + } + case LifecycleStateImpl::kInBackForwardCache: { + return FedCmLifecycleStateFailureReason::kInBackForwardCache; + } + case LifecycleStateImpl::kRunningUnloadHandlers: { + return FedCmLifecycleStateFailureReason::kRunningUnloadHandlers; + } + case LifecycleStateImpl::kReadyToBeDeleted: { + return FedCmLifecycleStateFailureReason::kReadyToBeDeleted; + } + default: { + return FedCmLifecycleStateFailureReason::kOther; + } + } +} + } // namespace content
diff --git a/content/browser/webid/fedcm_mappers.h b/content/browser/webid/fedcm_mappers.h index df0fd76..9e99904 100644 --- a/content/browser/webid/fedcm_mappers.h +++ b/content/browser/webid/fedcm_mappers.h
@@ -8,12 +8,17 @@ #include <string> #include <vector> +#include "content/browser/renderer_host/render_frame_host_impl.h" +#include "content/browser/webid/idp_network_request_manager.h" #include "content/public/browser/identity_request_dialog_controller.h" #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h" #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h" namespace content { +enum class FedCmRequestIdTokenStatus; +enum class FedCmLifecycleStateFailureReason; + // This header file defines functions which convert between FedCM types. It also // defines some constants used in some of these conversions. @@ -60,6 +65,19 @@ MetricsEndpointErrorCode FederatedAuthRequestResultToMetricsEndpointErrorCode( blink::mojom::FederatedAuthRequestResult result); +// Converts an error ParseStatus from the accounts response to a pair. The first +// member of the pair is a FederatedAuthRequestResult, which is a browser type +// for the result. The second member of the pair is a FedCmRequestIdTokenStatus, +// which is a type used in metrics recording. Should not be invoked with +// ParseStatus::kSuccess. +std::pair<blink::mojom::FederatedAuthRequestResult, FedCmRequestIdTokenStatus> +AccountParseStatusToRequestResultAndTokenStatus( + IdpNetworkRequestManager::ParseStatus status); + +FedCmLifecycleStateFailureReason +LifecycleStateImplLifecycleStateImplToFedCmLifecycleStateFailureReason( + RenderFrameHostImpl::LifecycleStateImpl lifecycle_state); + } // namespace content #endif // CONTENT_BROWSER_FEDCM_MAPPERS_H_
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc index 2b624a0..cff836b0 100644 --- a/content/browser/webid/federated_auth_request_impl.cc +++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -431,31 +431,9 @@ // the lifecycle state for further investigation. RenderFrameHostImpl* host_impl = static_cast<RenderFrameHostImpl*>(&render_frame_host()); - FedCmLifecycleStateFailureReason reason = - FedCmLifecycleStateFailureReason::kOther; - switch (host_impl->lifecycle_state()) { - case RenderFrameHostImpl::LifecycleStateImpl::kSpeculative: - reason = FedCmLifecycleStateFailureReason::kSpeculative; - break; - case RenderFrameHostImpl::LifecycleStateImpl::kPendingCommit: - reason = FedCmLifecycleStateFailureReason::kPendingCommit; - break; - case RenderFrameHostImpl::LifecycleStateImpl::kPrerendering: - reason = FedCmLifecycleStateFailureReason::kPrerendering; - break; - case RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache: - reason = FedCmLifecycleStateFailureReason::kInBackForwardCache; - break; - case RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers: - reason = FedCmLifecycleStateFailureReason::kRunningUnloadHandlers; - break; - case RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted: - reason = FedCmLifecycleStateFailureReason::kReadyToBeDeleted; - break; - default: - break; - } - RecordLifecycleStateFailureReason(reason); + RecordLifecycleStateFailureReason( + LifecycleStateImplLifecycleStateImplToFedCmLifecycleStateFailureReason( + host_impl->lifecycle_state())); std::move(callback).Run(RequestTokenStatus::kError, std::nullopt, "", /*error=*/nullptr, /*is_auto_selected=*/false); @@ -1822,172 +1800,131 @@ idp_info->has_failing_idp_signin_status, permission_delegate_); constexpr char kAccountsUrl[] = "accounts endpoint"; - switch (status.parse_status) { - case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: { - MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsHttpNotFound, - TokenStatus::kAccountsHttpNotFound); - return; + if (status.parse_status != IdpNetworkRequestManager::ParseStatus::kSuccess) { + std::pair<FederatedAuthRequestResult, TokenStatus> resultAndTokenStatus = + AccountParseStatusToRequestResultAndTokenStatus(status.parse_status); + MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); + HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, + resultAndTokenStatus.first, + resultAndTokenStatus.second); + return; + } + RecordRawAccountsSize(accounts.size()); + if (!FilterAccountsWithLabel(idp_info->metadata.requested_label, accounts)) { + // No accounts remain, so treat as account fetch failure. + render_frame_host().AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kError, + "Accounts were received, but none matched the label."); + // If there are no accounts after filtering based on the label, + // treat this exactly the same as if we had received an empty accounts + // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. + HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, + FederatedAuthRequestResult::kAccountsListEmpty, + TokenStatus::kAccountsListEmpty); + return; + } + if (!FilterAccountsWithLoginHint(idp_info->provider->login_hint, accounts)) { + // No accounts remain, so treat as account fetch failure. + render_frame_host().AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kError, + "Accounts were received, but none matched the loginHint."); + // If there are no accounts after filtering based on the login hint, + // treat this exactly the same as if we had received an empty accounts + // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. + HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, + FederatedAuthRequestResult::kAccountsListEmpty, + TokenStatus::kAccountsListEmpty); + return; + } + if (!FilterAccountsWithDomainHint(idp_info->provider->domain_hint, + accounts)) { + // No accounts remain, so treat as account fetch failure. + render_frame_host().AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kError, + "Accounts were received, but none matched the domainHint."); + // If there are no accounts after filtering based on the domain hint, + // treat this exactly the same as if we had received an empty accounts + // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. + HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, + FederatedAuthRequestResult::kAccountsListEmpty, + TokenStatus::kAccountsListEmpty); + return; + } + auto filter = [](const IdentityRequestAccountPtr& account) { + return account->is_filtered_out; + }; + if (!IsFedCmShowFilteredAccountsEnabled() || + !idps_user_tried_to_signin_to_.contains(idp_config_url) || + login_url_ != idp_info->metadata.idp_login_url) { + std::erase_if(accounts, filter); + } else { + // If the user is logging in to new accounts, only show filtered + // accounts if there are no new unfiltered accounts. This includes in + // particular the case where all accounts are filtered out. + size_t new_unfiltered = + std::count_if(accounts.begin(), accounts.end(), + [&](const IdentityRequestAccountPtr& account) { + return !account->is_filtered_out && + !account_ids_before_login_.contains(account->id); + }); + if (new_unfiltered > 0u) { + std::erase_if(accounts, filter); } - case IdpNetworkRequestManager::ParseStatus::kNoResponseError: { - MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsNoResponse, - TokenStatus::kAccountsNoResponse); - return; - } - case IdpNetworkRequestManager::ParseStatus::kInvalidResponseError: { - MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsInvalidResponse, - TokenStatus::kAccountsInvalidResponse); - return; - } - case IdpNetworkRequestManager::ParseStatus::kEmptyListError: { - MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); - HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsListEmpty, - TokenStatus::kAccountsListEmpty); - return; - } - case IdpNetworkRequestManager::ParseStatus::kInvalidContentTypeError: { - MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code); - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsInvalidContentType, - TokenStatus::kAccountsInvalidContentType); - return; - } - case IdpNetworkRequestManager::ParseStatus::kSuccess: { - RecordRawAccountsSize(accounts.size()); - if (!FilterAccountsWithLabel(idp_info->metadata.requested_label, - accounts)) { - // No accounts remain, so treat as account fetch failure. - render_frame_host().AddMessageToConsole( - blink::mojom::ConsoleMessageLevel::kError, - "Accounts were received, but none matched the label."); - // If there are no accounts after filtering based on the label, - // treat this exactly the same as if we had received an empty accounts - // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsListEmpty, - TokenStatus::kAccountsListEmpty); - return; - } - if (!FilterAccountsWithLoginHint(idp_info->provider->login_hint, - accounts)) { - // No accounts remain, so treat as account fetch failure. - render_frame_host().AddMessageToConsole( - blink::mojom::ConsoleMessageLevel::kError, - "Accounts were received, but none matched the loginHint."); - // If there are no accounts after filtering based on the login hint, - // treat this exactly the same as if we had received an empty accounts - // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsListEmpty, - TokenStatus::kAccountsListEmpty); - return; - } - if (!FilterAccountsWithDomainHint(idp_info->provider->domain_hint, - accounts)) { - // No accounts remain, so treat as account fetch failure. - render_frame_host().AddMessageToConsole( - blink::mojom::ConsoleMessageLevel::kError, - "Accounts were received, but none matched the domainHint."); - // If there are no accounts after filtering based on the domain hint, - // treat this exactly the same as if we had received an empty accounts - // list, i.e. IdpNetworkRequestManager::ParseStatus::kEmptyListError. - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsListEmpty, - TokenStatus::kAccountsListEmpty); - return; - } - auto filter = [](const IdentityRequestAccountPtr& account) { - return account->is_filtered_out; - }; - if (!IsFedCmShowFilteredAccountsEnabled() || - !idps_user_tried_to_signin_to_.contains(idp_config_url) || - login_url_ != idp_info->metadata.idp_login_url) { - std::erase_if(accounts, filter); - } else { - // If the user is logging in to new accounts, only show filtered - // accounts if there are no new unfiltered accounts. This includes in - // particular the case where all accounts are filtered out. - size_t new_unfiltered = std::count_if( - accounts.begin(), accounts.end(), - [&](const IdentityRequestAccountPtr& account) { - return !account->is_filtered_out && - !account_ids_before_login_.contains(account->id); - }); - if (new_unfiltered > 0u) { - std::erase_if(accounts, filter); - } - } - if (accounts.size() == 0u) { - // No accounts remain, so treat as account fetch failure. - render_frame_host().AddMessageToConsole( - blink::mojom::ConsoleMessageLevel::kError, - "Accounts were received, but none matched the login hint, domain " - "hint, and/or account labels provided."); - // If there are no accounts after filtering,treat this exactly the same - // as if we had received an empty accounts list, i.e. - // IdpNetworkRequestManager::ParseStatus::kEmptyListError. - HandleAccountsFetchFailure( - std::move(idp_info), old_idp_signin_status, - FederatedAuthRequestResult::kAccountsListEmpty, - TokenStatus::kAccountsListEmpty); - return; - } - RecordReadyToShowAccountsSize(accounts.size()); - ComputeLoginStates(idp_info->provider->config->config_url, accounts); + } + if (accounts.size() == 0u) { + // No accounts remain, so treat as account fetch failure. + render_frame_host().AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kError, + "Accounts were received, but none matched the login hint, domain " + "hint, and/or account labels provided."); + // If there are no accounts after filtering,treat this exactly the same + // as if we had received an empty accounts list, i.e. + // IdpNetworkRequestManager::ParseStatus::kEmptyListError. + HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status, + FederatedAuthRequestResult::kAccountsListEmpty, + TokenStatus::kAccountsListEmpty); + return; + } + RecordReadyToShowAccountsSize(accounts.size()); + ComputeLoginStates(idp_info->provider->config->config_url, accounts); - bool need_client_metadata = false; + bool need_client_metadata = false; - if (!idp_info->provider->config->from_idp_registration_api && - !GetDisclosureFields(*idp_info->provider).empty()) { - for (const auto& account : accounts) { - // ComputeLoginStates() should have populated - // IdentityRequestAccount::login_state. - DCHECK(account->login_state); - if (*account->login_state == LoginState::kSignUp) { - need_client_metadata = true; - break; - } - } - } - - if (need_client_metadata && - webid::IsEndpointSameOrigin(idp_info->provider->config->config_url, - idp_info->endpoints.client_metadata)) { - // Copy OnClientMetadataResponseReceived() parameters because `idp_info` - // is moved. - GURL client_metadata_endpoint = idp_info->endpoints.client_metadata; - std::string client_id = idp_info->provider->config->client_id; - int icon_ideal_size = - request_dialog_controller_->GetBrandIconIdealSize(rp_mode_); - int icon_minimum_size = - request_dialog_controller_->GetBrandIconMinimumSize(rp_mode_); - network_manager_->FetchClientMetadata( - client_metadata_endpoint, client_id, icon_ideal_size, - icon_minimum_size, - base::BindOnce( - &FederatedAuthRequestImpl::OnClientMetadataResponseReceived, - weak_ptr_factory_.GetWeakPtr(), std::move(idp_info), - std::move(accounts))); - } else { - FetchAccountPicturesAndBrandIcons( - std::move(idp_info), std::move(accounts), - IdpNetworkRequestManager::ClientMetadata()); + if (!idp_info->provider->config->from_idp_registration_api && + !GetDisclosureFields(*idp_info->provider).empty()) { + for (const auto& account : accounts) { + // ComputeLoginStates() should have populated + // IdentityRequestAccount::login_state. + DCHECK(account->login_state); + if (*account->login_state == LoginState::kSignUp) { + need_client_metadata = true; + break; } } } + + if (need_client_metadata && + webid::IsEndpointSameOrigin(idp_info->provider->config->config_url, + idp_info->endpoints.client_metadata)) { + // Copy OnClientMetadataResponseReceived() parameters because `idp_info` + // is moved. + GURL client_metadata_endpoint = idp_info->endpoints.client_metadata; + std::string client_id = idp_info->provider->config->client_id; + int icon_ideal_size = + request_dialog_controller_->GetBrandIconIdealSize(rp_mode_); + int icon_minimum_size = + request_dialog_controller_->GetBrandIconMinimumSize(rp_mode_); + network_manager_->FetchClientMetadata( + client_metadata_endpoint, client_id, icon_ideal_size, icon_minimum_size, + base::BindOnce( + &FederatedAuthRequestImpl::OnClientMetadataResponseReceived, + weak_ptr_factory_.GetWeakPtr(), std::move(idp_info), + std::move(accounts))); + } else { + FetchAccountPicturesAndBrandIcons( + std::move(idp_info), std::move(accounts), + IdpNetworkRequestManager::ClientMetadata()); + } } void FederatedAuthRequestImpl::FetchAccountPicturesAndBrandIcons(
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc index 102591a..2147009 100644 --- a/content/browser/webid/idp_network_request_manager.cc +++ b/content/browser/webid/idp_network_request_manager.cc
@@ -14,6 +14,7 @@ #include "base/task/sequenced_task_runner.h" #include "base/values.h" #include "content/browser/renderer_host/render_frame_host_impl.h" +#include "content/browser/webid/fedcm_mappers.h" #include "content/browser/webid/fedcm_metrics.h" #include "content/browser/webid/flags.h" #include "content/browser/webid/webid_utils.h"
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h index bb9851c..cfa14e5e 100644 --- a/content/browser/webid/idp_network_request_manager.h +++ b/content/browser/webid/idp_network_request_manager.h
@@ -11,7 +11,6 @@ #include <vector> #include "base/functional/callback.h" -#include "content/browser/webid/fedcm_mappers.h" #include "content/common/content_export.h" #include "content/public/browser/identity_request_account.h" #include "content/public/browser/identity_request_dialog_controller.h" @@ -40,6 +39,7 @@ using IdentityRequestAccountPtr = scoped_refptr<IdentityRequestAccount>; class FederatedIdentityPermissionContextDelegate; class RenderFrameHostImpl; +enum class MetricsEndpointErrorCode; // Manages network requests and maintains relevant state for interaction with // the Identity Provider across a FedCM transaction. Owned by
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java index 9ca4daa..63e07259 100644 --- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
@@ -151,7 +151,7 @@ private final List<RenderFrameHostImpl> mFrames = new ArrayList<>(); private long mNativeWebContentsAndroid; - private NavigationController mNavigationController; + private final NavigationController mNavigationController; // Lazily created proxy observer for handling all Java-based WebContentsObservers. private @Nullable WebContentsObserverProxy mObserverProxy;
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn index 7424d33c..b8fd9550 100644 --- a/content/renderer/BUILD.gn +++ b/content/renderer/BUILD.gn
@@ -178,15 +178,9 @@ "media/android/flinging_renderer_client.h", "media/android/flinging_renderer_client_factory.cc", "media/android/flinging_renderer_client_factory.h", - "media/android/stream_texture_factory.cc", - "media/android/stream_texture_factory.h", - "media/android/stream_texture_wrapper_impl.cc", - "media/android/stream_texture_wrapper_impl.h", "renderer_main_platform_delegate_android.cc", "seccomp_sandbox_status_android.cc", "seccomp_sandbox_status_android.h", - "stream_texture_host_android.cc", - "stream_texture_host_android.h", ] }
diff --git a/content/renderer/media/android/stream_texture_factory.cc b/content/renderer/media/android/stream_texture_factory.cc deleted file mode 100644 index 88151e87..0000000 --- a/content/renderer/media/android/stream_texture_factory.cc +++ /dev/null
@@ -1,190 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/renderer/media/android/stream_texture_factory.h" - -#include "base/functional/bind.h" -#include "base/logging.h" -#include "base/task/single_thread_task_runner.h" -#include "gpu/ipc/client/client_shared_image_interface.h" -#include "gpu/ipc/client/gpu_channel_host.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "mojo/public/cpp/bindings/pending_associated_remote.h" -#include "mojo/public/cpp/bindings/sync_call_restrictions.h" -#include "ui/gfx/geometry/size.h" - -namespace content { - -StreamTextureProxy::StreamTextureProxy(std::unique_ptr<StreamTextureHost> host) - : host_(std::move(host)) {} - -StreamTextureProxy::~StreamTextureProxy() {} - -void StreamTextureProxy::Release() { - // Cannot call |received_frame_cb_| after returning from here. - ClearReceivedFrameCB(); - - // |this| can be deleted by the |task_runner_| on the compositor thread by - // posting task to that thread. So we need to clear the - // |create_video_frame_cb_| here first so that its not called on the - // compositor thread before |this| is deleted. The problem is that - // |create_video_frame_cb_| is provided by the owner of StreamTextureProxy, - // which is being destroyed and is releasing StreamTextureProxy. - ClearCreateVideoFrameCB(); - - // Release is analogous to the destructor, so there should be no more external - // calls to this object in Release. Therefore there is no need to acquire the - // lock to access |task_runner_|. - if (!task_runner_.get() || task_runner_->BelongsToCurrentThread() || - !task_runner_->DeleteSoon(FROM_HERE, this)) { - delete this; - } -} - -void StreamTextureProxy::ClearReceivedFrameCB() { - base::AutoLock lock(lock_); - received_frame_cb_.Reset(); -} - -void StreamTextureProxy::ClearCreateVideoFrameCB() { - base::AutoLock lock(lock_); - create_video_frame_cb_.Reset(); -} - -void StreamTextureProxy::BindToTaskRunner( - const base::RepeatingClosure& received_frame_cb, - const CreateVideoFrameCB& create_video_frame_cb, - scoped_refptr<base::SingleThreadTaskRunner> task_runner) { - DCHECK(task_runner.get()); - - { - base::AutoLock lock(lock_); - DCHECK(!task_runner_.get() || (task_runner.get() == task_runner_.get())); - task_runner_ = task_runner; - received_frame_cb_ = received_frame_cb; - create_video_frame_cb_ = create_video_frame_cb; - } - - if (task_runner->BelongsToCurrentThread()) { - BindOnThread(); - return; - } - // Unretained is safe here only because the object is deleted on |loop_| - // thread. - task_runner->PostTask(FROM_HERE, - base::BindOnce(&StreamTextureProxy::BindOnThread, - base::Unretained(this))); -} - -void StreamTextureProxy::BindOnThread() { - host_->BindToCurrentThread(this); -} - -void StreamTextureProxy::OnFrameAvailable() { - base::AutoLock lock(lock_); - if (!received_frame_cb_.is_null()) - received_frame_cb_.Run(); -} - -void StreamTextureProxy::OnFrameWithInfoAvailable( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>& ycbcr_info) { - base::AutoLock lock(lock_); - // Set the ycbcr info before running the received frame callback so that the - // first frame has it. - if (!create_video_frame_cb_.is_null()) - create_video_frame_cb_.Run(mailbox, coded_size, visible_rect, ycbcr_info); - if (!received_frame_cb_.is_null()) - received_frame_cb_.Run(); -} - -void StreamTextureProxy::ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token) { - base::AutoLock lock(lock_); - if (!task_runner_) - return; - - if (!task_runner_->BelongsToCurrentThread()) { - // Note that Unretained is safe here because this object is deleted - // exclusively by posting a task to the same task runner, after its owner - // has dropped the only reference to it. - task_runner_->PostTask( - FROM_HERE, - base::BindOnce( - &StreamTextureProxy::ForwardStreamTextureForSurfaceRequest, - base::Unretained(this), request_token)); - return; - } - - host_->ForwardStreamTextureForSurfaceRequest(request_token); -} - -void StreamTextureProxy::UpdateRotatedVisibleSize(const gfx::Size& size) { - base::AutoLock lock(lock_); - if (!task_runner_) - return; - - if (!task_runner_->BelongsToCurrentThread()) { - // Note that Unretained is safe here because this object is deleted - // exclusively by posting a task to the same task runner, after its owner - // has dropped the only reference to it. - task_runner_->PostTask( - FROM_HERE, base::BindOnce(&StreamTextureProxy::UpdateRotatedVisibleSize, - base::Unretained(this), size)); - return; - } - host_->UpdateRotatedVisibleSize(size); -} - -// static -scoped_refptr<StreamTextureFactory> StreamTextureFactory::Create( - scoped_refptr<gpu::GpuChannelHost> channel) { - return new StreamTextureFactory(std::move(channel)); -} - -StreamTextureFactory::StreamTextureFactory( - scoped_refptr<gpu::GpuChannelHost> channel) - : channel_(std::move(channel)) { - DCHECK(channel_); -} - -StreamTextureFactory::~StreamTextureFactory() = default; - -ScopedStreamTextureProxy StreamTextureFactory::CreateProxy() { - // Send a StreamTexure receiver down to the GPU process. This will be bound to - // a concrete StreamTexture impl there. - int32_t stream_id = channel_->GenerateRouteID(); - bool succeeded = false; - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync; - mojo::PendingAssociatedRemote<gpu::mojom::StreamTexture> remote; - channel_->GetGpuChannel().CreateStreamTexture( - stream_id, remote.InitWithNewEndpointAndPassReceiver(), &succeeded); - if (!succeeded) { - DLOG(ERROR) << "CreateStreamTexture failed"; - return ScopedStreamTextureProxy(); - } - - // Now instantiate a new StreamTextureHost here to remotely control the new - // GPU-side StreamTexture instance. - return ScopedStreamTextureProxy( - new StreamTextureProxy(std::make_unique<StreamTextureHost>( - channel_, stream_id, std::move(remote)))); -} - -bool StreamTextureFactory::IsLost() const { - return channel_->IsLost(); -} - -gpu::SharedImageInterface* StreamTextureFactory::SharedImageInterface() { - if (shared_image_interface_) - return shared_image_interface_.get(); - - shared_image_interface_ = channel_->CreateClientSharedImageInterface(); - DCHECK(shared_image_interface_); - return shared_image_interface_.get(); -} - -} // namespace content
diff --git a/content/renderer/media/android/stream_texture_factory.h b/content/renderer/media/android/stream_texture_factory.h deleted file mode 100644 index 2160695..0000000 --- a/content/renderer/media/android/stream_texture_factory.h +++ /dev/null
@@ -1,139 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_FACTORY_H_ -#define CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_FACTORY_H_ - -#include <stdint.h> - -#include <memory> - -#include "base/memory/ref_counted.h" -#include "base/task/single_thread_task_runner.h" -#include "base/unguessable_token.h" -#include "cc/layers/video_frame_provider.h" -#include "content/common/content_export.h" -#include "content/renderer/stream_texture_host_android.h" -#include "gpu/command_buffer/common/mailbox.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "mojo/public/cpp/bindings/pending_associated_receiver.h" -#include "ui/gfx/geometry/rect.h" -#include "ui/gfx/geometry/size.h" - -namespace gpu { -class ClientSharedImageInterface; -class GpuChannelHost; -class SharedImageInterface; -struct VulkanYCbCrInfo; -} // namespace gpu - -namespace content { - -class StreamTextureFactory; - -// The proxy class for the gpu thread to notify the compositor thread -// when a new video frame is available. -class CONTENT_EXPORT StreamTextureProxy : public StreamTextureHost::Listener { - public: - using CreateVideoFrameCB = - base::RepeatingCallback<void(const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>&)>; - - StreamTextureProxy() = delete; - StreamTextureProxy(const StreamTextureProxy&) = delete; - StreamTextureProxy& operator=(const StreamTextureProxy&) = delete; - - ~StreamTextureProxy() override; - - // Initialize and bind to |task_runner|, which becomes the thread that the - // provided callback will be run on. This can be called on any thread, but - // must be called with the same |task_runner| every time. - void BindToTaskRunner( - const base::RepeatingClosure& received_frame_cb, - const CreateVideoFrameCB& create_video_frame_cb, - scoped_refptr<base::SingleThreadTaskRunner> task_runner); - - // StreamTextureHost::Listener implementation: - void OnFrameAvailable() override; - void OnFrameWithInfoAvailable( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>& ycbcr_info) override; - - // Sends an IPC to the GPU process. - // Asks the StreamTexture to forward its SurfaceTexture to the - // ScopedSurfaceRequestManager, using the gpu::ScopedSurfaceRequestConduit. - void ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token); - - // Notifies StreamTexture that video size has been changed and so it can - // recreate shared image. - void UpdateRotatedVisibleSize(const gfx::Size& size); - - // Clears |received_frame_cb_| in a thread safe way. - void ClearReceivedFrameCB(); - - // Clears |create_video_frame_cb_| in a thread safe way. - void ClearCreateVideoFrameCB(); - - struct Deleter { - inline void operator()(StreamTextureProxy* ptr) const { ptr->Release(); } - }; - private: - friend class StreamTextureFactory; - friend class StreamTextureProxyTest; - explicit StreamTextureProxy(std::unique_ptr<StreamTextureHost> host); - - void BindOnThread(); - void Release(); - - const std::unique_ptr<StreamTextureHost> host_; - - // Protects access to |received_frame_cb_| and |task_runner_|. - base::Lock lock_; - base::RepeatingClosure received_frame_cb_; - CreateVideoFrameCB create_video_frame_cb_; - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; -}; - -typedef std::unique_ptr<StreamTextureProxy, StreamTextureProxy::Deleter> - ScopedStreamTextureProxy; - -// Factory class for managing stream textures. -class CONTENT_EXPORT StreamTextureFactory - : public base::RefCountedThreadSafe<StreamTextureFactory> { - public: - static scoped_refptr<StreamTextureFactory> Create( - scoped_refptr<gpu::GpuChannelHost> channel); - - StreamTextureFactory() = delete; - StreamTextureFactory(const StreamTextureFactory&) = delete; - StreamTextureFactory& operator=(const StreamTextureFactory&) = delete; - - // Create the StreamTextureProxy object. This internally creates a - // gpu::StreamTexture and returns its route_id. If this route_id is invalid - // nullptr is returned. If the route_id is valid it returns - // StreamTextureProxy object. - ScopedStreamTextureProxy CreateProxy(); - - // Returns true if the StreamTextureFactory's channel is lost. - bool IsLost() const; - - gpu::SharedImageInterface* SharedImageInterface(); - - private: - friend class base::RefCountedThreadSafe<StreamTextureFactory>; - StreamTextureFactory(scoped_refptr<gpu::GpuChannelHost> channel); - ~StreamTextureFactory(); - - scoped_refptr<gpu::GpuChannelHost> channel_; - scoped_refptr<gpu::ClientSharedImageInterface> shared_image_interface_; -}; - -} // namespace content - -#endif // CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_FACTORY_H_
diff --git a/content/renderer/media/android/stream_texture_proxy_unittest.cc b/content/renderer/media/android/stream_texture_proxy_unittest.cc deleted file mode 100644 index 2cb2d3f6..0000000 --- a/content/renderer/media/android/stream_texture_proxy_unittest.cc +++ /dev/null
@@ -1,84 +0,0 @@ -// Copyright 2019 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <tuple> - -#include "base/memory/ptr_util.h" -#include "base/memory/scoped_refptr.h" -#include "base/test/task_environment.h" -#include "content/renderer/media/android/stream_texture_factory.h" -#include "content/renderer/stream_texture_host_android.h" -#include "gpu/command_buffer/common/mailbox.h" -#include "gpu/command_buffer/common/sync_token.h" -#include "gpu/ipc/client/gpu_channel_host.h" -#include "gpu/ipc/common/surface_handle.h" -#include "mojo/public/cpp/system/message_pipe.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace content { - -namespace { - -// GpuChannelHost is expected to be created on the IO thread, and posts tasks to -// setup its IPC listener, so it must be created after the thread task runner -// handle is set. -class TestGpuChannelHost : public gpu::GpuChannelHost { - public: - TestGpuChannelHost() - : gpu::GpuChannelHost( - 0 /* channel_id */, - gpu::GPUInfo(), - gpu::GpuFeatureInfo(), - gpu::SharedImageCapabilities(), - mojo::ScopedMessagePipeHandle( - mojo::MessagePipeHandle(mojo::kInvalidHandleValue))) {} - - protected: - ~TestGpuChannelHost() override {} -}; - -} // namespace - -class StreamTextureProxyTest : public testing::Test { - public: - StreamTextureProxyTest() - : channel_(base::MakeRefCounted<TestGpuChannelHost>()) {} - - ~StreamTextureProxyTest() override { channel_ = nullptr; } - - std::unique_ptr<StreamTextureProxy> CreateProxyWithNullGpuChannelHost() { - // Create the StreamTextureHost with a valid |channel_|. Note that route_id - // does not matter here for the test we are writing. - mojo::PendingAssociatedRemote<gpu::mojom::StreamTexture> texture; - std::ignore = texture.InitWithNewEndpointAndPassReceiver(); - texture.EnableUnassociatedUsage(); - auto host = std::make_unique<StreamTextureHost>(channel_, 1 /* route_id */, - std::move(texture)); - - // Force the StreamTextureHost's |channel_| to be null by calling - // OnDisconnectedFromGpuProcess. - host->OnDisconnectedFromGpuProcess(); - auto proxy = base::WrapUnique(new StreamTextureProxy(std::move(host))); - return proxy; - } - - protected: - base::test::SingleThreadTaskEnvironment task_environment_; - scoped_refptr<TestGpuChannelHost> channel_; -}; - -// This test is to make sure UpdateRotatedVisibleSize() do not crash even if -// StreamTextureHost's |channel_| is null. -TEST_F(StreamTextureProxyTest, - UpdateRotatedVisibleSizeDoesNotCrashWithNullGpuChannelHost) { - auto proxy = CreateProxyWithNullGpuChannelHost(); - gpu::Mailbox mailbox; - gpu::SyncToken texture_mailbox_sync_token; - - // This method should not crash even if the StreamTextureHost's |channel_| is - // null. - proxy->UpdateRotatedVisibleSize(gfx::Size(1, 1)); -} - -} // namespace content
diff --git a/content/renderer/media/android/stream_texture_wrapper_impl.cc b/content/renderer/media/android/stream_texture_wrapper_impl.cc deleted file mode 100644 index 33e9ef8..0000000 --- a/content/renderer/media/android/stream_texture_wrapper_impl.cc +++ /dev/null
@@ -1,212 +0,0 @@ -// Copyright 2016 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/renderer/media/android/stream_texture_wrapper_impl.h" - -#include "base/functional/bind.h" -#include "base/functional/callback.h" -#include "base/functional/callback_helpers.h" -#include "base/logging.h" -#include "base/task/bind_post_task.h" -#include "base/task/single_thread_task_runner.h" -#include "cc/layers/video_frame_provider.h" -#include "gpu/GLES2/gl2extchromium.h" -#include "gpu/command_buffer/client/client_shared_image.h" -#include "gpu/command_buffer/client/shared_image_interface.h" -#include "gpu/command_buffer/common/shared_image_usage.h" - -namespace { -// Non-member function to allow it to run even after this class is deleted. -void OnReleaseVideoFrame(scoped_refptr<content::StreamTextureFactory> factories, - scoped_refptr<gpu::ClientSharedImage> shared_image, - const gpu::SyncToken& sync_token) { - gpu::SharedImageInterface* sii = factories->SharedImageInterface(); - sii->DestroySharedImage(sync_token, std::move(shared_image)); - // ClientSharedImage destructor calls DestroySharedImage which in turn ensures - // that the deferred destroy request is flushed. Thus, clients don't need to - // call SharedImageInterface::Flush explicitly. -} -} - -namespace content { - -StreamTextureWrapperImpl::StreamTextureWrapperImpl( - bool enable_texture_copy, - scoped_refptr<StreamTextureFactory> factory, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) - : enable_texture_copy_(enable_texture_copy), - factory_(factory), - main_task_runner_(main_task_runner) {} - -StreamTextureWrapperImpl::~StreamTextureWrapperImpl() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - // Clears create video frame callback so it couldn't be called from compositor - // thread after |this| is being destroyed. - if (stream_texture_proxy_) - stream_texture_proxy_->ClearCreateVideoFrameCB(); - - SetCurrentFrameInternal(nullptr); -} - -media::ScopedStreamTextureWrapper StreamTextureWrapperImpl::Create( - bool enable_texture_copy, - scoped_refptr<StreamTextureFactory> factory, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) { - return media::ScopedStreamTextureWrapper(new StreamTextureWrapperImpl( - enable_texture_copy, factory, main_task_runner)); -} - -scoped_refptr<media::VideoFrame> StreamTextureWrapperImpl::GetCurrentFrame() { - base::AutoLock auto_lock(current_frame_lock_); - return current_frame_; -} - -void StreamTextureWrapperImpl::CreateVideoFrame( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>& ycbcr_info) { - gpu::SharedImageInterface* sii = factory_->SharedImageInterface(); - // The SI backing this VideoFrame will be read by the display compositor and - // raster. The latter will be over GL if not using OOP-R. NOTE: GL usage can - // be eliminated once OOP-R ships definitively. - scoped_refptr<gpu::ClientSharedImage> shared_image; - - // Ensure that the ClientSI holds the correct texture target (which is *not* - // the texture target that ClientSharedImage would compute internally for - // these parameters). - shared_image = - sii->NotifyMailboxAdded(mailbox, viz::SinglePlaneFormat::kRGBA_8888, - coded_size, gfx::ColorSpace::CreateSRGB(), - kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, - gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | - gpu::SHARED_IMAGE_USAGE_GLES2_READ | - gpu::SHARED_IMAGE_USAGE_RASTER_READ, - GL_TEXTURE_EXTERNAL_OES); - - // The pixel format doesn't matter here as long as it's valid for texture - // frames. But SkiaRenderer wants to ensure that the format of the resource - // used here which will eventually create a promise image must match the - // format of the resource(AndroidVideoImageBacking) used to create fulfill - // image. crbug.com/1028746. Since we create all the textures/abstract - // textures as well as shared images for video to be of format RGBA, we need - // to use the pixel format as ABGR here(which corresponds to 32bpp RGBA). - // - // This message comes from GPU process when the SharedImage is already - // created, so we don't need to wait on any synctoken, mailbox is ready to - // use. - scoped_refptr<media::VideoFrame> new_frame = - media::VideoFrame::WrapSharedImage( - media::PIXEL_FORMAT_ABGR, shared_image, gpu::SyncToken(), - base::BindPostTask(main_task_runner_, - base::BindOnce(&OnReleaseVideoFrame, factory_, - std::move(shared_image))), - coded_size, visible_rect, visible_rect.size(), base::TimeDelta()); - new_frame->set_ycbcr_info(ycbcr_info); - - if (enable_texture_copy_) - new_frame->metadata().copy_required = true; - - SetCurrentFrameInternal(new_frame); -} - -void StreamTextureWrapperImpl::ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token) { - stream_texture_proxy_->ForwardStreamTextureForSurfaceRequest(request_token); -} - -void StreamTextureWrapperImpl::ClearReceivedFrameCBOnAnyThread() { - // Safely stop StreamTextureProxy from signaling the arrival of new frames. - if (stream_texture_proxy_) - stream_texture_proxy_->ClearReceivedFrameCB(); -} - -void StreamTextureWrapperImpl::SetCurrentFrameInternal( - scoped_refptr<media::VideoFrame> video_frame) { - base::AutoLock auto_lock(current_frame_lock_); - current_frame_ = std::move(video_frame); -} - -void StreamTextureWrapperImpl::UpdateTextureSize(const gfx::Size& new_size) { - DVLOG(2) << __func__; - - if (!main_task_runner_->BelongsToCurrentThread()) { - main_task_runner_->PostTask( - FROM_HERE, base::BindOnce(&StreamTextureWrapperImpl::UpdateTextureSize, - weak_factory_.GetWeakPtr(), new_size)); - return; - } - - // InitializeOnMainThread() hasn't run, or failed. - if (!stream_texture_proxy_) - return; - - if (rotated_visible_size_ == new_size) - return; - - rotated_visible_size_ = new_size; - stream_texture_proxy_->UpdateRotatedVisibleSize(rotated_visible_size_); -} - -void StreamTextureWrapperImpl::Initialize( - const base::RepeatingClosure& received_frame_cb, - scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, - StreamTextureWrapperInitCB init_cb) { - DVLOG(2) << __func__; - - compositor_task_runner_ = compositor_task_runner; - - main_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&StreamTextureWrapperImpl::InitializeOnMainThread, - weak_factory_.GetWeakPtr(), received_frame_cb, - base::BindPostTaskToCurrentDefault(std::move(init_cb)))); -} - -void StreamTextureWrapperImpl::InitializeOnMainThread( - const base::RepeatingClosure& received_frame_cb, - StreamTextureWrapperInitCB init_cb) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - DVLOG(2) << __func__; - - // Normally, we have a factory. However, if the gpu process is restarting, - // then we might not. - if (!factory_) { - std::move(init_cb).Run(false); - return; - } - - stream_texture_proxy_ = factory_->CreateProxy(); - if (!stream_texture_proxy_) { - std::move(init_cb).Run(false); - return; - } - - // Unretained is safe here since |stream_texture_proxy_| is a scoped member of - // the this StreamTextureWrapperImpl class which clears/resets this callback - // before |this| is destroyed. - stream_texture_proxy_->BindToTaskRunner( - received_frame_cb, - base::BindRepeating(&StreamTextureWrapperImpl::CreateVideoFrame, - base::Unretained(this)), - compositor_task_runner_); - - std::move(init_cb).Run(true); -} - -void StreamTextureWrapperImpl::Destroy() { - if (!main_task_runner_->BelongsToCurrentThread()) { - // base::Unretained is safe here because this function is the only one that - // can call delete. - main_task_runner_->PostTask( - FROM_HERE, base::BindOnce(&StreamTextureWrapperImpl::Destroy, - base::Unretained(this))); - return; - } - - delete this; -} - -} // namespace content
diff --git a/content/renderer/media/android/stream_texture_wrapper_impl.h b/content/renderer/media/android/stream_texture_wrapper_impl.h deleted file mode 100644 index b91e3d7..0000000 --- a/content/renderer/media/android/stream_texture_wrapper_impl.h +++ /dev/null
@@ -1,133 +0,0 @@ -// Copyright 2016 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_WRAPPER_IMPL_H_ -#define CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_WRAPPER_IMPL_H_ - -#include "base/task/single_thread_task_runner.h" -#include "content/common/content_export.h" -#include "content/renderer/media/android/stream_texture_factory.h" -#include "gpu/command_buffer/common/mailbox.h" -#include "media/base/android/stream_texture_wrapper.h" -#include "media/base/video_frame.h" - -namespace content { - -// Concrete implementation of StreamTextureWrapper. Any method can be called on -// any thread, but additional threading considerations are listed in the -// comments of individual methods. -// -// The StreamTexture is an abstraction allowing Chrome to wrap a SurfaceOwner -// living in the GPU process. It allows VideoFrames to be created from the -// SurfaceOwner's texture, in the Renderer process. -// -// The general idea behind our use of StreamTexture is as follows: -// - We request the creation of a StreamTexture via the StreamTextureFactory. -// The call is sent to the GPU process via the CommandBuffer. A StreamTexture is -// then created, wrapping a SurfaceOwner. The SurfaceOwner's -// OnFrameAvailable() callback is tied to StreamTexture's OnFrameAvailable(), -// which fires an IPC across the GPU channel. -// - We create a StreamTextureProxy in the Renderer process which listens for -// the IPC fired by the StreamTexture's OnFrameAvailable() callback. -// - We bind the StreamTextureProxy's lifetime to the |compositor_task_runner_|. -// - We create a SharedImage mailbox representing the StreamTexture at a given -// size. -// - We create a VideoFrame which takes ownership of this SharedImage mailbox. -// - When the SurfaceOwner's OnFrameAvailable() callback is fired (and routed -// to the StreamTextureProxy living on the compositor thread), we notify -// |client_| that a new frame is available, via the DidReceiveFrame() callback. -// - When the StreamTextureProxy is destroyed, it delivers a notification over -// the channel, cleaning up the StreamTexture ref in the GPU process. -class CONTENT_EXPORT StreamTextureWrapperImpl - : public media::StreamTextureWrapper { - public: - // |enable_texture_copy| controls the VideoFrameMetadata::COPY_REQUIRED flag, - // making sure it is correctly set on |current_frame_|, for webview scenarios. - static media::ScopedStreamTextureWrapper Create( - bool enable_texture_copy, - scoped_refptr<StreamTextureFactory> factory, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner); - - StreamTextureWrapperImpl(const StreamTextureWrapperImpl&) = delete; - StreamTextureWrapperImpl& operator=(const StreamTextureWrapperImpl&) = delete; - - // Creates the underlying StreamTexture, and binds |stream_texture_proxy_| to - // |compositor_task_runner|. - // - // Additional threading considerations: - // - Can be called from any thread. - // - Initialization will be posted to |main_task_runner_|. - // - |init_cb| will be run on the calling thread, and will be passed a bool - // indicating whether the initialization was successful. - // - New frames will be signaled on |compositor_task_runner| via |client|'s - // DidReceiveFrame() method. - void Initialize( - const base::RepeatingClosure& received_frame_cb, - scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, - StreamTextureWrapperInitCB init_cb) override; - - // Should be called when the Video size changes. - // Can be called from any thread, but runs on |main_task_runner_|. - void UpdateTextureSize(const gfx::Size& rotated_visible_size) override; - - // Returns the latest frame. - // N.B: We create a single VideoFrame at initialization time (and update it - // in UpdateTextureSize()), and repeatedly return it here. The underlying - // texture's changes are signalled via |client|'s DidReceiveFrame() callback. - scoped_refptr<media::VideoFrame> GetCurrentFrame() override; - - // Sends the StreamTexture to the browser process, to fulfill the request - // identified by |request_token|. - // Uses the gpu::ScopedSurfaceRequestConduit to forward the underlying - // SurfaceTexture to the ScopedSurfaceRequestManager. - void ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token) override; - - // Clears the |received_frame_cb| passed in Initialize(). - // Should be safe to call from any thread. - void ClearReceivedFrameCBOnAnyThread() override; - - private: - StreamTextureWrapperImpl( - bool enable_texture_copy, - scoped_refptr<StreamTextureFactory> factory, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner); - ~StreamTextureWrapperImpl() override; - - // Destroys |this| safely on |main_task_runner_|. - void Destroy() override; - - void InitializeOnMainThread(const base::RepeatingClosure& received_frame_cb, - StreamTextureWrapperInitCB init_cb); - - void CreateVideoFrame(const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>& ycbcr_info); - - void SetCurrentFrameInternal(scoped_refptr<media::VideoFrame> video_frame); - - bool enable_texture_copy_; - - // Object for calling back the compositor thread to repaint the video when a - // frame is available. It should be bound to |compositor_task_runner_|. - ScopedStreamTextureProxy stream_texture_proxy_; - - // Visible size of the video with rotation applied. - gfx::Size rotated_visible_size_; - - scoped_refptr<StreamTextureFactory> factory_; - - base::Lock current_frame_lock_; - scoped_refptr<media::VideoFrame> current_frame_; - - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; - scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; - - base::WeakPtrFactory<StreamTextureWrapperImpl> weak_factory_{this}; -}; - -} // namespace media - -#endif // CONTENT_RENDERER_MEDIA_ANDROID_STREAM_TEXTURE_WRAPPER_IMPL_H_
diff --git a/content/renderer/media/android/stream_texture_wrapper_impl_unittest.cc b/content/renderer/media/android/stream_texture_wrapper_impl_unittest.cc deleted file mode 100644 index b3681e8..0000000 --- a/content/renderer/media/android/stream_texture_wrapper_impl_unittest.cc +++ /dev/null
@@ -1,48 +0,0 @@ -// Copyright 2016 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/renderer/media/android/stream_texture_wrapper_impl.h" - -#include "base/functional/callback_helpers.h" -#include "base/run_loop.h" -#include "base/test/task_environment.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" - -namespace content { - -class StreamTextureWrapperImplTest : public testing::Test { - public: - StreamTextureWrapperImplTest() {} - - StreamTextureWrapperImplTest(const StreamTextureWrapperImplTest&) = delete; - StreamTextureWrapperImplTest& operator=(const StreamTextureWrapperImplTest&) = - delete; - - // Necessary, or else GetSingleThreadTaskRunnerForTesting() fails. - base::test::SingleThreadTaskEnvironment task_environment_; -}; - -// This test's purpose is to make sure the StreamTextureWrapperImpl can properly -// be destroyed via StreamTextureWrapper::Deleter. -TEST_F(StreamTextureWrapperImplTest, ConstructionDestruction_ShouldSucceed) { - media::ScopedStreamTextureWrapper stream_texture_wrapper = - StreamTextureWrapperImpl::Create( - false, nullptr, - blink::scheduler::GetSingleThreadTaskRunnerForTesting()); - // Since we provided a null factory, make sure that it also doesn't crash if - // we try to initialize it. - int result = 0; - stream_texture_wrapper->Initialize( - base::DoNothing(), - blink::scheduler::GetSingleThreadTaskRunnerForTesting(), - base::BindOnce( - [](int* result_out, bool result) { *result_out = result ? 1 : 2; }, - &result)); - base::RunLoop().RunUntilIdle(); - // Should be called with false. - EXPECT_EQ(result, 2); -} - -} // Content
diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc index 23fab33e..dd201f0 100644 --- a/content/renderer/media/media_factory.cc +++ b/content/renderer/media/media_factory.cc
@@ -76,7 +76,6 @@ #if BUILDFLAG(IS_ANDROID) #include "content/renderer/media/android/flinging_renderer_client_factory.h" -#include "content/renderer/media/android/stream_texture_wrapper_impl.h" #include "media/base/android/media_codec_util.h" #include "media/base/media.h" #include "url/gurl.h"
diff --git a/content/renderer/media/win/dcomp_texture_wrapper_impl.cc b/content/renderer/media/win/dcomp_texture_wrapper_impl.cc index 47e80aba..3894289 100644 --- a/content/renderer/media/win/dcomp_texture_wrapper_impl.cc +++ b/content/renderer/media/win/dcomp_texture_wrapper_impl.cc
@@ -212,7 +212,6 @@ base::TimeDelta()); frame->set_color_space(color_space); - // Sets `dcomp_surface` to use StreamTexture. See `VideoResourceUpdater`. frame->metadata().dcomp_surface = true; std::move(create_video_frame_cb).Run(frame, mailbox_);
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc index 4176c77..8fb5656 100644 --- a/content/renderer/render_thread_impl.cc +++ b/content/renderer/render_thread_impl.cc
@@ -173,7 +173,6 @@ #if BUILDFLAG(IS_ANDROID) #include <cpu-features.h> -#include "content/renderer/media/android/stream_texture_factory.h" #include "media/base/android/media_codec_util.h" #endif @@ -1238,25 +1237,6 @@ return shared_main_thread_contexts_; } -#if BUILDFLAG(IS_ANDROID) -scoped_refptr<StreamTextureFactory> RenderThreadImpl::GetStreamTexureFactory() { - DCHECK(IsMainThread()); - if (!stream_texture_factory_ || stream_texture_factory_->IsLost()) { - scoped_refptr<gpu::GpuChannelHost> channel = EstablishGpuChannelSync(); - if (!channel) { - stream_texture_factory_ = nullptr; - return nullptr; - } - stream_texture_factory_ = StreamTextureFactory::Create(std::move(channel)); - } - return stream_texture_factory_; -} - -bool RenderThreadImpl::EnableStreamTextureCopy() { - return GetContentClient()->UsingSynchronousCompositing(); -} -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) scoped_refptr<DCOMPTextureFactory> RenderThreadImpl::GetDCOMPTextureFactory() { DCHECK(IsMainThread());
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h index be41c6ee..61630fd 100644 --- a/content/renderer/render_thread_impl.h +++ b/content/renderer/render_thread_impl.h
@@ -108,10 +108,6 @@ class RendererBlinkPlatformImpl; class VariationsRenderThreadObserver; -#if BUILDFLAG(IS_ANDROID) -class StreamTextureFactory; -#endif - #if BUILDFLAG(IS_WIN) class DCOMPTextureFactory; class OverlayStateServiceProvider; @@ -256,11 +252,6 @@ return url_loader_throttle_provider_.get(); } -#if BUILDFLAG(IS_ANDROID) - scoped_refptr<StreamTextureFactory> GetStreamTexureFactory(); - bool EnableStreamTextureCopy(); -#endif - #if BUILDFLAG(IS_WIN) scoped_refptr<DCOMPTextureFactory> GetDCOMPTextureFactory(); // The OverlayStateService is only available where Media Foundation for @@ -535,10 +526,6 @@ // Thread to run the VideoFrameCompositor on. std::unique_ptr<base::Thread> video_frame_compositor_thread_; -#if BUILDFLAG(IS_ANDROID) - scoped_refptr<StreamTextureFactory> stream_texture_factory_; -#endif - #if BUILDFLAG(IS_WIN) scoped_refptr<DCOMPTextureFactory> dcomp_texture_factory_; scoped_refptr<OverlayStateServiceProviderImpl>
diff --git a/content/renderer/stream_texture_host_android.cc b/content/renderer/stream_texture_host_android.cc deleted file mode 100644 index b35aebe..0000000 --- a/content/renderer/stream_texture_host_android.cc +++ /dev/null
@@ -1,109 +0,0 @@ -// Copyright 2012 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/renderer/stream_texture_host_android.h" - -#include "base/unguessable_token.h" -#include "content/renderer/render_thread_impl.h" -#include "gpu/ipc/client/gpu_channel_host.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "gpu/ipc/common/vulkan_ycbcr_info.h" -#include "ipc/ipc_message_macros.h" -#include "ipc/ipc_mojo_bootstrap.h" - -namespace content { - -StreamTextureHost::StreamTextureHost( - scoped_refptr<gpu::GpuChannelHost> channel, - int32_t route_id, - mojo::PendingAssociatedRemote<gpu::mojom::StreamTexture> texture) - : route_id_(route_id), - listener_(nullptr), - channel_(std::move(channel)), - pending_texture_(std::move(texture)) { - DCHECK(channel_); - DCHECK(route_id_); -} - -StreamTextureHost::~StreamTextureHost() { - if (channel_) { - // We destroy the StreamTexture as a deferred message followed by a flush - // to ensure this is ordered correctly with regards to previous deferred - // messages, such as CreateSharedImage. - uint32_t flush_id = channel_->EnqueueDeferredMessage( - gpu::mojom::DeferredRequestParams::NewDestroyStreamTexture(route_id_), - /*sync_token_fences=*/{}, /*release_count=*/0); - channel_->EnsureFlush(flush_id); - } -} - -bool StreamTextureHost::BindToCurrentThread(Listener* listener) { - listener_ = listener; - if (!pending_texture_) - return false; - - // Disable artificial limitations of Channel-associated interface bindings. - // Normally such interfaces can only be bound on the main or IO threads to - // prevent various classes of bugs that can arise from a Channel-associated - // endpoint being bound too late to receive messages already sent to it. - // - // Without this scoped allowance, binding from the compositor thread will - // actually still bind to the main thread, leading to potential memory bugs - // and other racy behavior since `this` is still destroyed on the compositor - // thread. - // - // The allowance is safe because binding these endpoints to the compositor - // thread cannot cause such late-binding bugs: remotes can only receive - // replies, but `texture_remote_` can't have made any calls yet; and - // `receiver_` doesn't even have a corresponding remote to call into it until - // the StartListening() line below. - // - // SUBTLE: If any message on StreamTextureClient (or any reply on - // StreamTexture) were to be added which accepts another associated interface - // endpoint, *that* could render this all unsafe since the new endpoint might - // get bound too late. - IPC::ScopedAllowOffSequenceChannelAssociatedBindings allow_off_thread_binding; - - texture_remote_.Bind(std::move(pending_texture_)); - texture_remote_->StartListening(receiver_.BindNewEndpointAndPassRemote()); - texture_remote_.set_disconnect_handler( - base::BindOnce(&StreamTextureHost::OnDisconnectedFromGpuProcess, - base::Unretained(this))); - return true; -} - -void StreamTextureHost::OnDisconnectedFromGpuProcess() { - channel_ = nullptr; - texture_remote_.reset(); - receiver_.reset(); -} - -void StreamTextureHost::OnFrameWithInfoAvailable( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - std::optional<gpu::VulkanYCbCrInfo> ycbcr_info) { - if (listener_) { - listener_->OnFrameWithInfoAvailable(mailbox, coded_size, visible_rect, - ycbcr_info); - } -} - -void StreamTextureHost::OnFrameAvailable() { - if (listener_) - listener_->OnFrameAvailable(); -} - -void StreamTextureHost::ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token) { - if (texture_remote_) - texture_remote_->ForwardForSurfaceRequest(request_token); -} - -void StreamTextureHost::UpdateRotatedVisibleSize(const gfx::Size& size) { - if (texture_remote_) - texture_remote_->UpdateRotatedVisibleSize(size); -} - -} // namespace content
diff --git a/content/renderer/stream_texture_host_android.h b/content/renderer/stream_texture_host_android.h deleted file mode 100644 index 910c4a665..0000000 --- a/content/renderer/stream_texture_host_android.h +++ /dev/null
@@ -1,103 +0,0 @@ -// Copyright 2012 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_RENDERER_STREAM_TEXTURE_HOST_ANDROID_H_ -#define CONTENT_RENDERER_STREAM_TEXTURE_HOST_ANDROID_H_ - -#include <stdint.h> - -#include "base/memory/raw_ptr.h" -#include "base/memory/weak_ptr.h" -#include "content/common/content_export.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "mojo/public/cpp/bindings/associated_receiver.h" -#include "mojo/public/cpp/bindings/associated_remote.h" - -namespace base { -class UnguessableToken; -} - -namespace gfx { -class Rect; -class Size; -} - -namespace gpu { -class GpuChannelHost; -struct Mailbox; -struct VulkanYCbCrInfo; -} - -namespace content { - -// Class for handling all the IPC messages between the GPU process and -// StreamTextureProxy. -class CONTENT_EXPORT StreamTextureHost - : public gpu::mojom::StreamTextureClient { - public: - StreamTextureHost() = delete; - - explicit StreamTextureHost( - scoped_refptr<gpu::GpuChannelHost> channel, - int32_t route_id, - mojo::PendingAssociatedRemote<gpu::mojom::StreamTexture> texture); - - StreamTextureHost(const StreamTextureHost&) = delete; - StreamTextureHost& operator=(const StreamTextureHost&) = delete; - - ~StreamTextureHost() override; - - // Listener class that is listening to the stream texture updates. It is - // implemented by StreamTextureProxyImpl. - class Listener { - public: - virtual void OnFrameAvailable() = 0; - virtual void OnFrameWithInfoAvailable( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const std::optional<gpu::VulkanYCbCrInfo>& ycbcr_info) = 0; - virtual ~Listener() {} - }; - - bool BindToCurrentThread(Listener* listener); - void OnDisconnectedFromGpuProcess(); - - void ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token); - void UpdateRotatedVisibleSize(const gfx::Size& size); - - private: - // gpu::mojom::StreamTextureClient: - void OnFrameAvailable() override; - void OnFrameWithInfoAvailable( - const gpu::Mailbox& mailbox, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - std::optional<gpu::VulkanYCbCrInfo> ycbcr_info) override; - - int32_t route_id_; - raw_ptr<Listener> listener_; - scoped_refptr<gpu::GpuChannelHost> channel_; - - // The StreamTextureHost may be created on another thread, but the Mojo - // endpoints below are to be bound on the compositor thread. This holds the - // pending renderer-side StreamTexture endpoint until it can be bound on the - // compositor thread. - mojo::PendingAssociatedRemote<gpu::mojom::StreamTexture> pending_texture_; - - // Receives client messages from a corresponding StreamTexture instance in - // the GPU process. - mojo::AssociatedReceiver<gpu::mojom::StreamTextureClient> receiver_{this}; - - // Sends messages to a corresponding StreamTexture instance in the GPU - // process. - mojo::AssociatedRemote<gpu::mojom::StreamTexture> texture_remote_; - - base::WeakPtrFactory<StreamTextureHost> weak_ptr_factory_{this}; -}; - -} // namespace content - -#endif // CONTENT_RENDERER_STREAM_TEXTURE_HOST_ANDROID_H_
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn index 5544ddc..d246458 100644 --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn
@@ -3470,8 +3470,6 @@ "../browser/renderer_host/render_widget_host_view_android_unittest.cc", "../browser/sms/sms_provider_gms_unittest.cc", "../renderer/java/gin_java_bridge_value_converter_unittest.cc", - "../renderer/media/android/stream_texture_proxy_unittest.cc", - "../renderer/media/android/stream_texture_wrapper_impl_unittest.cc", ] deps += [
diff --git a/content/test/data/accessibility/html/textarea-expected-blink.txt b/content/test/data/accessibility/html/textarea-expected-blink.txt index fe251522..95e9810a 100644 --- a/content/test/data/accessibility/html/textarea-expected-blink.txt +++ b/content/test/data/accessibility/html/textarea-expected-blink.txt
@@ -3,12 +3,15 @@ ++++genericContainer ++++++textField multiline value='The <newline>textarea tag defines a multi-line text input control.<newline>' textSelStart=0 textSelEnd=0 maxLength=99 ++++++++genericContainer -++++++++++staticText name='The <newline>textarea tag defines a multi-line text input control.<newline>' +++++++++++staticText name='The ' ++++++++++++inlineTextBox name='The ' +++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>' +++++++++++staticText name='textarea tag defines a multi-line text input control.' ++++++++++++inlineTextBox name='textarea tag defines a multi-line text input' ++++++++++++inlineTextBox name=' ' ++++++++++++inlineTextBox name='control.' +++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>' ++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>'
diff --git a/content/test/data/accessibility/html/textarea-read-only-expected-blink.txt b/content/test/data/accessibility/html/textarea-read-only-expected-blink.txt index 978e17c..445dc15 100644 --- a/content/test/data/accessibility/html/textarea-read-only-expected-blink.txt +++ b/content/test/data/accessibility/html/textarea-read-only-expected-blink.txt
@@ -3,10 +3,11 @@ ++++genericContainer ++++++textField multiline value='The textarea tag defines a multi-line text input control.<newline>' textSelStart=0 textSelEnd=0 restriction=readOnly ++++++++genericContainer -++++++++++staticText name='The textarea tag defines a multi-line text input control.<newline>' +++++++++++staticText name='The textarea tag defines a multi-line text input control.' ++++++++++++inlineTextBox name='The textarea tag defines a multi-line text input' ++++++++++++inlineTextBox name=' ' ++++++++++++inlineTextBox name='control.' +++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>' ++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>'
diff --git a/content/test/data/accessibility/html/textarea-with-selection-expected-blink.txt b/content/test/data/accessibility/html/textarea-with-selection-expected-blink.txt index 098a5a0..116635d 100644 --- a/content/test/data/accessibility/html/textarea-with-selection-expected-blink.txt +++ b/content/test/data/accessibility/html/textarea-with-selection-expected-blink.txt
@@ -3,10 +3,11 @@ ++++genericContainer ++++++textField multiline value='The textarea tag defines a multi-line text input control.<newline>' textSelStart=0 textSelEnd=58 ++++++++genericContainer -++++++++++staticText name='The textarea tag defines a multi-line text input control.<newline>' +++++++++++staticText name='The textarea tag defines a multi-line text input control.' ++++++++++++inlineTextBox name='The textarea tag defines a multi-line text input' ++++++++++++inlineTextBox name=' ' ++++++++++++inlineTextBox name='control.' +++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>' ++++++++++lineBreak name='<newline>' ++++++++++++inlineTextBox name='<newline>'
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 5e0e96e..5bb4372 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn
@@ -628,8 +628,6 @@ # which the extension system depends. if (enable_guest_view) { sources += [ - "api/guest_view/app_view/app_view_guest_internal_api.cc", - "api/guest_view/app_view/app_view_guest_internal_api.h", "api/guest_view/web_view/web_view_internal_api.cc", "api/guest_view/web_view/web_view_internal_api.h", "guest_view/app_view/app_view_constants.cc",
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn index 222dee1..3f5250d 100644 --- a/extensions/browser/api/BUILD.gn +++ b/extensions/browser/api/BUILD.gn
@@ -92,7 +92,10 @@ } if (enable_guest_view) { - public_deps += [ "//extensions/browser/api/guest_view" ] + public_deps += [ + "//extensions/browser/api/guest_view", + "//extensions/browser/api/guest_view/app_view", + ] } } }
diff --git a/extensions/browser/api/guest_view/app_view/BUILD.gn b/extensions/browser/api/guest_view/app_view/BUILD.gn new file mode 100644 index 0000000..4a450d2 --- /dev/null +++ b/extensions/browser/api/guest_view/app_view/BUILD.gn
@@ -0,0 +1,27 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//components/guest_view/buildflags/buildflags.gni") +import("//extensions/buildflags/buildflags.gni") + +assert(enable_extensions, + "Cannot depend on extensions because enable_extensions=false.") +assert(enable_guest_view, + "Cannot depend on guest_view because enable_guest_view=false.") + +source_set("app_view") { + sources = [ + "app_view_guest_internal_api.cc", + "app_view_guest_internal_api.h", + ] + + configs += [ "//build/config/compiler:wexit_time_destructors" ] + + public_deps = [ "//extensions/browser:browser_sources" ] + + deps = [ + "//content/public/browser", + "//extensions/common/api", + ] +}
diff --git a/fuchsia_web/webengine/browser/frame_impl.cc b/fuchsia_web/webengine/browser/frame_impl.cc index c61784e..bd09d0f3 100644 --- a/fuchsia_web/webengine/browser/frame_impl.cc +++ b/fuchsia_web/webengine/browser/frame_impl.cc
@@ -212,6 +212,8 @@ return blink::PermissionType::PROTECTED_MEDIA_IDENTIFIER; case fuchsia::web::PermissionType::PERSISTENT_STORAGE: return blink::PermissionType::DURABLE_STORAGE; + case fuchsia::web::PermissionType::NOTIFICATIONS: + return blink::PermissionType::NOTIFICATIONS; default: return std::nullopt; }
diff --git a/fuchsia_web/webengine/browser/frame_permission_controller.cc b/fuchsia_web/webengine/browser/frame_permission_controller.cc index 6c46f0d..bf6f2c0 100644 --- a/fuchsia_web/webengine/browser/frame_permission_controller.cc +++ b/fuchsia_web/webengine/browser/frame_permission_controller.cc
@@ -28,13 +28,21 @@ const url::Origin& requesting_origin, const url::Origin& embedding_origin) { // Logic in this function should match the logic in - // permissions::PermissionManager::GetCanonicalOrigin(). Currently it always - // returns embedding origin, which is correct for all permissions supported by - // WebEngine (AUDIO_CAPTURE, VIDEO_CAPTURE, PROTECTED_MEDIA_IDENTIFIER, - // DURABLE_STORAGE). + // permissions::PermissionUtil::GetCanonicalOrigin. // // TODO(crbug.com/40680523): Update this function when other permissions are // added. + if (permission == PermissionType::NOTIFICATIONS) { + return requesting_origin; + } + + // Return embedding origin by default, which is correct for all remaining + // permissions supported by WebEngine. + // + // - AUDIO_CAPTURE + // - VIDEO_CAPTURE + // - PROTECTED_MEDIA_IDENTIFIER + // - DURABLE_STORAGE return embedding_origin; } @@ -72,7 +80,8 @@ DCHECK(permission == PermissionType::AUDIO_CAPTURE || permission == PermissionType::VIDEO_CAPTURE || permission == PermissionType::PROTECTED_MEDIA_IDENTIFIER || - permission == PermissionType::DURABLE_STORAGE); + permission == PermissionType::DURABLE_STORAGE || + permission == PermissionType::NOTIFICATIONS); auto it = per_origin_permissions_.find(origin); if (it == per_origin_permissions_.end()) {
diff --git a/gpu/command_buffer/client/client_test_helper.h b/gpu/command_buffer/client/client_test_helper.h index d27c1a8..82c3fcd 100644 --- a/gpu/command_buffer/client/client_test_helper.h +++ b/gpu/command_buffer/client/client_test_helper.h
@@ -131,7 +131,6 @@ } MOCK_METHOD0(CancelAllQueries, void()); - MOCK_METHOD1(CreateStreamTexture, uint32_t(uint32_t)); MOCK_METHOD1(SetLock, void(base::Lock*)); MOCK_METHOD0(EnsureWorkVisible, void()); MOCK_CONST_METHOD0(GetNamespaceID, CommandBufferNamespace());
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc index d820b3d..3f9489c 100644 --- a/gpu/config/gpu_finch_features.cc +++ b/gpu/config/gpu_finch_features.cc
@@ -33,7 +33,7 @@ namespace { #if BUILDFLAG(IS_ANDROID) -bool IsDeviceBlocked(const char* field, const std::string& block_list) { +bool IsDeviceBlocked(const std::string& field, const std::string& block_list) { auto disable_patterns = base::SplitString( block_list, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& disable_pattern : disable_patterns) {
diff --git a/gpu/ipc/common/gpu_channel.mojom b/gpu/ipc/common/gpu_channel.mojom index 2448e6c..3a381ae 100644 --- a/gpu/ipc/common/gpu_channel.mojom +++ b/gpu/ipc/common/gpu_channel.mojom
@@ -194,11 +194,6 @@ viz.mojom.SharedImageFormat format, gfx.mojom.Size size, gfx.mojom.BufferUsage buffer_usage); - // Creates a StreamTexture associated with the given `stream_id`. - [Sync, EnableIf=is_android] CreateStreamTexture( - int32 stream_id, - pending_associated_receiver<StreamTexture> receiver) => (bool success); - // Creates a DCOMPTexture and attaches to the provided `route_id`. // Note: this `route_id` must not conflict with the routing ids for command // buffers or shared image stub. Otherwise, unexpected behaviour can happen @@ -339,54 +334,11 @@ OnSignalAck(uint32 signal_id, CommandBufferState state); }; -// Interface to control a single StreamTexture instance. Implemented in the GPU -// process and called by renderer clients. -// -// NOTE: Due to limitations of Channel-associated interfaces, message replies on -// this interface must not be used to pass other associated interface remotes or -// receivers. -[EnableIf=is_android] -interface StreamTexture { - // Tells the StreamTexture to send its SurfaceTexture to the browser process, - // via the ScopedSurfaceRequestConduit. `token` is used to uniquely identify - // the surface request to the browser, and therefore must be a token already - // minted by the browser and somehow procured by the caller. - ForwardForSurfaceRequest(mojo_base.mojom.UnguessableToken token); - - // Tells the service-side instance to start sending frame available - // notifications. - StartListening(pending_associated_remote<StreamTextureClient> client); - - // Updates the visible size from MediaPlayer. The size includes rotation of - // video. - UpdateRotatedVisibleSize(gfx.mojom.Size rotated_visible_size); -}; - -// Interface used by GPU to notify the client of a StreamTexture instance about -// new frame availability. Implemented by renderer clients and called by -// StreamTexture instances in the GPU process. -// -// NOTE: Due to limitations of Channel-associated interfaces, messages on this -// interface must not be used to pass other associated interface remotes or -// receivers. -[EnableIf=is_android] -interface StreamTextureClient { - // Informs the client that a new frame is available. - OnFrameAvailable(); - - // Informs the client that a new frame with VulkanYcbcrInfo is available. - OnFrameWithInfoAvailable(Mailbox mailbox, gfx.mojom.Size coded_size, - gfx.mojom.Rect visible_rect, - VulkanYCbCrInfo? info); -}; - // Interface to control a single DCOMPTexture instance. Implemented in the GPU // process and called by renderer clients. [EnableIf=is_win] interface DCOMPTexture { - // Starts sending DCOMP surface notifications via the `client`. Follows the - // pattern in `StreamTexture::StartListening()` to delay notifications until - // the client side is ready. + // Starts sending DCOMP surface notifications via the `client`. // // TODO(crbug.com/40642952): Investigate if we can remove this by having the // `client` as a parameter to `CreateDCOMPTexture()`. @@ -452,11 +404,6 @@ // This request pertains to shared image management. DeferredSharedImageRequest shared_image_request; - // Sent to indicate the client wants to destroy the StreamTexture identified - // by this ID. - [EnableIf=is_android] - int32 destroy_stream_texture; - // Sent to indicate the client wants to destroy the DCOMPTexture identified // by this ID. [EnableIf=is_win]
diff --git a/gpu/ipc/common/mock_gpu_channel.h b/gpu/ipc/common/mock_gpu_channel.h index a7491e2..aa9ed108 100644 --- a/gpu/ipc/common/mock_gpu_channel.h +++ b/gpu/ipc/common/mock_gpu_channel.h
@@ -60,12 +60,6 @@ CreateGpuMemoryBufferCallback)); MOCK_METHOD2(GetGpuMemoryBufferHandleInfo, void(const gpu::Mailbox&, GetGpuMemoryBufferHandleInfoCallback)); -#if BUILDFLAG(IS_ANDROID) - MOCK_METHOD3(CreateStreamTexture, - void(int32_t, - mojo::PendingAssociatedReceiver<mojom::StreamTexture>, - CreateStreamTextureCallback)); -#endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_WIN) MOCK_METHOD3(CreateDCOMPTexture, void(int32_t,
diff --git a/gpu/ipc/service/BUILD.gn b/gpu/ipc/service/BUILD.gn index 2ef6410..237bff8 100644 --- a/gpu/ipc/service/BUILD.gn +++ b/gpu/ipc/service/BUILD.gn
@@ -137,11 +137,7 @@ } } if (is_android) { - sources += [ - "image_transport_surface_android.cc", - "stream_texture_android.cc", - "stream_texture_android.h", - ] + sources += [ "image_transport_surface_android.cc" ] libs += [ "android" ] } if (is_linux || is_chromeos) {
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc index a4cd2fa8..9ec9036 100644 --- a/gpu/ipc/service/gpu_channel.cc +++ b/gpu/ipc/service/gpu_channel.cc
@@ -68,10 +68,6 @@ #include "ui/gl/gl_surface.h" #include "ui/gl/gl_utils.h" -#if BUILDFLAG(IS_ANDROID) -#include "gpu/ipc/service/stream_texture_android.h" -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) #include "components/viz/common/overlay_state/win/overlay_state_service.h" #include "gpu/ipc/service/dcomp_texture_win.h" @@ -85,17 +81,6 @@ namespace { -#if BUILDFLAG(IS_ANDROID) -bool TryCreateStreamTexture( - base::WeakPtr<GpuChannel> channel, - int32_t stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver) { - if (!channel) - return false; - return channel->CreateStreamTexture(stream_id, std::move(receiver)); -} -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) bool TryCreateDCOMPTexture( base::WeakPtr<GpuChannel> channel, @@ -204,12 +189,6 @@ void GetGpuMemoryBufferHandleInfo( const gpu::Mailbox& mailbox, GetGpuMemoryBufferHandleInfoCallback callback) override; -#if BUILDFLAG(IS_ANDROID) - void CreateStreamTexture( - int32_t stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver, - CreateStreamTextureCallback callback) override; -#endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_WIN) void CreateDCOMPTexture( int32_t route_id, @@ -381,12 +360,6 @@ for (auto& request : requests) { int32_t routing_id; switch (request->params->which()) { -#if BUILDFLAG(IS_ANDROID) - case mojom::DeferredRequestParams::Tag::kDestroyStreamTexture: - routing_id = request->params->get_destroy_stream_texture(); - break; -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) case mojom::DeferredRequestParams::Tag::kDestroyDcompTexture: routing_id = request->params->get_destroy_dcomp_texture(); @@ -609,24 +582,6 @@ decode_release_count); } -#if BUILDFLAG(IS_ANDROID) -void GpuChannelMessageFilter::CreateStreamTexture( - int32_t stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver, - CreateStreamTextureCallback callback) { - base::AutoLock auto_lock(gpu_channel_lock_); - if (!gpu_channel_) { - receiver_.reset(); - return; - } - main_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&TryCreateStreamTexture, gpu_channel_->AsWeakPtr(), - stream_id, std::move(receiver)), - std::move(callback)); -} -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) void GpuChannelMessageFilter::CreateDCOMPTexture( int32_t route_id, @@ -803,14 +758,6 @@ // Clear stubs first because of dependencies. stubs_.clear(); -#if BUILDFLAG(IS_ANDROID) - // Release any references to this channel held by StreamTexture. - for (auto& stream_texture : stream_textures_) { - stream_texture.second->ReleaseChannel(); - } - stream_textures_.clear(); -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) // Release any references to this channel held by DCOMPTexture. for (auto& dcomp_texture : dcomp_textures_) { @@ -934,12 +881,6 @@ FenceSyncReleaseDelegate* release_delegate) { TRACE_EVENT0("gpu", "GpuChannel::ExecuteDeferredRequest"); switch (params->which()) { -#if BUILDFLAG(IS_ANDROID) - case mojom::DeferredRequestParams::Tag::kDestroyStreamTexture: - DestroyStreamTexture(params->get_destroy_stream_texture()); - break; -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) case mojom::DeferredRequestParams::Tag::kDestroyDcompTexture: DestroyDCOMPTexture(params->get_destroy_dcomp_texture()); @@ -1056,15 +997,6 @@ return nullptr; } -void GpuChannel::DestroyStreamTexture(int32_t stream_id) { - auto found = stream_textures_.find(stream_id); - if (found == stream_textures_.end()) { - LOG(ERROR) << "Trying to destroy a non-existent stream texture."; - return; - } - found->second->ReleaseChannel(); - stream_textures_.erase(stream_id); -} #endif #if BUILDFLAG(IS_WIN) @@ -1227,26 +1159,6 @@ RemoveRoute(route_id); } -#if BUILDFLAG(IS_ANDROID) -bool GpuChannel::CreateStreamTexture( - int32_t stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver) { - auto found = stream_textures_.find(stream_id); - if (found != stream_textures_.end()) { - LOG(ERROR) - << "Trying to create a StreamTexture with an existing stream_id."; - return false; - } - scoped_refptr<StreamTexture> stream_texture = - StreamTexture::Create(this, stream_id, std::move(receiver)); - if (!stream_texture) { - return false; - } - stream_textures_.emplace(stream_id, std::move(stream_texture)); - return true; -} -#endif - #if BUILDFLAG(IS_WIN) bool GpuChannel::CreateDCOMPTexture( int32_t route_id,
diff --git a/gpu/ipc/service/gpu_channel.h b/gpu/ipc/service/gpu_channel.h index 3382243..d4eb3356 100644 --- a/gpu/ipc/service/gpu_channel.h +++ b/gpu/ipc/service/gpu_channel.h
@@ -52,7 +52,6 @@ class ImageDecodeAcceleratorWorker; class Scheduler; class SharedImageStub; -class StreamTexture; class SyncPointManager; // Encapsulates an IPC channel between the GPU process and one renderer @@ -173,14 +172,6 @@ #if BUILDFLAG(IS_ANDROID) const CommandBufferStub* GetOneStub() const; - - bool CreateStreamTexture( - int32_t stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver); - - // Called by StreamTexture to remove the GpuChannel's reference to the - // StreamTexture. - void DestroyStreamTexture(int32_t stream_id); #endif #if BUILDFLAG(IS_WIN) @@ -284,11 +275,6 @@ const bool is_gpu_host_; -#if BUILDFLAG(IS_ANDROID) - // Set of active StreamTextures. - base::flat_map<int32_t, scoped_refptr<StreamTexture>> stream_textures_; -#endif - #if BUILDFLAG(IS_WIN) // Set of active DCOMPTextures. base::flat_map<int32_t, scoped_refptr<DCOMPTexture>> dcomp_textures_;
diff --git a/gpu/ipc/service/raster_command_buffer_stub.cc b/gpu/ipc/service/raster_command_buffer_stub.cc index cb716a6..d66ef2a 100644 --- a/gpu/ipc/service/raster_command_buffer_stub.cc +++ b/gpu/ipc/service/raster_command_buffer_stub.cc
@@ -36,10 +36,6 @@ #include "base/win/win_util.h" #endif -#if BUILDFLAG(IS_ANDROID) -#include "gpu/ipc/service/stream_texture_android.h" -#endif - namespace gpu { RasterCommandBufferStub::RasterCommandBufferStub(
diff --git a/gpu/ipc/service/stream_texture_android.cc b/gpu/ipc/service/stream_texture_android.cc deleted file mode 100644 index 0279b339c8..0000000 --- a/gpu/ipc/service/stream_texture_android.cc +++ /dev/null
@@ -1,272 +0,0 @@ -// Copyright 2013 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "gpu/ipc/service/stream_texture_android.h" - -#include <string.h> - -#include "base/android/android_image_reader_compat.h" -#include "base/android/scoped_hardware_buffer_fence_sync.h" -#include "base/feature_list.h" -#include "base/functional/bind.h" -#include "base/task/single_thread_task_runner.h" -#include "components/viz/common/resources/resource_sizes.h" -#include "gpu/command_buffer/common/shared_image_usage.h" -#include "gpu/command_buffer/service/context_state.h" -#include "gpu/command_buffer/service/feature_info.h" -#include "gpu/command_buffer/service/ref_counted_lock.h" -#include "gpu/command_buffer/service/scheduler.h" -#include "gpu/command_buffer/service/scheduler_task_runner.h" -#include "gpu/command_buffer/service/shared_context_state.h" -#include "gpu/command_buffer/service/shared_image/android_video_image_backing.h" -#include "gpu/command_buffer/service/shared_image/shared_image_backing.h" -#include "gpu/command_buffer/service/shared_image/shared_image_factory.h" -#include "gpu/config/gpu_finch_features.h" -#include "gpu/ipc/common/android/scoped_surface_request_conduit.h" -#include "gpu/ipc/common/command_buffer_id.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "gpu/ipc/service/gpu_channel.h" -#include "gpu/ipc/service/gpu_channel_manager.h" -#include "ui/gfx/color_space.h" -#include "ui/gfx/geometry/size.h" -#include "ui/gl/gl_context.h" -#include "ui/gl/scoped_binders.h" -#include "ui/gl/scoped_make_current.h" - -namespace gpu { -namespace { - -std::unique_ptr<ui::ScopedMakeCurrent> MakeCurrent( - SharedContextState* context_state) { - std::unique_ptr<ui::ScopedMakeCurrent> scoped_make_current; - bool needs_make_current = - !context_state->IsCurrent(nullptr, /*needs_gl=*/true); - if (needs_make_current) { - scoped_make_current = std::make_unique<ui::ScopedMakeCurrent>( - context_state->context(), context_state->surface()); - } - return scoped_make_current; -} - -TextureOwner::Mode GetTextureOwnerMode() { - return base::android::EnableAndroidImageReader() - ? TextureOwner::Mode::kAImageReaderInsecure - : TextureOwner::Mode::kSurfaceTextureInsecure; -} - -scoped_refptr<gpu::RefCountedLock> CreateDrDcLockIfNeeded() { - if (features::NeedThreadSafeAndroidMedia()) { - return base::MakeRefCounted<gpu::RefCountedLock>(); - } - return nullptr; -} - -} // namespace - -// static -scoped_refptr<StreamTexture> StreamTexture::Create( - GpuChannel* channel, - int stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver) { - ContextResult result; - auto context_state = - channel->gpu_channel_manager()->GetSharedContextState(&result); - if (result != ContextResult::kSuccess) - return nullptr; - auto scoped_make_current = MakeCurrent(context_state.get()); - if (scoped_make_current && !scoped_make_current->IsContextCurrent()) - return nullptr; - return new StreamTexture(channel, stream_id, std::move(receiver), - std::move(context_state)); -} - -// static -void StreamTexture::RunCallback( - scoped_refptr<base::SingleThreadTaskRunner> task_runner, - base::WeakPtr<StreamTexture> weak_stream_texture) { - if (task_runner->BelongsToCurrentThread()) { - if (weak_stream_texture) - weak_stream_texture->OnFrameAvailable(); - } else { - task_runner->PostTask( - FROM_HERE, base::BindOnce(&StreamTexture::RunCallback, task_runner, - std::move(weak_stream_texture))); - } -} - -StreamTexture::StreamTexture( - GpuChannel* channel, - int32_t route_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver, - scoped_refptr<SharedContextState> context_state) - : RefCountedLockHelperDrDc(CreateDrDcLockIfNeeded()), - texture_owner_( - TextureOwner::Create(GetTextureOwnerMode(), - context_state, - GetDrDcLock(), - TextureOwnerCodecType::kStreamTexture)), - has_pending_frame_(false), - channel_(channel), - route_id_(route_id), - context_state_(std::move(context_state)), - sequence_(channel_->scheduler()->CreateSequence(SchedulingPriority::kLow, - channel_->task_runner())), - receiver_( - this, - std::move(receiver), - base::MakeRefCounted<SchedulerTaskRunner>(*channel_->scheduler(), - sequence_)) { - channel_->AddRoute(route_id, sequence_); - texture_owner_->SetFrameAvailableCallback( - base::BindRepeating(&StreamTexture::RunCallback, - base::SingleThreadTaskRunner::GetCurrentDefault(), - weak_factory_.GetWeakPtr())); -} - -StreamTexture::~StreamTexture() { - DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_); - - // |channel_| is always released before GpuChannel releases its reference to - // this class. - DCHECK(!channel_); -} - -void StreamTexture::ReleaseChannel() { - DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_); - DCHECK(channel_); - receiver_.ResetFromAnotherSequenceUnsafe(); - channel_->RemoveRoute(route_id_); - channel_->scheduler()->DestroySequence(sequence_); - sequence_ = SequenceId(); - channel_ = nullptr; -} - -void StreamTexture::UpdateAndBindTexImage() {} - -bool StreamTexture::HasTextureOwner() const { - return !!texture_owner_; -} - -TextureBase* StreamTexture::GetTextureBase() const { - return texture_owner_->GetTextureBase(); -} - -void StreamTexture::NotifyOverlayPromotion(bool promotion, - const gfx::Rect& bounds) {} - -bool StreamTexture::RenderToOverlay() { - NOTREACHED(); -} - -bool StreamTexture::TextureOwnerBindsTextureOnUpdate() { - DCHECK(texture_owner_); - return texture_owner_->binds_texture_on_update(); -} - -void StreamTexture::OnFrameAvailable() { - DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_); - has_pending_frame_ = true; - - if (!client_ || !texture_owner_ || !channel_) - return; - - // We haven't received size for first time yet from the MediaPlayer we will - // defer this sending OnFrameAvailable till then. - if (rotated_visible_size_.IsEmpty()) - return; - - texture_owner_->UpdateTexImage(/*discard=*/false); - has_pending_frame_ = false; - - gfx::Rect visible_rect; - gfx::Size coded_size; - if (!texture_owner_->GetCodedSizeAndVisibleRect(rotated_visible_size_, - &coded_size, &visible_rect)) { - // if we failed to get right size fallback to visible size. - coded_size = rotated_visible_size_; - visible_rect = gfx::Rect(coded_size); - } - - if (coded_size != coded_size_ || visible_rect != visible_rect_) { - coded_size_ = coded_size; - visible_rect_ = visible_rect; - - auto mailbox = CreateSharedImage(coded_size); - viz::VulkanContextProvider* vulkan_context_provider = nullptr; - DawnContextProvider* dawn_context_provider = nullptr; - if (context_state_->GrContextIsVulkan()) { - vulkan_context_provider = context_state_->vk_context_provider(); - } else if (context_state_->IsGraphiteDawnVulkan()) { - dawn_context_provider = context_state_->dawn_context_provider(); - } - auto ycbcr_info = AndroidVideoImageBacking::GetYcbcrInfo( - texture_owner_.get(), vulkan_context_provider, dawn_context_provider); - - client_->OnFrameWithInfoAvailable(mailbox, coded_size, visible_rect, - ycbcr_info); - } else { - client_->OnFrameAvailable(); - } -} - -void StreamTexture::StartListening( - mojo::PendingAssociatedRemote<mojom::StreamTextureClient> client) { - client_.Bind(std::move(client)); -} - -void StreamTexture::ForwardForSurfaceRequest( - const base::UnguessableToken& request_token) { - if (!channel_) - return; - - ScopedSurfaceRequestConduit::GetInstance() - ->ForwardSurfaceOwnerForSurfaceRequest(request_token, - texture_owner_.get()); -} - -gpu::Mailbox StreamTexture::CreateSharedImage(const gfx::Size& coded_size) { - DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_); - // We do not update |texture_owner_texture_|'s internal gles2::Texture's - // size. This is because the gles2::Texture is never used directly, the - // associated |texture_owner_texture_id_| being the only part of that - // object we interact with. If we ever use |texture_owner_texture_|, we - // need to ensure that it gets updated here. - - auto scoped_make_current = MakeCurrent(context_state_.get()); - auto mailbox = gpu::Mailbox::Generate(); - - // TODO(vikassoni): Hardcoding colorspace to SRGB. Figure how if we have a - // colorspace and wire it here. - auto shared_image = AndroidVideoImageBacking::Create( - mailbox, coded_size, gfx::ColorSpace::CreateSRGB(), - kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, - /*debug_label=*/"StreamTexture", this, context_state_, GetDrDcLock()); - channel_->shared_image_stub()->factory()->RegisterBacking( - std::move(shared_image)); - - return mailbox; -} - -void StreamTexture::UpdateRotatedVisibleSize( - const gfx::Size& rotated_visible_size) { - DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_); - DCHECK(channel_); - bool was_empty = rotated_visible_size_.IsEmpty(); - rotated_visible_size_ = rotated_visible_size; - - // It's possible that first OnUpdateRotatedVisibleSize will come after first - // OnFrameAvailable. We delay sending OnFrameWithInfoAvailable if it comes - // first so now it's time to send it. - if (was_empty && has_pending_frame_) - OnFrameAvailable(); -} - -std::unique_ptr<base::android::ScopedHardwareBufferFenceSync> -StreamTexture::GetAHardwareBuffer() { - DCHECK(texture_owner_); - - return texture_owner_->GetAHardwareBuffer(); -} - -} // namespace gpu
diff --git a/gpu/ipc/service/stream_texture_android.h b/gpu/ipc/service/stream_texture_android.h deleted file mode 100644 index 832059e1..0000000 --- a/gpu/ipc/service/stream_texture_android.h +++ /dev/null
@@ -1,119 +0,0 @@ -// Copyright 2013 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef GPU_IPC_SERVICE_STREAM_TEXTURE_ANDROID_H_ -#define GPU_IPC_SERVICE_STREAM_TEXTURE_ANDROID_H_ - -#include <stdint.h> - -#include <memory> - -#include "base/memory/raw_ptr.h" -#include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" -#include "base/task/single_thread_task_runner.h" -#include "base/threading/thread_checker.h" -#include "base/unguessable_token.h" -#include "gpu/command_buffer/service/ref_counted_lock.h" -#include "gpu/command_buffer/service/shared_context_state.h" -#include "gpu/command_buffer/service/stream_texture_shared_image_interface.h" -#include "gpu/command_buffer/service/texture_owner.h" -#include "gpu/ipc/common/gpu_channel.mojom.h" -#include "gpu/ipc/service/command_buffer_stub.h" -#include "mojo/public/cpp/bindings/associated_receiver.h" -#include "mojo/public/cpp/bindings/associated_remote.h" -#include "ui/gl/android/surface_texture.h" - -namespace gfx { -class Size; -} - -namespace gpu { -class GpuChannel; -struct Mailbox; - -// This class is thread safe to be used by multiple gpu threads as -// |texture_owner_| is thread safe and all other members are only accessed on -// gpu main thread. -class StreamTexture : public RefCountedLockHelperDrDc, - public StreamTextureSharedImageInterface, - public mojom::StreamTexture { - public: - static scoped_refptr<StreamTexture> Create( - GpuChannel* channel, - int stream_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver); - - StreamTexture(const StreamTexture&) = delete; - StreamTexture& operator=(const StreamTexture&) = delete; - - // Cleans up related data and nulls |channel_|. Called when the channel - // releases its ref on this class. - void ReleaseChannel(); - - private: - StreamTexture(GpuChannel* channel, - int32_t route_id, - mojo::PendingAssociatedReceiver<mojom::StreamTexture> receiver, - scoped_refptr<SharedContextState> context_state); - ~StreamTexture() override; - - // Static function which is used to access |weak_stream_texture| on correct - // thread since WeakPtr is not thread safe. - static void RunCallback( - scoped_refptr<base::SingleThreadTaskRunner> task_runner, - base::WeakPtr<StreamTexture> weak_stream_texture); - - // gpu::StreamTextureSharedImageInterface implementation. - void ReleaseResources() override {} - void UpdateAndBindTexImage() override; - bool HasTextureOwner() const override; - TextureBase* GetTextureBase() const override; - void NotifyOverlayPromotion(bool promotion, const gfx::Rect& bounds) override; - bool RenderToOverlay() override; - std::unique_ptr<base::android::ScopedHardwareBufferFenceSync> - GetAHardwareBuffer() override; - bool TextureOwnerBindsTextureOnUpdate() override; - - gpu::Mailbox CreateSharedImage(const gfx::Size& coded_size); - - // Called when a new frame is available for the SurfaceOwner. - void OnFrameAvailable(); - - // mojom::StreamTexture: - void ForwardForSurfaceRequest(const base::UnguessableToken& token) override; - void StartListening(mojo::PendingAssociatedRemote<mojom::StreamTextureClient> - client) override; - void UpdateRotatedVisibleSize(const gfx::Size& natural_size) override; - - // The TextureOwner which receives frames. - scoped_refptr<TextureOwner> texture_owner_; - - // Current visible size from media player, includes rotation. - gfx::Size rotated_visible_size_; - - // Whether a new frame is available that we should update to. - bool has_pending_frame_; - - raw_ptr<GpuChannel> channel_; - const int32_t route_id_; - scoped_refptr<SharedContextState> context_state_; - SequenceId sequence_; - - mojo::AssociatedReceiver<mojom::StreamTexture> receiver_; - mojo::AssociatedRemote<mojom::StreamTextureClient> client_; - - gfx::Size coded_size_; - gfx::Rect visible_rect_; - - // Bound to the thread on which StreamTexture is created. Some methods can - // only be called on this thread. StreamTexture is created on gpu main thread. - THREAD_CHECKER(gpu_main_thread_checker_); - - base::WeakPtrFactory<StreamTexture> weak_factory_{this}; -}; - -} // namespace gpu - -#endif // GPU_IPC_SERVICE_STREAM_TEXTURE_ANDROID_H_
diff --git a/gpu/ipc/service/webgpu_command_buffer_stub.cc b/gpu/ipc/service/webgpu_command_buffer_stub.cc index 00a780c..686fe2c 100644 --- a/gpu/ipc/service/webgpu_command_buffer_stub.cc +++ b/gpu/ipc/service/webgpu_command_buffer_stub.cc
@@ -37,10 +37,6 @@ #include "base/win/win_util.h" #endif -#if BUILDFLAG(IS_ANDROID) -#include "gpu/ipc/service/stream_texture_android.h" -#endif - namespace gpu { WebGPUCommandBufferStub::WebGPUCommandBufferStub(
diff --git a/gpu/vulkan/vulkan_util.cc b/gpu/vulkan/vulkan_util.cc index 24b2faa..d8ef027 100644 --- a/gpu/vulkan/vulkan_util.cc +++ b/gpu/vulkan/vulkan_util.cc
@@ -66,17 +66,18 @@ int GetEMUIVersion() { const auto* build_info = base::android::BuildInfo::GetInstance(); - std::string_view manufacturer(build_info->manufacturer()); // TODO(crbug.com/40136096): check Honor devices as well. - if (manufacturer != "HUAWEI") + if (build_info->manufacturer() != "HUAWEI") { return -1; + } // Huawei puts EMUI version in the build version incremental. // Example: 11.0.0.130C00 int version = 0; - if (sscanf(build_info->version_incremental(), "%d.", &version) != 1) + if (sscanf(build_info->version_incremental().c_str(), "%d.", &version) != 1) { return -1; + } return version; }
diff --git a/headless/test/data/window_open.html b/headless/test/data/window_open.html deleted file mode 100644 index 46ce703..0000000 --- a/headless/test/data/window_open.html +++ /dev/null
@@ -1,3 +0,0 @@ -<script> - window.open("", "", "width=200,height=100"); -</script>
diff --git a/headless/test/headless_web_contents_browsertest.cc b/headless/test/headless_web_contents_browsertest.cc index 1d9445b..978d0bc6 100644 --- a/headless/test/headless_web_contents_browsertest.cc +++ b/headless/test/headless_web_contents_browsertest.cc
@@ -70,49 +70,6 @@ UnorderedElementsAre(web_contents)); } -IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, WindowOpen) { - EXPECT_TRUE(embedded_test_server()->Start()); - - HeadlessBrowserContext* browser_context = - browser()->CreateBrowserContextBuilder().Build(); - - HeadlessWebContents* web_contents = - browser_context->CreateWebContentsBuilder() - .SetInitialURL(embedded_test_server()->GetURL("/window_open.html")) - .Build(); - EXPECT_TRUE(WaitForLoad(web_contents)); - - EXPECT_EQ(2u, browser_context->GetAllWebContents().size()); - - HeadlessWebContentsImpl* child = nullptr; - HeadlessWebContentsImpl* parent = nullptr; - for (HeadlessWebContents* c : browser_context->GetAllWebContents()) { - HeadlessWebContentsImpl* impl = HeadlessWebContentsImpl::From(c); - if (impl->window_id() == 1) - parent = impl; - else if (impl->window_id() == 2) - child = impl; - } - - EXPECT_NE(nullptr, parent); - EXPECT_NE(nullptr, child); - EXPECT_NE(parent, child); - - // Mac doesn't have WindowTreeHosts. - if (parent && child && parent->window_tree_host()) - EXPECT_NE(parent->window_tree_host(), child->window_tree_host()); - - gfx::Rect expected_bounds(0, 0, 200, 100); -#if !BUILDFLAG(IS_MAC) - EXPECT_EQ(expected_bounds, child->web_contents()->GetViewBounds()); - EXPECT_EQ(expected_bounds, child->web_contents()->GetContainerBounds()); -#else // !BUILDFLAG(IS_MAC) - // Mac does not support GetViewBounds() and view positions are random. - EXPECT_EQ(expected_bounds.size(), - child->web_contents()->GetContainerBounds().size()); -#endif // !BUILDFLAG(IS_MAC) -} - IN_PROC_BROWSER_TEST_F(HeadlessWebContentsTest, FocusOfHeadlessWebContents_IsIndependent) { EXPECT_TRUE(embedded_test_server()->Start());
diff --git a/internal b/internal index 70336560..4c66676c 160000 --- a/internal +++ b/internal
@@ -1 +1 @@ -Subproject commit 7033656091f15c89917d3fd040c03dd346684c3b +Subproject commit 4c66676ccee672657c3b79f4321f6ce3b6fd9a32
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/DEPS b/ios/chrome/browser/content_suggestions/ui_bundled/DEPS index 099ad44..d0d0060 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/DEPS +++ b/ios/chrome/browser/content_suggestions/ui_bundled/DEPS
@@ -62,7 +62,6 @@ "+ios/chrome/browser/price_notifications/ui_bundled/cells/price_notifications_price_chip_view.h", "+ios/chrome/browser/price_notifications/ui_bundled/cells/price_notifications_track_button.h", "+ios/chrome/browser/tips_notifications/ui", - "+ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h", "+ios/chrome/browser/content_notification/model/content_notification_util.h", "+ios/chrome/browser/page_image/model/page_image_service_factory.h", ]
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/save_passwords_instructional_view_controller.h b/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/save_passwords_instructional_view_controller.h index 4e2439c..0a8d9d0 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/save_passwords_instructional_view_controller.h +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/save_passwords_instructional_view_controller.h
@@ -5,7 +5,7 @@ #ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_TIPS_UI_SAVE_PASSWORDS_INSTRUCTIONAL_VIEW_CONTROLLER_H_ #define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_TIPS_UI_SAVE_PASSWORDS_INSTRUCTIONAL_VIEW_CONTROLLER_H_ -#import "ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h" +#import "ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h" // The view controller for the full-screen, animated Save Passwords // instructional tip.
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/use_autofill_instructional_view_controller.h b/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/use_autofill_instructional_view_controller.h index 8bf7a357..5d6a1c9 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/use_autofill_instructional_view_controller.h +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/use_autofill_instructional_view_controller.h
@@ -5,7 +5,7 @@ #ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_TIPS_UI_USE_AUTOFILL_INSTRUCTIONAL_VIEW_CONTROLLER_H_ #define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_TIPS_UI_USE_AUTOFILL_INSTRUCTIONAL_VIEW_CONTROLLER_H_ -#import "ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h" +#import "ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h" // The view controller for the full-screen, animated Use Autofill instructional // tip.
diff --git a/ios/chrome/browser/docking_promo/DEPS b/ios/chrome/browser/docking_promo/DEPS index df0bf569..4435d088 100644 --- a/ios/chrome/browser/docking_promo/DEPS +++ b/ios/chrome/browser/docking_promo/DEPS
@@ -5,4 +5,5 @@ "+ios/chrome/browser/first_run/ui_bundled/first_run_screen_delegate.h", "+ios/chrome/browser/promos_manager/ui_bundled", "+ios/chrome/browser/start_surface/ui_bundled/start_surface_util.h", + "+ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h", ]
diff --git a/ios/chrome/browser/docking_promo/ui/BUILD.gn b/ios/chrome/browser/docking_promo/ui/BUILD.gn index 9e8ef47c..1efb954d 100644 --- a/ios/chrome/browser/docking_promo/ui/BUILD.gn +++ b/ios/chrome/browser/docking_promo/ui/BUILD.gn
@@ -4,8 +4,6 @@ source_set("ui") { sources = [ - "animated_promo_view_controller.h", - "animated_promo_view_controller.mm", "docking_promo_display_handler.h", "docking_promo_display_handler.mm", "docking_promo_metrics.h", @@ -21,6 +19,7 @@ "//ios/chrome/browser/promos_manager/ui_bundled:promos", "//ios/chrome/browser/shared/public/commands", "//ios/chrome/browser/shared/ui/util", + "//ios/chrome/browser/tips_notifications/ui:shared", "//ios/chrome/common/ui/colors", "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/common/ui/instruction_view",
diff --git a/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.h b/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.h index 62e5c800..38e4b51a 100644 --- a/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.h +++ b/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.h
@@ -5,7 +5,7 @@ #ifndef IOS_CHROME_BROWSER_DOCKING_PROMO_UI_DOCKING_PROMO_VIEW_CONTROLLER_H_ #define IOS_CHROME_BROWSER_DOCKING_PROMO_UI_DOCKING_PROMO_VIEW_CONTROLLER_H_ -#import "ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h" +#import "ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h" // Container view controller for the Docking Promo. @interface DockingPromoViewController : AnimatedPromoViewController
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index 992a41ff..7d35468 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -2675,7 +2675,10 @@ {"colorful-tab-group", flag_descriptions::kColorfulTabGroupName, flag_descriptions::kColorfulTabGroupDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kColorfulTabGroup)}, - + {"lens-load-aim-in-lens-result-page", + flag_descriptions::kLensLoadAIMInLensResultPageName, + flag_descriptions::kLensLoadAIMInLensResultPageDescription, + flags_ui::kOsIos, FEATURE_VALUE_TYPE(kLensLoadAIMInLensResultPage)}, }; bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 65d544bb..ba8b8d6 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -779,6 +779,11 @@ const char kLensInkMultiSampleModeDisabledDescription[] = "When disabled, turns off multi-sample mode and uses less memory."; +const char kLensLoadAIMInLensResultPageName[] = + "Enable loading AIM in the Lens result page"; +const char kLensLoadAIMInLensResultPageDescription[] = + "Opens in Lens result page rather than a new tab."; + extern const char kLensOverlayAlternativeOnboardingName[] = "Lens Overlay Onboarding"; extern const char kLensOverlayAlternativeOnboardingDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index a29c0f97..e7a09d7 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -453,6 +453,9 @@ extern const char kLensInkMultiSampleModeDisabledName[]; extern const char kLensInkMultiSampleModeDisabledDescription[]; +extern const char kLensLoadAIMInLensResultPageName[]; +extern const char kLensLoadAIMInLensResultPageDescription[]; + extern const char kLensOverlayForceShowOnboardingScreenName[]; extern const char kLensOverlayForceShowOnboardingScreenDescription[];
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.mm b/ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.mm index 6f377030..59e5fbf 100644 --- a/ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.mm +++ b/ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.mm
@@ -80,7 +80,9 @@ /// them out explicitly. GURL URLByRemovingLensSurfaceParamIfNecessary(const GURL& URL) { // If not a finance or flights URL, do nothing - if (URLIsFinance(URL) || URLIsFlights(URL) || URLIsShopping(URL)) { + if (URLIsFinance(URL) || URLIsFlights(URL) || URLIsShopping(URL) || + (lens::IsLensAIMSRP(URL) && + !base::FeatureList::IsEnabled(kLensLoadAIMInLensResultPage))) { return net::AppendOrReplaceQueryParameter(URL, "lns_surface", std::nullopt); } @@ -327,6 +329,12 @@ [self.delegate lensResultPageMediator:self didOpenNewTabFromSource:lens::LensOverlayNewTabSource::kWebNavigation]; + } else if (lens::IsLensAIMSRP(URL) && + !base::FeatureList::IsEnabled(kLensLoadAIMInLensResultPage)) { + [self.delegate lensResultPageOpenURLInNewTabRequsted:URL]; + [self.delegate + lensResultPageMediator:self + didOpenNewTabFromSource:lens::LensOverlayNewTabSource::kWebNavigation]; } else { decisionHandler(web::WebStatePolicyDecider::PolicyDecision::Allow()); }
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h index 5c0cf2434..c5257ab 100644 --- a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h +++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h
@@ -19,6 +19,9 @@ /// Whether the `url` is a lens multimodal SRP. bool IsLensMultimodalSRP(const GURL& url); +/// Whether the `url` is a lens AIM SRP. +bool IsLensAIMSRP(const GURL& url); + /// Returns the search tearm of the lens overlay SRP. std::string ExtractQueryFromLensOverlaySRP(const GURL& url);
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm index ba3ff83f..68e8df8 100644 --- a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm +++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
@@ -57,6 +57,16 @@ has_unified_drilldown_param && lens_surface == "4" && udm == "24"; } +bool IsLensAIMSRP(const GURL& url) { + if (!IsGoogleHostURL(url)) { + return false; + } + std::string udm; + bool has_unified_drilldown_param = net::GetValueForKeyInQuery( + url, lens::kUnifiedDrillDownQueryParameter, &udm); + return has_unified_drilldown_param && udm == "50"; +} + std::string ExtractQueryFromLensOverlaySRP(const GURL& url) { std::string search_term = ""; net::GetValueForKeyInQuery(url, "q", &search_term);
diff --git a/ios/chrome/browser/policy/model/reporting/report_scheduler_ios_unittest.mm b/ios/chrome/browser/policy/model/reporting/report_scheduler_ios_unittest.mm index f75b4b4..8e35bbb 100644 --- a/ios/chrome/browser/policy/model/reporting/report_scheduler_ios_unittest.mm +++ b/ios/chrome/browser/policy/model/reporting/report_scheduler_ios_unittest.mm
@@ -206,10 +206,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -232,10 +229,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kTransientError)); CreateScheduler(); @@ -258,10 +252,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kPersistentError)); CreateScheduler(); @@ -318,10 +309,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -343,10 +331,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -369,10 +354,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler(); @@ -440,10 +422,7 @@ .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1))); EXPECT_CALL(*uploader_, SetRequestAndUpload( - ReportGenerationConfig(ReportType::kFull, - SecuritySignalsMode::kNoSignals, - /*use_cookies=*/false), - _, _)) + ReportGenerationConfig(ReportTrigger::kTriggerTimer), _, _)) .WillOnce(RunOnceCallback<2>(ReportUploader::kSuccess)); CreateScheduler();
diff --git a/ios/chrome/browser/push_notification/model/push_notification_client_unittest.mm b/ios/chrome/browser/push_notification/model/push_notification_client_unittest.mm index 3902638..e9f9ad3 100644 --- a/ios/chrome/browser/push_notification/model/push_notification_client_unittest.mm +++ b/ios/chrome/browser/push_notification/model/push_notification_client_unittest.mm
@@ -84,7 +84,7 @@ ScheduledNotificationRequest tip_request = { kTipsNotificationId, ContentForTipsNotificationType(TipsNotificationType::kDefaultBrowser, - false), + false, ""), TipsNotificationTriggerDelta(false, TipsNotificationUserType::kUnknown)}; id request_check =
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h index 872cad0..f9b0255 100644 --- a/ios/chrome/browser/shared/public/features/features.h +++ b/ios/chrome/browser/shared/public/features/features.h
@@ -291,6 +291,9 @@ // Feature flag to enable the Lens Context Menu Unified experience BASE_DECLARE_FEATURE(kEnableLensContextMenuUnifiedExperience); +// Whether to enable loading AIM in the lens result page. +BASE_DECLARE_FEATURE(kLensLoadAIMInLensResultPage); + // Feature flag to enable the Lens overlay location bar entrypoint. Enabled by // default. BASE_DECLARE_FEATURE(kLensOverlayEnableLocationBarEntrypoint);
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm index a401d22..f0271d70 100644 --- a/ios/chrome/browser/shared/public/features/features.mm +++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -215,6 +215,10 @@ // Also update in components/omnibox/browser/autocomplete_result.cc. const base::NotFatalUntil kLensOverlayNotFatalUntil = base::NotFatalUntil::M200; +BASE_FEATURE(kLensLoadAIMInLensResultPage, + "LensLoadAIMInLensResultPage", + base::FEATURE_DISABLED_BY_DEFAULT); + BASE_FEATURE(kLensOverlayDisablePriceInsights, "LensOverlayDisablePriceInsights", base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/browser/tips_notifications/eg_test/tips_notifications_egtest.mm b/ios/chrome/browser/tips_notifications/eg_test/tips_notifications_egtest.mm index 89f2339..6a915b6 100644 --- a/ios/chrome/browser/tips_notifications/eg_test/tips_notifications_egtest.mm +++ b/ios/chrome/browser/tips_notifications/eg_test/tips_notifications_egtest.mm
@@ -102,6 +102,10 @@ config.features_disabled.push_back(kIOSReactivationNotifications); } + if ([self isRunningTest:@selector(testNotificationMIM)]) { + config.features_enabled.push_back(kSeparateProfilesForManagedAccounts); + } + return config; } @@ -324,6 +328,12 @@ [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"escape" flags:0]; } +// Tests that a Tips notification can be triggered and tapped when multiprofile +// is enabled. +- (void)testNotificationMIM { + [self testLensNotification]; +} + // Tests that the ESB Promo appears when tapping on the ESB notification. - (void)testEnhancedSafeBrowsingNotification { MaybeDismissNotification();
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_client.h b/ios/chrome/browser/tips_notifications/model/tips_notification_client.h index 125c9b3..81ea6142 100644 --- a/ios/chrome/browser/tips_notifications/model/tips_notification_client.h +++ b/ios/chrome/browser/tips_notifications/model/tips_notification_client.h
@@ -19,8 +19,10 @@ enum class TipsNotificationType; enum class TipsNotificationUserType; -// A notification client responsible for registering notification requests and -// handling the receiving of user notifications that are user-ed "Tips". +// A notification client responsible for registering notification requests +// (see `UNNotificationRequest`) in order to trigger iOS local notifications +// and handling the receiving of user notifications (See `UNNotification`) +// that lead to user education promos or tips on how to use the app. class TipsNotificationClient : public PushNotificationClient { public: TipsNotificationClient(); @@ -76,36 +78,37 @@ // Request a notification of the given `type`. void RequestNotification(TipsNotificationType type, + std::string_view profile_name, base::OnceClosure completion); void OnNotificationRequested(TipsNotificationType type, NSError* error); // Returns true if a notification of the given `type` should be sent. - bool ShouldSendNotification(TipsNotificationType type); + bool ShouldSendNotification(TipsNotificationType type, ProfileIOS* profile); // Returns true if a Default Browser notification should be sent. bool ShouldSendDefaultBrowser(); // Returns true if a Signin notification should be sent. - bool ShouldSendSignin(); + bool ShouldSendSignin(ProfileIOS* profile); // Returns true if a WhatsNew notification should be sent. - bool ShouldSendWhatsNew(); + bool ShouldSendWhatsNew(ProfileIOS* profile); // Returns true if a SetUpList continuation notification should be sent. - bool ShouldSendSetUpListContinuation(); + bool ShouldSendSetUpListContinuation(ProfileIOS* profile); // Returns true if a Docking promo notification should be sent. - bool ShouldSendDocking(); + bool ShouldSendDocking(ProfileIOS* profile); // Returns true if an Omnibox Position promo notification should be sent. bool ShouldSendOmniboxPosition(); // Returns true if a Lens promo notification should be sent. - bool ShouldSendLens(); + bool ShouldSendLens(ProfileIOS* profile); // Returns true if an Enhanced Safe Browsing promo notification should be // sent. - bool ShouldSendEnhancedSafeBrowsing(); + bool ShouldSendEnhancedSafeBrowsing(ProfileIOS* profile); // Returns `true` if there is foreground active browser. bool IsSceneLevelForegroundActive();
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm b/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm index 2e5bb917..b8abbd8 100644 --- a/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm +++ b/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm
@@ -105,9 +105,9 @@ // Returns true if the Feature Engagement Tracker has ever triggered for the // given `feature`. -bool FETHasEverTriggered(Browser* browser, const base::Feature& feature) { +bool FETHasEverTriggered(ProfileIOS* profile, const base::Feature& feature) { feature_engagement::Tracker* tracker = - feature_engagement::TrackerFactory::GetForProfile(browser->GetProfile()); + feature_engagement::TrackerFactory::GetForProfile(profile); return tracker->HasEverTriggered(feature, true); } @@ -349,8 +349,16 @@ return; } + Browser* browser = GetActiveForegroundBrowser(); + if (!browser) { + std::move(completion).Run(); + return; + } + ProfileIOS* profile = browser->GetProfile(); + if (forced_type_.has_value()) { - RequestNotification(forced_type_.value(), std::move(completion)); + RequestNotification(forced_type_.value(), profile->GetProfileName(), + std::move(completion)); return; } @@ -372,8 +380,9 @@ // This type of notification is not enabled. continue; } - if (ShouldSendNotification(type)) { - RequestNotification(type, std::move(completion)); + if (ShouldSendNotification(type, profile)) { + RequestNotification(type, profile->GetProfileName(), + std::move(completion)); return; } } @@ -388,13 +397,15 @@ } void TipsNotificationClient::RequestNotification(TipsNotificationType type, + std::string_view profile_name, base::OnceClosure completion) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (IsNotificationCollisionManagementEnabled()) { ScheduledNotificationRequest request = { kTipsNotificationId, - ContentForTipsNotificationType(type, CanSendReactivation()), + ContentForTipsNotificationType(type, CanSendReactivation(), + profile_name), TipsNotificationTriggerDelta(CanSendReactivation(), user_type_)}; CheckRateLimitBeforeSchedulingNotification( request, @@ -410,7 +421,7 @@ UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:kTipsNotificationId content:ContentForTipsNotificationType( - type, CanSendReactivation()) + type, CanSendReactivation(), profile_name) trigger:[UNTimeIntervalNotificationTrigger triggerWithTimeInterval: TipsNotificationTriggerDelta( @@ -439,25 +450,26 @@ } } -bool TipsNotificationClient::ShouldSendNotification(TipsNotificationType type) { +bool TipsNotificationClient::ShouldSendNotification(TipsNotificationType type, + ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); switch (type) { case TipsNotificationType::kDefaultBrowser: return ShouldSendDefaultBrowser(); case TipsNotificationType::kWhatsNew: - return ShouldSendWhatsNew(); + return ShouldSendWhatsNew(profile); case TipsNotificationType::kSignin: - return ShouldSendSignin(); + return ShouldSendSignin(profile); case TipsNotificationType::kSetUpListContinuation: - return ShouldSendSetUpListContinuation(); + return ShouldSendSetUpListContinuation(profile); case TipsNotificationType::kDocking: - return ShouldSendDocking(); + return ShouldSendDocking(profile); case TipsNotificationType::kOmniboxPosition: return ShouldSendOmniboxPosition(); case TipsNotificationType::kLens: - return ShouldSendLens(); + return ShouldSendLens(profile); case TipsNotificationType::kEnhancedSafeBrowsing: - return ShouldSendEnhancedSafeBrowsing(); + return ShouldSendEnhancedSafeBrowsing(profile); case TipsNotificationType::kLensOverlay: case TipsNotificationType::kCPE: case TipsNotificationType::kIncognitoLock: @@ -471,23 +483,14 @@ return !IsChromeLikelyDefaultBrowser() && !DefaultBrowserPromoCanceled(); } -bool TipsNotificationClient::ShouldSendWhatsNew() { +bool TipsNotificationClient::ShouldSendWhatsNew(ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } - return !FETHasEverTriggered(browser, + return !FETHasEverTriggered(profile, feature_engagement::kIPHWhatsNewUpdatedFeature); } -bool TipsNotificationClient::ShouldSendSignin() { +bool TipsNotificationClient::ShouldSendSignin(ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } - ProfileIOS* profile = browser->GetProfile(); AuthenticationService* auth_service = AuthenticationServiceFactory::GetForProfile(profile); @@ -495,13 +498,10 @@ !auth_service->HasPrimaryIdentity(signin::ConsentLevel::kSignin); } -bool TipsNotificationClient::ShouldSendSetUpListContinuation() { - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } +bool TipsNotificationClient::ShouldSendSetUpListContinuation( + ProfileIOS* profile) { PrefService* local_prefs = GetApplicationContext()->GetLocalState(); - PrefService* user_prefs = browser->GetProfile()->GetPrefs(); + PrefService* user_prefs = profile->GetPrefs(); if (!set_up_list_utils::IsSetUpListActive(local_prefs, user_prefs)) { return false; } @@ -516,16 +516,12 @@ return !set_up_list_prefs::AllItemsComplete(local_prefs); } -bool TipsNotificationClient::ShouldSendDocking() { +bool TipsNotificationClient::ShouldSendDocking(ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } - return !FETHasEverTriggered(browser, + return !FETHasEverTriggered(profile, feature_engagement::kIPHiOSDockingPromoFeature) && !FETHasEverTriggered( - browser, + profile, feature_engagement::kIPHiOSDockingPromoRemindMeLaterFeature); } @@ -539,15 +535,11 @@ prefs::kBottomOmnibox); } -bool TipsNotificationClient::ShouldSendLens() { +bool TipsNotificationClient::ShouldSendLens(ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Early return if Lens is not available or disabled by policy. - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } TemplateURLService* template_url_service = - ios::TemplateURLServiceFactory::GetForProfile(browser->GetProfile()); + ios::TemplateURLServiceFactory::GetForProfile(profile); bool default_search_is_google = search::DefaultSearchProviderIsGoogle(template_url_service); const bool lens_enabled = @@ -562,13 +554,10 @@ return base::Time::Now() - last_opened > kLensOpenedRecency; } -bool TipsNotificationClient::ShouldSendEnhancedSafeBrowsing() { +bool TipsNotificationClient::ShouldSendEnhancedSafeBrowsing( + ProfileIOS* profile) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - Browser* browser = GetActiveForegroundBrowser(); - if (!browser) { - return false; - } - PrefService* user_prefs = browser->GetProfile()->GetPrefs(); + PrefService* user_prefs = profile->GetPrefs(); return user_prefs->GetBoolean(prefs::kAdvancedProtectionAllowed) && !safe_browsing::IsEnhancedProtectionEnabled(*user_prefs); }
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_client_unittest.mm b/ios/chrome/browser/tips_notifications/model/tips_notification_client_unittest.mm index 3a0050c..21e8306 100644 --- a/ios/chrome/browser/tips_notifications/model/tips_notification_client_unittest.mm +++ b/ios/chrome/browser/tips_notifications/model/tips_notification_client_unittest.mm
@@ -60,6 +60,8 @@ class TipsNotificationClientTest : public PlatformTest { protected: TipsNotificationClientTest() { + [[NSUserDefaults standardUserDefaults] + removeObjectForKey:@"TipsNotificationTrigger"]; SetupMockNotificationCenter(); profile_ = profile_manager_.AddProfileWithBuilder(TestProfileIOS::Builder()); @@ -89,6 +91,10 @@ swizzle_block); } + std::string_view GetProfileName() { + return browser_->GetProfile()->GetProfileName(); + } + // Writes the first run sentinel file, to allow notifications to be // registered. void WriteFirstRunSentinel() { @@ -129,8 +135,8 @@ id MockNotification(TipsNotificationType type, bool for_reactivation) { UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:kTipsNotificationId - content:ContentForTipsNotificationType(type, - for_reactivation) + content:ContentForTipsNotificationType( + type, for_reactivation, GetProfileName()) trigger:[UNTimeIntervalNotificationTrigger triggerWithTimeInterval: TipsNotificationTriggerDelta( @@ -263,8 +269,8 @@ // Tests that HandleNotificationReception does nothing and returns "NoData". TEST_F(TipsNotificationClientTest, HandleNotificationReception) { EXPECT_EQ(client_->HandleNotificationReception(nil), std::nullopt); - NSDictionary* user_info = - UserInfoForTipsNotificationType(TipsNotificationType::kWhatsNew, false); + NSDictionary* user_info = UserInfoForTipsNotificationType( + TipsNotificationType::kWhatsNew, false, GetProfileName()); EXPECT_EQ(client_->HandleNotificationReception(user_info), UIBackgroundFetchResultNoData); }
diff --git a/ios/chrome/browser/tips_notifications/model/utils.h b/ios/chrome/browser/tips_notifications/model/utils.h index 48aea80..2939115 100644 --- a/ios/chrome/browser/tips_notifications/model/utils.h +++ b/ios/chrome/browser/tips_notifications/model/utils.h
@@ -8,6 +8,7 @@ #import <UserNotifications/UserNotifications.h> #import <optional> +#import <string_view> #import <vector> namespace base { @@ -84,15 +85,18 @@ // Returns a userInfo dictionary pre-filled with the notification `type`. NSDictionary* UserInfoForTipsNotificationType(TipsNotificationType type, - bool for_reactivation); + bool for_reactivation, + std::string_view profile_name); // Returns the notification type found in a notification's userInfo dictionary. std::optional<TipsNotificationType> ParseTipsNotificationType( UNNotificationRequest* request); // Returns the notification content for a given Tips notification type. -UNNotificationContent* ContentForTipsNotificationType(TipsNotificationType type, - bool for_reactivation); +UNNotificationContent* ContentForTipsNotificationType( + TipsNotificationType type, + bool for_reactivation, + std::string_view profile_name); // Returns the time delta used to trigger Tips notifications. base::TimeDelta TipsNotificationTriggerDelta(
diff --git a/ios/chrome/browser/tips_notifications/model/utils.mm b/ios/chrome/browser/tips_notifications/model/utils.mm index 3359b42..522f912 100644 --- a/ios/chrome/browser/tips_notifications/model/utils.mm +++ b/ios/chrome/browser/tips_notifications/model/utils.mm
@@ -6,6 +6,7 @@ #import "base/strings/string_number_conversions.h" #import "base/strings/string_split.h" +#import "base/strings/sys_string_conversions.h" #import "base/time/time.h" #import "ios/chrome/browser/push_notification/model/constants.h" #import "ios/chrome/browser/shared/public/features/features.h" @@ -148,11 +149,13 @@ } NSDictionary* UserInfoForTipsNotificationType(TipsNotificationType type, - bool for_reactivation) { + bool for_reactivation, + std::string_view profile_name) { return @{ kTipsNotificationId : @YES, kTipsNotificationTypeKey : @(static_cast<int>(type)), kReactivationKey : for_reactivation ? @YES : @NO, + kOriginatingProfileNameKey : base::SysUTF8ToNSString(profile_name), }; } @@ -166,14 +169,17 @@ return static_cast<TipsNotificationType>(type.integerValue); } -UNNotificationContent* ContentForTipsNotificationType(TipsNotificationType type, - bool for_reactivation) { +UNNotificationContent* ContentForTipsNotificationType( + TipsNotificationType type, + bool for_reactivation, + std::string_view profile_name) { UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; ContentIDs content_ids = ContentIDsForType(type); content.title = l10n_util::GetNSString(content_ids.title); content.body = l10n_util::GetNSString(content_ids.body); - content.userInfo = UserInfoForTipsNotificationType(type, for_reactivation); + content.userInfo = + UserInfoForTipsNotificationType(type, for_reactivation, profile_name); content.sound = UNNotificationSound.defaultSound; return content; }
diff --git a/ios/chrome/browser/tips_notifications/ui/BUILD.gn b/ios/chrome/browser/tips_notifications/ui/BUILD.gn index 47474df..776f35b 100644 --- a/ios/chrome/browser/tips_notifications/ui/BUILD.gn +++ b/ios/chrome/browser/tips_notifications/ui/BUILD.gn
@@ -4,6 +4,8 @@ source_set("shared") { sources = [ + "animated_promo_view_controller.h", + "animated_promo_view_controller.mm", "instructions_bottom_sheet_view_controller.h", "instructions_bottom_sheet_view_controller.mm", "tips_promo_view_controller.h", @@ -12,7 +14,9 @@ deps = [ "//base", "//ios/chrome/browser/shared/ui/bottom_sheet:bottom_sheet_view_controller", + "//ios/chrome/browser/shared/ui/util", "//ios/chrome/common/ui/colors", + "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/common/ui/instruction_view", "//ios/chrome/common/ui/promo_style", "//ios/chrome/common/ui/promo_style:utils",
diff --git a/ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h b/ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h similarity index 84% rename from ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h rename to ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h index 5eb5ef1..95aea8e 100644 --- a/ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h +++ b/ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h
@@ -2,16 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_DOCKING_PROMO_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_ -#define IOS_CHROME_BROWSER_DOCKING_PROMO_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_ +#ifndef IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_ +#define IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_ #import <UIKit/UIKit.h> #import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h" -// TODO(crbug.com/373869553): Explore a better codebase location for -// `AnimatedPromoViewController`. - // Container view controller for a full-screen, animated promo. @interface AnimatedPromoViewController : UIViewController @@ -52,4 +49,4 @@ @end -#endif // IOS_CHROME_BROWSER_DOCKING_PROMO_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_ +#endif // IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_ANIMATED_PROMO_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.mm b/ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.mm similarity index 98% rename from ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.mm rename to ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.mm index 3cd583c5..d343712 100644 --- a/ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.mm +++ b/ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.mm
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/docking_promo/ui/animated_promo_view_controller.h" +#import "ios/chrome/browser/tips_notifications/ui/animated_promo_view_controller.h" #import "base/check.h" #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm index 26853bd2..085837f 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm +++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -1562,9 +1562,12 @@ UNUserNotificationCenter* center = UNUserNotificationCenter.currentNotificationCenter; + std::string_view profileName = + chrome_test_util::GetOriginalProfile()->GetProfileName(); UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:kTipsNotificationId - content:ContentForTipsNotificationType(type, false) + content:ContentForTipsNotificationType(type, false, + profileName) trigger:nil]; [center addNotificationRequest:request withCompletionHandler:nil];
diff --git a/media/base/android/BUILD.gn b/media/base/android/BUILD.gn index e04cdcf9..db92d8e 100644 --- a/media/base/android/BUILD.gn +++ b/media/base/android/BUILD.gn
@@ -52,7 +52,6 @@ "media_drm_storage_bridge.h", "media_server_crash_listener.cc", "media_server_crash_listener.h", - "stream_texture_wrapper.h", ] configs += [ "//media:subcomponent_config" ] public_deps = [ ":media_jni_headers" ]
diff --git a/media/base/android/stream_texture_wrapper.h b/media/base/android/stream_texture_wrapper.h deleted file mode 100644 index b0a4dd5..0000000 --- a/media/base/android/stream_texture_wrapper.h +++ /dev/null
@@ -1,64 +0,0 @@ -// Copyright 2016 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_BASE_ANDROID_STREAM_TEXTURE_WRAPPER_H_ -#define MEDIA_BASE_ANDROID_STREAM_TEXTURE_WRAPPER_H_ - -#include "base/task/single_thread_task_runner.h" -#include "base/unguessable_token.h" -#include "media/base/video_frame.h" - -namespace media { - -// StreamTextureWrapper encapsulates a StreamTexture's creation, initialization -// and registration for later retrieval (in the Browser process). -class MEDIA_EXPORT StreamTextureWrapper { - public: - using StreamTextureWrapperInitCB = base::OnceCallback<void(bool)>; - - StreamTextureWrapper() {} - - // Initialize the underlying StreamTexture. - // See StreamTextureWrapperImpl. - virtual void Initialize( - const base::RepeatingClosure& received_frame_cb, - scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, - StreamTextureWrapperInitCB init_cb) = 0; - - // Called whenever the video's natural size changes. - // See StreamTextureWrapperImpl. - virtual void UpdateTextureSize(const gfx::Size& natural_size) = 0; - - // Returns the latest frame. - // See StreamTextureWrapperImpl. - virtual scoped_refptr<VideoFrame> GetCurrentFrame() = 0; - - // Sends the StreamTexture to the browser process, to fulfill the request - // identified by |request_token|. - // See StreamTextureWrapperImpl. - virtual void ForwardStreamTextureForSurfaceRequest( - const base::UnguessableToken& request_token) = 0; - - // Clears the |received_frame_cb| passed in Initialize(). - // Should be safe to call from any thread. - virtual void ClearReceivedFrameCBOnAnyThread() = 0; - - struct Deleter { - inline void operator()(StreamTextureWrapper* ptr) const { ptr->Destroy(); } - }; - - protected: - virtual ~StreamTextureWrapper() {} - - // Safely destroys the StreamTextureWrapper. - // See StreamTextureWrapperImpl. - virtual void Destroy() = 0; -}; - -typedef std::unique_ptr<StreamTextureWrapper, StreamTextureWrapper::Deleter> - ScopedStreamTextureWrapper; - -} // namespace media - -#endif // MEDIA_BASE_ANDROID_STREAM_TEXTURE_WRAPPER_H_
diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h index 9cabc57..c608918 100644 --- a/mojo/public/cpp/bindings/sync_call_restrictions.h +++ b/mojo/public/cpp/bindings/sync_call_restrictions.h
@@ -25,7 +25,6 @@ namespace content { class AndroidOverlaySyncHelper; -class StreamTextureFactory; #if BUILDFLAG(IS_WIN) class DCOMPTextureFactory; #endif @@ -136,7 +135,6 @@ friend class gpu::GpuChannelHost; friend class gpu::CommandBufferProxyImpl; friend class gpu::SharedImageInterfaceProxy; - friend class content::StreamTextureFactory; #if BUILDFLAG(IS_WIN) friend class content::DCOMPTextureFactory; #endif
diff --git a/net/base/address_family.h b/net/base/address_family.h index be4310e..476d77d3 100644 --- a/net/base/address_family.h +++ b/net/base/address_family.h
@@ -41,7 +41,8 @@ // Maps the given AddressFamily to either AF_INET, AF_INET6 or AF_UNSPEC. NET_EXPORT int ConvertAddressFamily(AddressFamily address_family); -// Maps AF_INET, AF_INET6 or AF_UNSPEC to an AddressFamily. +// Maps AF_INET, AF_INET6 or AF_UNSPEC to an AddressFamily. Any other AF_ value +// (or any other value) passed in results in NOTREACHED(). NET_EXPORT AddressFamily ToAddressFamily(int family); } // namespace net
diff --git a/net/data/ssl/chrome_root_store/faq.md b/net/data/ssl/chrome_root_store/faq.md index a1ac3e380..6b5c479 100644 --- a/net/data/ssl/chrome_root_store/faq.md +++ b/net/data/ssl/chrome_root_store/faq.md
@@ -1,5 +1,5 @@ # Frequently Asked Questions -Last updated: January 23, 2025 +Last updated: May 8, 2025 [TOC] @@ -8,18 +8,18 @@ ### What is the Chrome Root Store? Chrome uses [digital certificates](https://en.wikipedia.org/wiki/Public_key_certificate) -(often referred to as “certificates,” “HTTPS certificates,” or “server -authentication certificates”) to ensure the connections it makes on behalf +(often referred to as "certificates," "HTTPS certificates," or "server +authentication certificates") to ensure the connections it makes on behalf of its users are secure and private. Certificates bind a domain name to a public key, which Chrome uses to encrypt data sent to and from the corresponding website. As part of establishing a secure connection to a website, Chrome verifies -that a recognized system known as a “Certification Authority” (CA) issued +that a recognized system known as a "Certification Authority" (CA) issued its certificate. Certificates issued by a CA not recognized by Chrome or a -user’s local settings can cause users to see warnings and error pages. +user's local settings can cause users to see warnings and error pages. -Root stores, sometimes called “trust stores,” tell operating systems and +Root stores, sometimes called "trust stores," tell operating systems and applications what certificates to trust. The @@ -70,11 +70,11 @@ ### How can I apply for my CA's inclusion in the Chrome Root Store? CA Owners who meet the Chrome Root Program [policy](https://g.co/chrome/root-policy) requirements may apply for a CA -certificate’s inclusion in the Chrome Root Store. CAs included in the +certificate's inclusion in the Chrome Root Store. CAs included in the Chrome Root Store are expected to adhere to the Chrome Root Program policy and continue to operate in a consistent and trustworthy manner. A CA -owner’s failure to follow the requirements defined in the Chrome Root -Program policy may result in a CA certificate’s removal from the Chrome +owner's failure to follow the requirements defined in the Chrome Root +Program policy may result in a CA certificate's removal from the Chrome Root Store, limitations on Chrome's acceptance of the certificates they issue, or other technical or policy restrictions. @@ -93,14 +93,6 @@ [these](https://www.chromium.org/Home/chromium-security/reporting-security-bugs/) instructions. -### Can I revert to the platform root store and verifier? -An -[enterprise policy](https://chromeenterprise.google/policies/?policy=ChromeRootStoreEnabled) -was available for a limited time in support of troubleshooting during the -transition to the Chrome Root Store and Chrome Certificate Verifier. - -This enterprise policy is now deprecated. - ## Additional Information for Administrators, Engineers, and Power Users ### How is the Chrome Root Store updated? @@ -131,7 +123,7 @@ Existing versions of Chrome [relying](#when-did-these-features-land) on the Chrome Root Store and capable of [receiving component updates](#how-is-the-chrome-root-store-updated) typically receive updated versions of the Chrome Root Store within 24-hours of -the component’s release. Upon receiving the updated component, Chrome will rely +the component's release. Upon receiving the updated component, Chrome will rely on the contents of the updated root store. Existing versions of Chrome *not* relying on the Chrome Root Store and/or that @@ -141,7 +133,7 @@ ### How does the Chrome Certificate Verifier consider local trust decisions? For TLS connections relying on the Transmission Control Protocol (TCP), the - Chrome Certificate Verifier considers local trust decisions for both adding and +Chrome Certificate Verifier considers local trust decisions for both adding and removing trust. Learn more [here](#how-does-the-chrome-certificate-verifier-integrate-with-platform-trust-stores-for-local-trust-decisions). For TLS connections relying on the Quick UDP Internet Connections (QUIC) @@ -183,7 +175,6 @@ set to "Always Trust" or - Any certificate where the "Secure Sockets Layer (SSL)" flag is set to "Always Trust." - - Distrust: - Any certificate where the "When using this certificate" flag is set to "Never Trust" or @@ -206,11 +197,58 @@ Policy```, or ```Trusted Root Certification Authorities\Enterprise``` trust stores). -### What about client authentication certificates? -Historically, Chrome has integrated with platform certificate stores to -support the use of client authentication certificates. This behavior is -unchanged by the rollout of the Chrome Root Store and the Chrome -Certificate Verifier. +### Can I use the Chrome Root Store for my application's trust needs? + +The Chrome Root Store is optimized specifically for public TLS server +authentication in scenarios that align with the security and +[policy](https://googlechrome.github.io/chromerootprogram/) requirements of the +Chrome web browser when establishing secure connections to websites. Its design +and the Certification Authorities (CAs) included are curated to meet these +particular user needs, risk profiles, and security objectives. + +While the contents of the Chrome Root Store are publicly available, applications +with different use cases, risk profiles, or security objectives, such as +authenticating client certificates, verifying email signatures, or validating +the authenticity of signed code, may find that the Chrome Root Store isn't an +ideal fit. Relying parties should carefully consider if their specific needs and +risk tolerance mirror those of Chrome users browsing the web. For use cases +beyond securing HTTPS connections, or where a different risk assessment is +appropriate, exploring or establishing a more tailored trust solution might be +more beneficial. + +### What's the Chrome Certificate Manager? +The Chrome Certificate Manager unifies certificate management across the Chrome +platforms [relying](#when-did-these-features-land) on the Chrome Root Store, +offering a simple and common interface for modifying trust (e.g., adding +certificates, constraining certificates, or removing certificates). + +It can be accessed by navigating to ```chrome://certificate-manager``` on Chrome +134 and up. + +### Are there enterprise policies available to modify default certificate trust? +Yes. + +Chrome's certificate management policies are described [here](https://chromeenterprise.google/policies/#CertificateManagement). + +### How does Chrome support client authentication use cases? +Chrome integrates with platform certificate stores to support the use of client +authentication certificates. + +Besides ensuring they are well-formed, Chrome passes client authentication +certificates to relying servers, which then evaluate and enforce their chosen +policy. + +### Does Chrome rely on the client authentication EKU when establishing secure connections to websites? +No, Chrome does **not** depend on the id-kp-clientAuth Extended Key Usage (EKU) +contained in server certificates when establishing secure connections to +websites. + +Beginning [June 15, 2026](https://googlechrome.github.io/chromerootprogram/#322-pki-hierarchies-included-in-the-chrome-root-store), +PKI hierarchies represented by a root CA certificate included in the Chrome Root +Store are expected to only issue TLS server authentication certificates that +contain *only* the id-kp-serverAuth EKU. This expectation does not apply to +hierarchies whose corresponding root is not included in the Chrome Root Store +(i.e., "enterprise", "private", or "only-locally trusted" PKI hierarchies). ### How can I tell which certificates are trusted by the Chrome Root Store? The most recent version of the Chrome Root Store is available @@ -233,7 +271,7 @@ Chrome Root Store is described [here](/net/cert/root_store.proto). To understand a constraint applied to a root included in the Chrome Root Store, -view the certificate’s entry in [root_store.textproto](/net/data/ssl/chrome_root_store/root_store.textproto). +view the certificate's entry in [root_store.textproto](/net/data/ssl/chrome_root_store/root_store.textproto). ### What criteria does the Chrome Certificate Verifier use to evaluate certificates? The Chrome Certificate Verifier applies standard processing to include @@ -263,6 +301,5 @@ and [//chrome/browser/component_updater/](/chrome/browser/component_updater/). ### Where is the Chrome Certificate Verifier source code located? -Source locations include -[//net/cert](/net/cert), [//net/cert/internal](/net/cert/internal), and -[//net/cert/pki](/net/cert/pki). \ No newline at end of file +Source locations include [//net/cert](/net/cert), +[//net/cert/internal](/net/cert/internal), and [//third_party/boringssl/src/pki/](https://boringssl.googlesource.com/boringssl/+/refs/heads/main/pki/). \ No newline at end of file
diff --git a/remoting/host/base/loggable.cc b/remoting/host/base/loggable.cc index 4599dcfa..4a68214 100644 --- a/remoting/host/base/loggable.cc +++ b/remoting/host/base/loggable.cc
@@ -6,6 +6,7 @@ #include <memory> #include <ostream> +#include <sstream> #include <string> #include <utility> #include <vector> @@ -65,6 +66,12 @@ return base::unexpected(std::move(*this)); } +std::string Loggable::ToString() const { + std::ostringstream str; + str << *this; + return str.str(); +} + std::ostream& operator<<(std::ostream& ostream, const Loggable& loggable) { CHECK(loggable.inner_); ostream << "[" << loggable.inner_->from_here.file_name() << ":"
diff --git a/remoting/host/base/loggable.h b/remoting/host/base/loggable.h index f4475979..e8e9d9c8 100644 --- a/remoting/host/base/loggable.h +++ b/remoting/host/base/loggable.h
@@ -50,6 +50,8 @@ base::unexpected<Loggable> UnexpectedWithContext(base::Location from_here, std::string context) &&; + std::string ToString() const; + friend std::ostream& operator<<(std::ostream&, const Loggable&); private:
diff --git a/remoting/host/linux/BUILD.gn b/remoting/host/linux/BUILD.gn index 6236b58..06e04437 100644 --- a/remoting/host/linux/BUILD.gn +++ b/remoting/host/linux/BUILD.gn
@@ -145,6 +145,8 @@ "desktop_resizer_x11.h", "ei_sender_session.cc", "ei_sender_session.h", + "gnome_remote_desktop_interaction_strategy.cc", + "gnome_remote_desktop_interaction_strategy.h", "input_injector_constants_linux.h", "input_injector_linux.cc", "input_injector_x11.cc", @@ -156,8 +158,12 @@ "pipewire_capture_stream.h", "pipewire_desktop_capturer.cc", "pipewire_desktop_capturer.h", + "pipewire_mouse_cursor_monitor.cc", + "pipewire_mouse_cursor_monitor.h", ] deps = [ + ":gvariant", + ":utils", ":x11", ":x11_display_utils", "//base", @@ -171,6 +177,7 @@ "//remoting/host:platform_interfaces", "//remoting/host/base", "//remoting/host/input_monitor", + "//remoting/host/linux/dbus_interfaces:interface_headers", "//remoting/proto", "//remoting/protocol", "//third_party/webrtc_overrides:webrtc_component",
diff --git a/remoting/host/linux/dbus_interfaces/BUILD.gn b/remoting/host/linux/dbus_interfaces/BUILD.gn index 7224b8b..f6cb5e7 100644 --- a/remoting/host/linux/dbus_interfaces/BUILD.gn +++ b/remoting/host/linux/dbus_interfaces/BUILD.gn
@@ -12,6 +12,9 @@ sources = [ "org_chromium_TestInterface.h", "org_freedesktop_DBus_Properties.h", + "org_gnome_Mutter_RemoteDesktop.h", + "org_gnome_Mutter_ScreenCast.h", + "org_gnome_ScreenSaver.h", ] deps = [ "//remoting/host/linux:gvariant" ] }
diff --git a/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_RemoteDesktop.h b/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_RemoteDesktop.h new file mode 100644 index 0000000..1850a96 --- /dev/null +++ b/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_RemoteDesktop.h
@@ -0,0 +1,369 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was generated from org.gnome.Mutter.RemoteDesktop.xml + +#ifndef REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_REMOTEDESKTOP_H_ +#define REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_REMOTEDESKTOP_H_ + +#include "remoting/host/linux/gvariant_type.h" + +namespace remoting { + +namespace org_gnome_Mutter_RemoteDesktop { + +// method +struct CreateSession { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.RemoteDesktop"; + static constexpr char kMethodName[] = "CreateSession"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // session_path + ")"}; +}; + +// method +struct CreateSession_Patched { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.RemoteDesktop"; + static constexpr char kMethodName[] = "CreateSession"; + static constexpr gvariant::Type kInType{"(b)"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // session_path + ")"}; +}; + +// property +struct SupportedDeviceTypes { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.RemoteDesktop"; + static constexpr char kPropertyName[] = "SupportedDeviceTypes"; + static constexpr gvariant::Type kType{"u"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +// property +struct Version { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.RemoteDesktop"; + static constexpr char kPropertyName[] = "Version"; + static constexpr gvariant::Type kType{"i"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +} // namespace org_gnome_Mutter_RemoteDesktop + +namespace org_gnome_Mutter_RemoteDesktop_Session { + +// method +struct Start { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "Start"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct Stop { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "Stop"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyKeyboardKeycode { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyKeyboardKeycode"; + static constexpr gvariant::Type kInType{ + "(" + "u" // keycode + "b" // state + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyKeyboardKeysym { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyKeyboardKeysym"; + static constexpr gvariant::Type kInType{ + "(" + "u" // keysym + "b" // state + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyPointerButton { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyPointerButton"; + static constexpr gvariant::Type kInType{ + "(" + "i" // button + "b" // state + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyPointerAxis { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyPointerAxis"; + static constexpr gvariant::Type kInType{ + "(" + "d" // dx + "d" // dy + "u" // flags + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyPointerAxisDiscrete { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyPointerAxisDiscrete"; + static constexpr gvariant::Type kInType{ + "(" + "u" // axis + "i" // steps + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyPointerMotionRelative { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyPointerMotionRelative"; + static constexpr gvariant::Type kInType{ + "(" + "d" // dx + "d" // dy + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyPointerMotionAbsolute { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyPointerMotionAbsolute"; + static constexpr gvariant::Type kInType{ + "(" + "s" // stream + "d" // x + "d" // y + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyTouchDown { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyTouchDown"; + static constexpr gvariant::Type kInType{ + "(" + "s" // stream + "u" // slot + "d" // x + "d" // y + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyTouchMotion { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyTouchMotion"; + static constexpr gvariant::Type kInType{ + "(" + "s" // stream + "u" // slot + "d" // x + "d" // y + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct NotifyTouchUp { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "NotifyTouchUp"; + static constexpr gvariant::Type kInType{ + "(" + "u" // slot + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct EnableClipboard { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "EnableClipboard"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // options + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct DisableClipboard { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "DisableClipboard"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct SetSelection { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "SetSelection"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // options + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct SelectionWrite { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "SelectionWrite"; + static constexpr gvariant::Type kInType{ + "(" + "u" // serial + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "h" // fd + ")"}; +}; + +// method +struct SelectionWriteDone { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "SelectionWriteDone"; + static constexpr gvariant::Type kInType{ + "(" + "u" // serial + "b" // success + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct SelectionRead { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "SelectionRead"; + static constexpr gvariant::Type kInType{ + "(" + "s" // mime_type + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "h" // fd + ")"}; +}; + +// method +struct ConnectToEIS { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kMethodName[] = "ConnectToEIS"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // options + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "h" // fd + ")"}; +}; + +// property +struct SessionId { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kPropertyName[] = "SessionId"; + static constexpr gvariant::Type kType{"s"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +// property +struct CapsLockState { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kPropertyName[] = "CapsLockState"; + static constexpr gvariant::Type kType{"b"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +// property +struct NumLockState { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kPropertyName[] = "NumLockState"; + static constexpr gvariant::Type kType{"b"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +// signal +struct Closed { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kSignalName[] = "Closed"; + static constexpr gvariant::Type kType{"()"}; +}; + +// signal +struct SelectionOwnerChanged { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kSignalName[] = "SelectionOwnerChanged"; + static constexpr gvariant::Type kType{ + "(" + "a{sv}" // options + ")"}; +}; + +// signal +struct SelectionTransfer { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.RemoteDesktop.Session"; + static constexpr char kSignalName[] = "SelectionTransfer"; + static constexpr gvariant::Type kType{ + "(" + "s" // mime_type + "u" // serial + ")"}; +}; + +} // namespace org_gnome_Mutter_RemoteDesktop_Session + +} // namespace remoting + +#endif // REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_REMOTEDESKTOP_H_
diff --git a/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_ScreenCast.h b/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_ScreenCast.h new file mode 100644 index 0000000..579988f --- /dev/null +++ b/remoting/host/linux/dbus_interfaces/org_gnome_Mutter_ScreenCast.h
@@ -0,0 +1,177 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was generated from org.gnome.Mutter.ScreenCast.xml + +#ifndef REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_SCREENCAST_H_ +#define REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_SCREENCAST_H_ + +#include "remoting/host/linux/gvariant_type.h" + +namespace remoting { + +namespace org_gnome_Mutter_ScreenCast { + +// method +struct CreateSession { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast"; + static constexpr char kMethodName[] = "CreateSession"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // properties + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // session_path + ")"}; +}; + +// property +struct Version { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast"; + static constexpr char kPropertyName[] = "Version"; + static constexpr gvariant::Type kType{"i"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +} // namespace org_gnome_Mutter_ScreenCast + +namespace org_gnome_Mutter_ScreenCast_Session { + +// method +struct Start { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "Start"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct Stop { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "Stop"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct RecordMonitor { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "RecordMonitor"; + static constexpr gvariant::Type kInType{ + "(" + "s" // connector + "a{sv}" // properties + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // stream_path + ")"}; +}; + +// method +struct RecordWindow { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "RecordWindow"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // properties + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // stream_path + ")"}; +}; + +// method +struct RecordArea { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "RecordArea"; + static constexpr gvariant::Type kInType{ + "(" + "i" // x + "i" // y + "i" // width + "i" // height + "a{sv}" // properties + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // stream_path + ")"}; +}; + +// method +struct RecordVirtual { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kMethodName[] = "RecordVirtual"; + static constexpr gvariant::Type kInType{ + "(" + "a{sv}" // properties + ")"}; + static constexpr gvariant::Type kOutType{ + "(" + "o" // stream_path + ")"}; +}; + +// signal +struct Closed { + static constexpr char kInterfaceName[] = + "org.gnome.Mutter.ScreenCast.Session"; + static constexpr char kSignalName[] = "Closed"; + static constexpr gvariant::Type kType{"()"}; +}; + +} // namespace org_gnome_Mutter_ScreenCast_Session + +namespace org_gnome_Mutter_ScreenCast_Stream { + +// method +struct Start { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast.Stream"; + static constexpr char kMethodName[] = "Start"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct Stop { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast.Stream"; + static constexpr char kMethodName[] = "Stop"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// property +struct Parameters { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast.Stream"; + static constexpr char kPropertyName[] = "Parameters"; + static constexpr gvariant::Type kType{"a{sv}"}; + static constexpr bool kReadable = true; + static constexpr bool kWritable = false; +}; + +// signal +struct PipeWireStreamAdded { + static constexpr char kInterfaceName[] = "org.gnome.Mutter.ScreenCast.Stream"; + static constexpr char kSignalName[] = "PipeWireStreamAdded"; + static constexpr gvariant::Type kType{ + "(" + "u" // node_id + ")"}; +}; + +} // namespace org_gnome_Mutter_ScreenCast_Stream + +} // namespace remoting + +#endif // REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_MUTTER_SCREENCAST_H_
diff --git a/remoting/host/linux/dbus_interfaces/org_gnome_ScreenSaver.h b/remoting/host/linux/dbus_interfaces/org_gnome_ScreenSaver.h new file mode 100644 index 0000000..03c2f46 --- /dev/null +++ b/remoting/host/linux/dbus_interfaces/org_gnome_ScreenSaver.h
@@ -0,0 +1,74 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was generated from org.gnome.ScreenSaver.xml + +#ifndef REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_SCREENSAVER_H_ +#define REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_SCREENSAVER_H_ + +#include "remoting/host/linux/gvariant_type.h" + +namespace remoting::org_gnome_ScreenSaver { + +// method +struct Lock { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kMethodName[] = "Lock"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct GetActive { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kMethodName[] = "GetActive"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{ + "(" + "b" // active + ")"}; +}; + +// method +struct SetActive { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kMethodName[] = "SetActive"; + static constexpr gvariant::Type kInType{ + "(" + "b" // value + ")"}; + static constexpr gvariant::Type kOutType{"()"}; +}; + +// method +struct GetActiveTime { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kMethodName[] = "GetActiveTime"; + static constexpr gvariant::Type kInType{"()"}; + static constexpr gvariant::Type kOutType{ + "(" + "u" // value + ")"}; +}; + +// signal +struct ActiveChanged { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kSignalName[] = "ActiveChanged"; + static constexpr gvariant::Type kType{ + "(" + "b" // new_value + ")"}; +}; + +// signal +struct WakeUpScreen { + static constexpr char kInterfaceName[] = "org.gnome.ScreenSaver"; + static constexpr char kSignalName[] = "WakeUpScreen"; + static constexpr gvariant::Type kType{"()"}; +}; + +} // namespace remoting::org_gnome_ScreenSaver + +#endif // REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_GNOME_SCREENSAVER_H_
diff --git a/remoting/host/linux/gnome_remote_desktop_interaction_strategy.cc b/remoting/host/linux/gnome_remote_desktop_interaction_strategy.cc new file mode 100644 index 0000000..9f6b4720 --- /dev/null +++ b/remoting/host/linux/gnome_remote_desktop_interaction_strategy.cc
@@ -0,0 +1,620 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/host/linux/gnome_remote_desktop_interaction_strategy.h" + +#include <glib.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> + +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "base/functional/callback_forward.h" +#include "base/functional/callback_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/notimplemented.h" +#include "base/sequence_checker.h" +#include "base/strings/strcat.h" +#include "base/task/sequenced_task_runner.h" +#include "base/types/expected.h" +#include "remoting/base/logging.h" +#include "remoting/host/action_executor.h" +#include "remoting/host/active_display_monitor.h" +#include "remoting/host/audio_capturer.h" +#include "remoting/host/base/screen_resolution.h" +#include "remoting/host/desktop_display_info.h" +#include "remoting/host/desktop_display_info_loader.h" +#include "remoting/host/desktop_display_info_monitor.h" +#include "remoting/host/desktop_resizer.h" +#include "remoting/host/input_monitor/local_input_monitor.h" +#include "remoting/host/keyboard_layout_monitor.h" +#include "remoting/host/linux/dbus_interfaces/org_gnome_Mutter_RemoteDesktop.h" +#include "remoting/host/linux/dbus_interfaces/org_gnome_Mutter_ScreenCast.h" +#include "remoting/host/linux/dbus_interfaces/org_gnome_ScreenSaver.h" +#include "remoting/host/linux/ei_sender_session.h" +#include "remoting/host/linux/pipewire_desktop_capturer.h" +#include "remoting/host/linux/pipewire_mouse_cursor_monitor.h" +#include "remoting/proto/action.pb.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" +#include "third_party/webrtc/modules/portal/pipewire_utils.h" + +namespace remoting { + +namespace { + +using gvariant::Boxed; +using gvariant::BoxedRef; +using gvariant::ObjectPath; +using gvariant::ObjectPathCStr; + +constexpr char kRemoteDesktopBusName[] = "org.gnome.Mutter.RemoteDesktop"; +constexpr ObjectPathCStr kRemoteDesktopObjectPath = + "/org/gnome/Mutter/RemoteDesktop"; +constexpr char kScreenCastBusName[] = "org.gnome.Mutter.ScreenCast"; +constexpr ObjectPathCStr kScreenCastObjectPath = "/org/gnome/Mutter/ScreenCast"; + +const ScreenResolution kInitialResolution{{1280, 960}, {96, 96}}; + +template <typename Ret, typename Success, typename Error> +base::OnceCallback<Ret(base::expected<Success, Error>)> MakeExpectedCallback( + base::OnceCallback<Ret(Success)> success, + base::OnceCallback<Ret(Error)> error) { + return base::BindOnce( + [](base::OnceCallback<Ret(Success)> success, + base::OnceCallback<Ret(Error)> error, + base::expected<Success, Error> result) { + if (result.has_value()) { + return std::move(success).Run(result.value()); + } else { + return std::move(error).Run(result.error()); + } + }, + std::move(success), std::move(error)); +} + +} // namespace + +GnomeRemoteDesktopInteractionStrategy:: + ~GnomeRemoteDesktopInteractionStrategy() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (session_path_ != ObjectPath()) { + connection_.Call<org_gnome_Mutter_RemoteDesktop_Session::Stop>( + kRemoteDesktopBusName, session_path_, std::tuple(), + GDBusConnectionRef::CallCallback<std::tuple<>>(base::DoNothing())); + } +} + +std::unique_ptr<ActionExecutor> +GnomeRemoteDesktopInteractionStrategy::CreateActionExecutor() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + class GnomeActionExecutor : public ActionExecutor { + public: + explicit GnomeActionExecutor(GDBusConnectionRef connection) + : connection_(std::move(connection)) {} + ~GnomeActionExecutor() override = default; + void ExecuteAction(const protocol::ActionRequest& request) override { + switch (request.action()) { + case protocol::ActionRequest::LOCK_WORKSTATION: + connection_.Call<org_gnome_ScreenSaver::Lock>( + "org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", std::tuple(), + base::BindOnce([](base::expected<std::tuple<>, Loggable> result) { + if (!result.has_value()) { + LOG(WARNING) << "Failed to lock screen: " << result.error(); + } + })); + break; + default: + break; + } + } + + private: + GDBusConnectionRef connection_; + }; + + return std::make_unique<GnomeActionExecutor>(connection_); +} + +std::unique_ptr<AudioCapturer> +GnomeRemoteDesktopInteractionStrategy::CreateAudioCapturer() { + // TODO(jamiewalch): Support both pipe and session capture. + return AudioCapturer::Create(); +} + +std::unique_ptr<InputInjector> +GnomeRemoteDesktopInteractionStrategy::CreateInputInjector() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + class GnomeInputInjector : public InputInjector { + public: + explicit GnomeInputInjector( + base::WeakPtr<GnomeRemoteDesktopInteractionStrategy> session) + : session_(std::move(session)) {} + ~GnomeInputInjector() override = default; + + // InputInjector implementation + void Start( + std::unique_ptr<protocol::ClipboardStub> client_clipboard) override {} + + // InputStub implementation + void InjectKeyEvent(const protocol::KeyEvent& event) override { + if (!session_) { + return; + } + session_->InjectKeyEvent(event); + } + void InjectTextEvent(const protocol::TextEvent& event) override { + NOTIMPLEMENTED(); + } + void InjectMouseEvent(const protocol::MouseEvent& event) override { + if (!session_) { + return; + } + session_->InjectMouseEvent(event); + } + void InjectTouchEvent(const protocol::TouchEvent& event) override { + NOTIMPLEMENTED(); + } + + // ClipboardStub implementation + void InjectClipboardEvent(const protocol::ClipboardEvent& event) override { + NOTIMPLEMENTED(); + } + + private: + base::WeakPtr<GnomeRemoteDesktopInteractionStrategy> session_; + }; + return std::make_unique<GnomeInputInjector>(weak_ptr_factory_.GetWeakPtr()); +} + +std::unique_ptr<DesktopResizer> +GnomeRemoteDesktopInteractionStrategy::CreateDesktopResizer() { + // TODO(jamiewalch): Actually implement. + class GnomeDesktopResizer : public DesktopResizer { + public: + explicit GnomeDesktopResizer( + base::WeakPtr<GnomeRemoteDesktopInteractionStrategy> session) + : session_(std::move(session)) {} + ~GnomeDesktopResizer() override = default; + ScreenResolution GetCurrentResolution(webrtc::ScreenId screen_id) override { + // TODO(jamiewalch): Expose resolution from SharedScreencastStream + return {}; + } + std::list<ScreenResolution> GetSupportedResolutions( + const ScreenResolution& preferred, + webrtc::ScreenId screen_id) override { + return {preferred}; + } + + void SetResolution(const ScreenResolution& resolution, + webrtc::ScreenId screen_id) override { + if (!session_) { + return; + } + DCHECK_CALLED_ON_VALID_SEQUENCE(session_->sequence_checker_); + session_->capture_stream_.SetResolution(resolution); + } + + void RestoreResolution(const ScreenResolution& original, + webrtc::ScreenId screen_id) override {} + void SetVideoLayout(const protocol::VideoLayout& layout) override {} + + private: + base::WeakPtr<GnomeRemoteDesktopInteractionStrategy> session_; + }; + return std::make_unique<GnomeDesktopResizer>(weak_ptr_factory_.GetWeakPtr()); +} + +std::unique_ptr<DesktopCapturer> +GnomeRemoteDesktopInteractionStrategy::CreateVideoCapturer( + webrtc::ScreenId id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return std::make_unique<PipewireDesktopCapturer>( + capture_stream_.GetWeakPtr()); +} +std::unique_ptr<webrtc::MouseCursorMonitor> +GnomeRemoteDesktopInteractionStrategy::CreateMouseCursorMonitor() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return std::make_unique<PipewireMouseCursorMonitor>( + capture_stream_.GetWeakPtr()); +} + +std::unique_ptr<KeyboardLayoutMonitor> +GnomeRemoteDesktopInteractionStrategy::CreateKeyboardLayoutMonitor( + base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) { + // TODO(jamiewalch): Implement + class GnomeKeyboardLayoutMonitor : public KeyboardLayoutMonitor { + public: + ~GnomeKeyboardLayoutMonitor() override = default; + void Start() override {} + }; + return std::make_unique<GnomeKeyboardLayoutMonitor>(); +} + +std::unique_ptr<ActiveDisplayMonitor> +GnomeRemoteDesktopInteractionStrategy::CreateActiveDisplayMonitor( + base::RepeatingCallback<void(webrtc::ScreenId)> callback) { + // TODO(jamiewalch): Implement + class GnomeActiveDisplayMonitor : public ActiveDisplayMonitor { + public: + ~GnomeActiveDisplayMonitor() override = default; + }; + return std::make_unique<GnomeActiveDisplayMonitor>(); +} + +std::unique_ptr<DesktopDisplayInfoMonitor> +GnomeRemoteDesktopInteractionStrategy::CreateDisplayInfoMonitor() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // TODO(jamiewalch): Implement + class GnomeDisplayInfoLoader : public DesktopDisplayInfoLoader { + public: + DesktopDisplayInfo GetCurrentDisplayInfo() override { + DesktopDisplayInfo info; + // TODO(jamiewalch): + info.AddDisplay( + DisplayGeometry{0, 0, 0, 1280, 960, 96, 24, true, "Default display"}); + return info; + } + }; + return std::make_unique<DesktopDisplayInfoMonitor>( + ui_task_runner_, std::make_unique<GnomeDisplayInfoLoader>()); +} + +std::unique_ptr<LocalInputMonitor> +GnomeRemoteDesktopInteractionStrategy::CreateLocalInputMonitor() { + // TODO(jamiewalch): Implement + class GnomeLocalInputMonitor : public LocalInputMonitor { + public: + void StartMonitoringForClientSession( + base::WeakPtr<ClientSessionControl> client_session_control) override {} + void StartMonitoring(PointerMoveCallback on_pointer_input, + KeyPressedCallback on_keyboard_input, + base::RepeatingClosure on_error) override {} + }; + return std::make_unique<GnomeLocalInputMonitor>(); +} + +GnomeRemoteDesktopInteractionStrategy::GnomeRemoteDesktopInteractionStrategy( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner) + : ui_task_runner_(std::move(ui_task_runner)), weak_ptr_factory_(this) {} + +template <typename SuccessType, typename String> +GDBusConnectionRef::CallCallback<SuccessType> +GnomeRemoteDesktopInteractionStrategy::CheckResultAndContinue( + void (GnomeRemoteDesktopInteractionStrategy::*success_method)(SuccessType), + String&& error_context) { + // Unretained is sound because callback owns this. + return base::BindOnce( + [](GnomeRemoteDesktopInteractionStrategy* that, + decltype(success_method) success_method, + std::string_view error_context, + base::expected<SuccessType, Loggable> result) { + if (result.has_value()) { + (that->*success_method)(std::move(result).value()); + } else { + that->OnInitError(error_context, std::move(result).error()); + } + }, + base::Unretained(this), success_method, + std::forward<String>(error_context)); +} + +void GnomeRemoteDesktopInteractionStrategy::OnInitError( + std::string_view error_message, + Loggable error_context) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + std::move(init_callback_) + .Run(base::unexpected( + base::StrCat({error_message, ": ", error_context.ToString()}))); +} + +void GnomeRemoteDesktopInteractionStrategy::Init( + base::OnceCallback<void(base::expected<void, std::string>)> callback) { + HOST_LOG << "Starting Mutter remote desktop session"; + DCHECK(!init_callback_); + init_callback_ = std::move(callback); + GDBusConnectionRef::CreateForSessionBus(CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnConnectionCreated, + "Failed to connect to D-Bus session bus")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnConnectionCreated( + GDBusConnectionRef connection) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + connection_ = std::move(connection); + + // One of the gLinux patches modifies the method signature of CreateSession. + // To ease the transition, try the patched signature if the upstream signature + // fails. + auto call_patched_if_failed = + [](GnomeRemoteDesktopInteractionStrategy* that, + base::expected<std::tuple<ObjectPath>, Loggable> result) { + DCHECK_CALLED_ON_VALID_SEQUENCE(that->sequence_checker_); + if (!result.has_value()) { + that->connection_.Call< + org_gnome_Mutter_RemoteDesktop::CreateSession_Patched>( + kRemoteDesktopBusName, kRemoteDesktopObjectPath, std::tuple(true), + base::BindOnce( + [](Loggable previous_error, + base::expected<std::tuple<ObjectPath>, Loggable> result) { + return result.transform_error([&previous_error]( + auto new_error) { + // If both fail, include the first as context for the + // second. + Loggable result(new_error); + result.AddContext(FROM_HERE, previous_error.ToString()); + return result; + }); + }, + std::move(result).error()) + .Then(that->CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnSessionCreated, + "Failed to create remote-desktop session"))); + } else { + that->OnSessionCreated(std::move(result).value()); + } + }; + + connection_.Call<org_gnome_Mutter_RemoteDesktop::CreateSession>( + kRemoteDesktopBusName, kRemoteDesktopObjectPath, std::tuple(), + base::BindOnce(call_patched_if_failed, base::Unretained(this))); +} + +void GnomeRemoteDesktopInteractionStrategy::OnSessionCreated( + std::tuple<gvariant::ObjectPath> args) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::tie(session_path_) = args; + // TODO(jamiewalch): Listen for Closed event. + + connection_.GetProperty<org_gnome_Mutter_RemoteDesktop_Session::SessionId>( + kRemoteDesktopBusName, session_path_, + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnGotSessionId, + "Failed to get session ID")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnGotSessionId( + std::string session_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + connection_.Call<org_gnome_Mutter_ScreenCast::CreateSession>( + kScreenCastBusName, kScreenCastObjectPath, + std::tuple(std::array{ + std::pair{"remote-desktop-session-id", + GVariantFrom(BoxedRef(session_id))}, + std::pair{"disable-animations", GVariantFrom(Boxed{true})}}), + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnScreenCastSessionCreated, + "Failed to create screen-cast session")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnScreenCastSessionCreated( + std::tuple<gvariant::ObjectPath> args) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::tie(screencast_session_path_) = args; + + connection_.Call<org_gnome_Mutter_RemoteDesktop_Session::Start>( + kRemoteDesktopBusName, session_path_, std::tuple(), + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnSessionStarted, + "Failed to start remote-desktop session")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnSessionStarted(std::tuple<>) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + connection_.Call<org_gnome_Mutter_RemoteDesktop_Session::ConnectToEIS>( + kRemoteDesktopBusName, session_path_, + std::tuple(gvariant::EmptyArrayOf<"{sv}">()), + CheckResultAndContinue(&GnomeRemoteDesktopInteractionStrategy::OnEisFd, + "Failed to get EIS FD")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnEisFd( + std::pair<std::tuple<GDBusFdList::Handle>, GDBusFdList> args) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto fd_list = std::move(args.second).MakeSparse(); + auto [handle] = args.first; + auto eis_fd = fd_list.Extract(handle); + if (!eis_fd.is_valid()) { + OnInitError("Failed to get EIS FD", + Loggable(FROM_HERE, "Handle not present in FD list")); + return; + } + EiSenderSession::CreateWithFd( + std::move(eis_fd), + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnEiSession, + "Failed to create EI session")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnEiSession( + std::unique_ptr<EiSenderSession> ei_session) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ei_session_ = std::move(ei_session); + + // Include the cursor in the Pipewire stream metadata. + constexpr std::uint32_t kCursorModeMetadata = 2; + + connection_.Call<org_gnome_Mutter_ScreenCast_Session::RecordVirtual>( + kScreenCastBusName, screencast_session_path_, + std::tuple{std::array{ + std::pair{"cursor-mode", GVariantFrom(Boxed{kCursorModeMetadata})}, + std::pair{"is-platform", GVariantFrom(Boxed{true})}}}, + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnStreamCreated, + "Failed to record virtual monitor")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnStreamCreated( + std::tuple<gvariant::ObjectPath> args) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + HOST_LOG << "Starting initial monitor stream"; + std::tie(stream_path_) = args; + + connection_.GetProperty<org_gnome_Mutter_ScreenCast_Stream::Parameters>( + kScreenCastBusName, stream_path_, + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnStreamParameters, + "Failed to retrieve stream parameters")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnStreamParameters( + GVariantRef<"a{sv}"> parameters) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + gchar* param_str = g_variant_print(parameters.raw(), true); + HOST_LOG << "Stream parameters: " << param_str; + g_free(param_str); + + auto maybe_boxed_mapping_id = parameters.LookUp("mapping-id"); + if (!maybe_boxed_mapping_id.has_value()) { + std::move(init_callback_) + .Run(base::unexpected("mapping-id stream parameter not present")); + return; + } + std::string mapping_id; + auto destructure_result = maybe_boxed_mapping_id->TryDestructure(mapping_id); + if (!destructure_result.has_value()) { + std::move(init_callback_) + .Run(base::unexpected( + base::StrCat({" Failed to retrieve mapping-id stream parameter: ", + destructure_result.error().ToString()}))); + return; + } + // Note that both OnStreamStarted and OnPipeWireStreamAdded may invoke + // init_callback_, but the former only does so on error and the latter + // unsubscribes from the signal, meaning that it is guaranteed only to + // be called once. + stream_added_signal_ = connection_.SignalSubscribe< + org_gnome_Mutter_ScreenCast_Stream::PipeWireStreamAdded>( + kScreenCastBusName, stream_path_, + base::BindRepeating( + &GnomeRemoteDesktopInteractionStrategy::OnPipeWireStreamAdded, + weak_ptr_factory_.GetWeakPtr(), std::move(mapping_id))); + connection_.Call<org_gnome_Mutter_ScreenCast_Stream::Start>( + kScreenCastBusName, stream_path_, std::tuple(), + CheckResultAndContinue( + &GnomeRemoteDesktopInteractionStrategy::OnStreamStarted, + "Failed to start monitor stream")); +} + +void GnomeRemoteDesktopInteractionStrategy::OnStreamStarted(std::tuple<> args) { + // Do nothing. Still need to wait for PipeWire-stream-added signal. +} + +void GnomeRemoteDesktopInteractionStrategy::OnPipeWireStreamAdded( + std::string mapping_id, + std::tuple<std::uint32_t> args) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Ensure method is only run this once. + stream_added_signal_.release(); + + capture_stream_.SetPipeWireStream(get<0>(args), kInitialResolution, + mapping_id, webrtc::kInvalidPipeWireFd); + + std::move(init_callback_).Run(base::ok()); +} + +void GnomeRemoteDesktopInteractionStrategy::InjectKeyEvent( + const protocol::KeyEvent& event) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!event.has_usb_keycode() || !event.has_pressed()) { + LOG(WARNING) << "Key event with no key info"; + return; + } + ei_session_->InjectKeyEvent(event.usb_keycode(), event.pressed()); +} + +void GnomeRemoteDesktopInteractionStrategy::InjectMouseEvent( + const protocol::MouseEvent& event) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + bool event_sent = false; + if (event.has_fractional_coordinate() && + event.fractional_coordinate().has_x() && + event.fractional_coordinate().has_y()) { + ei_session_->InjectAbsolutePointerMove(capture_stream_.mapping_id(), + event.fractional_coordinate().x(), + event.fractional_coordinate().y()); + event_sent = true; + + } else if (event.has_delta_x() || event.has_delta_y()) { + ei_session_->InjectRelativePointerMove( + event.has_delta_x() ? event.delta_x() : 0, + event.has_delta_y() ? event.delta_y() : 0); + event_sent = true; + } + + if (event.has_button() && event.has_button_down()) { + ei_session_->InjectButton(event.button(), event.button_down()); + event_sent = true; + } + + if (event.has_wheel_delta_x() || event.has_wheel_delta_y()) { + ei_session_->InjectScrollDelta( + event.has_wheel_delta_x() ? event.wheel_delta_x() : 0, + event.has_wheel_delta_y() ? event.wheel_delta_y() : 0); + event_sent = true; + } else if (event.has_wheel_ticks_x() || event.has_wheel_ticks_y()) { + ei_session_->InjectScrollDiscrete( + event.has_wheel_ticks_x() ? event.wheel_ticks_x() : 0, + event.has_wheel_ticks_y() ? event.wheel_ticks_y() : 0); + event_sent = true; + } + + if (event_sent) { + } else { + LOG(WARNING) << "Mouse event with no relevant fields"; + } +} + +GnomeRemoteDesktopInteractionStrategyFactory:: + GnomeRemoteDesktopInteractionStrategyFactory( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner) + : ui_task_runner_(std::move(ui_task_runner)) {} + +GnomeRemoteDesktopInteractionStrategyFactory:: + ~GnomeRemoteDesktopInteractionStrategyFactory() = default; + +void GnomeRemoteDesktopInteractionStrategyFactory::Create( + const DesktopEnvironmentOptions& options, + CreateCallback callback) { + auto session = base::WrapUnique( + new GnomeRemoteDesktopInteractionStrategy(ui_task_runner_)); + auto* raw = session.get(); + raw->Init(base::BindOnce( + [](std::unique_ptr<GnomeRemoteDesktopInteractionStrategy> session, + CreateCallback callback, base::expected<void, std::string> result) { + if (!result.has_value()) { + LOG(ERROR) << result.error(); + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run(std::move(session)); + }, + std::move(session), std::move(callback))); +} + +void GnomeRemoteDesktopInteractionStrategyFactory::OnSessionInit( + std::unique_ptr<GnomeRemoteDesktopInteractionStrategy> session, + CreateCallback callback, + base::expected<void, std::string> result) { + if (!result.has_value()) { + LOG(ERROR) << "Failed to initialize Gnome Remote Desktop session: " + << result.error(); + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run(std::move(session)); +} + +} // namespace remoting
diff --git a/remoting/host/linux/gnome_remote_desktop_interaction_strategy.h b/remoting/host/linux/gnome_remote_desktop_interaction_strategy.h new file mode 100644 index 0000000..64b7cc73 --- /dev/null +++ b/remoting/host/linux/gnome_remote_desktop_interaction_strategy.h
@@ -0,0 +1,127 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_HOST_LINUX_GNOME_REMOTE_DESKTOP_INTERACTION_STRATEGY_H_ +#define REMOTING_HOST_LINUX_GNOME_REMOTE_DESKTOP_INTERACTION_STRATEGY_H_ + +#include <memory> +#include <string> +#include <tuple> + +#include "base/functional/callback.h" +#include "base/functional/callback_forward.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "base/task/sequenced_task_runner.h" +#include "base/types/expected.h" +#include "remoting/host/base/loggable.h" +#include "remoting/host/desktop_interaction_strategy.h" +#include "remoting/host/linux/ei_sender_session.h" +#include "remoting/host/linux/gdbus_connection_ref.h" +#include "remoting/host/linux/gdbus_fd_list.h" +#include "remoting/host/linux/gvariant_ref.h" +#include "remoting/host/linux/pipewire_capture_stream.h" + +namespace remoting { + +class GnomeRemoteDesktopInteractionStrategy + : public DesktopInteractionStrategy { + public: + GnomeRemoteDesktopInteractionStrategy( + const GnomeRemoteDesktopInteractionStrategy&) = delete; + GnomeRemoteDesktopInteractionStrategy& operator=( + const GnomeRemoteDesktopInteractionStrategy&) = delete; + ~GnomeRemoteDesktopInteractionStrategy() override; + + // Correspond to the equivalent methods on DesktopEnvironment. + std::unique_ptr<ActionExecutor> CreateActionExecutor() override; + std::unique_ptr<AudioCapturer> CreateAudioCapturer() override; + std::unique_ptr<InputInjector> CreateInputInjector() override; + std::unique_ptr<DesktopResizer> CreateDesktopResizer() override; + std::unique_ptr<DesktopCapturer> CreateVideoCapturer( + webrtc::ScreenId id) override; + std::unique_ptr<webrtc::MouseCursorMonitor> CreateMouseCursorMonitor() + override; + std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor( + base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) + override; + std::unique_ptr<ActiveDisplayMonitor> CreateActiveDisplayMonitor( + base::RepeatingCallback<void(webrtc::ScreenId)> callback) override; + + // Used by DesktopEnvironment implementations. + std::unique_ptr<DesktopDisplayInfoMonitor> CreateDisplayInfoMonitor() + override; + std::unique_ptr<LocalInputMonitor> CreateLocalInputMonitor() override; + + private: + friend class GnomeRemoteDesktopInteractionStrategyFactory; + friend class GnomeInputInjector; + + using InitCallback = + base::OnceCallback<void(base::expected<void, std::string>)>; + explicit GnomeRemoteDesktopInteractionStrategy( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner); + template <typename SuccessType, typename String> + GDBusConnectionRef::CallCallback<SuccessType> CheckResultAndContinue( + void (GnomeRemoteDesktopInteractionStrategy::*success_method)( + SuccessType), + String&& error_context); + void OnInitError(std::string_view what, Loggable why); + void Init(InitCallback callback); + void OnConnectionCreated(GDBusConnectionRef connection); + void OnSessionCreated(std::tuple<gvariant::ObjectPath> args); + void OnGotSessionId(std::string session_id); + void OnScreenCastSessionCreated(std::tuple<gvariant::ObjectPath> args); + void OnSessionStarted(std::tuple<>); + void OnEisFd(std::pair<std::tuple<GDBusFdList::Handle>, GDBusFdList> args); + void OnEiSession(std::unique_ptr<EiSenderSession> ei_session); + void OnStreamCreated(std::tuple<gvariant::ObjectPath> args); + void OnStreamParameters(GVariantRef<"a{sv}"> parameters); + void OnStreamStarted(std::tuple<> args); + void OnPipeWireStreamAdded(std::string mapping_id, + std::tuple<std::uint32_t> args); + + void InjectKeyEvent(const protocol::KeyEvent& event); + void InjectTextEvent(const protocol::TextEvent& event); + void InjectMouseEvent(const protocol::MouseEvent& event); + + GDBusConnectionRef connection_ GUARDED_BY_CONTEXT(sequence_checker_); + InitCallback init_callback_; + gvariant::ObjectPath session_path_ GUARDED_BY_CONTEXT(sequence_checker_); + gvariant::ObjectPath screencast_session_path_ + GUARDED_BY_CONTEXT(sequence_checker_); + std::unique_ptr<EiSenderSession> ei_session_ + GUARDED_BY_CONTEXT(sequence_checker_); + gvariant::ObjectPath stream_path_ GUARDED_BY_CONTEXT(sequence_checker_); + std::unique_ptr<GDBusConnectionRef::SignalSubscription> stream_added_signal_ + GUARDED_BY_CONTEXT(sequence_checker_); + PipewireCaptureStream capture_stream_ GUARDED_BY_CONTEXT(sequence_checker_); + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_ + GUARDED_BY_CONTEXT(sequence_checker_); + + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<GnomeRemoteDesktopInteractionStrategy> weak_ptr_factory_; +}; + +class GnomeRemoteDesktopInteractionStrategyFactory + : public DesktopInteractionStrategyFactory { + public: + explicit GnomeRemoteDesktopInteractionStrategyFactory( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner); + ~GnomeRemoteDesktopInteractionStrategyFactory() override; + void Create(const DesktopEnvironmentOptions& options, + CreateCallback callback) override; + + private: + static void OnSessionInit( + std::unique_ptr<GnomeRemoteDesktopInteractionStrategy> session, + base::OnceCallback<void(std::unique_ptr<DesktopInteractionStrategy>)> + callback, + base::expected<void, std::string> result); + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_LINUX_GNOME_REMOTE_DESKTOP_INTERACTION_STRATEGY_H_
diff --git a/remoting/host/linux/pipewire_mouse_cursor_monitor.cc b/remoting/host/linux/pipewire_mouse_cursor_monitor.cc new file mode 100644 index 0000000..d06f421 --- /dev/null +++ b/remoting/host/linux/pipewire_mouse_cursor_monitor.cc
@@ -0,0 +1,51 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/host/linux/pipewire_mouse_cursor_monitor.h" + +#include <memory> +#include <optional> + +#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" +#include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h" +#include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +namespace remoting { + +PipewireMouseCursorMonitor::PipewireMouseCursorMonitor( + base::WeakPtr<PipewireCaptureStream> stream) + : stream_(std::move(stream)) {} + +PipewireMouseCursorMonitor::~PipewireMouseCursorMonitor() = default; + +void PipewireMouseCursorMonitor::Init(Callback* callback, Mode mode) { + callback_ = callback; + report_position_ = mode == SHAPE_AND_POSITION; +} + +void PipewireMouseCursorMonitor::Capture() { + if (!stream_) { + return; + } + + std::optional<webrtc::DesktopVector> mouse_cursor_position = + stream_->CaptureCursorPosition(); + // Invalid cursor or position + if (!mouse_cursor_position.has_value()) { + callback_->OnMouseCursor(nullptr); + return; + } + + std::unique_ptr<webrtc::MouseCursor> mouse_cursor = stream_->CaptureCursor(); + + if (mouse_cursor && mouse_cursor->image()->data()) { + callback_->OnMouseCursor(mouse_cursor.release()); + } + + if (report_position_) { + callback_->OnMouseCursorPosition(*mouse_cursor_position); + } +} + +} // namespace remoting
diff --git a/remoting/host/linux/pipewire_mouse_cursor_monitor.h b/remoting/host/linux/pipewire_mouse_cursor_monitor.h new file mode 100644 index 0000000..257a3dd --- /dev/null +++ b/remoting/host/linux/pipewire_mouse_cursor_monitor.h
@@ -0,0 +1,32 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_HOST_LINUX_PIPEWIRE_MOUSE_CURSOR_MONITOR_H_ +#define REMOTING_HOST_LINUX_PIPEWIRE_MOUSE_CURSOR_MONITOR_H_ + +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "remoting/host/linux/pipewire_capture_stream.h" +#include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +namespace remoting { + +class PipewireMouseCursorMonitor : public webrtc::MouseCursorMonitor { + public: + explicit PipewireMouseCursorMonitor( + base::WeakPtr<PipewireCaptureStream> stream); + ~PipewireMouseCursorMonitor() override; + // MouseCursorMonitor implementation. + void Init(Callback* callback, Mode mode) override; + void Capture() override; + + private: + raw_ptr<Callback> callback_; + bool report_position_; + base::WeakPtr<PipewireCaptureStream> stream_; +}; + +} // namespace remoting + +#endif // REMOTING_HOST_LINUX_PIPEWIRE_MOUSE_CURSOR_MONITOR_H_
diff --git a/remoting/host/setup/start_host_as_root.cc b/remoting/host/setup/start_host_as_root.cc index 59ed6ca..8610ffaf 100644 --- a/remoting/host/setup/start_host_as_root.cc +++ b/remoting/host/setup/start_host_as_root.cc
@@ -16,6 +16,11 @@ #include <sys/wait.h> #include <unistd.h> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + #include "base/check.h" #include "base/command_line.h" #include "base/containers/contains.h" @@ -26,7 +31,12 @@ namespace remoting { +namespace { + constexpr char kChromotingGroupName[] = "chrome-remote-desktop"; +constexpr std::string_view kUserNameSwitch = "user-name"; +constexpr std::string_view kCorpUserSwitch = "corp-user"; +constexpr std::string_view kCloudUserSwitch = "cloud-user"; void PrintGroupMembershipError(const char* user_name) { fprintf(stderr, @@ -74,53 +84,79 @@ return true; } -int StartHostAsRoot(int argc, char** argv) { - DCHECK(getuid() == 0); +bool ValidateCommandLine(const base::CommandLine& command_line) { + bool has_username = command_line.HasSwitch(kUserNameSwitch); + bool has_corp_user = command_line.HasSwitch(kCorpUserSwitch); + bool has_cloud_user = command_line.HasSwitch(kCloudUserSwitch); - base::CommandLine command_line(argc, argv); + if (!has_username && !has_corp_user && !has_cloud_user) { + fprintf(stderr, + "At least one of the following args must be provided:\n" + " --user-name=<username>\n" + " --corp-user=<username>\n" + " --cloud-user=<email>\n"); + return false; + } else if (has_corp_user && has_cloud_user) { + fprintf( + stderr, + "'--corp-user' and '--cloud-user' flags cannot be used together.\n"); + return false; + } else if (has_cloud_user) { + std::string arg_value = command_line.GetSwitchValueASCII(kCloudUserSwitch); + if (!base::SplitStringOnce(arg_value, '@')) { + fprintf(stderr, "The --cloud-user flag requires an email address.\n"); + return false; + } + } + + return true; +} + +std::string ExtractUsernameFromCommandLine( + const base::CommandLine& command_line) { + // The 'user-name' flag contains the local account to run the host as. + // If '--user-name' is not provided, then we will try to extract it from + // either '--cloud-user' or '--corp-user'. + // Note, it is expected that the switch contents have been validated via + // ValidateCommandLine() before calling this function. std::string user_name; - if (command_line.HasSwitch("corp-user")) { + if (command_line.HasSwitch(kUserNameSwitch)) { + user_name = command_line.GetSwitchValueASCII(kUserNameSwitch); + } else if (command_line.HasSwitch(kCorpUserSwitch)) { // For compat reasons, we support either email or username for this param. - // TODO: joedow - Remove support for the email param around M135 or so. - std::string arg_value = command_line.GetSwitchValueASCII("corp-user"); + // TODO: joedow - Remove support for the email param in M139. + std::string arg_value = command_line.GetSwitchValueASCII(kCorpUserSwitch); auto parts = base::SplitStringOnce(arg_value, '@'); if (!parts) { user_name = std::move(arg_value); } else { user_name = std::string(parts->first); } - } else if (command_line.HasSwitch("cloud-user")) { - std::string arg_value = command_line.GetSwitchValueASCII("cloud-user"); - auto parts = base::SplitStringOnce(arg_value, '@'); - if (parts) { - user_name = std::string(parts->first); - } else { - fprintf(stderr, "The --cloud-user flag requires an email address.\n"); - return 1; - } - // The 'user-name' flag can be used to override the username portion of the - // value provided via the 'cloud-user' flag for Cloud hosts. Note that this - // is not allowed for Corp hosts. - if (command_line.HasSwitch("user-name")) { - std::string user_name_switch_value = - command_line.GetSwitchValueASCII("user-name"); - if (user_name == user_name_switch_value) { - fprintf(stderr, - "The --user-name flag is not required when the value matches " - "the username portion of the email provided via the cloud-user " - "flag.\n"); - } - user_name = user_name_switch_value; - } - } else if (command_line.HasSwitch("user-name")) { - user_name = command_line.GetSwitchValueASCII("user-name"); + } else if (command_line.HasSwitch(kCloudUserSwitch)) { + std::string arg_value = command_line.GetSwitchValueASCII(kCloudUserSwitch); + user_name = base::SplitStringOnce(arg_value, '@')->first; } + return user_name; +} + +} // namespace + +int StartHostAsRoot(int argc, char** argv) { + DCHECK(getuid() == 0); + + base::CommandLine command_line(argc, argv); + if (!ValidateCommandLine(command_line)) { + return 1; + } + std::string user_name = ExtractUsernameFromCommandLine(command_line); if (user_name.empty()) { fprintf(stderr, - "Must specify one of the following arguments when running as root:" - "\n --user-name=<username>\n --corp-user=<username>" - "\n --cloud-user=<email>\n"); + "A username and/or email must be provided from one of the " + "following switches:\n" + " --user-name=<username>\n" + " --corp-user=<username>\n" + " --cloud-user=<email>\n"); return 1; } fprintf(stdout, "Configuring the host service to run as local account: %s\n", @@ -161,7 +197,7 @@ } int return_value = 1; - command_line.RemoveSwitch("user-name"); + command_line.RemoveSwitch(kUserNameSwitch); command_line.RemoveSwitch("sysvinit"); command_line.AppendSwitch("no-start"); std::vector<std::string> create_config_command_line{
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc index bcb0e28..c4484bd 100644 --- a/services/network/network_context_unittest.cc +++ b/services/network/network_context_unittest.cc
@@ -62,6 +62,7 @@ #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "mojo/public/cpp/system/data_pipe_utils.h" #include "mojo/public/cpp/system/functions.h" +#include "net/base/address_family.h" #include "net/base/cache_type.h" #include "net/base/features.h" #include "net/base/hash_value.h" @@ -2048,7 +2049,7 @@ base::RunLoop run_loop; std::vector<net::IPAddress> results; socket_manager->GetHostAddress( - kHostname, false /* enable_mdns */, + kHostname, std::nullopt /* address family */, false /* enable_mdns */, base::BindLambdaForTesting( [&](const std::vector<net::IPAddress>& addresses) { EXPECT_EQ(std::vector<net::IPAddress>{ip_address}, addresses); @@ -2135,8 +2136,9 @@ base::RunLoop run_loop; std::vector<net::IPAddress> results; // Expect IPv4 address when family passed as AF_INET. - socket_manager->GetHostAddressWithFamily( - kIPv4Hostname, AF_INET, false /* enable_mdns */, + socket_manager->GetHostAddress( + kIPv4Hostname, net::AddressFamily::ADDRESS_FAMILY_IPV4, + false /* enable_mdns */, base::BindLambdaForTesting( [&](const std::vector<net::IPAddress>& addresses) { EXPECT_EQ(std::vector<net::IPAddress>{ipv4_address}, addresses); @@ -2149,8 +2151,9 @@ base::RunLoop run_loop; std::vector<net::IPAddress> results; // Expect IPv6 address when family passed as AF_INET6. - socket_manager->GetHostAddressWithFamily( - kIPv6Hostname, AF_INET6, false /* enable_mdns */, + socket_manager->GetHostAddress( + kIPv6Hostname, net::AddressFamily::ADDRESS_FAMILY_IPV6, + false /* enable_mdns */, base::BindLambdaForTesting( [&](const std::vector<net::IPAddress>& addresses) { EXPECT_EQ(std::vector<net::IPAddress>{ipv6_address}, addresses);
diff --git a/services/network/p2p/socket_manager.cc b/services/network/p2p/socket_manager.cc index 9ce493e..f4e6340 100644 --- a/services/network/p2p/socket_manager.cc +++ b/services/network/p2p/socket_manager.cc
@@ -75,10 +75,6 @@ return EndsWith(host_name, kLocalTld, base::CompareCase::INSENSITIVE_ASCII); } -net::DnsQueryType FamilyToDnsQueryType(int family) { - return net::AddressFamilyToDnsQueryType(net::ToAddressFamily(family)); -} - } // namespace DefaultLocalAddresses::DefaultLocalAddresses() = default; @@ -92,7 +88,7 @@ : resolver_(host_resolver), enable_mdns_(enable_mdns) {} void Resolve(const std::string& host_name, - std::optional<int> family, + std::optional<net::AddressFamily> family, const net::NetworkAnonymizationKey& network_anonymization_key, DoneCallback done_callback) { DCHECK(!done_callback.is_null()); @@ -123,7 +119,7 @@ #endif // ENABLE_MDNS } if (family.has_value()) { - parameters.dns_query_type = FamilyToDnsQueryType(family.value()); + parameters.dns_query_type = net::AddressFamilyToDnsQueryType(*family); } request_ = resolver_->CreateRequest(host, network_anonymization_key, net::NetLogWithSource(), parameters); @@ -387,24 +383,7 @@ void P2PSocketManager::GetHostAddress( const std::string& host_name, - bool enable_mdns, - mojom::P2PSocketManager::GetHostAddressCallback callback) { - DoGetHostAddress(host_name, /*address_family=*/std::nullopt, enable_mdns, - std::move(callback)); -} - -void P2PSocketManager::GetHostAddressWithFamily( - const std::string& host_name, - int address_family, - bool enable_mdns, - mojom::P2PSocketManager::GetHostAddressCallback callback) { - DoGetHostAddress(host_name, std::make_optional(address_family), enable_mdns, - std::move(callback)); -} - -void P2PSocketManager::DoGetHostAddress( - const std::string& host_name, - std::optional<int> address_family, + std::optional<net::AddressFamily> address_family, bool enable_mdns, mojom::P2PSocketManager::GetHostAddressCallback callback) { auto request = std::make_unique<DnsRequest>(
diff --git a/services/network/p2p/socket_manager.h b/services/network/p2p/socket_manager.h index cd3e15b..a5477c0 100644 --- a/services/network/p2p/socket_manager.h +++ b/services/network/p2p/socket_manager.h
@@ -126,11 +126,7 @@ mojo::PendingRemote<mojom::P2PNetworkNotificationClient> client) override; void GetHostAddress( const std::string& host_name, - bool enable_mdns, - mojom::P2PSocketManager::GetHostAddressCallback callback) override; - void GetHostAddressWithFamily( - const std::string& host_name, - int address_family, + std::optional<net::AddressFamily> address_family, bool enable_mdns, mojom::P2PSocketManager::GetHostAddressCallback callback) override; void CreateSocket( @@ -149,12 +145,6 @@ void NetworkNotificationClientConnectionError(); - void DoGetHostAddress( - const std::string& host_name, - std::optional<int> address_family, - bool enable_mdns, - mojom::P2PSocketManager::GetHostAddressCallback callback); - void OnAddressResolved( DnsRequest* request, mojom::P2PSocketManager::GetHostAddressCallback callback,
diff --git a/services/network/public/cpp/BUILD.gn b/services/network/public/cpp/BUILD.gn index dc53a43..e5e9833 100644 --- a/services/network/public/cpp/BUILD.gn +++ b/services/network/public/cpp/BUILD.gn
@@ -192,6 +192,8 @@ # need to depend on mojom_ip_address does not depend on the entire cpp_base. component("ip_address_mojom_support") { sources = [ + "address_family_mojom_traits.cc", + "address_family_mojom_traits.h", "address_list_mojom_traits.cc", "address_list_mojom_traits.h", "alternate_protocol_usage_mojom_traits.cc",
diff --git a/services/network/public/cpp/address_family_mojom_traits.h b/services/network/public/cpp/address_family_mojom_traits.h index 085f3eaa..727edd7 100644 --- a/services/network/public/cpp/address_family_mojom_traits.h +++ b/services/network/public/cpp/address_family_mojom_traits.h
@@ -5,14 +5,16 @@ #ifndef SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_ #define SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_ +#include "base/component_export.h" #include "mojo/public/cpp/bindings/enum_traits.h" #include "net/base/address_family.h" -#include "services/network/public/mojom/address_family.mojom.h" +#include "services/network/public/mojom/address_family.mojom-shared.h" namespace mojo { template <> -struct EnumTraits<network::mojom::AddressFamily, net::AddressFamily> { +struct COMPONENT_EXPORT(NETWORK_CPP_IP_ADDRESS) + EnumTraits<network::mojom::AddressFamily, net::AddressFamily> { static network::mojom::AddressFamily ToMojom( net::AddressFamily address_family); static bool FromMojom(network::mojom::AddressFamily address_family,
diff --git a/services/network/public/cpp/cors/cors_error_status.h b/services/network/public/cpp/cors/cors_error_status.h index 80dc8577..fbec49ff9 100644 --- a/services/network/public/cpp/cors/cors_error_status.h +++ b/services/network/public/cpp/cors/cors_error_status.h
@@ -39,7 +39,6 @@ ~CorsErrorStatus(); bool operator==(const CorsErrorStatus& rhs) const; - bool operator!=(const CorsErrorStatus& rhs) const { return !(*this == rhs); } // This constructor is used by generated IPC serialization code. explicit CorsErrorStatus(mojo::DefaultConstruct::Tag);
diff --git a/services/network/public/cpp/optional_trust_token_params.h b/services/network/public/cpp/optional_trust_token_params.h index eea367d..022b5d1 100644 --- a/services/network/public/cpp/optional_trust_token_params.h +++ b/services/network/public/cpp/optional_trust_token_params.h
@@ -49,9 +49,6 @@ // This comparison operator wraps mojo::Equals. bool operator==(const OptionalTrustTokenParams&) const; - bool operator!=(const OptionalTrustTokenParams& rhs) const { - return !(*this == rhs); - } explicit operator bool() const { return has_value(); } bool has_value() const { return !!ptr_; }
diff --git a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 index 114946f..5df4b65 100644 --- a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 +++ b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5
@@ -418,6 +418,11 @@ permissions_policy_name: "midi", }, { + name: "OnDeviceSpeechRecognition", + permissions_policy_name: "on-device-speech-recognition", + depends_on: ["OnDeviceWebSpeechAvailable"], + }, + { name: "OTPCredentials", permissions_policy_name: "otp-credentials", depends_on: ["WebOTPAssertionFeaturePolicy"],
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn index 9631e36..736a548 100644 --- a/services/network/public/mojom/BUILD.gn +++ b/services/network/public/mojom/BUILD.gn
@@ -108,6 +108,22 @@ "//services/network/public/cpp:ip_address_mojom_support", ] }, + { + types = [ + { + mojom = "network.mojom.AddressFamily" + cpp = "::net::AddressFamily" + }, + ] + traits_headers = [ + "//net/base/address_family.h", + "//services/network/public/cpp/address_family_mojom_traits.h", + ] + traits_public_deps = [ + "//net", + "//services/network/public/cpp:ip_address_mojom_support", + ] + }, ] cpp_typemaps = [ @@ -128,19 +144,6 @@ { types = [ { - mojom = "network.mojom.AddressFamily" - cpp = "::net::AddressFamily" - }, - ] - traits_headers = - [ "//services/network/public/cpp/address_family_mojom_traits.h" ] - traits_sources = - [ "//services/network/public/cpp/address_family_mojom_traits.cc" ] - traits_public_deps = [ "//net" ] - }, - { - types = [ - { mojom = "network.mojom.AddressList" cpp = "::net::AddressList" },
diff --git a/services/network/public/mojom/p2p.mojom b/services/network/public/mojom/p2p.mojom index 44f5b43..036f96f 100644 --- a/services/network/public/mojom/p2p.mojom +++ b/services/network/public/mojom/p2p.mojom
@@ -7,6 +7,7 @@ import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/read_only_buffer.mojom"; +import "services/network/public/mojom/address_family.mojom"; import "services/network/public/mojom/network_interface.mojom"; import "services/network/public/mojom/ip_address.mojom"; import "services/network/public/mojom/ip_endpoint.mojom"; @@ -63,15 +64,13 @@ StartNetworkNotifications( pending_remote<P2PNetworkNotificationClient> client); - // Performs DNS hostname resolution. - GetHostAddress(string host_name, bool enable_mdns) + // Performs DNS hostname resolution, optionally with a specific address + // family. + GetHostAddress(string host_name, + AddressFamily? address_family, + bool enable_mdns) => (array<IPAddress> addresses); - // Performs DNS hostname resolution for a specific IP address family. - GetHostAddressWithFamily(string host_name, - int32 address_family, - bool enable_mdns) => (array<IPAddress> addresses); - // Creates a P2PSocket CreateSocket(P2PSocketType type, IPEndPoint local_address,
diff --git a/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom b/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom index ecb03e6..a83d9c8 100644 --- a/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom +++ b/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom
@@ -331,6 +331,10 @@ // See https://github.com/WICG/turtledove/blob/main/FLEDGE.md#37-view-and-click-data kRecordAdAuctionEvents = 137, + // Allows sites to delegate access to install on-device speech via the Web + // Speech API to cross-origin iframes. + kOnDeviceSpeechRecognition = 138, + // Don't change assigned numbers of any item, and don't reuse removed slots. // Add new features at the end of the enum. // Also, run update_permissions_policy_enum.py in
diff --git a/services/on_device_model/fake/fake_chrome_ml_api.cc b/services/on_device_model/fake/fake_chrome_ml_api.cc index 6ec2976..549e0ad 100644 --- a/services/on_device_model/fake/fake_chrome_ml_api.cc +++ b/services/on_device_model/fake/fake_chrome_ml_api.cc
@@ -279,7 +279,7 @@ if (instance->model_instance->performance_hint == ml::ModelPerformanceHint::kFastestInference) { - OutputChunk("Fastest inference\n"); + OutputChunk("Fastest inference"); } if (!instance->adaptation_data.empty()) { std::string adaptation_str = "Adaptation: " + instance->adaptation_data; @@ -287,19 +287,19 @@ adaptation_str += " (" + base::NumberToString(*instance->adaptation_file_id) + ")"; } - OutputChunk(adaptation_str + "\n"); + OutputChunk(adaptation_str); } // Only include sampling params if they're not the respective default values. if (instance->top_k != 1 || instance->temperature != 0) { OutputChunk(base::StrCat( {"TopK: ", base::NumberToString(instance->top_k), - ", Temp: ", base::NumberToString(instance->temperature), "\n"})); + ", Temp: ", base::NumberToString(instance->temperature)})); } if (!instance->context.empty()) { for (const std::string& context : instance->context) { - OutputChunk("Context: " + context + "\n"); + OutputChunk(context); } } if (options->constraint) {
diff --git a/services/on_device_model/on_device_model_service_unittest.cc b/services/on_device_model/on_device_model_service_unittest.cc index 5c38a1549..7cacebe 100644 --- a/services/on_device_model/on_device_model_service_unittest.cc +++ b/services/on_device_model/on_device_model_service_unittest.cc
@@ -179,9 +179,9 @@ TEST_F(OnDeviceModelServiceTest, Responds) { auto model = LoadModel(); - EXPECT_THAT(GetResponses(*model, "bar"), ElementsAre("Context: bar\n")); + EXPECT_THAT(GetResponses(*model, "bar"), ElementsAre("bar")); // Try another input on the same model. - EXPECT_THAT(GetResponses(*model, "cat"), ElementsAre("Context: cat\n")); + EXPECT_THAT(GetResponses(*model, "cat"), ElementsAre("cat")); } TEST_F(OnDeviceModelServiceTest, Append) { @@ -196,9 +196,7 @@ session->Generate(mojom::GenerateOptions::New(), response.BindRemote()); response.WaitForCompletion(); - EXPECT_THAT(response.responses(), - ElementsAre("Context: cheese\n", "Context: more\n", - "Context: cheddar\n")); + EXPECT_THAT(response.responses(), ElementsAre("cheese", "more", "cheddar")); } TEST_F(OnDeviceModelServiceTest, PerSessionSamplingParams) { @@ -221,8 +219,7 @@ response.WaitForCompletion(); EXPECT_THAT(response.responses(), - ElementsAre("TopK: 2, Temp: 0.5\n", "Context: cheese\n", - "Context: more\n", "Context: cheddar\n")); + ElementsAre("TopK: 2, Temp: 0.5", "cheese", "more", "cheddar")); } TEST_F(OnDeviceModelServiceTest, CloneContextAndContinue) { @@ -240,15 +237,13 @@ TestResponseHolder response; cloned->Generate(mojom::GenerateOptions::New(), response.BindRemote()); response.WaitForCompletion(); - EXPECT_THAT(response.responses(), - ElementsAre("Context: cheese\n", "Context: more\n")); + EXPECT_THAT(response.responses(), ElementsAre("cheese", "more")); } { TestResponseHolder response; session->Generate(mojom::GenerateOptions::New(), response.BindRemote()); response.WaitForCompletion(); - EXPECT_THAT(response.responses(), - ElementsAre("Context: cheese\n", "Context: more\n")); + EXPECT_THAT(response.responses(), ElementsAre("cheese", "more")); } session->Append(MakeInput("foo"), {}); @@ -257,17 +252,13 @@ TestResponseHolder response; session->Generate(mojom::GenerateOptions::New(), response.BindRemote()); response.WaitForCompletion(); - EXPECT_THAT( - response.responses(), - ElementsAre("Context: cheese\n", "Context: more\n", "Context: foo\n")); + EXPECT_THAT(response.responses(), ElementsAre("cheese", "more", "foo")); } { TestResponseHolder response; cloned->Generate(mojom::GenerateOptions::New(), response.BindRemote()); response.WaitForCompletion(); - EXPECT_THAT( - response.responses(), - ElementsAre("Context: cheese\n", "Context: more\n", "Context: bar\n")); + EXPECT_THAT(response.responses(), ElementsAre("cheese", "more", "bar")); } } @@ -310,21 +301,11 @@ response4.WaitForCompletion(); response5.WaitForCompletion(); - EXPECT_THAT(response1.responses(), - ElementsAre("Context: cheese\n", "Context: more\n", - "Context: cheddar\n")); - EXPECT_THAT( - response2.responses(), - ElementsAre("Context: apple\n", "Context: banana\n", "Context: candy\n")); - EXPECT_THAT( - response3.responses(), - ElementsAre("Context: apple\n", "Context: banana\n", "Context: chip\n")); - EXPECT_THAT( - response4.responses(), - ElementsAre("Context: cheese\n", "Context: more\n", "Context: choco\n")); - EXPECT_THAT(response5.responses(), - ElementsAre("Context: apple\n", "Context: banana\n", - "Context: orange\n")); + EXPECT_THAT(response1.responses(), ElementsAre("cheese", "more", "cheddar")); + EXPECT_THAT(response2.responses(), ElementsAre("apple", "banana", "candy")); + EXPECT_THAT(response3.responses(), ElementsAre("apple", "banana", "chip")); + EXPECT_THAT(response4.responses(), ElementsAre("cheese", "more", "choco")); + EXPECT_THAT(response5.responses(), ElementsAre("apple", "banana", "orange")); } TEST_F(OnDeviceModelServiceTest, CountTokens) { @@ -369,8 +350,7 @@ response.WaitForCompletion(); EXPECT_THAT(response.responses(), - ElementsAre("Context: big \n", "Context: big cheese\n", - "Context: cheddar\n")); + ElementsAre("big ", "big cheese", "cheddar")); } TEST_F(OnDeviceModelServiceTest, MultipleSessionsWaitPreviousSession) { @@ -392,14 +372,14 @@ // Response from first session should still work. response1.WaitForCompletion(); - EXPECT_THAT(response1.responses(), ElementsAre("Context: 1\n")); + EXPECT_THAT(response1.responses(), ElementsAre("1")); // Second session still works. TestResponseHolder response2; session2->Append(MakeInput("2"), {}); session2->Generate(mojom::GenerateOptions::New(), response2.BindRemote()); response2.WaitForCompletion(); - EXPECT_THAT(response2.responses(), ElementsAre("Context: 2\n")); + EXPECT_THAT(response2.responses(), ElementsAre("2")); } TEST_F(OnDeviceModelServiceTest, LoadsAdaptation) { @@ -407,18 +387,18 @@ FakeFile weights2("Adapt2"); auto model = LoadModel(); auto adaptation1 = LoadAdaptation(*model, weights1.Open()); - EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("Context: foo\n")); + EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); auto adaptation2 = LoadAdaptation(*model, weights2.Open()); - EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("Context: foo\n")); + EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); EXPECT_THAT(GetResponses(*adaptation2, "foo"), - ElementsAre("Adaptation: Adapt2 (1)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt2 (1)", "foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); } TEST_F(OnDeviceModelServiceTest, LoadsAdaptationWithPath) { @@ -426,18 +406,18 @@ FakeFile weights2("Adapt2"); auto model = LoadModel(ml::ModelBackendType::kApuBackend); auto adaptation1 = LoadAdaptation(*model, weights1.Path()); - EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("Context: foo\n")); + EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); auto adaptation2 = LoadAdaptation(*model, weights2.Path()); - EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("Context: foo\n")); + EXPECT_THAT(GetResponses(*model, "foo"), ElementsAre("foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); EXPECT_THAT(GetResponses(*adaptation2, "foo"), - ElementsAre("Adaptation: Adapt2 (1)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt2 (1)", "foo")); EXPECT_THAT(GetResponses(*adaptation1, "foo"), - ElementsAre("Adaptation: Adapt1 (0)\n", "Context: foo\n")); + ElementsAre("Adaptation: Adapt1 (0)", "foo")); } TEST_F(OnDeviceModelServiceTest, LoadingAdaptationDoesNotCancelSession) { @@ -532,9 +512,8 @@ } response.WaitForCompletion(); - EXPECT_THAT(response.responses(), ElementsAre("Context: System: hi End.\n", - "Context: Model: hello End.\n", - "Context: User: bye\n")); + EXPECT_THAT(response.responses(), + ElementsAre("System: hi End.", "Model: hello End.", "User: bye")); } TEST_F(OnDeviceModelServiceTest, AppendWithImages) { @@ -580,8 +559,8 @@ } EXPECT_THAT(response.responses(), - ElementsAre("Context: cheddar[Bitmap of size 7x21]cheese\n", - "Context: bleu[Bitmap of size 63x42]cheese\n")); + ElementsAre("cheddar[Bitmap of size 7x21]cheese", + "bleu[Bitmap of size 63x42]cheese")); } TEST_F(OnDeviceModelServiceTest, ClassifyTextSafety) { @@ -641,7 +620,7 @@ auto model = LoadModel(ml::ModelBackendType::kGpuBackend, ml::ModelPerformanceHint::kFastestInference); EXPECT_THAT(GetResponses(*model, "foo"), - ElementsAre("Fastest inference\n", "Context: foo\n")); + ElementsAre("Fastest inference", "foo")); } TEST_F(OnDeviceModelServiceTest, Capabilities) {
diff --git a/services/on_device_model/public/cpp/test_support/fake_service.cc b/services/on_device_model/public/cpp/test_support/fake_service.cc index dab87f8..ef5df8d 100644 --- a/services/on_device_model/public/cpp/test_support/fake_service.cc +++ b/services/on_device_model/public/cpp/test_support/fake_service.cc
@@ -181,29 +181,28 @@ if (model_->performance_hint() == ml::ModelPerformanceHint::kFastestInference) { auto chunk = mojom::ResponseChunk::New(); - chunk->text = "Fastest inference\n"; + chunk->text = "Fastest inference"; remote->OnResponse(std::move(chunk)); } if (model_->data().base_weight != "0") { auto chunk = mojom::ResponseChunk::New(); - chunk->text = "Base model: " + model_->data().base_weight + "\n"; + chunk->text = "Base model: " + model_->data().base_weight; remote->OnResponse(std::move(chunk)); } if (!model_->data().adaptation_model_weight.empty()) { auto chunk = mojom::ResponseChunk::New(); - chunk->text = - "Adaptation model: " + model_->data().adaptation_model_weight + "\n"; + chunk->text = "Adaptation model: " + model_->data().adaptation_model_weight; remote->OnResponse(std::move(chunk)); } if (!model_->data().cache_weight.empty()) { auto chunk = mojom::ResponseChunk::New(); - chunk->text = "Cache weight: " + model_->data().cache_weight + "\n"; + chunk->text = "Cache weight: " + model_->data().cache_weight; remote->OnResponse(std::move(chunk)); } if (priority_ == on_device_model::mojom::Priority::kBackground) { auto chunk = mojom::ResponseChunk::New(); - chunk->text = "Priority: background\n"; + chunk->text = "Priority: background"; remote->OnResponse(std::move(chunk)); } @@ -211,11 +210,11 @@ const auto& constraint = *options->constraint; auto chunk = mojom::ResponseChunk::New(); if (constraint.is_json_schema()) { - chunk->text = "Constraint: json " + constraint.get_json_schema() + "\n"; + chunk->text = "Constraint: json " + constraint.get_json_schema(); } else if (constraint.is_regex()) { - chunk->text = "Constraint: regex " + constraint.get_regex() + "\n"; + chunk->text = "Constraint: regex " + constraint.get_regex(); } else { - chunk->text = "Constraint: unknown\n"; + chunk->text = "Constraint: unknown"; } remote->OnResponse(std::move(chunk)); } @@ -226,15 +225,14 @@ std::string text = CtxToString(*context, params_->capabilities); output_token_count += text.size(); auto chunk = mojom::ResponseChunk::New(); - chunk->text = "Context: " + text + "\n"; + chunk->text = text; remote->OnResponse(std::move(chunk)); } if (params_->top_k != ml::kMinTopK || params_->temperature != ml::kMinTemperature) { auto chunk = mojom::ResponseChunk::New(); chunk->text += "TopK: " + base::NumberToString(params_->top_k) + - ", Temp: " + base::NumberToString(params_->temperature) + - "\n"; + ", Temp: " + base::NumberToString(params_->temperature); remote->OnResponse(std::move(chunk)); } } else {
diff --git a/services/on_device_model/public/mojom/on_device_model.mojom b/services/on_device_model/public/mojom/on_device_model.mojom index 1667823..60626c3 100644 --- a/services/on_device_model/public/mojom/on_device_model.mojom +++ b/services/on_device_model/public/mojom/on_device_model.mojom
@@ -154,9 +154,8 @@ // Text to be provided as input. string text; // Bitmap provided as input. This is not ImageSkia because there is no desire - // to support multiple scale factors. The bitmap is supplied by the - // browser process. - skia.mojom.BitmapMappedFromTrustedProcess bitmap; + // to support multiple scale factors. + skia.mojom.BitmapWithArbitraryBpp bitmap; // Used to handle version skew. [Default]
diff --git a/services/tracing/public/cpp/perfetto/metadata_data_source.cc b/services/tracing/public/cpp/perfetto/metadata_data_source.cc index 437732d..a0f477c 100644 --- a/services/tracing/public/cpp/perfetto/metadata_data_source.cc +++ b/services/tracing/public/cpp/perfetto/metadata_data_source.cc
@@ -42,9 +42,9 @@ #if BUILDFLAG(IS_ANDROID) && defined(OFFICIAL_BUILD) // Version code is only set for official builds on Android. - const char* version_code_str = + const std::string& version_code_str = base::android::BuildInfo::GetInstance()->package_version_code(); - if (version_code_str) { + if (!version_code_str.empty()) { int version_code = 0; bool res = base::StringToInt(version_code_str, &version_code); DCHECK(res);
diff --git a/services/tracing/public/cpp/perfetto/trace_event_metadata_source.cc b/services/tracing/public/cpp/perfetto/trace_event_metadata_source.cc index e3ecd11..46a03d7 100644 --- a/services/tracing/public/cpp/perfetto/trace_event_metadata_source.cc +++ b/services/tracing/public/cpp/perfetto/trace_event_metadata_source.cc
@@ -127,9 +127,9 @@ bool privacy_filtering_enabled) { #if BUILDFLAG(IS_ANDROID) && defined(OFFICIAL_BUILD) // Version code is only set for official builds on Android. - const char* version_code_str = + const std::string& version_code_str = base::android::BuildInfo::GetInstance()->package_version_code(); - if (version_code_str) { + if (!version_code_str.empty()) { int version_code = 0; bool res = base::StringToInt(version_code_str, &version_code); DCHECK(res);
diff --git a/testing/buildbot/filters/fuchsia.browser_tests.filter b/testing/buildbot/filters/fuchsia.browser_tests.filter index 09afb76..ae314091 100644 --- a/testing/buildbot/filters/fuchsia.browser_tests.filter +++ b/testing/buildbot/filters/fuchsia.browser_tests.filter
@@ -73,9 +73,6 @@ # TODO(crbug.com/40840678): Timed out waiting for tab title -TaskManagerBrowserTest.IdleWakeups -# TODO(crbug.com/40840681): Cancel button should be focused, but nothing is focused --FolderUploadConfirmationViewTest.InitiallyFocusesCancel - # TODO(crbug.com/40840682): Dialog not focused as expected -ExternalProtocolDialogBrowserTest.TestFocus
diff --git a/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter b/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter index 5fd9ce64..379626f8 100644 --- a/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter +++ b/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter
@@ -10,7 +10,6 @@ -BrowserViewTest.GetAccessibleTabModalDialogTitle -ChromeSitePerProcessTest.PopupWindowFocus -ExternalProtocolDialogBrowserTest.TestFocus --FolderUploadConfirmationViewTest.InitiallyFocusesCancel -JavaScriptTabModalDialogViewViewsBrowserTest.AlertDialogAccessibleNameDescriptionAndRole -JavaScriptTabModalDialogViewViewsBrowserTest.AlertDialogCloseButtonAccessibilityIgnored -PreservedWindowPlacement.Test
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py index 60bccaf..65e4ea9 100755 --- a/testing/scripts/run_performance_tests.py +++ b/testing/scripts/run_performance_tests.py
@@ -742,6 +742,8 @@ self._parse_arguments() self.isolated_out_dir = isolated_out_dir self.network = self._get_network_arg(options.passthrough_args) + self.is_chrome = (not self.cb_options.official_browser + or self.cb_options.official_browser.startswith('chrome')) if self.options.luci_chromium: # In luci.chromium the Chrome and driver are in the user path. self.browser = '--browser=%s' % get_abs_user_path('chrome') @@ -866,12 +868,11 @@ return [] def _get_default_args(self): - default_args = [ - '--no-symlinks', - # Required until crbug/41491492 and crbug/346323630 are fixed. - '--enable-features=DisablePrivacySandboxPrompts', - ] - if not self.is_android: + default_args = ['--no-symlinks'] + if self.is_chrome: + # Required until crbug.com/41491492 and crbug.com/346323630 are fixed. + default_args.append('--enable-features=DisablePrivacySandboxPrompts') + if self.is_chrome and not self.is_android: # See http://shortn/_xGSaVM9P5g default_args.append('--enable-field-trial-config') if self.options.luci_chromium:
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 814081ad..d363e74 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -8509,21 +8509,6 @@ ] } ], - "EnableExtensibleEnterpriseSSO": [ - { - "platforms": [ - "mac" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "EnableExtensibleEnterpriseSSO" - ] - } - ] - } - ], "EnableExtensionsExplicitBrowserSignin": [ { "platforms": [ @@ -13784,6 +13769,21 @@ ] } ], + "MacKeychainApiMigration": [ + { + "platforms": [ + "mac" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "AppleKeychainUseSecItem" + ] + } + ] + } + ], "MahiEnabled": [ { "platforms": [ @@ -16777,6 +16777,25 @@ ] } ], + "PermissionSiteSettingsRadioButton": [ + { + "platforms": [ + "chromeos", + "linux", + "windows", + "mac", + "android" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "PermissionSiteSettingsRadioButton" + ] + } + ] + } + ], "PermissionsAIv1": [ { "platforms": [ @@ -23922,6 +23941,23 @@ ] } ], + "UnoDesktopHistorySyncPillExperiment": [ + { + "platforms": [ + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "EnableHistorySyncOptinExpansionPill" + ] + } + ] + } + ], "UnoDesktopM0FollowUpPart2": [ { "platforms": [
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl index 3a140c372..6a783af 100644 --- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl +++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -8607,6 +8607,7 @@ media-playback-while-not-visible microphone midi + on-device-speech-recognition otp-credentials payment picture-in-picture
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom index 454e82d..89bb9d9e 100644 --- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom +++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4886,6 +4886,7 @@ kClearSiteData = 5577, kScrollIntoViewContainerNearest = 5578, kPopoverShown = 5579, + kInputParsedParentOptionOrOptgroup = 5580, // Add new features immediately above this line. Don't change assigned // numbers of any item, and don't reuse removed slots. Also don't add extra
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index 1b5e616..bae0728 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1497,6 +1497,9 @@ "//components/paint_preview/common:common", "//components/shared_highlighting/core/common", "//components/shared_highlighting/core/common:data_driven_testing", + "//components/subresource_filter/content/renderer", + "//components/subresource_filter/core/common", + "//components/subresource_filter/core/common:test_support", "//components/ukm:test_support", "//components/viz/test:test_support", "//content/test:test_support",
diff --git a/third_party/blink/renderer/core/DEPS b/third_party/blink/renderer/core/DEPS index 298cdc4a..e20070b 100644 --- a/third_party/blink/renderer/core/DEPS +++ b/third_party/blink/renderer/core/DEPS
@@ -152,6 +152,9 @@ "+base/threading/thread_task_runner_handle.h", # Test harness may use cc directly instead of going through WebViewImpl etc. "+cc", + "+components/subresource_filter/content/renderer", + "+components/subresource_filter/core/common", + "+components/subresource_filter/core/mojom", "+components/ukm/test_ukm_recorder.h", "+components/viz/test", # TODO(crbug.com/838693): Test harnesses use LayerTreeView
diff --git a/third_party/blink/renderer/core/animation/css_font_variation_settings_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_font_variation_settings_interpolation_type.cc index 66eb6af4..f7101987 100644 --- a/third_party/blink/renderer/core/animation/css_font_variation_settings_interpolation_type.cc +++ b/third_party/blink/renderer/core/animation/css_font_variation_settings_interpolation_type.cc
@@ -8,6 +8,7 @@ #include <utility> #include "base/memory/ptr_util.h" +#include "third_party/blink/renderer/core/animation/tree_counting_checker.h" #include "third_party/blink/renderer/core/animation/underlying_value_owner.h" #include "third_party/blink/renderer/core/css/css_font_variation_value.h" #include "third_party/blink/renderer/core/css/css_value_list.h" @@ -144,9 +145,18 @@ InterpolationValue CSSFontVariationSettingsInterpolationType::MaybeConvertValue( const CSSValue& value, const StyleResolverState& state, - ConversionCheckers&) const { - // TODO(crbug.com/415626999): Create a TreeCountingChecker for sibling-index() - // and sibling-count() if necessary. + ConversionCheckers& conversion_checkers) const { + if (const auto* value_list = DynamicTo<CSSValueList>(value)) { + for (const CSSValue* feature : *value_list) { + if (To<cssvalue::CSSFontVariationValue>(feature) + ->Value() + ->IsElementDependent()) { + conversion_checkers.push_back( + TreeCountingChecker::Create(state.CssToLengthConversionData())); + break; + } + } + } // TODO(crbug.com/415572412): Create a LengthUnitsChecker for relative units // if necessary. scoped_refptr<FontVariationSettings> settings =
diff --git a/third_party/blink/renderer/core/css/css_style_sheet.cc b/third_party/blink/renderer/core/css/css_style_sheet.cc index cfed2bf..cba24ad0 100644 --- a/third_party/blink/renderer/core/css/css_style_sheet.cc +++ b/third_party/blink/renderer/core/css/css_style_sheet.cc
@@ -292,6 +292,7 @@ bool CSSStyleSheet::IsContentsShared() const { return contents_->IsUsedFromTextCache() || + contents_->IsUsedFromResourceCache() || contents_->IsReferencedFromResource(); }
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc index 41865c8..cf58c392 100644 --- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc +++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -3861,6 +3861,146 @@ return list; } +CSSValueList* ComputedStyleUtils::ValueForGapDecorationRuleShorthand( + const StylePropertyShorthand& shorthand, + const ComputedStyle& style, + const LayoutObject* layout_object, + bool allow_visited_style, + CSSValuePhase value_phase) { + // If the CSSGapDecorations feature is not enabled, fallback to legacy + // behavior of handling the shorthand since values are stored as single + // values and not lists. + if (!RuntimeEnabledFeatures::CSSGapDecorationEnabled()) { + return ValuesForShorthandProperty(shorthand, style, layout_object, + allow_visited_style, value_phase); + } + + CHECK_EQ(shorthand.length(), 3u); + CHECK(shorthand.properties()[0]->IDEquals(CSSPropertyID::kColumnRuleWidth)); + CHECK(shorthand.properties()[1]->IDEquals(CSSPropertyID::kColumnRuleStyle)); + CHECK(shorthand.properties()[2]->IDEquals(CSSPropertyID::kColumnRuleColor)); + + const CSSValueList* width_values = DynamicTo<CSSValueList>( + shorthand.properties()[0]->CSSValueFromComputedStyle( + style, layout_object, allow_visited_style, value_phase)); + const CSSValueList* style_values = DynamicTo<CSSValueList>( + shorthand.properties()[1]->CSSValueFromComputedStyle( + style, layout_object, allow_visited_style, value_phase)); + const CSSValueList* color_values = DynamicTo<CSSValueList>( + shorthand.properties()[2]->CSSValueFromComputedStyle( + style, layout_object, allow_visited_style, value_phase)); + + const size_t count = width_values->length(); + + // If the longhands differ in length, return nullptr. + // Constructing a shorthand from misaligned longhands is non-trivial and + // currently not supported. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands that + // don't align. + if (count != style_values->length() || count != color_values->length()) { + return nullptr; + } + + CSSValueList* result = CSSValueList::CreateCommaSeparated(); + + for (size_t i = 0; i < count; ++i) { + const auto* style_repeat_value = + DynamicTo<cssvalue::CSSRepeatValue>(style_values->Item(i)); + const auto* color_repeat_value = + DynamicTo<cssvalue::CSSRepeatValue>(color_values->Item(i)); + // If this segment was a repeat() value originally, we need to unpack + // its sub-values and rebuild the original repeat(<integer>|auto, + // <gap-rule>#). For example: repeat(2, 3px) repeat(2, solid) repeat(2, red) + // should return repeat(2, 3px solid red). + if (const auto* width_repeat_value = + DynamicTo<cssvalue::CSSRepeatValue>(width_values->Item(i))) { + // Return nullptr if values don't align. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands that + // don't align. + if (!style_repeat_value || !color_repeat_value) { + return nullptr; + } + + const bool is_auto_repeat_value = width_repeat_value->IsAutoRepeatValue(); + // Return nullptr if values don't align. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands that + // don't align. + if (is_auto_repeat_value != style_repeat_value->IsAutoRepeatValue() || + is_auto_repeat_value != color_repeat_value->IsAutoRepeatValue()) { + return nullptr; + } + + const CSSPrimitiveValue* repetitions = nullptr; + if (!is_auto_repeat_value) { + repetitions = width_repeat_value->Repetitions(); + // Return nullptr if values don't align. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands + // that don't align. + if (!base::ValuesEquivalent(repetitions, + style_repeat_value->Repetitions()) || + !base::ValuesEquivalent(repetitions, + color_repeat_value->Repetitions())) { + return nullptr; + } + } + + // Reconstruct each <gap-rule> as "width style color" triplets. + CSSValueList* repeated_gap_rules = CSSValueList::CreateCommaSeparated(); + wtf_size_t rules_count = width_repeat_value->Values().length(); + + // Return nullptr if values don't align. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands that + // don't align. + if (rules_count != style_repeat_value->Values().length() || + rules_count != color_repeat_value->Values().length()) { + return nullptr; + } + + for (size_t j = 0; j < rules_count; ++j) { + CSSValueList* gap_rule = CSSValueList::CreateSpaceSeparated(); + gap_rule->Append(width_repeat_value->Values().Item(j)); + gap_rule->Append(style_repeat_value->Values().Item(j)); + gap_rule->Append(color_repeat_value->Values().Item(j)); + repeated_gap_rules->Append(*gap_rule); + } + + CSSValue* repeater_gap_rule = + MakeGarbageCollected<cssvalue::CSSRepeatValue>(repetitions, + *repeated_gap_rules); + result->Append(*repeater_gap_rule); + } else { + // A simple gap rule, just append width, style and color values. + + // Return nullptr if values don't align. + // + // TODO(crbug.com/416535734): Figure out a way to handle cases where we + // need to construct the shorthand from individual separate longhands + // that don't align. + if (style_repeat_value || color_repeat_value) { + return nullptr; + } + + CSSValueList* gap_rule = CSSValueList::CreateSpaceSeparated(); + gap_rule->Append(width_values->Item(i)); + gap_rule->Append(style_values->Item(i)); + gap_rule->Append(color_values->Item(i)); + result->Append(*gap_rule); + } + } + + return result; +} + CSSValuePair* ComputedStyleUtils::ValuesForGapShorthand( const StylePropertyShorthand& shorthand, const ComputedStyle& style,
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.h b/third_party/blink/renderer/core/css/properties/computed_style_utils.h index b820acf..2d4786c 100644 --- a/third_party/blink/renderer/core/css/properties/computed_style_utils.h +++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.h
@@ -333,6 +333,12 @@ const LayoutObject*, bool allow_visited_style, CSSValuePhase value_phase); + static CSSValueList* ValueForGapDecorationRuleShorthand( + const StylePropertyShorthand&, + const ComputedStyle&, + const LayoutObject*, + bool allow_visited_style, + CSSValuePhase value_phase); static CSSValuePair* ValuesForGapShorthand(const StylePropertyShorthand&, const ComputedStyle&, const LayoutObject*,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc index f82c9d5..28899ef 100644 --- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc +++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -6779,6 +6779,246 @@ CSSPrimitiveValue::ValueRange::kNonNegative); } +// A <gap-rule> is defined as: [ <line-width> || <line-style> || <color> ] +// This function processes a single <gap-rule> and ensures that exactly one +// value is assigned for each of the three properties: `width_value`, +// `style_value`, and `color_value`. If any property is not explicitly +// specified, a default value is applied. +bool ConsumeGapDecorationsLonghandsGreedily(CSSParserTokenStream& stream, + const CSSParserContext& context, + CSSValue*& width_value, + CSSValue*& style_value, + CSSValue*& color_value) { + enum class ParseResult { kSuccess, kFailure, kDuplicate }; + auto attemptToConsumeValue = [&](CSSGapDecorationPropertyType type, + CSSValue*& value) { + if (CSSValue* v = + ConsumeGapDecorationPropertyValue(stream, context, type)) { + // Multiple values for the same property `type` are not allowed. + if (value) { + return ParseResult::kDuplicate; + } + value = v; + return ParseResult::kSuccess; + } + return ParseResult::kFailure; + }; + + typedef std::pair<CSSGapDecorationPropertyType, CSSValue*&> PropertyPair; + PropertyPair property_pairs[] = { + {CSSGapDecorationPropertyType::kWidth, width_value}, + {CSSGapDecorationPropertyType::kStyle, style_value}, + {CSSGapDecorationPropertyType::kColor, color_value}, + }; + + while (!stream.AtEnd() && stream.Peek().GetType() != kCommaToken) { + bool consumed_any = false; + for (const auto& property_pair : property_pairs) { + CSSGapDecorationPropertyType type = property_pair.first; + CSSValue*& value = property_pair.second; + + // Attempt to consume the value for the current property type. + ParseResult result = attemptToConsumeValue(type, value); + if (result == ParseResult::kDuplicate) { + return false; + } + consumed_any |= result == ParseResult::kSuccess; + } + + if (!consumed_any) { + return false; + } + } + + // Fill in any missing properties with the property's default. + if (!width_value) { + width_value = CSSIdentifierValue::Create(CSSValueID::kMedium); + } + if (!style_value) { + style_value = CSSIdentifierValue::Create(CSSValueID::kNone); + } + if (!color_value) { + color_value = CSSIdentifierValue::Create(CSSValueID::kCurrentcolor); + } + + return true; +} + +// Handles consuming the <gap-rule> repeat values which is defined as: +// repeat(<integer> | auto, <gap-rule>+). Returns true if the +// <gap-rule> repeat function was successfully consumed. +// `has_seen_auto_repeater` is a input and output param that is only set to true +// if the value is false and we encounter an auto-repeater. +bool ConsumeGapDecorationsShorthandRepeatFunction( + CSSParserTokenStream& stream, + const CSSParserContext& context, + CSSValue*& repeater_width, + CSSValue*& repeater_style, + CSSValue*& repeater_color, + bool& has_seen_auto_repeater) { + DCHECK_EQ(stream.Peek().GetType(), kFunctionToken); + + CSSParserTokenStream::RestoringBlockGuard guard(stream); + stream.ConsumeWhitespace(); + + CSSPrimitiveValue* repetition_value = nullptr; + + if (IdentMatches<CSSValueID::kAuto>(stream.Peek().Id())) { + CHECK_EQ(stream.ConsumeIncludingWhitespace().Id(), CSSValueID::kAuto); + + // Only one auto-repeater is allowed. + if (has_seen_auto_repeater) { + return false; + } + has_seen_auto_repeater = true; + } else { + // If it isn't an auto repeater, that means it is an integer repeater. + repetition_value = ConsumeIntegerOrNumberCalc( + stream, context, CSSPrimitiveValue::ValueRange::kPositiveInteger); + if (!repetition_value) { + return false; + } + } + + // After "auto" or <integer>, we expect a comma. + if (!ConsumeCommaIncludingWhitespace(stream)) { + return false; + } + + // Lists to hold repeated values for each property. + CSSValueList* width_repeated_values = CSSValueList::CreateSpaceSeparated(); + CSSValueList* style_repeated_values = CSSValueList::CreateSpaceSeparated(); + CSSValueList* color_repeated_values = CSSValueList::CreateSpaceSeparated(); + + // Consume one or more <gap-rule> values, each separated by a comma. + while (!stream.AtEnd()) { + CSSValue* width_value = nullptr; + CSSValue* style_value = nullptr; + CSSValue* color_value = nullptr; + + if (!ConsumeGapDecorationsLonghandsGreedily(stream, context, width_value, + style_value, color_value)) { + return false; + } + + // Ensure all three values have been hydrated. + CHECK(width_value); + CHECK(style_value); + CHECK(color_value); + + width_repeated_values->Append(*width_value); + style_repeated_values->Append(*style_value); + color_repeated_values->Append(*color_value); + + // If there's a comma, continue to the next <gap-rule>. + if (ConsumeCommaIncludingWhitespace(stream)) { + continue; + } else { + break; + } + } + + if (!width_repeated_values->length() || !style_repeated_values->length() || + !color_repeated_values->length()) { + return false; + } + + guard.Release(); + stream.ConsumeWhitespace(); + + repeater_width = MakeGarbageCollected<cssvalue::CSSRepeatValue>( + repetition_value, *width_repeated_values); + repeater_style = MakeGarbageCollected<cssvalue::CSSRepeatValue>( + repetition_value, *style_repeated_values); + repeater_color = MakeGarbageCollected<cssvalue::CSSRepeatValue>( + repetition_value, *color_repeated_values); + + return true; +} + +// Top level parsing function for the gap-decorations shorthand, which handles +// the parsing of simple <gap-rule> or repeat <gap-rule> values. +bool ConsumeGapDecorationsRuleShorthand( + bool important, + const CSSParserContext& context, + CSSParserTokenStream& stream, + HeapVector<CSSPropertyValue, 64>& properties) { + // TODO(samomekarajr): Add support for `row-rule` shorthand when implemented. + const StylePropertyShorthand& gapDecorationsRuleShorthand = + columnRuleShorthand(); + DCHECK_EQ(gapDecorationsRuleShorthand.length(), 3u); + + // If the CSSGapDecorations feature is not enabled, consume greedily since + // only single values are supported by 'column-rule' today. + if (!RuntimeEnabledFeatures::CSSGapDecorationEnabled()) { + return css_parsing_utils::ConsumeShorthandGreedilyViaLonghands( + gapDecorationsRuleShorthand, important, context, stream, properties); + } + + CSSValueList* rule_widths = CSSValueList::CreateSpaceSeparated(); + CSSValueList* rule_styles = CSSValueList::CreateSpaceSeparated(); + CSSValueList* rule_colors = CSSValueList::CreateSpaceSeparated(); + + bool has_seen_auto_repeat = false; + + while (!stream.AtEnd()) { + // Values for the current segment, which can either be a <gap-rule> or a + // <gap-rule> repeater. + CSSValue* width_value = nullptr; + CSSValue* style_value = nullptr; + CSSValue* color_value = nullptr; + + if (stream.Peek().GetType() == kFunctionToken && + stream.Peek().FunctionId() == CSSValueID::kRepeat) { + if (!ConsumeGapDecorationsShorthandRepeatFunction( + stream, context, width_value, style_value, color_value, + has_seen_auto_repeat)) { + return false; + } + } else { + if (!ConsumeGapDecorationsLonghandsGreedily(stream, context, width_value, + style_value, color_value)) { + return false; + } + } + + CHECK(width_value); + CHECK(style_value); + CHECK(color_value); + + rule_widths->Append(*width_value); + rule_styles->Append(*style_value); + rule_colors->Append(*color_value); + + // Continue to the next segment if we have a comma. + if (ConsumeCommaIncludingWhitespace(stream)) { + continue; + } else { + break; + } + } + + if (!rule_widths->length() || !rule_styles->length() || + !rule_colors->length()) { + return false; + } + + css_parsing_utils::AddProperty( + CSSPropertyID::kColumnRuleWidth, CSSPropertyID::kColumnRule, *rule_widths, + important, css_parsing_utils::IsImplicitProperty::kNotImplicit, + properties); + css_parsing_utils::AddProperty( + CSSPropertyID::kColumnRuleStyle, CSSPropertyID::kColumnRule, *rule_styles, + important, css_parsing_utils::IsImplicitProperty::kNotImplicit, + properties); + css_parsing_utils::AddProperty( + CSSPropertyID::kColumnRuleColor, CSSPropertyID::kColumnRule, *rule_colors, + important, css_parsing_utils::IsImplicitProperty::kNotImplicit, + properties); + + return true; +} + CSSValue* ConsumeHyphenateLimitChars(CSSParserTokenStream& stream, const CSSParserContext& context) { CSSValueList* const list = CSSValueList::CreateSpaceSeparated();
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h index 7b565dd..e9934530 100644 --- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h +++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -526,6 +526,12 @@ CSSValue* ConsumeItemTolerance(CSSParserTokenStream&, const CSSParserContext&); +bool ConsumeGapDecorationsRuleShorthand( + bool important, + const CSSParserContext&, + CSSParserTokenStream&, + HeapVector<CSSPropertyValue, 64>& properties); + CSSValue* ConsumeHyphenateLimitChars(CSSParserTokenStream&, const CSSParserContext&);
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc index 07afdc7..d71048c5 100644 --- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc +++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -1193,8 +1193,8 @@ const CSSParserContext& context, const CSSParserLocalContext&, HeapVector<CSSPropertyValue, 64>& properties) const { - return css_parsing_utils::ConsumeShorthandGreedilyViaLonghands( - columnRuleShorthand(), important, context, stream, properties); + return css_parsing_utils::ConsumeGapDecorationsRuleShorthand( + important, context, stream, properties); } const CSSValue* ColumnRule::CSSValueFromComputedStyleInternal( @@ -1202,7 +1202,7 @@ const LayoutObject* layout_object, bool allow_visited_style, CSSValuePhase value_phase) const { - return ComputedStyleUtils::ValuesForShorthandProperty( + return ComputedStyleUtils::ValueForGapDecorationRuleShorthand( columnRuleShorthand(), style, layout_object, allow_visited_style, value_phase); }
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc index 56429f6..a1a46c2 100644 --- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc +++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -3299,6 +3299,11 @@ *relative_color_value, document, color_scheme); } + if (auto* unresolved_color_value = + DynamicTo<cssvalue::CSSUnresolvedColorValue>(value)) { + return ComputeColorValue(css_to_length_conversion_data, + *unresolved_color_value, document, color_scheme); + } return value; }
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.cc b/third_party/blink/renderer/core/css/style_sheet_contents.cc index 6bfcfe1d..0e9491de 100644 --- a/third_party/blink/renderer/core/css/style_sheet_contents.cc +++ b/third_party/blink/renderer/core/css/style_sheet_contents.cc
@@ -82,6 +82,7 @@ has_media_queries_(false), has_single_owner_document_(true), is_used_from_text_cache_(false), + is_used_from_resource_cache_(false), parser_context_(context) {} StyleSheetContents::StyleSheetContents(const StyleSheetContents& o) @@ -102,6 +103,7 @@ has_media_queries_(o.has_media_queries_), has_single_owner_document_(true), is_used_from_text_cache_(false), + is_used_from_resource_cache_(false), parser_context_(o.parser_context_) { for (unsigned i = 0; i < pre_import_layer_statement_rules_.size(); ++i) { pre_import_layer_statement_rules_[i] = To<StyleRuleLayerStatement>(
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.h b/third_party/blink/renderer/core/css/style_sheet_contents.h index d248847..4ea07c6a2 100644 --- a/third_party/blink/renderer/core/css/style_sheet_contents.h +++ b/third_party/blink/renderer/core/css/style_sheet_contents.h
@@ -217,9 +217,25 @@ bool IsMutable() const { return is_mutable_; } void StartMutation(); + // Set to true whenever this StyleSheetContents was returned as a cache hit + // from the text cache (StyleEngine::CreateSheet()). If this flag is true, + // is means that this StyleSheetContents may be shared between multiple + // CSSStyleSheets. bool IsUsedFromTextCache() const { return is_used_from_text_cache_; } void SetIsUsedFromTextCache() { is_used_from_text_cache_ = true; } + // Set to true whenever this StyleSheetContents was returned as a cache hit + // from the resource cache [1]. If this flag is true, is means that this + // StyleSheetContents may be shared between multiple CSSStyleSheets. + // + // [1] CSSStyleSheetResource::CreateParsedStyleSheetFromCache + bool IsUsedFromResourceCache() const { return is_used_from_resource_cache_; } + void SetIsUsedFromResourceCache() { is_used_from_resource_cache_ = true; } + + // The CSSStyleSheetResource is set whenever this StyleSheetContents is + // the cached stylesheet of that CSSStyleSheetResource. We must not modify + // this StyleSheetContents while this is true, and any mutations must + // therefore perform a copy-on-write first. bool IsReferencedFromResource() const { return referenced_from_resource_ != nullptr; } @@ -282,6 +298,7 @@ bool has_media_queries_ : 1; bool has_single_owner_document_ : 1; bool is_used_from_text_cache_ : 1; + bool is_used_from_resource_cache_ : 1; Member<const CSSParserContext> parser_context_;
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc index 33cd220d..e6a2c8c 100644 --- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc +++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -9,6 +9,11 @@ #include "base/containers/contains.h" #include "base/run_loop.h" #include "base/test/scoped_feature_list.h" +#include "components/subresource_filter/content/renderer/web_document_subresource_filter_impl.h" +#include "components/subresource_filter/core/common/memory_mapped_ruleset.h" +#include "components/subresource_filter/core/common/test_ruleset_creator.h" +#include "components/subresource_filter/core/common/test_ruleset_utils.h" +#include "components/subresource_filter/core/mojom/subresource_filter.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/renderer/core/dom/element_traversal.h" @@ -90,6 +95,43 @@ } )CSS"; +// Returns a new instance of `WebDocumentSubresourceFilterImpl`. The caller +// assumes ownership and is responsible for its proper destruction. +subresource_filter::WebDocumentSubresourceFilterImpl* CreateSubresourceFilter( + scoped_refptr<const subresource_filter::MemoryMappedRuleset> ruleset) { + subresource_filter::mojom::ActivationState activation_state( + subresource_filter::mojom::ActivationLevel::kDryRun, + /*filtering_disabled_for_document=*/false, + /*generic_blocking_rules_disabled=*/false, + /*measure_performance=*/false, + /*enable_logging=*/false); + + return new subresource_filter::WebDocumentSubresourceFilterImpl( + url::Origin::Create(GURL("https://example.com")), activation_state, + ruleset, + /*first_disallowed_load_callback=*/base::DoNothing()); +} + +class FixedSubresourceFilterWebFrameClient + : public frame_test_helpers::TestWebFrameClient { + public: + explicit FixedSubresourceFilterWebFrameClient( + scoped_refptr<const subresource_filter::MemoryMappedRuleset> ruleset) + : ruleset_(ruleset) {} + + void DidCommitNavigation( + WebHistoryCommitType commit_type, + bool should_reset_browser_interface_broker, + const network::ParsedPermissionsPolicy& permissions_policy_header, + const DocumentPolicyFeatureState& document_policy_header) override { + Frame()->GetDocumentLoader()->SetSubresourceFilter( + CreateSubresourceFilter(ruleset_)); + } + + private: + scoped_refptr<const subresource_filter::MemoryMappedRuleset> ruleset_; +}; + class TestAdTracker : public AdTracker { public: explicit TestAdTracker(LocalFrame* frame) : AdTracker(frame) {} @@ -98,7 +140,6 @@ execution_context_ = execution_context; } - void SetAdSuffix(const String& ad_suffix) { ad_suffix_ = ad_suffix; } ~TestAdTracker() override {} void Trace(Visitor* visitor) const override { @@ -174,29 +215,24 @@ const KURL& request_url, ResourceType resource_type, const FetchInitiatorInfo& initiator_info, - bool ad_request) override { - if (!ad_suffix_.empty() && request_url.GetString().EndsWith(ad_suffix_)) { - ad_request = true; - } - - ad_request = AdTracker::CalculateIfAdSubresource( + bool known_ad) override { + bool observed_result = AdTracker::CalculateIfAdSubresource( execution_context, request_url, resource_type, initiator_info, - ad_request); + known_ad); String resource_url = request_url.GetString(); - is_ad_.insert(resource_url, ad_request); + is_ad_.insert(resource_url, observed_result); if (quit_closure_ && url_to_wait_for_ == resource_url) { std::move(quit_closure_).Run(); } - return ad_request; + return observed_result; } private: HashMap<String, bool> is_ad_; String script_at_top_; Member<ExecutionContext> execution_context_; - String ad_suffix_; bool sim_test_ = false; Vector<AdScriptIdentifier> last_ad_script_ancestry_; @@ -555,6 +591,22 @@ class AdTrackerSimTest : public SimTest { protected: + AdTrackerSimTest() { + // Build a subresource filter ruleset that will consider *ad_script.js and + // *ad=true urls as ads. + std::vector<url_pattern_index::proto::UrlRule> rules; + rules.push_back( + subresource_filter::testing::CreateSuffixRule("ad_script.js")); + rules.push_back(subresource_filter::testing::CreateSuffixRule("ad=true")); + + subresource_filter::testing::TestRulesetPair test_ruleset_pair; + subresource_filter::testing::TestRulesetCreator ruleset_creator; + ruleset_creator.CreateRulesetWithRules(rules, &test_ruleset_pair); + ruleset_ = subresource_filter::MemoryMappedRuleset::CreateAndInitialize( + subresource_filter::testing::TestRuleset::Open( + test_ruleset_pair.indexed)); + } + void SetUp() override { SimTest::SetUp(); main_resource_ = std::make_unique<SimRequest>( @@ -571,12 +623,18 @@ SimTest::TearDown(); } + std::unique_ptr<frame_test_helpers::TestWebFrameClient> + CreateWebFrameClientForMainFrame() override { + return std::make_unique<FixedSubresourceFilterWebFrameClient>(ruleset_); + } + bool IsKnownAdScript(ExecutionContext* execution_context, const String& url) { return ad_tracker_->IsKnownAdScript(execution_context, url); } std::unique_ptr<SimRequest> main_resource_; Persistent<TestAdTracker> ad_tracker_; + scoped_refptr<const subresource_filter::MemoryMappedRuleset> ruleset_; }; // Script loaded by ad script is tagged as ad. @@ -586,8 +644,6 @@ SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); SimSubresourceRequest vanilla_script(kVanillaUrl, "text/javascript"); - ad_tracker_->SetAdSuffix("ad_script.js"); - main_resource_->Complete("<body></body><script src=ad_script.js></script>"); ad_resource.Complete(R"SCRIPT( @@ -633,8 +689,6 @@ SimSubresourceRequest image_resource("https://example.com/image.gif", "image/gif"); - ad_tracker_->SetAdSuffix("ad_script.js"); - // Create an iframe that's considered an ad. main_resource_->Complete(R"(<body> <script src='vanilla_script.js'></script> @@ -680,8 +734,6 @@ SimSubresourceRequest ad_script("https://example.com/ad_script.js", "text/javascript"); - ad_tracker_->SetAdSuffix("ad_script.js"); - main_resource_->Complete( "<body><script src='redirect_script.js'></script></body>"); @@ -723,7 +775,6 @@ SimSubresourceRequest ad_script("https://example.com/ad_script.js", "text/javascript"); SimRequest ad_iframe("https://example.com/ad_frame.html", "text/html"); - ad_tracker_->SetAdSuffix("ad_script.js"); main_resource_->Complete("<body><script src='ad_script.js'></script></body>"); ad_script.Complete(R"SCRIPT( @@ -771,8 +822,6 @@ SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); SimSubresourceRequest vanilla_image(kVanillaUrl, "image/gif"); - ad_tracker_->SetAdSuffix("ad_script.js"); - main_resource_->Complete("<body></body><script src=ad_script.js></script>"); ad_resource.Complete(R"SCRIPT( @@ -820,8 +869,6 @@ const char kAdUrl[] = "https://example.com/ad_script.js"; SimSubresourceRequest ad_resource(kAdUrl, "text/javascript"); - ad_tracker_->SetAdSuffix("ad_script.js"); - main_resource_->Complete("<body></body><script src=ad_script.js></script>"); ad_resource.Complete(R"SCRIPT( @@ -857,8 +904,6 @@ SimRequest vanilla_page(kVanillaUrl, "text/html"); SimSubresourceRequest vanilla_image(kVanillaImgUrl, "image/jpeg"); - ad_tracker_->SetAdSuffix("ad_script.js"); - main_resource_->Complete("<body></body><script src=ad_script.js></script>"); ad_resource.Complete(R"SCRIPT( @@ -904,12 +949,27 @@ // Complete the main frame's library.js. library_resource.Complete(""); + base::RunLoop().RunUntilIdle(); + + Frame* subframe = GetDocument().GetFrame()->Tree().FirstChild(); + auto* local_subframe = To<LocalFrame>(subframe); + + subresource_filter::testing::TestRulesetPair test_ruleset_pair; + subresource_filter::testing::TestRulesetCreator ruleset_creator; + ruleset_creator.CreateRulesetToDisallowURLsWithPathSuffix("library.js", + &test_ruleset_pair); + scoped_refptr<const subresource_filter::MemoryMappedRuleset> new_ruleset = + subresource_filter::MemoryMappedRuleset::CreateAndInitialize( + subresource_filter::testing::TestRuleset::Open( + test_ruleset_pair.indexed)); + + local_subframe->GetDocument()->Loader()->SetSubresourceFilter( + CreateSubresourceFilter(new_ruleset)); // The library script is loaded for a second time, this time in the - // subframe. Mark it as an ad. + // subframe that treats 'library.js' as an ad suffix. SimSubresourceRequest library_resource_for_subframe( "https://example.com/library.js", "text/javascript"); - ad_tracker_->SetAdSuffix("library.js"); iframe_resource.Complete(R"HTML( <script src="library.js"></script> @@ -918,8 +978,6 @@ // Verify that library.js is an ad script in the subframe's context but not // in the main frame's context. - Frame* subframe = GetDocument().GetFrame()->Tree().FirstChild(); - auto* local_subframe = To<LocalFrame>(subframe); EXPECT_TRUE( IsKnownAdScript(local_subframe->GetDocument()->GetExecutionContext(), String("https://example.com/library.js"))); @@ -932,7 +990,6 @@ SimSubresourceRequest ad_resource("https://example.com/ad_script.js", "text/javascript"); SimRequest iframe_resource("https://example.com/iframe.html", "text/html"); - ad_tracker_->SetAdSuffix("ad_script.js"); main_resource_->Complete(R"HTML( <body></body><script src=ad_script.js></script> @@ -956,7 +1013,6 @@ TEST_F(AdTrackerSimTest, SameOriginDocWrittenSubframeFromAdScript) { SimSubresourceRequest ad_resource("https://example.com/ad_script.js", "text/javascript"); - ad_tracker_->SetAdSuffix("ad_script.js"); main_resource_->Complete(R"HTML( <body></body><script src=ad_script.js></script> @@ -998,8 +1054,6 @@ SimSubresourceRequest font(font_url, "font/woff2"); SimSubresourceRequest image(image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(kPageWithVanillaExternalStylesheet); stylesheet.Complete(IsAdRun() ? kStylesheetWithAdResources : kStylesheetWithVanillaResources); @@ -1024,8 +1078,6 @@ SimSubresourceRequest font(font_url, "font/woff2"); SimSubresourceRequest image(image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(kPageWithAdExternalStylesheet); stylesheet.Complete(IsAdRun() ? kStylesheetWithAdResources : kStylesheetWithVanillaResources); @@ -1052,8 +1104,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdScript : kPageWithVanillaScript); script.Complete(R"SCRIPT( @@ -1092,8 +1142,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(kPageWithFrame); if (IsAdRun()) { auto* subframe = @@ -1126,8 +1174,6 @@ SimSubresourceRequest script(script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdScript : kPageWithVanillaScript); script.Complete(R"SCRIPT( @@ -1150,8 +1196,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(kPageWithStyleTagLoadingVanillaResources); // Wait for stylesheet to fetch resources. @@ -1175,8 +1219,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(kPageWithFrame); if (IsAdRun()) { auto* subframe = @@ -1206,8 +1248,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdScript : kPageWithVanillaScript); script.Complete(String::Format( @@ -1243,8 +1283,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet : kPageWithVanillaExternalStylesheet); stylesheet.Complete(R"CSS( @@ -1280,8 +1318,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet : kPageWithVanillaExternalStylesheet); stylesheet.Complete(R"CSS( @@ -1309,8 +1345,6 @@ SimSubresourceRequest stylesheet(stylesheet_url, "text/css"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdExternalStylesheet : kPageWithVanillaExternalStylesheet); @@ -1341,8 +1375,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(IsAdRun() ? kPageWithAdScript : kPageWithVanillaScript); script.Complete(R"SCRIPT( @@ -1385,8 +1417,6 @@ SimSubresourceRequest font(vanilla_font_url, "font/woff2"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <head><link rel="stylesheet" href="style.css"> <script async src="script.js?ad=true"></script></head> @@ -1435,8 +1465,6 @@ SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body><script src="script.js?ad=true"></script> <script src="script.js"></script></body> @@ -1475,8 +1503,6 @@ SimSubresourceRequest vanilla_script2(vanilla_script2_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body><script src="script.js"></script> <script src="script.js?ad=true"></script> @@ -1514,8 +1540,6 @@ SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <head><script type="module" src="script.js"></script></head> <body><div>Test</div></body> @@ -1537,8 +1561,6 @@ SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <head><script type="module" src="script.js?ad=true"></script></head> <body><div>Test</div></body> @@ -1564,8 +1586,6 @@ SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <head><script src="script.js?ad=true"></script> <script src="script.js"></script></head> @@ -1600,8 +1620,6 @@ SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript"); SimSubresourceRequest image(vanilla_image_url, "image/png"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body><script src="script.js?ad=true"></script> <script src="script.js"></script></body> @@ -1644,8 +1662,6 @@ SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); SimRequest ad_document(ad_document_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body><script src="script.js?ad=true"></script> <script src="script.js"></script></body> @@ -1697,8 +1713,6 @@ SimSubresourceRequest ad_script(ad_script_url, "text/javascript"); SimRequest ad_document(ad_document_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body><script src="script.js?ad=true"></script> <script src="script.js"></script></body> @@ -1763,8 +1777,6 @@ SimRequest ad_document1(ad_document1_url, "text/html"); SimRequest ad_document2(ad_document2_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script src="script.js?ad=true"></script> @@ -1846,8 +1858,6 @@ SimRequest ad_document1(ad_document1_url, "text/html"); SimRequest ad_document2(ad_document2_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script src="script.js?ad=true"></script> @@ -1930,8 +1940,6 @@ SimRequest ad_document1(ad_document1_url, "text/html"); SimRequest ad_document2(ad_document2_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script src="script.js?ad=true"></script> @@ -2025,8 +2033,6 @@ SimRequest ad_document3(ad_document3_url, "text/html"); SimRequest ad_document4(ad_document4_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script src="script.js?ad=true"></script> @@ -2175,8 +2181,6 @@ SimRequest ad_document(ad_document_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script src="script.js?ad=true"></script> @@ -2250,8 +2254,6 @@ SimRequest ad_document2(ad_document2_url, "text/html"); SimRequest ad_document3(ad_document3_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <script> @@ -2360,8 +2362,6 @@ SimRequest ad_document1(ad_document1_url, "text/html"); SimRequest ad_document2(ad_document2_url, "text/html"); - ad_tracker_->SetAdSuffix("ad=true"); - main_resource_->Complete(R"HTML( <body> <iframe src="vanilla_document.html"></iframe> @@ -2374,6 +2374,9 @@ To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild()); EXPECT_FALSE(child_frame1->IsFrameCreatedByAdScript()); + child_frame1->GetDocument()->Loader()->SetSubresourceFilter( + CreateSubresourceFilter(ruleset_)); + vanilla_document.Complete(R"HTML( <body> <script src="script.js?ad=true"></script>
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc index 3d0f6731..7f881d96 100644 --- a/third_party/blink/renderer/core/html/html_element.cc +++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1849,7 +1849,9 @@ } MarkPopoverInvokersDirty(*this); - SetPopoverInvoker(nullptr); + if (!RuntimeEnabledFeatures::ClearPopoverInvokerAfterBeforeToggleEnabled()) { + SetPopoverInvoker(nullptr); + } // Events are only fired in the case that the popover is not being removed // from the document. if (transition_behavior == @@ -1939,6 +1941,10 @@ } } + if (RuntimeEnabledFeatures::ClearPopoverInvokerAfterBeforeToggleEnabled()) { + SetPopoverInvoker(nullptr); + } + // Re-apply display:none, and stop matching `:popover-open`. GetPopoverData()->setVisibilityState(PopoverVisibilityState::kHidden);
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc index 0a2de968..0b2ccad 100644 --- a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc +++ b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc
@@ -37,6 +37,8 @@ #include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" +#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" +#include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/forms/html_select_element.h" #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" #include "third_party/blink/renderer/core/html/html_template_element.h" @@ -740,9 +742,19 @@ tree_.CurrentNode())) { if (tree_.OpenElements()->InScope(HTMLTag::kSelect)) { bool parent_select = IsA<HTMLSelectElement>(tree_.CurrentNode()); + bool parent_option_optgroup = + IsA<HTMLOptionElement>(tree_.CurrentNode()) || + IsA<HTMLOptGroupElement>(tree_.CurrentNode()); + if (parent_select) { UseCounter::Count(tree_.CurrentNode()->GetDocument(), WebFeature::kInputParsedParentSelect); + } else if (parent_option_optgroup) { + UseCounter::Count(tree_.CurrentNode()->GetDocument(), + WebFeature::kInputParsedParentOptionOrOptgroup); + } + + if (parent_select || parent_option_optgroup) { if (RuntimeEnabledFeatures::InputInSelectEnabled()) { ProcessFakeEndTag(HTMLTag::kSelect); } @@ -750,6 +762,7 @@ UseCounter::Count(tree_.CurrentNode()->GetDocument(), WebFeature::kInputParsedAncestorSelect); } + if (!RuntimeEnabledFeatures::InputInSelectEnabled()) { ProcessFakeEndTag(HTMLTag::kSelect); }
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc index 074ddc3f..1b4e2e0 100644 --- a/third_party/blink/renderer/core/layout/layout_box.cc +++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -882,13 +882,19 @@ SnapAreaDidChange(); } +bool LayoutBox::ShouldBeHandledAsFloating(const ComputedStyle& style) const { + NOT_DESTROYED(); + return style.IsFloating() && + ToPositionedState(style.GetPosition()) != kIsOutOfFlowPositioned && + !style.IsInsideDisplayIgnoringFloatingChildren(); +} + void LayoutBox::UpdateFromStyle() { NOT_DESTROYED(); LayoutBoxModelObject::UpdateFromStyle(); const ComputedStyle& style_to_use = StyleRef(); - SetFloating(style_to_use.IsFloating() && !IsOutOfFlowPositioned() && - !style_to_use.IsInsideDisplayIgnoringFloatingChildren()); + SetFloating(ShouldBeHandledAsFloating(style_to_use)); SetHasTransformRelatedProperty( IsSVGChild() ? style_to_use.HasTransformRelatedPropertyForSVG() : style_to_use.HasTransformRelatedProperty()); @@ -3010,7 +3016,8 @@ // The spec says that column-span only applies to in-flow block-level // elements. - if (IsInline() || IsFloatingOrOutOfFlowPositioned()) { + if (ShouldBeHandledAsInline() || ShouldBeHandledAsFloating() || + ToPositionedState() == kIsOutOfFlowPositioned) { return false; }
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h index 73f354c..3ca88d7c 100644 --- a/third_party/blink/renderer/core/layout/layout_box.h +++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -1253,6 +1253,11 @@ void StyleWillChange(StyleDifference, const ComputedStyle& new_style) override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; + virtual bool ShouldBeHandledAsFloating(const ComputedStyle& style) const; + bool ShouldBeHandledAsFloating() const { + NOT_DESTROYED(); + return ShouldBeHandledAsFloating(StyleRef()); + } void UpdateFromStyle() override; void InLayoutNGInlineFormattingContextWillChange(bool) final;
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc index 0a3a10c..12ff2db 100644 --- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc +++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -475,7 +475,7 @@ const ComputedStyle& style = StyleRef(); SetHasBoxDecorationBackground(style.HasBoxDecorationBackground()); SetInline(ShouldBeHandledAsInline(style)); - SetPositionState(style.GetPosition()); + SetPositionState(ToPositionedState(style.GetPosition())); SetHorizontalWritingMode(style.IsHorizontalWritingMode()); const bool is_fixed_container = ComputeIsFixedContainer(style);
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.h b/third_party/blink/renderer/core/layout/layout_box_model_object.h index 4ea00d6..106f3806 100644 --- a/third_party/blink/renderer/core/layout/layout_box_model_object.h +++ b/third_party/blink/renderer/core/layout/layout_box_model_object.h
@@ -430,7 +430,11 @@ const PhysicalOffset& additional_offset, OutlineType) const; - bool ShouldBeHandledAsInline(const ComputedStyle& style) const; + virtual bool ShouldBeHandledAsInline(const ComputedStyle&) const; + bool ShouldBeHandledAsInline() const { + NOT_DESTROYED(); + return ShouldBeHandledAsInline(StyleRef()); + } void StyleWillChange(StyleDifference, const ComputedStyle& new_style) override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override;
diff --git a/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h b/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h index 456fc0d..8ed6c83 100644 --- a/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h +++ b/third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h
@@ -97,6 +97,14 @@ bool suppress_use_counters); private: + bool ShouldBeHandledAsInline(const ComputedStyle&) const override { + NOT_DESTROYED(); + return false; + } + bool ShouldBeHandledAsFloating(const ComputedStyle&) const override { + NOT_DESTROYED(); + return false; + } void UpdateFromStyle() override; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; void ImageChanged(WrappedImagePtr, CanDeferInvalidation) override;
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc index 1a3e9ce..782f499a 100644 --- a/third_party/blink/renderer/core/layout/layout_inline.cc +++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -162,11 +162,6 @@ NOT_DESTROYED(); LayoutBoxModelObject::UpdateFromStyle(); - // This is needed (at a minimum) for LayoutSVGInline, which (including - // subclasses) is constructed for svg:a, svg:textPath, and svg:tspan, - // regardless of CSS 'display'. - SetInline(true); - // FIXME: Support transforms and reflections on inline flows someday. SetHasTransformRelatedProperty(false); SetHasReflection(false);
diff --git a/third_party/blink/renderer/core/layout/layout_inline.h b/third_party/blink/renderer/core/layout/layout_inline.h index 371524e..fee6359 100644 --- a/third_party/blink/renderer/core/layout/layout_inline.h +++ b/third_party/blink/renderer/core/layout/layout_inline.h
@@ -325,6 +325,13 @@ void AddDraggableRegions(Vector<DraggableRegionValue>&) final; + bool ShouldBeHandledAsInline(const ComputedStyle&) const override { + NOT_DESTROYED(); + // This is needed (at a minimum) for LayoutSVGInline, which (including + // subclasses) is constructed for svg:a, svg:textPath, and svg:tspan, + // regardless of CSS 'display'. + return true; + } void UpdateFromStyle() final; bool AnonymousHasStylePropagationOverride() final { NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h index ce13038..ee6f19c7 100644 --- a/third_party/blink/renderer/core/layout/layout_object.h +++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -1949,7 +1949,7 @@ // which is already marked for subtree recalc. void InvalidateSubtreePositionTry(bool mark_style_dirty); - private: + protected: enum PositionedState { kIsStaticallyPositioned = 0, kIsRelativelyPositioned = 1, @@ -1958,7 +1958,7 @@ }; public: - void SetPositionState(EPosition position) { + PositionedState ToPositionedState(EPosition position) const { NOT_DESTROYED(); DCHECK( (position != EPosition::kAbsolute && position != EPosition::kFixed) || @@ -1967,22 +1967,27 @@ // IsOutOfFlowPositioned, saving one bit. switch (position) { case EPosition::kStatic: - positioned_state_ = kIsStaticallyPositioned; - break; + return kIsStaticallyPositioned; case EPosition::kRelative: - positioned_state_ = kIsRelativelyPositioned; - break; + return kIsRelativelyPositioned; case EPosition::kAbsolute: case EPosition::kFixed: - positioned_state_ = kIsOutOfFlowPositioned; - break; + return kIsOutOfFlowPositioned; case EPosition::kSticky: - positioned_state_ = kIsStickyPositioned; - break; + return kIsStickyPositioned; default: NOTREACHED(); } } + PositionedState ToPositionedState() const { + NOT_DESTROYED(); + return ToPositionedState(StyleRef().GetPosition()); + } + + void SetPositionState(PositionedState position) { + NOT_DESTROYED(); + positioned_state_ = position; + } void ClearPositionedState() { NOT_DESTROYED(); positioned_state_ = kIsStaticallyPositioned;
diff --git a/third_party/blink/renderer/core/layout/layout_view.cc b/third_party/blink/renderer/core/layout/layout_view.cc index 2cf13ab..15f5382 100644 --- a/third_party/blink/renderer/core/layout/layout_view.cc +++ b/third_party/blink/renderer/core/layout/layout_view.cc
@@ -95,7 +95,7 @@ SetIntrinsicLogicalWidthsDirty(kMarkOnlyThis); - SetPositionState(EPosition::kAbsolute); // to 0,0 :) + SetPositionState(kIsOutOfFlowPositioned); // Update the cached bit here since the Document is made the effective root // scroller before we've created the layout tree.
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc index 1a20a279..19e91618 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_block.cc
@@ -71,12 +71,6 @@ LayoutBlockFlow::WillBeRemovedFromTree(); } -void LayoutSVGBlock::UpdateFromStyle() { - NOT_DESTROYED(); - LayoutBlockFlow::UpdateFromStyle(); - SetFloating(false); -} - bool LayoutSVGBlock::CheckForImplicitTransformChange( const SVGLayoutInfo& layout_info, bool bbox_changed) const {
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_block.h b/third_party/blink/renderer/core/layout/svg/layout_svg_block.h index 4ec4c1a..0e64806 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_block.h +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_block.h
@@ -86,7 +86,10 @@ void UpdateTransformBeforeLayout(); bool UpdateTransformAfterLayout(const SVGLayoutInfo&, bool bounds_changed); void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override; - void UpdateFromStyle() override; + bool ShouldBeHandledAsFloating(const ComputedStyle&) const override { + NOT_DESTROYED(); + return false; + } private: // LayoutSVGBlock subclasses should use GetElement() instead.
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc index 9416525d..6ec783cd 100644 --- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -890,6 +890,9 @@ return false; } ImageResource* image_resource = To<ImageResource>(resource); + if (image_resource->RequestedSpeculativeDecode()) { + return false; + } Image* image = image_resource->GetContent()->GetImage(); if (IsA<SVGImage>(image)) { return false; @@ -899,6 +902,7 @@ } PaintImage paint_image = image->PaintImageForCurrentFrame(); if (paint_image) { + image_resource->OnRequestSpeculativeDecode(); SkM44 matrix; gfx::Size image_size(image->width(), image->height()); gfx::SizeF content_size(image_resource->GetContent()->MaxSize());
diff --git a/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc b/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc index 23b1fe3..e1fe958 100644 --- a/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc +++ b/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.cc
@@ -254,6 +254,8 @@ // adding, or removing stylesheets, while at the same time have different // media query evaluations in the different documents should be quite rare. + parsed_style_sheet_cache_->SetIsUsedFromResourceCache(); + DCHECK(!parsed_style_sheet_cache_->IsLoading()); return parsed_style_sheet_cache_.Get(); }
diff --git a/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc b/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc index 9525012..e1671251 100644 --- a/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc +++ b/third_party/blink/renderer/core/loader/resource/css_style_sheet_resource_test.cc
@@ -13,6 +13,7 @@ #include "third_party/blink/renderer/core/css/parser/css_parser_context.h" #include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h" #include "third_party/blink/renderer/core/css/style_sheet_contents.h" +#include "third_party/blink/renderer/core/css/style_sheet_list.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/execution_context/security_context.h" #include "third_party/blink/renderer/core/html/html_iframe_element.h" @@ -260,5 +261,72 @@ EXPECT_EQ(target2->GetComputedStyle()->Opacity(), 0.3f); } +// https://crbug.com/417406834 +TEST_F(CSSStyleSheetResourceSimTest, CopyOnWriteSharedContentsCrash) { + SimRequest main_resource("https://example.com", "text/html"); + SimRequest::Params params; + params.response_http_headers = {{"Cache-Control", "max-age=3600"}}; + SimSubresourceRequest css_resource("https://example.com/style.css", + "text/css", params); + SimRequest frame_resource("https://example.com/frame.html", "text/html"); + + LoadURL("https://example.com"); + + // This is intentionally in quirks mode, while the iframe's document + // is in standards mode. This is to make that we're using different + // CSSParserContexts against the same resource. + main_resource.Complete(R"HTML( + <link rel="stylesheet" href="style.css"> + <link rel="stylesheet" href="style.css"> + Some content + <iframe src="frame.html"></iframe> + )HTML"); + + test::RunPendingTasks(); + + css_resource.Complete(R"HTML( + div { color: green; } + )HTML"); + + // This frame exists to access style.css under a CSSParserContext which is + // different from the parent document's CSSParserContext. + frame_resource.Complete(R"HTML( + <!DOCTYPE html> + <link rel="stylesheet" href="style.css"> + <div>iframe</div> + )HTML"); + + test::RunPendingTasks(); + + ASSERT_EQ(2u, GetDocument().StyleSheets().length()); + auto* sheet0 = To<CSSStyleSheet>(GetDocument().StyleSheets().item(0)); + auto* sheet1 = To<CSSStyleSheet>(GetDocument().StyleSheets().item(1)); + + // Make sure CSSStyleRule wrappers exist. (Don't crash.) + for (wtf_size_t i = 0; i < sheet0->length(); ++i) { + ASSERT_TRUE(sheet0->ItemInternal(i)); + } + for (wtf_size_t i = 0; i < sheet1->length(); ++i) { + ASSERT_TRUE(sheet1->ItemInternal(i)); + } + + EXPECT_EQ(sheet0->Contents(), sheet1->Contents()); + + DummyExceptionStateForTesting exception_state; + // Copy-on-write should occur here: + sheet1->insertRule("div { left:0; }", /*index=*/0u, exception_state); + + // Access CSSStyleRule wrappers again. (Don't crash.) + for (wtf_size_t i = 0; i < sheet0->length(); ++i) { + ASSERT_TRUE(sheet0->ItemInternal(i)); + } + for (wtf_size_t i = 0; i < sheet1->length(); ++i) { + ASSERT_TRUE(sheet1->ItemInternal(i)); + } + + // Copy-on-write should have happened: + EXPECT_NE(sheet0->Contents(), sheet1->Contents()); +} + } // namespace } // namespace blink
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource.h b/third_party/blink/renderer/core/loader/resource/image_resource.h index 3bef43f..825ff98 100644 --- a/third_party/blink/renderer/core/loader/resource/image_resource.h +++ b/third_party/blink/renderer/core/loader/resource/image_resource.h
@@ -108,6 +108,11 @@ void OnePartInMultipartReceived(const ResourceResponse&) final; void MultipartDataReceived(base::span<const uint8_t> bytes) final; + bool RequestedSpeculativeDecode() const { + return requested_speculative_decode_; + } + void OnRequestSpeculativeDecode() { requested_speculative_decode_ = true; } + // If the ImageResource came from a user agent CSS stylesheet then we should // flag it so that it can persist beyond navigation. void FlagAsUserAgentResource(); @@ -158,6 +163,8 @@ bool is_pending_flushing_ = false; + bool requested_speculative_decode_ = false; + V8ExternalMemoryAccounter external_memory_accounter_; };
diff --git a/third_party/blink/renderer/modules/ai/ai_utils.cc b/third_party/blink/renderer/modules/ai/ai_utils.cc index d3ecb0b..26f988b 100644 --- a/third_party/blink/renderer/modules/ai/ai_utils.cc +++ b/third_party/blink/renderer/modules/ai/ai_utils.cc
@@ -296,4 +296,17 @@ return canonicalized_languages; } +RunOnDestruction::RunOnDestruction(base::OnceClosure callback) + : callback_(std::move(callback)) {} + +RunOnDestruction::~RunOnDestruction() { + if (!callback_.is_null()) { + std::move(callback_).Run(); + } +} + +void RunOnDestruction::Reset() { + callback_.Reset(); +} + } // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/ai_utils.h b/third_party/blink/renderer/modules/ai/ai_utils.h index c85268b..cbee176 100644 --- a/third_party/blink/renderer/modules/ai/ai_utils.h +++ b/third_party/blink/renderer/modules/ai/ai_utils.h
@@ -117,6 +117,24 @@ v8::Isolate* isolate, const Vector<String>& languages); +// Runs `callback` on destruction unless `Reset` is called. +class RunOnDestruction { + public: + explicit RunOnDestruction(base::OnceClosure callback); + ~RunOnDestruction(); + + RunOnDestruction(const RunOnDestruction&) = delete; + RunOnDestruction& operator=(const RunOnDestruction&) = delete; + + RunOnDestruction(RunOnDestruction&& other) = default; + RunOnDestruction& operator=(RunOnDestruction&& other) = default; + + void Reset(); + + private: + base::OnceClosure callback_; +}; + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_AI_AI_UTILS_H_
diff --git a/third_party/blink/renderer/modules/ai/ai_writing_assistance_create_client.h b/third_party/blink/renderer/modules/ai/ai_writing_assistance_create_client.h index f2dab28..f0a74bde 100644 --- a/third_party/blink/renderer/modules/ai/ai_writing_assistance_create_client.h +++ b/third_party/blink/renderer/modules/ai/ai_writing_assistance_create_client.h
@@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h" #include "third_party/blink/renderer/modules/ai/ai_context_observer.h" +#include "third_party/blink/renderer/modules/ai/ai_interface_proxy.h" #include "third_party/blink/renderer/modules/ai/ai_utils.h" #include "third_party/blink/renderer/modules/ai/create_monitor.h" #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h" @@ -78,9 +79,14 @@ // AIMojoCreateClient: void OnResult(mojo::PendingRemote<AIMojoClient> pending_remote) override { + // Call `Cleanup` when this function returns. + RunOnDestruction run_on_destruction(WTF::BindOnce( + &AIWritingAssistanceCreateClient::Cleanup, WrapWeakPersistent(this))); + if (!this->GetResolver()) { return; } + if (pending_remote && monitor_) { // Ensure that a download completion event is sent. monitor_->OnDownloadProgressUpdate(kNormalizedDownloadProgressMax, @@ -96,10 +102,13 @@ DOMExceptionCode::kInvalidStateError, kExceptionMessageUnableToCreateSession); } - this->Cleanup(); } void OnError(mojom::blink::AIManagerCreateClientError error) override { + // Call `Cleanup` when this function returns. + RunOnDestruction run_on_destruction(WTF::BindOnce( + &AIWritingAssistanceCreateClient::Cleanup, WrapWeakPersistent(this))); + if (!this->GetResolver()) { return; } @@ -127,7 +136,6 @@ break; } } - this->Cleanup(); } // AIContextObserver:
diff --git a/third_party/blink/renderer/modules/ai/language_model.cc b/third_party/blink/renderer/modules/ai/language_model.cc index 69c1f291..55474188 100644 --- a/third_party/blink/renderer/modules/ai/language_model.cc +++ b/third_party/blink/renderer/modules/ai/language_model.cc
@@ -11,10 +11,8 @@ #include "base/types/expected_macros.h" #include "base/types/pass_key.h" #include "services/on_device_model/public/mojom/on_device_model.mojom-blink.h" -#include "third_party/blink/public/mojom/ai/ai_language_model.mojom-blink-forward.h" #include "third_party/blink/public/mojom/ai/ai_language_model.mojom-blink.h" #include "third_party/blink/public/mojom/ai/model_streaming_responder.mojom-blink.h" -#include "third_party/blink/renderer/bindings/core/v8/idl_types.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_language_model_create_options.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_language_model_prompt_dict.h" @@ -27,6 +25,8 @@ #include "third_party/blink/renderer/core/fileapi/blob.h" #include "third_party/blink/renderer/core/fileapi/file_reader_client.h" #include "third_party/blink/renderer/core/frame/web_feature.h" +#include "third_party/blink/renderer/core/streams/readable_stream_default_controller.h" +#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h" #include "third_party/blink/renderer/modules/ai/ai_context_observer.h" #include "third_party/blink/renderer/modules/ai/ai_interface_proxy.h" #include "third_party/blink/renderer/modules/ai/ai_metrics.h" @@ -54,6 +54,11 @@ using AILanguageModelPromptContentOrError = std::variant<mojom::blink::AILanguageModelPromptContentPtr, DOMException*>; +void RejectResolver(ScriptPromiseResolverBase* resolver, + const ScriptValue& value) { + resolver->Reject(value); +} + class CloneLanguageModelClient : public GarbageCollected<CloneLanguageModelClient>, public mojom::blink::AIManagerCreateLanguageModelClient, @@ -130,8 +135,7 @@ ScriptPromiseResolver<IDLUndefined>* resolver, WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts, AbortSignal* signal, - base::RepeatingClosure overflow_callback, - base::PassKey<LanguageModel>) + base::RepeatingClosure overflow_callback) : AIContextObserver(script_state, language_model, resolver, signal), language_model_(language_model), overflow_callback_(overflow_callback), @@ -148,6 +152,19 @@ AppendClient(const AppendClient&) = delete; AppendClient& operator=(const AppendClient&) = delete; + // Helper function to make MeasureInputUsageClient with no return type. + static void Create( + ScriptState* script_state, + LanguageModel* language_model, + ScriptPromiseResolver<IDLUndefined>* resolver, + AbortSignal* signal, + base::RepeatingClosure overflow_callback, + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> input) { + MakeGarbageCollected<AppendClient>( + std::move(script_state), std::move(language_model), std::move(resolver), + std::move(input), std::move(signal), std::move(overflow_callback)); + } + void Trace(Visitor* visitor) const override { AIContextObserver::Trace(visitor); visitor->Trace(language_model_); @@ -408,17 +425,17 @@ const V8LanguageModelPromptInput* input, const LanguageModelPromptOptions* options, ExceptionState& exception_state) { - std::optional<ValidateAndProcessPromptInputResult> processed_input = - ValidateAndProcessPromptInput(script_state, input, options, - exception_state); - if (!processed_input.has_value()) { + std::optional<on_device_model::mojom::blink::ResponseConstraintPtr> + processed_constraint = ValidateAndProcessPromptInput( + script_state, input, options, exception_state); + if (!processed_constraint.has_value()) { return EmptyPromise(); } base::UmaHistogramEnumeration(AIMetrics::GetAIAPIUsageMetricName( AIMetrics::AISessionType::kLanguageModel), AIMetrics::AIAPI::kSessionPrompt); - ScriptPromiseResolver<IDLString>* resolver = + auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLString>>(script_state); auto promise = resolver->Promise(); @@ -430,10 +447,12 @@ WTF::BindRepeating(&LanguageModel::OnQuotaOverflow, WrapWeakPersistent(this))); - language_model_remote_->Prompt( - std::move(processed_input->processed_prompts), - std::move(processed_input->processed_constraint), - std::move(pending_remote)); + ConvertPromptInputsToMojo( + script_state, options->getSignalOr(nullptr), input, input_types_, + WTF::BindOnce(&LanguageModel::ExecutePrompt, WrapPersistent(this), + std::move(*processed_constraint), + std::move(pending_remote)), + WTF::BindOnce(&RejectResolver, WrapPersistent(resolver))); return promise; } @@ -442,10 +461,10 @@ const V8LanguageModelPromptInput* input, const LanguageModelPromptOptions* options, ExceptionState& exception_state) { - std::optional<ValidateAndProcessPromptInputResult> processed_input = - ValidateAndProcessPromptInput(script_state, input, options, - exception_state); - if (!processed_input.has_value()) { + std::optional<on_device_model::mojom::blink::ResponseConstraintPtr> + processed_constraint = ValidateAndProcessPromptInput( + script_state, input, options, exception_state); + if (!processed_constraint.has_value()) { return nullptr; } base::UmaHistogramEnumeration(AIMetrics::GetAIAPIUsageMetricName( @@ -460,14 +479,66 @@ WTF::BindRepeating(&LanguageModel::OnQuotaOverflow, WrapWeakPersistent(this))); - language_model_remote_->Prompt( - std::move(processed_input->processed_prompts), - std::move(processed_input->processed_constraint), std::move(remote)); + ConvertPromptInputsToMojo( + script_state, options->getSignalOr(nullptr), input, input_types_, + WTF::BindOnce(&LanguageModel::ExecutePrompt, WrapPersistent(this), + std::move(*processed_constraint), std::move(remote)), + WTF::BindOnce( + [](ReadableStream* readable_stream, ScriptState* script_state, + const ScriptValue& error) { + ReadableStreamController* controller = + readable_stream->GetController(); + // TODO(crbug.com/414906618): Make this more elegant (i.e. + // crrev.com/c/6528669). + CHECK(controller->IsDefaultController()); + MakeGarbageCollected< + ReadableStreamDefaultControllerWithScriptScope>( + script_state, To<ReadableStreamDefaultController>(controller)) + ->Error(error.V8ValueFor(script_state)); + }, + WrapPersistent(stream), WrapPersistent(script_state))); return stream; } +void LanguageModel::ExecutePrompt( + on_device_model::mojom::blink::ResponseConstraintPtr constraint, + mojo::PendingRemote<mojom::blink::ModelStreamingResponder> + pending_responder, + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts) { + language_model_remote_->Prompt(std::move(prompts), std::move(constraint), + std::move(pending_responder)); +} -std::optional<LanguageModel::ValidateAndProcessPromptInputResult> +void LanguageModel::ExecuteMeasureInputUsage( + ScriptPromiseResolver<IDLDouble>* resolver, + AbortSignal* signal, + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts) { + language_model_remote_->MeasureInputUsage( + std::move(prompts), + WTF::BindOnce( + [](ScriptPromiseResolver<IDLDouble>* resolver, AbortSignal* signal, + std::optional<uint32_t> usage) { + ExecutionContext* context = resolver->GetExecutionContext(); + if (!context) { + return; + } + if (signal && signal->aborted()) { + resolver->Reject(signal->reason(resolver->GetScriptState())); + return; + } + if (!usage.has_value()) { + resolver->Reject( + DOMException::Create(kExceptionMessageUnableToCalculateUsage, + DOMException::GetErrorName( + DOMExceptionCode::kOperationError))); + return; + } + resolver->Resolve(static_cast<double>(usage.value())); + }, + WrapPersistent(resolver), WrapPersistent(signal))); +} + +std::optional<on_device_model::mojom::blink::ResponseConstraintPtr> LanguageModel::ValidateAndProcessPromptInput( ScriptState* script_state, const V8LanguageModelPromptInput* input, @@ -495,12 +566,6 @@ return std::nullopt; } - auto prompts = BuildPrompts(input, script_state, exception_state, - GetExecutionContext(), input_types_); - if (!prompts.has_value()) { - return std::nullopt; - } - // TODO(crbug.com/411470034): Aggregate other input type sizes for UMA. if (input->IsString()) { base::UmaHistogramCounts1M( @@ -514,10 +579,7 @@ return std::nullopt; } - return ValidateAndProcessPromptInputResult{ - .processed_constraint = std::move(constraint), - .processed_prompts = std::move(prompts).value(), - }; + return constraint; } ScriptPromise<IDLUndefined> LanguageModel::append( @@ -540,15 +602,6 @@ exception_state.ThrowTypeError("Input type not supported"); return promise; } - - auto prompts = BuildPrompts(input, script_state, exception_state, - GetExecutionContext(), input_types_); - if (!prompts.has_value()) { - // `BuildPrompts` will throw the exception if it fails. - resolver->Reject(); - return promise; - } - if (!language_model_remote_) { ThrowSessionDestroyedException(exception_state); return promise; @@ -559,11 +612,14 @@ return promise; } - MakeGarbageCollected<AppendClient>( - script_state, this, resolver, std::move(prompts).value(), signal, - WTF::BindRepeating(&LanguageModel::OnQuotaOverflow, - WrapWeakPersistent(this)), - base::PassKey<LanguageModel>()); + ConvertPromptInputsToMojo( + script_state, options->getSignalOr(nullptr), input, input_types_, + WTF::BindOnce(&AppendClient::Create, WrapPersistent(script_state), + WrapPersistent(this), WrapPersistent(resolver), + WrapPersistent(signal), + WTF::BindRepeating(&LanguageModel::OnQuotaOverflow, + WrapWeakPersistent(this))), + WTF::BindOnce(&RejectResolver, WrapPersistent(resolver))); return promise; } @@ -627,12 +683,6 @@ MakeGarbageCollected<ScriptPromiseResolver<IDLDouble>>(script_state); auto promise = resolver->Promise(); - auto prompts = BuildPrompts(input, script_state, exception_state, - GetExecutionContext(), input_types_); - if (!prompts.has_value()) { - return promise; - } - if (!language_model_remote_) { ThrowSessionDestroyedException(exception_state); return promise; @@ -644,30 +694,12 @@ return promise; } - language_model_remote_->MeasureInputUsage( - std::move(prompts).value(), - WTF::BindOnce( - [](ScriptPromiseResolver<IDLDouble>* resolver, AbortSignal* signal, - std::optional<uint32_t> usage) { - ExecutionContext* context = resolver->GetExecutionContext(); - if (!context) { - return; - } - if (signal && signal->aborted()) { - resolver->Reject(signal->reason(resolver->GetScriptState())); - return; - } - if (!usage.has_value()) { - resolver->Reject( - DOMException::Create(kExceptionMessageUnableToCalculateUsage, - DOMException::GetErrorName( - DOMExceptionCode::kOperationError))); - return; - } - resolver->Resolve(static_cast<double>(usage.value())); - }, - WrapPersistent(resolver), - WrapPersistent(options->getSignalOr(nullptr)))); + ConvertPromptInputsToMojo( + script_state, options->getSignalOr(nullptr), input, input_types_, + WTF::BindOnce(&LanguageModel::ExecuteMeasureInputUsage, + WrapPersistent(this), WrapPersistent(resolver), + WrapPersistent(signal)), + WTF::BindOnce(&RejectResolver, WrapPersistent(resolver))); return promise; }
diff --git a/third_party/blink/renderer/modules/ai/language_model.h b/third_party/blink/renderer/modules/ai/language_model.h index b80df7e..fd6ea57 100644 --- a/third_party/blink/renderer/modules/ai/language_model.h +++ b/third_party/blink/renderer/modules/ai/language_model.h
@@ -102,14 +102,24 @@ mojom::blink::ModelExecutionContextInfoPtr context_info); void OnQuotaOverflow(); - struct ValidateAndProcessPromptInputResult { - on_device_model::mojom::blink::ResponseConstraintPtr processed_constraint; - WTF::Vector<mojom::blink::AILanguageModelPromptPtr> processed_prompts; - }; + // Helper to make AILanguageModelProxy::Prompt compatible with + // ConvertPromptInputsToMojo callback. + void ExecutePrompt( + on_device_model::mojom::blink::ResponseConstraintPtr constraint, + mojo::PendingRemote<mojom::blink::ModelStreamingResponder> + pending_responder, + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts); - // Validates and processed prompt input and returns the processed results. + // Helper to make AILanguageModelProxy::MeasureInputUsage compatible with + // ConvertPromptInputsToMojo callback. + void ExecuteMeasureInputUsage( + ScriptPromiseResolver<IDLDouble>* resolver, + AbortSignal* signal, + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts); + + // Validates and processed prompt input and returns the processed constraints. // Returns std::nullopt on failure. - std::optional<ValidateAndProcessPromptInputResult> + std::optional<on_device_model::mojom::blink::ResponseConstraintPtr> ValidateAndProcessPromptInput(ScriptState* script_state, const V8LanguageModelPromptInput* input, const LanguageModelPromptOptions* options,
diff --git a/third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc b/third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc index 6c96814..9d34c9d0 100644 --- a/third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc +++ b/third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc
@@ -30,18 +30,219 @@ namespace blink { namespace { -mojom::blink::AILanguageModelPromptContentPtr ToMojo(String prompt) { +using ResolveCallback = base::OnceCallback<void( + WTF::Vector<mojom::blink::AILanguageModelPromptPtr>)>; +using RejectCallback = base::OnceCallback<void(const ScriptValue& error)>; + +// Helper class for converting types and managing async processing. +class LanguageModelPromptBuilder + : public GarbageCollected<LanguageModelPromptBuilder> { + public: + explicit LanguageModelPromptBuilder( + ScriptState* script_state, + AbortSignal* abort_signal, + WTF::HashSet<mojom::blink::AILanguageModelPromptType> allowed_types, + const V8LanguageModelPromptInput* input, + ResolveCallback resolve_callback, + RejectCallback reject_callback); + void Trace(Visitor*) const; + + private: + void Build(const V8LanguageModelPromptInput* input); + // Called to reject the promise and return an error to the callback. + void Reject(DOMException* value); + void Reject(ScriptValue value); + // Resolve the promise and invoke the callback with `processed_prompts_`. + void Resolve(); + // Process a single dictionary entry in the prompt input array. + void ProcessEntry(const LanguageModelPromptDict* entry); + // Process a single content entry and convert it to a mojo struct; returns + // nullptr on error. + mojom::blink::AILanguageModelPromptContentPtr ProcessContent( + const V8LanguageModelPromptType& type, + V8LanguageModelPromptContent* content); + + // ToMojo converts various V8 types to AILanguageModelPromptContent. + mojom::blink::AILanguageModelPromptContentPtr ToMojo(String prompt); + mojom::blink::AILanguageModelPromptContentPtr ToMojo( + AudioBuffer* audio_buffer); + mojom::blink::AILanguageModelPromptContentPtr ToMojo( + base::span<uint8_t> audio_bytes); + mojom::blink::AILanguageModelPromptContentPtr ToMojo(Blob* blob); + mojom::blink::AILanguageModelPromptContentPtr ToMojo( + V8ImageBitmapSource* bitmap); + + SelfKeepAlive<LanguageModelPromptBuilder> keep_alive_{this}; + WTF::Vector<mojom::blink::AILanguageModelPromptPtr> processed_prompts_; + Member<ScriptState> script_state_; + Member<AbortSignal> abort_signal_; + WTF::HashSet<mojom::blink::AILanguageModelPromptType> allowed_types_; + + ResolveCallback resolve_callback_; + RejectCallback reject_callback_; +}; + +LanguageModelPromptBuilder::LanguageModelPromptBuilder( + ScriptState* script_state, + AbortSignal* abort_signal, + WTF::HashSet<mojom::blink::AILanguageModelPromptType> allowed_types, + const V8LanguageModelPromptInput* input, + ResolveCallback resolve_callback, + RejectCallback reject_callback) + : script_state_(script_state), + abort_signal_(abort_signal), + allowed_types_(allowed_types), + resolve_callback_(std::move(resolve_callback)), + reject_callback_(std::move(reject_callback)) { + Build(input); +} + +void LanguageModelPromptBuilder::Reject(DOMException* value) { + Reject(ScriptValue::From(script_state_, value)); +} + +void LanguageModelPromptBuilder::Reject(ScriptValue value) { + if (resolve_callback_.is_null() || reject_callback_.is_null()) { + return; // Already rejected or resolved. + } + std::move(reject_callback_).Run(value); + keep_alive_.Clear(); +} + +void LanguageModelPromptBuilder::Resolve() { + if (resolve_callback_.is_null() || reject_callback_.is_null()) { + return; // Already rejected or resolved. + } + std::move(resolve_callback_).Run(std::move(processed_prompts_)); + keep_alive_.Clear(); +} + +void LanguageModelPromptBuilder::Build( + const V8LanguageModelPromptInput* input) { + HeapVector<Member<V8UnionLanguageModelPromptDictOrString>> sequence; + if (input->IsLanguageModelPromptDictOrStringSequence()) { + sequence = std::move(input->GetAsLanguageModelPromptDictOrStringSequence()); + } + if (input->IsV8LanguageModelPrompt()) { + auto* entry = input->GetAsV8LanguageModelPrompt(); + sequence.push_back(entry); + } + for (const auto& entry : sequence) { + if (reject_callback_.is_null()) { + return; + } + if (abort_signal_ && abort_signal_->aborted()) { + Reject(abort_signal_->reason(script_state_)); + return; + } + + if (entry->GetContentType() == + V8LanguageModelPrompt::ContentType::kString) { + LanguageModelPromptDict* dict = + MakeGarbageCollected<LanguageModelPromptDict>(); + V8LanguageModelPromptContent* content = + MakeGarbageCollected<V8LanguageModelPromptContent>( + entry->GetAsString()); + dict->setRole(V8LanguageModelPromptRole::Enum::kUser); + dict->setType(V8LanguageModelPromptType::Enum::kText); + dict->setContent(std::move(content)); + ProcessEntry(dict); + continue; + } + CHECK(entry->GetContentType() == + V8LanguageModelPrompt::ContentType::kLanguageModelPromptDict); + ProcessEntry(entry->GetAsLanguageModelPromptDict()); + } + // TODO(crbug.com/414906618): This just synchronously resolves for now, but + // will asynchronously resolve in the future. + Resolve(); +} + +void LanguageModelPromptBuilder::Trace(Visitor* visitor) const { + visitor->Trace(script_state_); + visitor->Trace(abort_signal_); +} + +void LanguageModelPromptBuilder::ProcessEntry( + const LanguageModelPromptDict* entry) { + CHECK(!resolve_callback_.is_null() && !reject_callback_.is_null()); + mojom::blink::AILanguageModelPromptContentPtr content = + ProcessContent(entry->type(), entry->content()); + auto mojo_prompt = mojom::blink::AILanguageModelPrompt::New(); + mojo_prompt->role = LanguageModel::ConvertRoleToMojo(entry->role()); + mojo_prompt->content = std::move(content); + processed_prompts_.push_back(std::move(mojo_prompt)); +} + +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ProcessContent( + const V8LanguageModelPromptType& type, + V8LanguageModelPromptContent* content) { + switch (type.AsEnum()) { + case V8LanguageModelPromptType::Enum::kText: + return ToMojo(content->GetAsString()); + case V8LanguageModelPromptType::Enum::kImage: { + if (!allowed_types_.Contains( + mojom::blink::AILanguageModelPromptType::kImage)) { + Reject(DOMException::Create( + "Image not supported. Session is not initialized with image " + "support.", + DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError))); + return nullptr; + } + + UseCounter::Count(ExecutionContext::From(script_state_), + WebFeature::kLanguageModel_Prompt_Input_Image); + if (content->IsV8ImageBitmapSource()) { + return ToMojo(content->GetAsV8ImageBitmapSource()); + } + Reject(DOMException::Create( + "Unsupported image type", + DOMException::GetErrorName(DOMExceptionCode::kSyntaxError))); + return nullptr; + } + case V8LanguageModelPromptType::Enum::kAudio: { + if (!allowed_types_.Contains( + mojom::blink::AILanguageModelPromptType::kAudio)) { + Reject(DOMException::Create( + "Audio not supported. Session is not initialized with audio " + "support.", + DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError))); + return nullptr; + } + UseCounter::Count(ExecutionContext::From(script_state_), + WebFeature::kLanguageModel_Prompt_Input_Audio); + switch (content->GetContentType()) { + case V8LanguageModelPromptContent::ContentType::kAudioBuffer: + return ToMojo(content->GetAsAudioBuffer()); + case V8LanguageModelPromptContent::ContentType::kBlob: + return ToMojo(content->GetAsBlob()); + case V8LanguageModelPromptContent::ContentType::kArrayBuffer: + return ToMojo(content->GetAsArrayBuffer()->Content()->ByteSpan()); + case V8LanguageModelPromptContent::ContentType::kArrayBufferView: + return ToMojo(content->GetAsArrayBufferView()->ByteSpan()); + default: + Reject(DOMException::Create( + "Unsupported audio content type", + DOMException::GetErrorName(DOMExceptionCode::kSyntaxError))); + return nullptr; + } + } + } +} + +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ToMojo(String prompt) { return mojom::blink::AILanguageModelPromptContent::NewText(prompt); } -mojom::blink::AILanguageModelPromptContentPtr ToMojo( - AudioBuffer* audio_buffer, - ExceptionState& exception_state) { +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ToMojo(AudioBuffer* audio_buffer) { if (audio_buffer->numberOfChannels() > 2) { // TODO(crbug.com/382180351): Support more than 2 channels. - exception_state.ThrowDOMException( - DOMExceptionCode::kSyntaxError, - "Audio with more than 2 channels is not supported."); + Reject(DOMException::Create( + "Audio with more than 2 channels is not supported.", + DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError))); return nullptr; } on_device_model::mojom::blink::AudioDataPtr audio_data = @@ -66,18 +267,17 @@ std::move(audio_data)); } -mojom::blink::AILanguageModelPromptContentPtr ToMojo( - base::span<uint8_t> audio_bytes, - ExecutionContext* execution_context, - ExceptionState& exception_state) { +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ToMojo(base::span<uint8_t> audio_bytes) { // TODO(crbug.com/401010825): Use the file sample rate. scoped_refptr<AudioBus> bus = AudioBus::CreateBusFromInMemoryAudioFile( audio_bytes, /*mix_to_mono=*/true, /*sample_rate=*/48000); if (!bus) { // TODO(crbug.com/409615288): This should throw a TypeError according to the // spec. - exception_state.ThrowDOMException(DOMExceptionCode::kDataError, - "Missing or invalid audio data."); + Reject(DOMException::Create( + "Missing or invalid audio data.", + DOMException::GetErrorName(DOMExceptionCode::kDataError))); return nullptr; } @@ -95,173 +295,60 @@ std::move(audio_data)); } -mojom::blink::AILanguageModelPromptContentPtr ToMojo( - Blob* blob, - ExecutionContext* execution_context, - ExceptionState& exception_state) { +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ToMojo(Blob* blob) { // TODO(crbug.com/382180351): Make blob reading async or alternatively // use FileReaderSync instead (fix linker and exception issues). SyncedFileReaderAccumulator* blobReader = MakeGarbageCollected<SyncedFileReaderAccumulator>(); auto [error_code, reader_data] = blobReader->Load( - blob->GetBlobDataHandle(), - execution_context->GetTaskRunner(TaskType::kFileReading)); + blob->GetBlobDataHandle(), ExecutionContext::From(script_state_) + ->GetTaskRunner(TaskType::kFileReading)); if (error_code != FileErrorCode::kOK) { - exception_state.ThrowDOMException(DOMExceptionCode::kDataError, - "Failed to read blob."); + Reject(DOMException::Create( + "Failed to read blob.", + DOMException::GetErrorName(DOMExceptionCode::kDataError))); return nullptr; } ArrayBufferContents audio_contents = std::move(reader_data).AsArrayBufferContents(); if (!audio_contents.IsValid()) { - exception_state.ThrowDOMException(DOMExceptionCode::kDataError, - "Failed to read blob."); + Reject(DOMException::Create( + "Failed to read contents of blob.", + DOMException::GetErrorName(DOMExceptionCode::kDataError))); return nullptr; } - return ToMojo(audio_contents.ByteSpan(), execution_context, exception_state); + return ToMojo(audio_contents.ByteSpan()); } -mojom::blink::AILanguageModelPromptContentPtr ToMojo( - V8ImageBitmapSource* bitmap, - ScriptState* script_state, - ExceptionState& exception_state) { +mojom::blink::AILanguageModelPromptContentPtr +LanguageModelPromptBuilder::ToMojo(V8ImageBitmapSource* bitmap) { + ExceptionState exception_state(nullptr); std::optional<SkBitmap> skia_bitmap = - GetBitmapFromV8ImageBitmapSource(script_state, bitmap, exception_state); - if (exception_state.HadException()) { + GetBitmapFromV8ImageBitmapSource(script_state_, bitmap, exception_state); + if (!skia_bitmap) { + CHECK(exception_state.HadException()); + Reject(DOMException::Create( + "Unable to get bitmap from image content", + DOMException::GetErrorName(DOMExceptionCode::kSyntaxError))); return nullptr; } return mojom::blink::AILanguageModelPromptContent::NewBitmap( skia_bitmap.value()); } - -mojom::blink::AILanguageModelPromptContentPtr ConvertPromptToMojoContent( - V8LanguageModelPromptType content_type, - const V8LanguageModelPromptContent* content, - ScriptState* script_state, - ExceptionState& exception_state, - ExecutionContext* execution_context, - WTF::HashSet<mojom::blink::AILanguageModelPromptType>& allowed_types) { - switch (content_type.AsEnum()) { - case V8LanguageModelPromptType::Enum::kText: - return ToMojo(content->GetAsString()); - case V8LanguageModelPromptType::Enum::kImage: - if (!allowed_types.Contains( - mojom::blink::AILanguageModelPromptType::kImage)) { - exception_state.ThrowDOMException( - DOMExceptionCode::kNotSupportedError, - "Image not supported. Session is not initialized with image " - "support."); - return nullptr; - } - UseCounter::Count(execution_context, - WebFeature::kLanguageModel_Prompt_Input_Image); - if (content->IsV8ImageBitmapSource()) { - return ToMojo(content->GetAsV8ImageBitmapSource(), script_state, - exception_state); - } - exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, - "Unsupported image content type"); - return nullptr; - case V8LanguageModelPromptType::Enum::kAudio: - if (!allowed_types.Contains( - mojom::blink::AILanguageModelPromptType::kAudio)) { - exception_state.ThrowDOMException( - DOMExceptionCode::kNotSupportedError, - "Audio not supported. Session is not initialized with audio " - "support."); - return nullptr; - } - UseCounter::Count(execution_context, - WebFeature::kLanguageModel_Prompt_Input_Audio); - switch (content->GetContentType()) { - case V8LanguageModelPromptContent::ContentType::kAudioBuffer: - return ToMojo(content->GetAsAudioBuffer(), exception_state); - case V8LanguageModelPromptContent::ContentType::kBlob: - return ToMojo(content->GetAsBlob(), execution_context, - exception_state); - case V8LanguageModelPromptContent::ContentType::kArrayBuffer: - return ToMojo(content->GetAsArrayBuffer()->Content()->ByteSpan(), - execution_context, exception_state); - case V8LanguageModelPromptContent::ContentType::kArrayBufferView: - return ToMojo(content->GetAsArrayBufferView()->ByteSpan(), - execution_context, exception_state); - default: - exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, - "Unsupported audio content type"); - return nullptr; - } - } -} - -// Return `prompt`'s content as a mojo struct or nullptr if there was an error. -mojom::blink::AILanguageModelPromptPtr ConvertPromptToMojo( - const V8LanguageModelPrompt* prompt, - ScriptState* script_state, - ExceptionState& exception_state, - ExecutionContext* execution_context, - WTF::HashSet<mojom::blink::AILanguageModelPromptType>& allowed_types) { - switch (prompt->GetContentType()) { - // Handle basic string prompt. - case V8LanguageModelPrompt::ContentType::kString: { - auto result = mojom::blink::AILanguageModelPrompt::New(); - result->content = ToMojo(prompt->GetAsString()); - if (result->content.is_null()) { - return nullptr; - } - result->role = mojom::blink::AILanguageModelPromptRole::kUser; - return result; - } - // Handle dictionary for multimodal input. - case V8LanguageModelPrompt::ContentType::kLanguageModelPromptDict: - LanguageModelPromptDict* dict = prompt->GetAsLanguageModelPromptDict(); - auto result = mojom::blink::AILanguageModelPrompt::New(); - result->content = ConvertPromptToMojoContent( - dict->type(), dict->content(), script_state, exception_state, - execution_context, allowed_types); - if (result->content.is_null()) { - return nullptr; - } - result->role = LanguageModel::ConvertRoleToMojo(dict->role()); - return result; - } -} - } // namespace -// Populates the `prompts` mojo struct vector from `input`. Returns an exception -// if some input was specified incorrectly or inaccessible, nullptr otherwise. -std::optional<WTF::Vector<mojom::blink::AILanguageModelPromptPtr>> BuildPrompts( - const V8LanguageModelPromptInput* input, +void ConvertPromptInputsToMojo( ScriptState* script_state, - ExceptionState& exception_state, - ExecutionContext* execution_context, - WTF::HashSet<mojom::blink::AILanguageModelPromptType>& allowed_types) { - WTF::Vector<mojom::blink::AILanguageModelPromptPtr> prompts; - if (input->IsLanguageModelPromptDictOrStringSequence()) { - const auto& sequence = - input->GetAsLanguageModelPromptDictOrStringSequence(); - for (const auto& entry : sequence) { - mojom::blink::AILanguageModelPromptPtr prompt = - ConvertPromptToMojo(entry, script_state, exception_state, - execution_context, allowed_types); - if (prompt.is_null()) { - return std::nullopt; - } - prompts.push_back(std::move(prompt)); - } - } else { - CHECK(input->IsV8LanguageModelPrompt()); - auto* entry = input->GetAsV8LanguageModelPrompt(); - mojom::blink::AILanguageModelPromptPtr prompt = ConvertPromptToMojo( - entry, script_state, exception_state, execution_context, allowed_types); - if (prompt.is_null()) { - return std::nullopt; - } - prompts.push_back(std::move(prompt)); - } - - return prompts; + AbortSignal* abort_signal, + const V8LanguageModelPromptInput* input, + WTF::HashSet<mojom::blink::AILanguageModelPromptType> allowed_types, + ResolveCallback resolve_callback, + RejectCallback reject_callback) { + MakeGarbageCollected<LanguageModelPromptBuilder>( + script_state, abort_signal, allowed_types, input, + std::move(resolve_callback), std::move(reject_callback)); } } // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/language_model_prompt_builder.h b/third_party/blink/renderer/modules/ai/language_model_prompt_builder.h index f0dae024..8281b14 100644 --- a/third_party/blink/renderer/modules/ai/language_model_prompt_builder.h +++ b/third_party/blink/renderer/modules/ai/language_model_prompt_builder.h
@@ -15,14 +15,18 @@ class ScriptState; class AbortSignal; class V8LanguageModelPromptInput; -class ExceptionState; -std::optional<WTF::Vector<mojom::blink::AILanguageModelPromptPtr>> BuildPrompts( - const V8LanguageModelPromptInput* input, +// Helper function to convert V8 prompt input into mojo objects for transport +// across IPC. Works asynchronously to allow for asynchronous fetching/decoding +// of image, audio and blobs. +void ConvertPromptInputsToMojo( ScriptState* script_state, - ExceptionState& exception_state, - ExecutionContext* execution_context, - WTF::HashSet<mojom::blink::AILanguageModelPromptType>& allowed_types); + AbortSignal* abort_signal, + const V8LanguageModelPromptInput* input, + WTF::HashSet<mojom::blink::AILanguageModelPromptType> allowed_types, + base::OnceCallback<void( + WTF::Vector<mojom::blink::AILanguageModelPromptPtr>)> resolved_callback, + base::OnceCallback<void(const ScriptValue& error)> rejected_callback); } // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/create_translator_client.cc b/third_party/blink/renderer/modules/ai/on_device_translation/create_translator_client.cc index 5e93cb93..3e94a38 100644 --- a/third_party/blink/renderer/modules/ai/on_device_translation/create_translator_client.cc +++ b/third_party/blink/renderer/modules/ai/on_device_translation/create_translator_client.cc
@@ -107,21 +107,17 @@ void CreateTranslatorClient::OnResult( mojom::blink::CreateTranslatorResultPtr result) { + // Call `Cleanup` when this function returns. + RunOnDestruction run_on_destruction(WTF::BindOnce( + &CreateTranslatorClient::Cleanup, WrapWeakPersistent(this))); + if (!GetResolver()) { // The request was aborted. Note: Currently abort signal is not supported. // TODO(crbug.com/331735396): Support abort signal. return; } - if (result->is_translator()) { - if (monitor_) { - // Ensure that a download completion event is sent. - monitor_->OnDownloadProgressUpdate(kNormalizedDownloadProgressMax, - kNormalizedDownloadProgressMax); - } - GetResolver()->Resolve(MakeGarbageCollected<Translator>( - std::move(result->get_translator()), task_runner_, - std::move(source_language_), std::move(target_language_))); - } else { + + if (!result->is_translator()) { CHECK(result->is_error()); GetExecutionContext()->AddConsoleMessage( mojom::blink::ConsoleMessageSource::kJavaScript, @@ -130,8 +126,18 @@ GetResolver()->Reject(DOMException::Create( kExceptionMessageUnableToCreateTranslator, DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError))); + return; } - Cleanup(); + + if (monitor_) { + // Ensure that a download completion event is sent. + monitor_->OnDownloadProgressUpdate(kNormalizedDownloadProgressMax, + kNormalizedDownloadProgressMax); + } + + GetResolver()->Resolve(MakeGarbageCollected<Translator>( + std::move(result->get_translator()), task_runner_, + std::move(source_language_), std::move(target_language_))); } void CreateTranslatorClient::OnGotAvailability(
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc index 735e90d..0b54699 100644 --- a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc +++ b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc
@@ -55,30 +55,6 @@ } } -// Runs `callback` on destruction unless `Reset` is called. -class RunOnDestruction { - public: - explicit RunOnDestruction(base::OnceClosure callback) - : callback_(std::move(callback)) {} - - RunOnDestruction(const RunOnDestruction&) = delete; - RunOnDestruction& operator=(const RunOnDestruction&) = delete; - - RunOnDestruction(RunOnDestruction&& other) = default; - RunOnDestruction& operator=(RunOnDestruction&& other) = default; - - void Reset() { callback_.Reset(); } - - ~RunOnDestruction() { - if (!callback_.is_null()) { - std::move(callback_).Run(); - } - } - - private: - base::OnceClosure callback_; -}; - // Rejects if the OnceClosure is destroyed before it is ran. template <typename T> base::OnceClosure RejectOnDestruction(ScriptPromiseResolver<T>* resolver) {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc index fc9fdb59..48bccd3 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc +++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -191,14 +191,22 @@ if (element->parentElement() != canvas_element) { exception_state.ThrowTypeError( "Only immediate children of the <canvas> element can be used with " - "placeElement()."); + "drawElement()."); + return false; + } + + if (!element->GetLayoutObject()) { + exception_state.ThrowTypeError( + "An element used with drawElement() must have been laid out. " + "Add layoutsubtree=`true` to the canvas element and be sure the " + "element is not `display: none`."); return false; } // TODO(crbug.com/413728246): Maybe we can support canvas element. if (IsA<HTMLCanvasElement>(element)) { exception_state.ThrowTypeError( - "<canvas> elements cannot be used with placeElement()."); + "<canvas> elements cannot be used with drawElement()."); return false; }
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc index 0a305b6..fcfd5f9 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc +++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -708,19 +708,19 @@ std::optional<double> dheight, ExceptionState& exception_state) { CHECK(RuntimeEnabledFeatures::CanvasDrawElementEnabled()); + + HTMLCanvasElement* canvas_element = HostAsHTMLCanvasElement(); + DCHECK(canvas_element); + canvas_element->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( + DocumentUpdateReason::kCanvasDrawElement); + if (!IsDrawElementEligible(element, exception_state)) { return; } - HTMLCanvasElement* canvas_element = HostAsHTMLCanvasElement(); - DCHECK(canvas_element); - // TODO(crbug.com/380277045): Taint for cross-origin and PII content. // SetOriginTaintedByContent(); - canvas_element->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( - DocumentUpdateReason::kCanvasDrawElement); - PaintRecordBuilder builder; LayoutBox* layout_box = element->GetLayoutBox(); // All drawn elements should have their own stacking contexts.
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition.cc b/third_party/blink/renderer/modules/speech/speech_recognition.cc index 335dd821..7751523 100644 --- a/third_party/blink/renderer/modules/speech/speech_recognition.cc +++ b/third_party/blink/renderer/modules/speech/speech_recognition.cc
@@ -60,6 +60,11 @@ #include "third_party/blink/renderer/platform/runtime_enabled_features.h" namespace { +const char kExceptionMessageCrossOriginAccess[] = + "Access denied from cross-origin iframes."; +const char kExceptionMessagePermissionPolicy[] = + "Access denied because the Permission Policy is not enabled."; + blink::V8AvailabilityStatus AvailabilityStatusToV8( media::mojom::blink::AvailabilityStatus status) { switch (status) { @@ -205,7 +210,6 @@ ExceptionState& exception_state) { LocalDOMWindow& window = *LocalDOMWindow::From(script_state); auto* controller = SpeechRecognitionController::From(window); - if (!controller || !script_state->ContextIsValid()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Execution context is detached."); @@ -216,6 +220,18 @@ MakeGarbageCollected<ScriptPromiseResolver<V8AvailabilityStatus>>( script_state, exception_state.GetContext()); auto result = resolver->Promise(); + bool is_cross_origin_iframe = window.IsCrossSiteSubframeIncludingScheme(); + + // Return unavailable if the Permission Policy is not enabled, or if the API + // is accessed from a cross-origin iframe. + if (!window.IsFeatureEnabled(network::mojom::PermissionsPolicyFeature:: + kOnDeviceSpeechRecognition) || + is_cross_origin_iframe) { + resolver->Resolve(AvailabilityStatusToV8( + media::mojom::blink::AvailabilityStatus::kUnavailable)); + return result; + } + controller->OnDeviceWebSpeechAvailable( lang, WTF::BindOnce( [](ScriptPromiseResolver<V8AvailabilityStatus>* resolver, @@ -236,7 +252,6 @@ ExceptionState& exception_state) { LocalDOMWindow& window = *LocalDOMWindow::From(script_state); auto* controller = SpeechRecognitionController::From(window); - if (!controller || !script_state->ContextIsValid()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Execution context is detached."); @@ -245,6 +260,21 @@ auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>( script_state, exception_state.GetContext()); + // Block access for cross-origin iframes. + if (window.IsCrossSiteSubframeIncludingScheme()) { + resolver->Reject( + MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError, + kExceptionMessageCrossOriginAccess)); + return resolver->Promise(); + } + + // Block access if the Permission Policy is not enabled. + if (!window.IsFeatureEnabled(network::mojom::PermissionsPolicyFeature:: + kOnDeviceSpeechRecognition)) { + resolver->Reject(MakeGarbageCollected<DOMException>( + DOMExceptionCode::kNotAllowedError, kExceptionMessagePermissionPolicy)); + return resolver->Promise(); + } auto result = resolver->Promise(); controller->OnDeviceWebSpeechAvailable( @@ -255,8 +285,7 @@ media::mojom::blink::AvailabilityStatus status) { LocalDOMWindow& window = *LocalDOMWindow::From(script_state); auto* controller = SpeechRecognitionController::From(window); - if (!ExecutionContext::From(script_state) - ->IsServiceWorkerGlobalScope() && + if (!window.IsServiceWorkerGlobalScope() && status == media::mojom::blink::AvailabilityStatus::kDownloadable && !LocalFrame::ConsumeTransientUserActivation(
diff --git a/third_party/blink/renderer/platform/p2p/DEPS b/third_party/blink/renderer/platform/p2p/DEPS index 045d86c9..2a3589b6 100644 --- a/third_party/blink/renderer/platform/p2p/DEPS +++ b/third_party/blink/renderer/platform/p2p/DEPS
@@ -2,6 +2,7 @@ "+crypto/random.h", "+components/webrtc/net_address_utils.h", "+media/base/media_permission.h", + "+net/base/address_family.h", "+net/base/ip_address.h", "+net/base/ip_endpoint.h", "+net/base/network_change_notifier.h",
diff --git a/third_party/blink/renderer/platform/p2p/host_address_request.cc b/third_party/blink/renderer/platform/p2p/host_address_request.cc index 53d0f52..4444f09 100644 --- a/third_party/blink/renderer/platform/p2p/host_address_request.cc +++ b/third_party/blink/renderer/platform/p2p/host_address_request.cc
@@ -10,6 +10,7 @@ #include "base/feature_list.h" #include "base/location.h" #include "components/webrtc/net_address_utils.h" +#include "net/base/address_family.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/renderer/platform/p2p/socket_dispatcher.h" #include "third_party/blink/renderer/platform/wtf/functional.h" @@ -38,14 +39,15 @@ blink::features::kWebRtcHideLocalIpsWithMdns); auto callback = WTF::BindOnce(&P2PAsyncAddressResolver::OnResponse, scoped_refptr<P2PAsyncAddressResolver>(this)); + + std::optional<net::AddressFamily> family = std::nullopt; if (address_family.has_value()) { - dispatcher_->GetP2PSocketManager()->GetHostAddressWithFamily( - String(host_name.hostname().data()), address_family.value(), - enable_mdns, std::move(callback)); - } else { - dispatcher_->GetP2PSocketManager()->GetHostAddress( - String(host_name.hostname().data()), enable_mdns, std::move(callback)); + family = net::ToAddressFamily(*address_family); } + + dispatcher_->GetP2PSocketManager()->GetHostAddress( + String(host_name.hostname().data()), family, enable_mdns, + std::move(callback)); dispatcher_ = nullptr; }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 1e707e8..6708e4d1 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -857,6 +857,16 @@ name: "CheckVisibilityExtraProperties", status: "stable", }, + // This flag shifts the timing of clearing the popover invoker so that it + // happens after the `beforetoggle` event is fired. This is something of a + // bug fix: see the spec discussion at + // https://github.com/whatwg/html/issues/11246#issuecomment-2860876897. + // This is enabled by default, and is a kill switch. It shipped in M138, + // and can be removed in M140. + { + name: "ClearPopoverInvokerAfterBeforeToggle", + status: "stable", + }, // crbug.com/40851596: Send click to the capture pointer target instead of // the common ancestor of pointerdown and pointerup targets. { @@ -2592,6 +2602,8 @@ // <select>, but it isn't web compatible enough. This flag allows <input> // inside <select> but only if there is another open tag in between the // <input> and the <select>, like <select><div><input>. + // For now, this flag also makes <input> close <select> like it used to + // when the <input> is put directly within an <option> or <optgroup>. name: "InputInSelect", status: "experimental", depends_on: ["SelectParserRelaxation"], @@ -4642,9 +4654,9 @@ status: "stable", }, { - // crbug.com/341564372 + // crbug.com/414858401 name: "TextareaLineEndingsAsBr", - status: "experimental", + status: "stable", depends_on: ["EditingFastRichReplace"], }, { @@ -5561,7 +5573,7 @@ }, { name: "XMLSerializerConsistentDefaultNsDeclMatching", - status: "stable", + status: "test", }, { // If enabled, the `getDisplayMedia()` family of APIs will ask for NV12
diff --git a/third_party/blink/renderer/platform/wtf/stack_util.cc b/third_party/blink/renderer/platform/wtf/stack_util.cc index 0ae79af4..946906e 100644 --- a/third_party/blink/renderer/platform/wtf/stack_util.cc +++ b/third_party/blink/renderer/platform/wtf/stack_util.cc
@@ -23,6 +23,10 @@ extern "C" void* __libc_stack_end; // NOLINT #endif +#if defined(ADDRESS_SANITIZER) +#include <sanitizer/asan_interface.h> +#endif + namespace WTF { size_t GetUnderestimatedStackSize() { @@ -101,7 +105,12 @@ #endif } -void* GetStackStart() { +namespace { + +// A pointer to current thread's stack beginning. +thread_local void* thread_stack_start = nullptr; + +void* GetStackStartImpl() { #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \ BUILDFLAG(IS_FREEBSD) || BUILDFLAG(IS_FUCHSIA) pthread_attr_t attr; @@ -157,6 +166,35 @@ #endif } +} // namespace + +void* GetStackStart() { + if (!thread_stack_start) { + thread_stack_start = GetStackStartImpl(); + } + return thread_stack_start; +} + +bool IsOnStack(void* address) { +#if defined(ADDRESS_SANITIZER) + // If the address is part of a fake frame, then it is definitely on the stack. + if (__asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), address, + nullptr, nullptr)) { + return true; + } + // Fall through as there is still a regular stack present even when running + // with ASAN fake stacks. +#endif // defined(ADDRESS_SANITIZER) +#if __has_feature(safe_stack) + if (__builtin___get_unsafe_stack_ptr() <= address && + address <= __builtin___get_unsafe_stack_top()) { + return true; + } +#endif // __has_feature(safe_stack) + return (GetCurrentStackPosition() <= reinterpret_cast<uintptr_t>(address)) && + (address <= GetStackStart()); +} + uintptr_t GetCurrentStackPosition() { #if defined(COMPILER_MSVC) return reinterpret_cast<uintptr_t>(_AddressOfReturnAddress());
diff --git a/third_party/blink/renderer/platform/wtf/stack_util.h b/third_party/blink/renderer/platform/wtf/stack_util.h index 17dde31..82995f0 100644 --- a/third_party/blink/renderer/platform/wtf/stack_util.h +++ b/third_party/blink/renderer/platform/wtf/stack_util.h
@@ -15,6 +15,7 @@ WTF_EXPORT size_t GetUnderestimatedStackSize(); WTF_EXPORT void* GetStackStart(); +WTF_EXPORT bool IsOnStack(void* address); // Returns the current stack position such that it works correctly with ASAN and // SafeStack. Must be marked noinline because it relies on compiler intrinsics
diff --git a/third_party/blink/renderer/platform/wtf/vector.h b/third_party/blink/renderer/platform/wtf/vector.h index 8255a2340..fde195b 100644 --- a/third_party/blink/renderer/platform/wtf/vector.h +++ b/third_party/blink/renderer/platform/wtf/vector.h
@@ -51,6 +51,7 @@ #include "third_party/blink/renderer/platform/wtf/container_annotations.h" #include "third_party/blink/renderer/platform/wtf/forward.h" // For default Vector template parameters. #include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h" +#include "third_party/blink/renderer/platform/wtf/stack_util.h" #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" #include "third_party/blink/renderer/platform/wtf/type_traits.h" #include "third_party/blink/renderer/platform/wtf/vector_traits.h" @@ -672,10 +673,16 @@ public: using OffsetRange = typename Base::OffsetRange; - VectorBuffer() : Base(InlineBuffer(), InlineCapacity) { InitInlinedBuffer(); } + VectorBuffer() : Base(InlineBuffer(), InlineCapacity) { +#if DCHECK_IS_ON() + VerifyInlinedBuffer(); +#endif + } explicit VectorBuffer(HashTableDeletedValueType value) : Base(value) { - InitInlinedBuffer(); +#if DCHECK_IS_ON() + VerifyInlinedBuffer(); +#endif } bool IsHashTableDeletedValue() const { return Base::IsHashTableDeletedValue(); @@ -683,7 +690,9 @@ explicit VectorBuffer(wtf_size_t capacity) : Base(InlineBuffer(), InlineCapacity) { - InitInlinedBuffer(); +#if DCHECK_IS_ON() + VerifyInlinedBuffer(); +#endif if (capacity > InlineCapacity) { Base::AllocateBuffer(capacity, VectorOperationOrigin::kConstruction); } @@ -975,9 +984,14 @@ return unsafe_reinterpret_cast_ptr<const T*>(inline_buffer_); } - void InitInlinedBuffer() { - if (Allocator::kIsGarbageCollected) { - memset(&inline_buffer_, 0, kInlineBufferSize); + void VerifyInlinedBuffer() { + // On heap allocations are always zero-initialized. Stack is anyway scanned + // conservatively, stack-to-stack pointers are filtered out, so no need to + // clear out the inlined buffer. + if constexpr (Allocator::kIsGarbageCollected) { + const bool is_zeroed = + std::ranges::all_of(inline_buffer_, [](char c) { return c == 0; }); + DCHECK(is_zeroed || WTF::IsOnStack(inline_buffer_)); } }
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 2d3205e..5f8b87d 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -9326,11 +9326,6 @@ crbug.com/391006701 [ Debug Mac ] images/yuv-decode-eligible/color-profile-image-profile-match.html [ Skip Timeout ] crbug.com/391006701 [ Debug Mac ] images/yuv-decode-eligible/webp-image-decoding.html [ Skip Timeout ] -#Gardener 2025-03-17 -crbug.com/404060200 [ Mac12 ] external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window.html [ Failure ] -crbug.com/404060200 [ Mac12 ] external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window.html?include=variant-2-test [ Failure Pass ] -crbug.com/404060200 [ Mac12 ] external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window.html?include=variant-4-test [ Failure Pass ] - # Gardener 2025-03-19 crbug.com/402521712 [ Linux ] external/wpt/webrtc-encoded-transform/tentative/RTCEncodedFrame-timestamps.html [ Failure ] crbug.com/404571518 [ Win11 ] external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https.html?mimeType=video/mp4;codecs=av01,mp4a.40.2 [ Failure ] @@ -9356,7 +9351,6 @@ crbug.com/407865639 external/wpt/css/css-grid/grid-extrinsically-sized-mutations.html [ Failure Pass ] # Gardener 2025-04-08 -crbug.com/409149439 [ Win11-arm64 ] external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window.html?include=variant-5-test [ Failure Pass ] crbug.com/409032332 http/tests/inspector-protocol/no-crash-with-stack-id-and-stale-while-revalidate.js [ Crash Pass Timeout ] crbug.com/411529366 [ Mac ] external/wpt/nav-tracking-mitigations/stateful-client-bounce.sub.https.html [ Crash Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js index dbf41cd..26eae65 100644 --- a/third_party/blink/web_tests/external/wpt/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js +++ b/third_party/blink/web_tests/external/wpt/ai/summarizer/summarizer-summarize-streaming.tentative.https.window.js
@@ -19,7 +19,7 @@ if (done) { break; } - result = value; + result += value; } assert_greater_than(result.length, 0); }, 'Summarizer.summarizeStreaming() returns ReadableStream with a non-empty text.');
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html new file mode 100644 index 0000000..562c166 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed-from-longhands.html
@@ -0,0 +1,76 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <title>CSS Gap Decorations: individual separate longhands form shorthand</title> + <link rel="help" href="https://drafts.csswg.org/css-multicol/#propdef-column-rule"> + <meta name="assert" content="Setting *-rule-width, *-rule-style, and *-rule-color results in the misaligned column-rule shorthand."> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> + <div id="target"></div> +<script> +const testCases = [ + { + width: '5px', + style: 'solid', + color: 'rgb(0, 128, 0)', + expected: '5px solid rgb(0, 128, 0)' + }, + { + width: 'repeat(auto, 5px)', + style: 'repeat(auto, solid)', + color: 'repeat(auto, rgb(255, 0, 0))', + expected: 'repeat(auto, 5px solid rgb(255, 0, 0))' + }, + + // The following test cases return an empty string because the longhands do not + // line up. + { + width: 'repeat(auto, thin medium)', + style: 'solid', + color: 'repeat(8, red blue)', + expected: '' + }, + { + width: 'repeat(6, 15px thick)', + style: 'repeat(auto, solid)', + color: 'repeat(auto, red)', + expected: '' + }, + { + width: '15px 25px 35px', + style: 'solid dotted', + color: 'green', + expected: '' + }, + { + width: 'repeat(auto, 5px)', + style: 'solid double', + color: 'repeat(7, red)', + expected: '' + }, + { + width: 'repeat(auto, 5px 8px 10px)', + style: 'repeat(auto, solid double)', + color: 'repeat(auto, red green blue)', + expected: '' + }, + { + width: 'repeat(2, 1px 3px 5px)', + style: 'repeat(2, solid double)', + color: 'repeat(2, red)', + expected: '' + }, +]; + +for (const {width, style, color, expected} of testCases) { + let div = document.querySelector('#target'); + + div.style.columnRuleWidth = width; + div.style.columnRuleStyle = style; + div.style.columnRuleColor = color; + test(() => { + assert_equals(window.getComputedStyle(div).columnRule, expected); + }, `column-rule computed from width: ${width}, style: ${style}, color: ${color}`); +} +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html new file mode 100644 index 0000000..7bb3e18 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-computed.html
@@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Gap Decoration: *rule getComputedStyle()</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#propdef-column-rule"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<meta name="assert" content="*rule computed value is as specified."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/computed-testcommon.js"></script> +</head> +<body> +<div id="reference"></div> +<div id="target"></div> +<style> + #reference { + column-rule-style: dotted; + column-rule-width: medium; + } + #target { + color: lime; + } +</style> +<script> +// TODO(samomekarajr): Add `row-rule` to this test when implemented. +const properties = ["column-rule",]; +for (let property of properties) { + const currentcolor = "rgb(0, 255, 0)"; + const mediumWidth = getComputedStyle(document.getElementById('reference')).columnRuleWidth; // e.g. 3px. + + // <gap-rule> = [<line-width> || <line-style> || <line-color>] + test_computed_value(property, "5px solid currentcolor", "5px solid " + currentcolor); + test_computed_value(property, "rgb(0, 0, 255) 10px solid", "10px solid rgb(0, 0, 255)"); + test_computed_value(property, "dotted", mediumWidth + " dotted " + currentcolor); + + // <gap-auto-repeat-rule> = repeat( auto , <gap-rule># ) + test_computed_value(property, "repeat(auto, 5px solid rgb(0, 0, 255))"); + test_computed_value(property, "repeat(auto, 5px solid rgb(255, 255, 0), 10px dotted rgb(0, 128, 0))"); + + + // <gap-repeat-rule> = repeat( <integer [1,∞]> , <gap-rule># ) + test_computed_value(property, "repeat(4, 15px dotted rgb(0, 255, 255))"); + test_computed_value(property, "repeat(1, 15px ridge rgb(255, 0, 0), 10px dotted rgb(0, 255, 0), 15px double rgb(0, 0, 255))"); + + + // <gap-rule-list> = <gap-rule-or-repeat># + // <gap-rule-or-repeat> = <gap-rule> | <gap-repeat-rule> + test_computed_value(property, "5px double rgb(58, 58, 16), repeat(4, 5px ridge rgb(18, 18, 18))"); + test_computed_value(property, "15px dashed rgb(0, 255, 0), repeat(3, 3px double rgb(255, 0, 0), 10px dotted rgb(0, 0, 255))"); + test_computed_value(property, "repeat(4, 5px solid rgb(255, 0, 255)), repeat(3, 5px solid rgb(0, 0, 255), 10px dotted rgb(0, 128, 128))"); + + // <gap-auto-rule-list> = <gap-rule-or-repeat>#? , + // <gap-auto-repeat-rule> , + // <gap-rule-or-repeat>#? + test_computed_value(property, "repeat(auto, 5px solid rgb(255, 0, 255)), 13px dotted rgb(0, 0, 128), 10px dotted rgb(0, 128, 128), 15px double rgb(0, 0, 128)"); + test_computed_value(property, "5px solid rgb(255, 0, 255), repeat(auto, 5px solid rgb(255, 0, 255)), 10px dotted rgb(0, 128, 128)"); + test_computed_value(property, "10px dotted rgb(0, 128, 128), repeat(4, 20px hidden rgb(0, 128, 128), 30px ridge rgb(255, 0, 255)), repeat(auto, 5px solid rgb(255, 0, 255))"); +} +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html new file mode 100644 index 0000000..f7c6b45 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand-invalid.html
@@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Gap Decorations: *-rule parsing</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#propdef-column-rule"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<meta name="assert" content="*-rule supports the full grammar '[ <gap-rule-list> | <gap-auto-rule-list> ]'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +</head> +<body> +<script> +// TODO(samomekarajr): Add `row-rule` and `rule` to this test. +const properties = ["column-rule",]; +for (let property of properties) { + test_invalid_value(property, "auto"); + + test_invalid_value(property, "red 5px solid red"); + test_invalid_value(property, "repeat(auto, red 5px green ridge)"); + test_invalid_value(property, "repeat(auto, 5px solid red), 5px solid red, repeat(auto, 5px solid red)"); + test_invalid_value(property, "repeat(0, 5px red)"); + test_invalid_value(property, "repeat(-1, thin green red)"); + test_invalid_value(property, "repeat(auto, )"); + test_invalid_value(property, "repeat()"); + test_invalid_value(property, ""); +} +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand.html new file mode 100644 index 0000000..420b6757 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/parsing/gap-decorations-rule-shorthand.html
@@ -0,0 +1,127 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Gap Decorations: *-rule sets longhands</title> +<link rel="help" href="https://drafts.csswg.org/css-multicol/#propdef-column-rule"> +<meta name="assert" content="column-rule supports the full grammar '<gap-rule-list> | <gap-auto-rule-list>'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/shorthand-testcommon.js"></script> +</head> +<body> +<script> +// TODO(samomekarajr): Add `row-rule` to this test. +const rule_properties = { + 'column-rule': ['column-rule-width', + 'column-rule-style', + 'column-rule-color'], +}; + +for(rule_property in rule_properties) { + const [width, style, color] = rule_properties[rule_property]; + // <gap-rule> = [<line-width> || <line-style> || <line-color>]. + test_shorthand_value(rule_property, '5px solid red', { + [width]: '5px', + [style]: 'solid', + [color]: 'red' + }); + + test_shorthand_value(rule_property, 'double', { + [width]: 'medium', + [style]: 'double', + [color]: 'currentcolor' + }); + + test_shorthand_value(rule_property, 'blue 10px', { + [width]: '10px', + [style]: 'none', + [color]: 'blue' + }); + + // <gap-auto-repeat-rule> = repeat( auto , <gap-rule># ). + test_shorthand_value(rule_property, 'repeat(auto, 5px solid green)', { + [width]: 'repeat(auto, 5px)', + [style]: 'repeat(auto, solid)', + [color]: 'repeat(auto, green)' + }); + + test_shorthand_value(rule_property, 'repeat(auto, 5px solid yellow, 10px dotted blue)', { + [width]: 'repeat(auto, 5px 10px)', + [style]: 'repeat(auto, solid dotted)', + [color]: 'repeat(auto, yellow blue)' + }); + + test_shorthand_value(rule_property, 'repeat(auto, blue 6px, 5px solid red)', { + [width]: 'repeat(auto, 6px 5px)', + [style]: 'repeat(auto, none solid)', + [color]: 'repeat(auto, blue red)' + }); + + // <gap-repeat-rule> = repeat( <integer [1,∞]> , <gap-rule># ). + test_shorthand_value(rule_property, 'repeat(4, 15px dotted pink)', { + [width]: 'repeat(4, 15px)', + [style]: 'repeat(4, dotted)', + [color]: 'repeat(4, pink)' + }); + test_shorthand_value(rule_property, 'repeat(1, 15px ridge yellow, 10px dotted blue, 15px double green)', { + [width]: 'repeat(1, 15px 10px 15px)', + [style]: 'repeat(1, ridge dotted double)', + [color]: 'repeat(1, yellow blue green)' + }); + test_shorthand_value(rule_property, 'repeat(3, lime 16px, dashed purple, 10px dotted)', { + [width]: 'repeat(3, 16px medium 10px)', + [style]: 'repeat(3, none dashed dotted)', + [color]: 'repeat(3, lime purple currentcolor)' + }); + + // <gap-rule-list> = <gap-rule-or-repeat>#. + // <gap-rule-or-repeat> = <gap-rule> | <gap-repeat-rule>. + test_shorthand_value(rule_property, 'thin, dashed, hotpink', { + [width]: 'thin medium medium', + [style]: 'none dashed none', + [color]: 'currentcolor currentcolor hotpink' + }); + test_shorthand_value(rule_property, '5px double salmon, repeat(4, 5px ridge red)', { + [width]: '5px repeat(4, 5px)', + [style]: 'double repeat(4, ridge)', + [color]: 'salmon repeat(4, red)' + }); + test_shorthand_value(rule_property, + 'repeat(2, dashed gray, 10px blue dotted, 20px double), 5px solid red, repeat(4, blue 6px, 5px solid white)', { + [width]: 'repeat(2, medium 10px 20px) 5px repeat(4, 6px 5px)', + [style]: 'repeat(2, dashed dotted double) solid repeat(4, none solid)', + [color]: 'repeat(2, gray blue currentcolor) red repeat(4, blue white)' + }); + test_shorthand_value(rule_property, 'repeat(4, thick hidden skyblue), repeat(3, 5px solid red, 10px dotted)', { + [width]: 'repeat(4, thick) repeat(3, 5px 10px)', + [style]: 'repeat(4, hidden) repeat(3, solid dotted)', + [color]: 'repeat(4, skyblue) repeat(3, red currentcolor)' + }); + + // <gap-auto-rule-list> = <gap-rule-or-repeat>#? , + // <gap-auto-repeat-rule> , + // <gap-rule-or-repeat>#?. + test_shorthand_value(rule_property, + 'repeat(auto, 10px solid red), medium dotted green, repeat(3, thick dashed blue, 15px double green)', { + [width]: 'repeat(auto, 10px) medium repeat(3, thick 15px)', + [style]: 'repeat(auto, solid) dotted repeat(3, dashed double)', + [color]: 'repeat(auto, red) green repeat(3, blue green)' + }); + + test_shorthand_value(rule_property, 'ridge red, repeat(auto, 5px solid green), 10px dotted blue', { + [width]: 'medium repeat(auto, 5px) 10px', + [style]: 'ridge repeat(auto, solid) dotted', + [color]: 'red repeat(auto, green) blue' + }); + + test_shorthand_value(rule_property, + '10px dotted salmon, repeat(4, thin blue, hidden 5px purple), repeat(auto, 5px solid red, teal)', { + [width]: '10px repeat(4, thin 5px) repeat(auto, 5px medium)', + [style]: 'dotted repeat(4, none hidden) repeat(auto, solid none)', + [color]: 'salmon repeat(4, blue purple) repeat(auto, red teal)' + }); +} +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/serialization/gap-decorations-properties.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/serialization/gap-decorations-properties.html deleted file mode 100644 index 4985b555..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-gaps/serialization/gap-decorations-properties.html +++ /dev/null
@@ -1,300 +0,0 @@ -<!DOCTYPE HTML> -<html lang="en"> - -<head> - <meta charset="UTF-8"> - <title>CSS Test: Gap Decorations - Properties exist</title> - <link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> - <link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> - <meta name="flags" content="ahem dom"> - <meta name="assert" content="Test checks that css properties of gap decorations exist."> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> - <style> - #container { - width: 800px; - height: 600px; - } - - #myDiv { - font: 50px/1 Ahem; - justify-content: start; - align-content: start; - } - </style> -</head> - -<body> - <div id="log"></div> - <div id="container"> - <div id="myDiv"> - <div>I T</div> - <div>IT</div> - <div>I</div> - </div> - </div> - - <script> - setup({ explicit_done: true }); - document.fonts.ready.then(() => { - var myDiv = document.getElementById('myDiv') - - void function (data) { - - myDiv.style.display = 'grid' - - Object.keys(data).forEach(function (prop) { - test(function () { - assert_true(prop in myDiv.style) - }, prop) - - var syntaxTests = data[prop] - Object.keys(syntaxTests).forEach(function (testcase) { - test(function () { - assert_true(prop in myDiv.style) - myDiv.style[prop] = syntaxTests[testcase][0] - assert_equals(myDiv.style[prop], syntaxTests[testcase][0], testcase) - assert_equals(getComputedStyle(myDiv)[prop], syntaxTests[testcase][1], testcase) - }, prop + '.' + testcase) - }) - }) - - }({ - 'gap-rule-paint-order': { - 'row-over-column': [ - 'row-over-column', - 'row-over-column' - ], - 'column-over-row': [ - 'column-over-row', - 'column-over-row' - ], - }, - 'column-rule-break': { - 'none': [ - 'none', - 'none' - ], - 'spanning-item': [ - 'spanning-item', - 'spanning-item' - ], - 'intersection': [ - 'intersection', - 'intersection' - ], - }, - 'column-rule-color': { - 'red': [ - 'red', - 'rgb(255, 0, 0)' - ], - 'blue': [ - 'blue', - 'rgb(0, 0, 255)' - ], - 'repeat(4, blue red green) repeat(auto, red)': [ - 'repeat(4, blue red green) repeat(auto, red)', - 'repeat(4, rgb(0, 0, 255) rgb(255, 0, 0) rgb(0, 128, 0)) repeat(auto, rgb(255, 0, 0))' - ], - 'blue red': [ - 'blue red', - 'rgb(0, 0, 255) rgb(255, 0, 0)' - ], - }, - 'column-rule-outset': { - '10px': [ - '10px', - '10px' - ], - '50%': [ - '50%', - '50%' - ], - }, - 'column-rule-style': { - 'none': [ - 'none', - 'none' - ], - 'hidden': [ - 'hidden', - 'hidden' - ], - 'dotted': [ - 'dotted', - 'dotted' - ], - 'dashed': [ - 'dashed', - 'dashed' - ], - 'solid': [ - 'solid', - 'solid' - ], - 'double': [ - 'double', - 'double' - ], - 'groove': [ - 'groove', - 'groove' - ], - 'ridge': [ - 'ridge', - 'ridge' - ], - 'inset': [ - 'inset', - 'inset' - ], - 'outset': [ - 'outset', - 'outset' - ], - 'dotted dashed': [ - 'dotted dashed', - 'dotted dashed' - ], - 'repeat(3, dotted)': [ - 'repeat(3, dotted)', - 'repeat(3, dotted)' - ], - }, - 'column-rule-width': { - '10px': [ - '10px', - '10px' - ], - 'thin': [ - 'thin', - '1px' - ], - 'medium': [ - 'medium', - '3px' - ], - 'thick': [ - 'thick', - '5px' - ], - }, - 'row-rule-break': { - 'none': [ - 'none', - 'none' - ], - 'spanning-item': [ - 'spanning-item', - 'spanning-item' - ], - 'intersection': [ - 'intersection', - 'intersection' - ], - }, - 'row-rule-color': { - 'red': [ - 'red', - 'rgb(255, 0, 0)' - ], - 'blue': [ - 'blue', - 'rgb(0, 0, 255)' - ], - 'repeat(4, blue red green) repeat(auto, red)': [ - 'repeat(4, blue red green) repeat(auto, red)', - 'repeat(4, rgb(0, 0, 255) rgb(255, 0, 0) rgb(0, 128, 0)) repeat(auto, rgb(255, 0, 0))' - ], - 'blue red': [ - 'blue red', - 'rgb(0, 0, 255) rgb(255, 0, 0)' - ], - }, - 'row-rule-outset': { - '10px': [ - '10px', - '10px' - ], - '50%': [ - '50%', - '50%' - ], - }, - 'row-rule-style': { - 'none': [ - 'none', - 'none' - ], - 'hidden': [ - 'hidden', - 'hidden' - ], - 'dotted': [ - 'dotted', - 'dotted' - ], - 'dashed': [ - 'dashed', - 'dashed' - ], - 'solid': [ - 'solid', - 'solid' - ], - 'double': [ - 'double', - 'double' - ], - 'groove': [ - 'groove', - 'groove' - ], - 'ridge': [ - 'ridge', - 'ridge' - ], - 'inset': [ - 'inset', - 'inset' - ], - 'outset': [ - 'outset', - 'outset' - ], - 'dotted dashed': [ - 'dotted dashed', - 'dotted dashed' - ], - 'repeat(3, dotted)': [ - 'repeat(3, dotted)', - 'repeat(3, dotted)' - ], - }, - 'row-rule-width': { - '10px': [ - '10px', - '10px' - ], - 'thin': [ - 'thin', - '1px' - ], - 'medium': [ - 'medium', - '3px' - ], - 'thick': [ - 'thick', - '5px' - ], - }, - }) - done(); - }); - </script> -</body> - -</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/float-cannot-be-spanner.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/float-cannot-be-spanner.html new file mode 100644 index 0000000..f366e79 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/float-cannot-be-spanner.html
@@ -0,0 +1,11 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://issues.chromium.org/issues/417256483"> +<div style="columns:2;"> + <div id="e59" style="float:left; width:100px; column-span:all;"></div> + m +</div> +<script> + document.body.offsetTop; + e59.style.width = "101px"; +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-computation-color-005.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-computation-color-005.html new file mode 100644 index 0000000..9660c93 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-computation-color-005.html
@@ -0,0 +1,17 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values" /> +<link rel="match" href="registered-property-computation-color-001-ref.html"> +<style> +@property --x { + inherits: true; + initial-value: black; + syntax: "<color>"; +} +div { + --x: color(srgb 0 calc(0.5 * sibling-index()) 0); + background-color: var(--x); + width: 100px; + height: 100px; +} +</style> +<div></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/resolved-color-value.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/resolved-color-value.html new file mode 100644 index 0000000..2515726 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/resolved-color-value.html
@@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>CSS Properties and Values API Test: resolved registered <color> values</title> +<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @property --color { + inherits: false; + initial-value: black; + syntax: "<color>"; + } + #target { + --color: color(srgb 0 sibling-index() 0); + } +</style> +<div> + <div id="target"></div> +</div> +<script> + test(() => { + assert_equals(getComputedStyle(target).getPropertyValue("--color"), "color(srgb 0 1 0)"); + }, "Resolved <color> value with sibling-index()"); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.html new file mode 100644 index 0000000..d98a72a2 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.html
@@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>CSS Values and Units Test: sibling-index() changing font-variation-settings during @keyframes animation</title> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#tree-counting"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @keyframes --anim { + from { + font-variation-settings: "wght" sibling-index(); + } + to { + font-variation-settings: "wght" 10; + } + } + #target { + animation: --anim 1000s step-end; + } +</style> +<div> + <div id="rm"></div> + <div></div> + <div id="target"></div> +</div> +<script> + test(() => { + assert_equals(getComputedStyle(target).fontVariationSettings, '"wght" 3'); + }, "Initially, the sibling-index() is 3 for #target"); + + test(() => { + rm.remove(); + assert_equals(getComputedStyle(target).fontVariationSettings, '"wght" 2'); + }, "Removing a preceding sibling of #target reduces the sibling-index()"); + +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/web-animation-pseudo-incorrect-name.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/web-animation-pseudo-incorrect-name.html index e8d14f1b..6743ae8a 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/web-animation-pseudo-incorrect-name.html +++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/web-animation-pseudo-incorrect-name.html
@@ -3,6 +3,7 @@ <title>View transitions: creating animation for non-existant view transition pseudo</title> <link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/"> <link rel="author" href="mailto:khushalsagar@chromium.org"> +<script src="/dom/events/scrolling/scroll_support.js"></script> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> @@ -22,18 +23,16 @@ <script> promise_test(async t => { assert_implements(document.startViewTransition, "Missing document.startViewTransition"); - return new Promise(async (resolve, reject) => { + await waitForCompositorReady(); + return new Promise(async (resolve) => { let transition = document.startViewTransition(); await transition.ready; let animation = document.documentElement.animate( { transform: ['translate(100px)', 'translate(200px)'] }, {duration: 100, pseudoElement: '::view-transition-group(bad-target)', fill: "forwards"}); - - requestAnimationFrame(() => { - animation.currentTime = 200; - requestAnimationFrame(() => requestAnimationFrame(resolve)); - }); + assert_true(!!animation, "animation is created"); + resolve(); }); }, "animation created with incorrect name"); </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-parsing.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-parsing.tentative.html index 0c6972bc..5fe50d5 100644 --- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-parsing.tentative.html +++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-parsing.tentative.html
@@ -104,6 +104,14 @@ </div> </select> +<select class=test + data-description='Input tags should close select when directly inside an <option>' + data-expect='<option></option>'> + <option> + <input> + </option> +</select> + <div id=afterlast> keep this div after the last test case </div>
diff --git a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/smoke/tentative/dom.html b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/smoke/tentative/dom.html new file mode 100644 index 0000000..66d23b2 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/smoke/tentative/dom.html
@@ -0,0 +1,116 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <title>The DOM Modification Criterion for Soft Navigation Detection.</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script> + // Uses Element.innerHTML to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML + function elementInnerHTML() { + document.getElementById("element-inner-html").innerHTML = "<div>Hello, World.</div>"; + history.pushState({}, "", "/element-inner-html"); + } + + // Uses Node.appendChild to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild + function nodeAppendChild() { + const greeting = document.createElement("div"); + greeting.textContent = "Hello, World."; + document.body.appendChild(greeting); + history.pushState({}, "", "/node-append-child"); + } + + // Uses Node.insertBefore to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore + function nodeInsertBefore() { + const greeting = document.createElement("div"); + greeting.textContent = "Hello, World."; + document.body.insertBefore(greeting, document.body.firstChild); + history.pushState({}, "", "/node-insert-before"); + } + + // Uses Document.importNode to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode + function documentImportNode() { + const iframe = document.getElementById("iframe-example"); + const oldNode = iframe.contentWindow.document.getElementById("import-this"); + const newNode = document.importNode(oldNode, true); + document.body.appendChild(newNode); + history.pushState({}, "", "/document-import-node"); + } + + // Uses Document.adoptNode to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptNode + function documentAdoptNode() { + const iframe = document.getElementById("iframe-example"); + const oldNode = iframe.contentWindow.document.getElementById("import-this"); + const newNode = document.adoptNode(oldNode); + document.body.appendChild(newNode); + history.pushState({}, "", "/document-adopt-node"); + } + + // Uses a template element to add to the DOM. + // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template + function templateElement() { + const template = document.getElementById("template-example"); + const cloned = template.content.cloneNode(true); + document.body.appendChild(cloned); + history.pushState({}, "", "/template-element"); + } + </script> + </head> + <body> + <div id="element-inner-html" onclick="elementInnerHTML()">Click here!</div> + <div id="node-append-child" onclick="nodeAppendChild()">Click here!</div> + <div id="node-insert-before" onclick="nodeInsertBefore()">Click here!</div> + <div id="document-import-node" onclick="documentImportNode()">Click here!</div> + <div id="document-adopt-node" onclick="documentAdoptNode()">Click here!</div> + <div id="template-element" onclick="templateElement()">Click here!</div> + + <iframe id="iframe-example" srcdoc="<div id='import-this'>Hello, World.</div>"></iframe> + + <template id="template-example"> + <div>Hello, World.</div> + </template> + + <script> + function test_template(test_id, description) { + promise_test(async (t) => { + let entries; + new PerformanceObserver((list, observer) => { + entries = list.getEntries(); + observer.disconnect(); + }).observe({ type: "soft-navigation" }); + if (test_driver) { + test_driver.click(document.getElementById(test_id)); + } + await t.step_wait(() => entries !== undefined, "Soft navigation event not fired."); + + assert_equals(entries.length, 1, "Expected exactly one soft navigation."); + assert_equals( + entries[0].name.replace(/.*\//, ""), + test_id, + "URL should end with the test ID.", + ); + }, description); + } + + test_template("element-inner-html", "Soft Navigation Detection supports Element.innerHTML."); + test_template("node-append-child", "Soft Navigation Detection supports Node.appendChild."); + test_template("node-insert-before", "Soft Navigation Detection supports Node.insertBefore."); + test_template( + "document-import-node", + "Soft Navigation Detection supports Document.importNode.", + ); + test_template( + "document-adopt-node", + "Soft Navigation Detection supports Document.adoptNode.", + ); + test_template("template-element", "Soft Navigation Detection supports template elements."); + </script> + </body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-availableOnDevice.https.html b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-availableOnDevice.https.html index db48f2a..ace8edd 100644 --- a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-availableOnDevice.https.html +++ b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-availableOnDevice.https.html
@@ -5,7 +5,8 @@ <script> promise_test(async (t) => { const lang = "en-US"; - window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + window.SpeechRecognition = window.SpeechRecognition || + window.webkitSpeechRecognition; // Test that it returns a promise. const resultPromise = SpeechRecognition.availableOnDevice(lang); @@ -24,7 +25,8 @@ assert_true( result === "unavailable" || result === "downloadable" || result === "downloading" || result === "available", - "The resolved value of the availableOnDevice promise should be a valid value." + "The resolved value of the availableOnDevice promise should be a " + + "valid value." ); }, "SpeechRecognition.availableOnDevice resolves with a string value."); @@ -44,4 +46,138 @@ frameSpeechRecognition.availableOnDevice("en-US"), ); }, "SpeechRecognition.availableOnDevice rejects in a detached context."); + +promise_test(async (t) => { + const iframe = document.createElement("iframe"); + // This policy should make the on-device speech recognition + // feature unavailable. + iframe.setAttribute("allow", "on-device-speech-recognition 'none'"); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + await new Promise(resolve => { + if (iframe.contentWindow && + iframe.contentWindow.document.readyState === 'complete') { + resolve(); + } else { + iframe.onload = resolve; + } + }); + + const frameWindow = iframe.contentWindow; + const frameSpeechRecognition = frameWindow.SpeechRecognition || + frameWindow.webkitSpeechRecognition; + + assert_true(!!frameSpeechRecognition, + "SpeechRecognition should exist in iframe."); + assert_true(!!frameSpeechRecognition.availableOnDevice, + "availableOnDevice method should exist on SpeechRecognition in iframe."); + + // Call availableOnDevice and expect it to resolve to "unavailable". + const availabilityStatus = + await frameSpeechRecognition.availableOnDevice("en-US"); + assert_equals(availabilityStatus, "unavailable", + "availableOnDevice should resolve to 'unavailable' if " + + "'on-device-speech-recognition' Permission Policy is 'none'." + ); +}, "SpeechRecognition.availableOnDevice resolves to 'unavailable' if " + + "'on-device-speech-recognition' Permission Policy is 'none'."); + +promise_test(async (t) => { + const html = ` + <!DOCTYPE html> + <script> + window.addEventListener('message', async (event) => { + // Ensure we only process the message intended to trigger the test. + if (event.data !== "runTestCallAvailableOnDevice") return; + + try { + const SpeechRecognition = window.SpeechRecognition || + window.webkitSpeechRecognition; + if (!SpeechRecognition || !SpeechRecognition.availableOnDevice) { + parent.postMessage({ + type: "error", // Use "error" for API not found or other issues. + name: "NotSupportedError", + message: "SpeechRecognition.availableOnDevice API not " + + "available in iframe" + }, "*"); + return; + } + + // Call availableOnDevice and post its resolution. + const availabilityStatus = + await SpeechRecognition.availableOnDevice("en-US"); + parent.postMessage( + { type: "resolution", result: availabilityStatus }, + "*" + ); // Post the string status + } catch (err) { + // Catch any unexpected errors during the API call or message post. + parent.postMessage({ + type: "error", + name: err.name, + message: err.message + }, "*"); + } + }); + <\/script> + `; + + const blob = new Blob([html], { type: "text/html" }); + const blobUrl = URL.createObjectURL(blob); + // Important: Revoke the blob URL after the test to free up resources. + t.add_cleanup(() => URL.revokeObjectURL(blobUrl)); + + const iframe = document.createElement("iframe"); + iframe.src = blobUrl; + // Sandboxing with "allow-scripts" is needed for the script inside + // the iframe to run. + // The cross-origin nature is primarily due to the blob URL's origin being + // treated as distinct from the parent page's origin for security + // purposes. + iframe.setAttribute("sandbox", "allow-scripts"); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + await new Promise(resolve => iframe.onload = resolve); + + const testResult = await new Promise((resolve, reject) => { + const timeoutId = t.step_timeout(() => { + reject(new Error("Test timed out waiting for message from iframe. " + + "Ensure iframe script is correctly posting a message.")); + }, 6000); // 6-second timeout + + window.addEventListener("message", t.step_func((event) => { + // Basic check to ensure the message is from our iframe. + if (event.source !== iframe.contentWindow) return; + clearTimeout(timeoutId); + resolve(event.data); + })); + + // Send a distinct message to the iframe to trigger its test logic. + iframe.contentWindow.postMessage("runTestCallAvailableOnDevice", "*"); + }); + + // Check if the iframe's script reported an error (e.g., API not found). + if (testResult.type === "error") { + const errorMessage = + `Iframe reported an error: ${testResult.name} - ` + + testResult.message; + assert_unreached(errorMessage); + } + + assert_equals( + testResult.type, + "resolution", + "The call from the iframe should resolve and post a 'resolution' " + + "message." + ); + assert_equals( + testResult.result, // Expecting the string "unavailable". + "unavailable", + "availableOnDevice should resolve to 'unavailable' in a cross-origin " + + "iframe." + ); +}, "SpeechRecognition.availableOnDevice should resolve to 'unavailable' " + + "in a cross-origin iframe."); </script>
diff --git a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html index 7422f5d..84afde82 100644 --- a/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html +++ b/third_party/blink/web_tests/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html
@@ -154,6 +154,116 @@ } ) ); -}, "SpeechRecognition.installOnDevice rejects in a detached context " + - "(with user gesture)."); +}, "SpeechRecognition.installOnDevice rejects in a detached context."); + +promise_test(async (t) => { + const iframe = document.createElement("iframe"); + iframe.setAttribute("allow", + "on-device-speech-recognition 'none'"); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + await new Promise(resolve => { + if (iframe.contentWindow && + iframe.contentWindow.document.readyState === 'complete') { + resolve(); + } else { + iframe.onload = resolve; + } + }); + + const frameWindow = iframe.contentWindow; + const frameSpeechRecognition = frameWindow.SpeechRecognition || + frameWindow.webkitSpeechRecognition; + const frameDOMException = frameWindow.DOMException; + + assert_true(!!frameSpeechRecognition, + "SpeechRecognition should exist in iframe."); + assert_true(!!frameSpeechRecognition.installOnDevice, + "installOnDevice method should exist on SpeechRecognition in iframe."); + + await promise_rejects_dom( + t, + "NotAllowedError", + frameDOMException, + frameSpeechRecognition.installOnDevice("en-US"), + "installOnDevice should reject with NotAllowedError if " + + "'install-on-device-speech-recognition' Permission Policy is " + + "disabled." + ); +}, "SpeechRecognition.installOnDevice rejects if " + + "'install-on-device-speech-recognition' Permission Policy is disabled."); + +promise_test(async (t) => { + const html = ` + <!DOCTYPE html> + <script> + window.addEventListener('message', async (event) => { + try { + const SpeechRecognition = window.SpeechRecognition || + window.webkitSpeechRecognition; + if (!SpeechRecognition || !SpeechRecognition.installOnDevice) { + parent.postMessage({ + type: "rejection", + name: "NotSupportedError", + message: "API not available" + }, "*"); + return; + } + + await SpeechRecognition.installOnDevice("en-US"); + parent.postMessage({ type: "resolution", result: "success" }, "*"); + } catch (err) { + parent.postMessage({ + type: "rejection", + name: err.name, + message: err.message + }, "*"); + } + }); + <\/script> + `; + + // Create a cross-origin Blob URL by fetching from remote origin + const blob = new Blob([html], { type: "text/html" }); + const blobUrl = URL.createObjectURL(blob); + + const iframe = document.createElement("iframe"); + iframe.src = blobUrl; + iframe.setAttribute("sandbox", "allow-scripts"); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + await new Promise(resolve => iframe.onload = resolve); + + const testResult = await new Promise((resolve, reject) => { + const timeoutId = t.step_timeout(() => { + reject(new Error("Timed out waiting for message from iframe")); + }, 6000); + + window.addEventListener("message", t.step_func((event) => { + if (event.source !== iframe.contentWindow) return; + clearTimeout(timeoutId); + resolve(event.data); + })); + + iframe.contentWindow.postMessage("runTest", "*"); + }); + + assert_equals( + testResult.type, + "rejection", + "Should reject due to cross-origin restriction" + ); + assert_equals( + testResult.name, + "NotAllowedError", + "Should reject with NotAllowedError" + ); + assert_true( + testResult.message.includes("cross-origin iframe") || + testResult.message.includes("cross-site subframe"), + `Error message should reference cross-origin. Got: "${testResult.message}"` + ); +}, "SpeechRecognition.installOnDevice should reject in a cross-origin iframe."); </script>
diff --git a/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt index a68bd47..fcb9546a 100644 --- a/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt +++ b/third_party/blink/web_tests/fast/forms/select/customizable-select/disallowed-select-descendants-console-message-expected.txt
@@ -3,7 +3,6 @@ CONSOLE ERROR: Non-phrasing content was found within an <option> element. The <option> element allows only non-interactive phrasing content, text, and <div> elements as its children. The semantics of non-phrasing content elements do not make sense as children of an <option>, and such semantics will largely be ignored by assistive technology since they are inappropriate in this context. Consider removing or changing such elements to one of the allowed phrasing content elements. CONSOLE ERROR: Non-phrasing content was found within an <option> element. The <option> element allows only non-interactive phrasing content, text, and <div> elements as its children. The semantics of non-phrasing content elements do not make sense as children of an <option>, and such semantics will largely be ignored by assistive technology since they are inappropriate in this context. Consider removing or changing such elements to one of the allowed phrasing content elements. CONSOLE ERROR: Non-phrasing content was found within an <option> element. The <option> element allows only non-interactive phrasing content, text, and <div> elements as its children. The semantics of non-phrasing content elements do not make sense as children of an <option>, and such semantics will largely be ignored by assistive technology since they are inappropriate in this context. Consider removing or changing such elements to one of the allowed phrasing content elements. -CONSOLE ERROR: Non-phrasing content was found within an <option> element. The <option> element allows only non-interactive phrasing content, text, and <div> elements as its children. The semantics of non-phrasing content elements do not make sense as children of an <option>, and such semantics will largely be ignored by assistive technology since they are inappropriate in this context. Consider removing or changing such elements to one of the allowed phrasing content elements. CONSOLE ERROR: An element which is not allowed in the content model of the <select> element was found within a <select> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <select> element should either be removed or relocated to a valid element that allows text descendants, e.g., an <optgroup> with a <legend> element or <option> elements. CONSOLE ERROR: An element which is not allowed in the content model of the <select> element was found within a <select> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <select> element should either be removed or relocated to a valid element that allows text descendants, e.g., an <optgroup> with a <legend> element or <option> elements. CONSOLE ERROR: An element which is not allowed in the content model of the <select> element was found within a <select> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <select> element should either be removed or relocated to a valid element that allows text descendants, e.g., an <optgroup> with a <legend> element or <option> elements. @@ -11,7 +10,6 @@ CONSOLE ERROR: An element which is not allowed in the content model of the <optgroup> element was found within an <optgroup> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <optgroup> element should either be removed or relocated to a valid element that allows text descendants, e.g., the <legend> or <option> elements. CONSOLE ERROR: An element which is not allowed in the content model of the <optgroup> element was found within an <optgroup> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <optgroup> element should either be removed or relocated to a valid element that allows text descendants, e.g., the <legend> or <option> elements. CONSOLE ERROR: An interactive element which is not allowed in the content model of the <legend> element was found within a <legend> element. Interactive elements are not allowed children of a <legend> element when used within an <optgroup> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. -CONSOLE ERROR: An element which is not allowed in the content model of the <optgroup> element was found within an <optgroup> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. If using disallowed elements for layout structure and styling, consider using the allowed <div> element instead. Any text existing within the <optgroup> element should either be removed or relocated to a valid element that allows text descendants, e.g., the <legend> or <option> elements. CONSOLE ERROR: An interactive element which is not allowed in the content model of the <option> element was found within an <option> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. CONSOLE ERROR: An interactive element which is not allowed in the content model of the <option> element was found within an <option> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology. CONSOLE ERROR: An interactive element which is not allowed in the content model of the <option> element was found within an <option> element. These elements will not consistently be accessible to people navigating by keyboard or using assistive technology.
diff --git a/third_party/blink/web_tests/fast/parser/inselect-tokenization-expected.txt b/third_party/blink/web_tests/fast/parser/inselect-tokenization-expected.txt index e57e097..8660292 100644 --- a/third_party/blink/web_tests/fast/parser/inselect-tokenization-expected.txt +++ b/third_party/blink/web_tests/fast/parser/inselect-tokenization-expected.txt
@@ -11,8 +11,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<noembed>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><noembed>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><noembed>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><noembed>2<element></element> @@ -23,8 +21,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<noframes>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><noframes>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><noframes>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><noframes>2<element></element> @@ -35,8 +31,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<noscript>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><noscript>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><noscript>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><noscript>2<element></element> @@ -47,8 +41,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<plaintext>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><plaintext>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><plaintext>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><plaintext>2<element></element> @@ -59,8 +51,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<style>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><style>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><style>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><style>2<element></element> @@ -71,8 +61,6 @@ assert_equals: select expected "12" but got "12<element></element>" [FAIL] <form><select><option>1<xmp>2<element></element>3 assert_equals: select expected "123" but got "12<element></element>3" -[FAIL] <form><select><option>1<input><xmp>2<element></element> - assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<keygen><xmp>2<element></element> assert_equals: select expected "1" but got "12<element></element>" [FAIL] <form><select><option>1<textarea></textarea><xmp>2<element></element>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/select-element-accessibility-issue-creation-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/select-element-accessibility-issue-creation-expected.txt index 927da6ac..0e1c410 100644 --- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/select-element-accessibility-issue-creation-expected.txt +++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/select-element-accessibility-issue-creation-expected.txt
@@ -30,18 +30,6 @@ selectElementAccessibilityIssueDetails : { hasDisallowedAttributes : false nodeId : <number> - selectElementAccessibilityIssueReason : DisallowedOptGroupChild - } - } - } -} -Inspector issue: { - issue : { - code : SelectElementAccessibilityIssue - details : { - selectElementAccessibilityIssueDetails : { - hasDisallowedAttributes : false - nodeId : <number> selectElementAccessibilityIssueReason : NonPhrasingContentOptionChild } }
diff --git a/third_party/blink/web_tests/platform/mac-mac11/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt b/third_party/blink/web_tests/platform/mac-mac11/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt deleted file mode 100644 index 5b37deb..0000000 --- a/third_party/blink/web_tests/platform/mac-mac11/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -All subtests passed and are omitted for brevity. -See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details. -Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/mac-mac12/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt b/third_party/blink/web_tests/platform/mac-mac12/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt deleted file mode 100644 index e177199..0000000 --- a/third_party/blink/web_tests/platform/mac-mac12/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-4-test-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -Harness Error. harness_status.status = 1 , harness_status.message = Timeout while running cleanup for test named "Verify Partitioned Popins have access to the proper cookie/storage partitions - Cross-site frame opens main-host popin.". -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/platform/win11-arm64/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-5-test-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-5-test-expected.txt deleted file mode 100644 index 34962ed5..0000000 --- a/third_party/blink/web_tests/platform/win11-arm64/external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window_include=variant-5-test-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -Harness Error. harness_status.status = 1 , harness_status.message = Timeout while running cleanup for test named "Verify Partitioned Popins have access to the proper cookie/storage partitions - Cross-site frame opens alternative-host popin.". -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt index 698c734..3eed75f7 100644 --- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt +++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -62,6 +62,7 @@ media-playback-while-not-visible microphone midi +on-device-speech-recognition otp-credentials payment picture-in-picture
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/basic-rect.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/basic-rect.html index 83e7655..e36d778 100644 --- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/basic-rect.html +++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/basic-rect.html
@@ -26,7 +26,7 @@ requestAnimationFrame(takeScreenshot); } -onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest)); +onload = () => runTest(); </script> </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-get-image-data.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-get-image-data.html index c1624f13..b142b6e 100644 --- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-get-image-data.html +++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-get-image-data.html
@@ -19,27 +19,27 @@ </canvas> <script> -promise_test(async () => { - let ctx = canvas.getContext('2d'); - ctx.fillStyle = "rgb(3, 4, 5)"; - ctx.fillRect(0, 0, 200, 200); - ctx.drawElement(child, 20, 30); + promise_test(async () => { + let ctx = canvas.getContext('2d'); + ctx.fillStyle = 'rgb(3, 4, 5)'; + ctx.fillRect(0, 0, 200, 200); + ctx.drawElement(child, 20, 30); - const image_data = ctx.getImageData(19, 35, 2, 1); - assert_equals(image_data.width, 2); - assert_equals(image_data.height, 1); + const image_data = ctx.getImageData(19, 35, 2, 1); + assert_equals(image_data.width, 2); + assert_equals(image_data.height, 1); - const data = image_data.data; - // The background pixel. - assert_equals(data[0], 3); - assert_equals(data[1], 4); - assert_equals(data[2], 5); - assert_equals(data[3], 255); + const data = image_data.data; + // The background pixel. + assert_equals(data[0], 3); + assert_equals(data[1], 4); + assert_equals(data[2], 5); + assert_equals(data[3], 255); - // The drawn element pixel. - assert_equals(data[4], 1); - assert_equals(data[5], 255); - assert_equals(data[6], 2); - assert_equals(data[7], 255); -}, 'canvas drawElement readback'); + // The drawn element pixel. + assert_equals(data[4], 1); + assert_equals(data[5], 255); + assert_equals(data[6], 2); + assert_equals(data[7], 255); + }, 'canvas drawElement readback'); </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-missing-layout.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-missing-layout.html new file mode 100644 index 0000000..18a00be1 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-missing-layout.html
@@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Canvas.drawElement: Throw on lack of layout</title> +<link rel="help" href="https://github.com/WICG/html-in-canvas"> +<link rel="author" href="mailto:schenney@chromium.org"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> +#child { + width: 100px; + height: 100px; + background: #01ff02; +} +</style> + +<canvas id=canvas width="200" height="200"> + <div id=child></div> +</canvas> + +<script> + promise_test(async t => { + let ctx = canvas.getContext('2d'); + ctx.fillStyle = 'rgb(3, 4, 5)'; + ctx.fillRect(0, 0, 200, 200); + try { + ctx.drawElement(child, 20, 30); + assert_unreached('Should throw TypeError exception'); + } catch (e) { + assert_true(e instanceof TypeError, 'Exception should be a TypeError'); + } + }, 'canvas drawElement throws when the element is not laid out'); +</script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-scale-variant.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-scale-variant.html index b1966a2c..0d186c8 100644 --- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-scale-variant.html +++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElement/draw-element-scale-variant.html
@@ -26,7 +26,7 @@ requestAnimationFrame(takeScreenshot); } -onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest)); +onload = () => runTest(); </script> </html>
diff --git a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html index 71bc2cf..5ed49406 100644 --- a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html +++ b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
@@ -73,6 +73,7 @@ 'media-playback-while-not-visible', 'microphone', 'midi', + 'on-device-speech-recognition', 'otp-credentials', 'payment', 'picture-in-picture',
diff --git a/third_party/boringssl/src b/third_party/boringssl/src index 2a514a5..8997380 160000 --- a/third_party/boringssl/src +++ b/third_party/boringssl/src
@@ -1 +1 @@ -Subproject commit 2a514a51baebd5a232fc64f7b082f7a8b28cd29d +Subproject commit 89973806bc2ef652189e157f2736a7d32229c404
diff --git a/third_party/catapult b/third_party/catapult index 83c00a3..5da8152 160000 --- a/third_party/catapult +++ b/third_party/catapult
@@ -1 +1 @@ -Subproject commit 83c00a37f40a93932204f684172a13c62a379e7e +Subproject commit 5da8152ef93c63ea38e38c3ce1d9791b7e529b4d
diff --git a/third_party/chromite b/third_party/chromite index 7ac1ad3..48aa362 160000 --- a/third_party/chromite +++ b/third_party/chromite
@@ -1 +1 @@ -Subproject commit 7ac1ad382fe7fe238e0785e62cc3c6d70b4685f7 +Subproject commit 48aa362d807edeb3393b18741c055f55772c2f0d
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index c56793a..a27ccc3 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit c56793a7e9ddabd1ff60ab33b100bee65475b50a +Subproject commit a27ccc3f91f79fc39b9c2d3c7d67069a211ffffb
diff --git a/third_party/google_toolbox_for_mac/BUILD.gn b/third_party/google_toolbox_for_mac/BUILD.gn index 848e633..9e800672 100644 --- a/third_party/google_toolbox_for_mac/BUILD.gn +++ b/third_party/google_toolbox_for_mac/BUILD.gn
@@ -25,14 +25,10 @@ if (!is_ios) { sources += [ - "src/AppKit/GTMCarbonEvent.h", - "src/AppKit/GTMCarbonEvent.m", "src/AppKit/GTMUILocalizer.h", "src/AppKit/GTMUILocalizer.m", "src/AppKit/GTMUILocalizerAndLayoutTweaker.h", "src/AppKit/GTMUILocalizerAndLayoutTweaker.m", - "src/DebugUtils/GTMDebugSelectorValidation.h", - "src/DebugUtils/GTMTypeCasting.h", ] frameworks = [ @@ -74,11 +70,6 @@ "src/iPhone/GTMUILocalizer.m", ] - # TODO(crbug.com/569158): Suppresses warnings that are treated as errors - # when minimum iOS version support is increased to iOS 9 and up. - # This should be removed once all deprecation violations have been fixed. - cflags = [ "-Wno-deprecated-declarations" ] - libs = [ "sqlite3", "z",
diff --git a/third_party/jetstream/main b/third_party/jetstream/main index 19a8be2..539ab94 160000 --- a/third_party/jetstream/main +++ b/third_party/jetstream/main
@@ -1 +1 @@ -Subproject commit 19a8be2645191e2b149c8bd681cc7941678bd754 +Subproject commit 539ab943598b505832a25a2222aa8957f1a20d6f
diff --git a/third_party/libunwind/src b/third_party/libunwind/src index 2bd5f3c..8575f4a 160000 --- a/third_party/libunwind/src +++ b/third_party/libunwind/src
@@ -1 +1 @@ -Subproject commit 2bd5f3cae13e8ad6727d0a77a2ec9cdc6b06becb +Subproject commit 8575f4ae4fcf8892938bd9766cf1a5c90a0ed04e
diff --git a/third_party/libwebm/README.chromium b/third_party/libwebm/README.chromium index bcac26a..dfce6122 100644 --- a/third_party/libwebm/README.chromium +++ b/third_party/libwebm/README.chromium
@@ -2,7 +2,7 @@ Short Name: libwebm URL: https://chromium.googlesource.com/webm/libwebm Version: N/A -Revision: b4f01ea3ed6fd00923caa383bb2cf6f7a0b7f633 +Revision: c4522d6cd68582d66f1adfd24debfa9bee202afa CPEPrefix: cpe:/a:webmproject:libwebm:1.0.0.31 License: BSD-3-Clause, Patent License File: source/LICENSE.TXT, source/PATENTS.TXT
diff --git a/third_party/libwebm/source b/third_party/libwebm/source index e79a981..c4522d6 160000 --- a/third_party/libwebm/source +++ b/third_party/libwebm/source
@@ -1 +1 @@ -Subproject commit e79a98159fdf6d1aa37b3500e32c6410a2cbe268 +Subproject commit c4522d6cd68582d66f1adfd24debfa9bee202afa
diff --git a/third_party/node/README.chromium b/third_party/node/README.chromium index 762509f..98bc57a 100644 --- a/third_party/node/README.chromium +++ b/third_party/node/README.chromium
@@ -592,3 +592,22 @@ Local Modifications: (none) + +-------------------- DEPENDENCY DIVIDER -------------------- + +Name: messageformat +Short Name: messageformat +URL: https://www.npmjs.com/package/messageformat +Version: 4.0.0-11 +License: Apache-2.0 +License File: Apache-LICENSE-2.0.txt +License Android Compatible: Yes +Security Critical: no +Shipped: yes + +Description: +Provides a formatter and other tools for Unicode MessageFormat 2.0 (MF2), the +new standard for localization developed by the MessageFormat Working Group. + +Local Modifications: +(none)
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1 index a0a84e04..6457c93 100644 --- a/third_party/node/node_modules.tar.gz.sha1 +++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@ -311eec6c718124cfa732a37a817cae28fce10adc +13eea9a3163f9e91f343e6a3197079986d7276b2
diff --git a/third_party/node/npm_include.txt b/third_party/node/npm_include.txt index c42f0f6..821d999 100644 --- a/third_party/node/npm_include.txt +++ b/third_party/node/npm_include.txt
@@ -55,6 +55,8 @@ /chai/chai.js /chai/LICENSE /chai/package.json +/messageformat/**/**.js +/messageformat/**/**.d.ts /mocha/LICENSE /mocha/mocha.js /mocha/package.json
diff --git a/third_party/node/package-lock.json b/third_party/node/package-lock.json index d9dc888..81fd01a792 100644 --- a/third_party/node/package-lock.json +++ b/third_party/node/package-lock.json
@@ -36,6 +36,7 @@ "eslint-plugin-jsdoc": "50.2.2", "html-minifier": "4.0.0", "lit": "3.3.0", + "messageformat": "4.0.0-11", "svgo": "3.0.2", "terser": "5.37.0", "ts-proto": "2.7.0", @@ -1907,6 +1908,11 @@ "node": ">= 8" } }, + "node_modules/messageformat": { + "version": "4.0.0-11", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-4.0.0-11.tgz", + "integrity": "sha512-8OZN4+rgXmsRkkjcVGAZtdp91a4USScLHr5fiQrAgIhQGWEY3Ii+VMCfsrw4sWVAwsbTzPIckOtVRKOiv7WPLg==" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3823,6 +3829,11 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, + "messageformat": { + "version": "4.0.0-11", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-4.0.0-11.tgz", + "integrity": "sha512-8OZN4+rgXmsRkkjcVGAZtdp91a4USScLHr5fiQrAgIhQGWEY3Ii+VMCfsrw4sWVAwsbTzPIckOtVRKOiv7WPLg==" + }, "micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
diff --git a/third_party/node/package.json b/third_party/node/package.json index 5bd628e6..1a62d4f 100644 --- a/third_party/node/package.json +++ b/third_party/node/package.json
@@ -32,6 +32,7 @@ "eslint-plugin-jsdoc": "50.2.2", "html-minifier": "4.0.0", "lit": "3.3.0", + "messageformat": "4.0.0-11", "svgo": "3.0.2", "terser": "5.37.0", "ts-proto": "2.7.0",
diff --git a/third_party/pdfium b/third_party/pdfium index a9fd58f..0be9b6b 160000 --- a/third_party/pdfium +++ b/third_party/pdfium
@@ -1 +1 @@ -Subproject commit a9fd58f6fb0de203c721033bb06709770a2871bc +Subproject commit 0be9b6bf152df319d17d9e4b05b2845d3226f5a5
diff --git a/third_party/perfetto b/third_party/perfetto index 060d748..9b47377 160000 --- a/third_party/perfetto +++ b/third_party/perfetto
@@ -1 +1 @@ -Subproject commit 060d748587295617a22efdc6e6336997deb250cb +Subproject commit 9b47377deba086351a83567e04cc06f8c18996d0
diff --git a/third_party/skia b/third_party/skia index a522cbe..4e2d183 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit a522cbe99752e25c919c985f53c388111f71fbeb +Subproject commit 4e2d1834d3515c28e00dc20f3f9459e198a1ecaf
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps index 3bb9a2c..9e7c771 160000 --- a/third_party/vulkan-deps +++ b/third_party/vulkan-deps
@@ -1 +1 @@ -Subproject commit 3bb9a2c5d2ef0dc54ae9697f8a6e628cc06b9fd3 +Subproject commit 9e7c7717aab8bf9d8ad4794923488d64bdc2119b
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src index 76473009..a989499 160000 --- a/third_party/vulkan-validation-layers/src +++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@ -Subproject commit 7647300983abe4aa095dd74c2544c2cbe5906a7c +Subproject commit a9894996b6df1b58546df2c8c211ae69d7de8442
diff --git a/tools/android/checkxmlstyle/helpers.py b/tools/android/checkxmlstyle/helpers.py index 9328d87..7b60e7330 100644 --- a/tools/android/checkxmlstyle/helpers.py +++ b/tools/android/checkxmlstyle/helpers.py
@@ -46,7 +46,7 @@ ] # TODO(lazzzis): Check color references in java source files. COLOR_REFERENCE_PATTERN = re.compile( - ''' +r''' @color/ # starts with '@color' ([\w|_]+) # color name is only composed of numbers, letters and underscore ''', re.VERBOSE) @@ -65,4 +65,4 @@ KNOWN_STYLE_ATTRIBUTE = re.compile( r' (android:theme|android:textAppearance|style)=\"(.*)\"') -STYLE_REF_PREFIX = re.compile('^(@style|\?attr|\?android:attr)/') +STYLE_REF_PREFIX = re.compile(r'^(@style|\?attr|\?android:attr)/')
diff --git a/tools/gdb/gdb_chrome.py b/tools/gdb/gdb_chrome.py index 92db3d34..5172b87f 100644 --- a/tools/gdb/gdb_chrome.py +++ b/tools/gdb/gdb_chrome.py
@@ -257,21 +257,6 @@ StdOptionalPrinter) -class ClampedNumericPrinter(Printer): - type_re = r'^base::internal::ClampedNumeric<(.*)>$' - - def to_string(self): - m = type_re.search(self.val.type) - if m is None: - return self.val['value'] - return '(%s) %s' % (m.group(1), self.val['value_']) - - -pp_set.add_printer('base::internal::ClampedNumeric', - '^base::internal::ClampedNumeric<.*>$', - ClampedNumericPrinter) - - class TimeDeltaPrinter(object): def __init__(self, val):
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml index e10afa1b..f2bdab5 100644 --- a/tools/metrics/histograms/metadata/android/enums.xml +++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -809,6 +809,18 @@ <int value="3" label="Neither Multi-Window nor Edge-to-Edge"/> </enum> +<enum name="BrowserControlsLockReason"> + <summary>The reason why browser controls were locked.</summary> + <int value="0" label="CHROME_URL"/> + <int value="1" label="TAB_CONTENT_DANGEROUS"/> + <int value="2" label="EDITABLE_NODE_FOCUS"/> + <int value="3" label="TAB_ERROR"/> + <int value="4" label="TAB_HIDDEN"/> + <int value="5" label="FULLSCREEN_LOADING"/> + <int value="6" label="A11Y_ENABLED"/> + <int value="7" label="FULLSCREEN_DISABLED"/> +</enum> + <enum name="CaptureNativeViewResult"> <int value="0" label="Not captured: Null WindowAndroid"/> <int value="1" label="Not captured: View not laid out"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml index 510e0953..6d1bc572 100644 --- a/tools/metrics/histograms/metadata/android/histograms.xml +++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1116,6 +1116,28 @@ </summary> </histogram> +<histogram name="Android.BrowserControls.LockedByTabState" enum="Boolean" + expires_after="2026-05-13"> + <owner>wenyufu@chromium.org</owner> + <owner>clank-app-team@google.com</owner> + <summary> + Records whether browser controls is forced to be visible by the tab state + when controls visibility is updated. Recorded at + TabStateBrowserControlsVisibilityDelegate.enableHidingBrowserControls(). + </summary> +</histogram> + +<histogram name="Android.BrowserControls.LockedByTabState.Reason" + enum="BrowserControlsLockReason" expires_after="2025-11-07"> + <owner>wenyufu@chromium.org</owner> + <owner>clank-app-team@google.com</owner> + <summary> + Records the reason when browser controls is forced to be visible. The enums + in the histogram can be recorded multiple times. Recorded at + TabStateBrowserControlsVisibilityDelegate.enableHidingBrowserControls(). + </summary> +</histogram> + <histogram name="Android.BrowserControls.OutstandingChangedOnFinish" enum="BooleanChanged" expires_after="2025-10-26"> <owner>skym@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml index ec8de8e9..08a4169 100644 --- a/tools/metrics/histograms/metadata/blink/enums.xml +++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -6207,6 +6207,7 @@ <int value="5577" label="ClearSiteData"/> <int value="5578" label="ScrollIntoViewContainerNearest"/> <int value="5579" label="PopoverShown"/> + <int value="5580" label="InputParsedParentOptionOrOptgroup"/> </enum> <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom:WebFeature) --> @@ -6355,6 +6356,7 @@ <int value="135" label="DeviceAttributes"/> <int value="136" label="LocalNetworkAccess"/> <int value="137" label="RecordAdAuctionEvents"/> + <int value="138" label="OnDeviceSpeechRecognition"/> </enum> <enum name="FedCmAccountChooserResult">
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml index 0c9c4d12..e8f0b6a2 100644 --- a/tools/metrics/histograms/metadata/ios/histograms.xml +++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -2047,7 +2047,7 @@ </token> </histogram> -<histogram name="IOS.GLICConsent" enum="GLICConsentType" +<histogram name="IOS.GLIC.Outcome" enum="GLICConsentType" expires_after="2026-05-04"> <owner>prasanaa@google.com</owner> <owner>bling-alchemy-eng@google.com</owner> @@ -2323,7 +2323,6 @@ <histogram name="IOS.IsEnabled.Password.BottomSheet" enum="Boolean" expires_after="2025-10-12"> <owner>sugoi@chromium.org</owner> - <owner>veronguyen@google.com</owner> <owner>tmartino@chromium.org</owner> <summary> At the moment of trying to enable the bottom sheet at page load time, track @@ -3825,7 +3824,6 @@ <histogram name="IOS.PasswordBottomSheet.ExitReason" enum="PasswordSuggestionBottomSheetExitReason" expires_after="2025-09-14"> <owner>sugoi@chromium.org</owner> - <owner>veronguyen@google.com</owner> <owner>tmartino@chromium.org</owner> <summary> Tracks the bottom sheet dismissal, use of a password suggestion and long @@ -3899,7 +3897,6 @@ <histogram name="IOS.PaymentsBottomSheet.ExitReason" enum="PaymentsSuggestionBottomSheetExitReason" expires_after="2025-10-26"> <owner>sugoi@chromium.org</owner> - <owner>veronguyen@google.com</owner> <owner>tmartino@chromium.org</owner> <summary> Tracks the bottom sheet dismissal or use of a payments suggestion. @@ -4527,7 +4524,6 @@ <histogram name="IOS.Reauth.Password.BottomSheet" enum="ReauthenticationEvent" expires_after="2025-07-13"> <owner>sugoi@chromium.org</owner> - <owner>veronguyen@google.com</owner> <owner>tmartino@chromium.org</owner> <summary> Tracks the results and attempts of reauthentication when using suggestions @@ -4558,13 +4554,14 @@ </histogram> <histogram name="IOS.ResetDismissCount.Password.BottomSheet" units="dismisses" - expires_after="2024-05-08"> + expires_after="2025-10-08"> <owner>sugoi@chromium.org</owner> - <owner>veronguyen@google.com</owner> <owner>tmartino@chromium.org</owner> <summary> Tracks the number of times in a row the Password Bottom Sheet had been dismissed at the moment the dismiss count was reset to 0 (between 1 and 3). + + Note: this metric was expired from 2024-05 to 2025-05; data may be missing. </summary> </histogram>
diff --git a/tools/metrics/histograms/metadata/webauthn/enums.xml b/tools/metrics/histograms/metadata/webauthn/enums.xml index be28a88d..40d9b119 100644 --- a/tools/metrics/histograms/metadata/webauthn/enums.xml +++ b/tools/metrics/histograms/metadata/webauthn/enums.xml
@@ -308,6 +308,14 @@ WebAuthn credential get request was successfully resolved with different authenticator than Google Password Manager. </int> + <int value="4" label="CreateResolvedGpm"> + WebAuthn credential create request was successfully resolved with Google + Password Manager as an authenticator. + </int> + <int value="5" label="CreateResolvedNonGpm"> + WebAuthn credential create request was successfully resolved with different + authenticator than Google Password Manager. + </int> </enum> <!-- LINT.ThenChange(//components/webauthn/ios/passkey_tab_helper.mm) -->
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index db898ed..492c945 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@ "full_remote_path": "perfetto-luci-artifacts/v50.1/linux-arm64/trace_processor_shell" }, "win": { - "hash": "8d81c216e59a8ccb7a111ff732216ccc8693e006", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4402fd7953d5dedf65659e99ab80404f6e94a004/trace_processor_shell.exe" + "hash": "c1a4db7572fa8e84f2dbc00e0e886617aa84142a", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/060d748587295617a22efdc6e6336997deb250cb/trace_processor_shell.exe" }, "linux_arm": { "hash": "99f971ca131f6d11c73f4b918099d434bdd8093c",
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java index 89f3d572..2a977958 100644 --- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java +++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -47,6 +47,7 @@ import org.chromium.base.PackageManagerUtils; import org.chromium.base.TraceEvent; import org.chromium.base.UnownedUserDataHost; +import org.chromium.base.lifetime.Destroyable; import org.chromium.base.lifetime.LifetimeAssert; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.ObservableSupplierImpl; @@ -79,7 +80,8 @@ public class WindowAndroid implements AndroidPermissionDelegate, DisplayAndroidObserver, - View.OnAttachStateChangeListener { + View.OnAttachStateChangeListener, + Destroyable { private static final String TAG = "WindowAndroid"; private static final ImmutableWeakReference<Activity> NULL_ACTIVITY_WEAK_REF = new ImmutableWeakReference<>(null); @@ -872,12 +874,13 @@ return mIsDestroyed; } - /** Destroys the c++ WindowAndroid object if one has been created. */ @CalledByNative + @Override public void destroy() { - LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + LifetimeAssert.destroy(mLifetimeAssert); mIsDestroyed = true; mDisplayAndroid.removeObserver(this); + // Destroys the c++ WindowAndroid object if one has been created. if (mNativeWindowAndroid != 0) { // Native code clears |mNativeWindowAndroid|. WindowAndroidJni.get().destroy(mNativeWindowAndroid, WindowAndroid.this);
diff --git a/ui/base/cocoa/appkit_utils.h b/ui/base/cocoa/appkit_utils.h index 1d83e88..c9e3686 100644 --- a/ui/base/cocoa/appkit_utils.h +++ b/ui/base/cocoa/appkit_utils.h
@@ -20,7 +20,7 @@ // Returns true if both CGFloat values are equal. COMPONENT_EXPORT(UI_BASE) bool IsCGFloatEqual(CGFloat a, CGFloat b); -// Returns true if the current application owns the menu bar. +// Returns true if the current application is the active application. COMPONENT_EXPORT(UI_BASE) bool IsActiveApplication(); // The NSServicesMenuRequestor protocol does not pass modern NSPasteboardType
diff --git a/ui/gfx/mac/nswindow_frame_controls.h b/ui/gfx/mac/nswindow_frame_controls.h index 5d8ac102..3c631a02 100644 --- a/ui/gfx/mac/nswindow_frame_controls.h +++ b/ui/gfx/mac/nswindow_frame_controls.h
@@ -23,6 +23,9 @@ // Sets the min/max size of the window as well as showing/hiding resize, // maximize, and fullscreen controls. +// +// For the maximum size, a height/width specified as 0 means unbounded. +// // Sizes refer to the content size (inner bounds). COMPONENT_EXPORT(GFX) void ApplyNSWindowSizeConstraints(NSWindow* window,
diff --git a/ui/gfx/mac/nswindow_frame_controls.mm b/ui/gfx/mac/nswindow_frame_controls.mm index 046d1de..d23b0760 100644 --- a/ui/gfx/mac/nswindow_frame_controls.mm +++ b/ui/gfx/mac/nswindow_frame_controls.mm
@@ -8,41 +8,28 @@ #include "ui/gfx/geometry/size.h" -namespace { - -// The value used to represent an unbounded width or height. -const int kUnboundedSize = 0; - -void SetResizableStyleMask(NSWindow* window, bool resizable) { - NSUInteger style_mask = window.styleMask; - if (resizable) - style_mask |= NSWindowStyleMaskResizable; - else - style_mask &= ~NSWindowStyleMaskResizable; - window.styleMask = style_mask; -} - -} // namespace - namespace gfx { void SetNSWindowCanFullscreen(NSWindow* window, bool allow_fullscreen) { NSWindowCollectionBehavior behavior = window.collectionBehavior; - if (behavior & NSWindowCollectionBehaviorFullScreenAuxiliary) + if (behavior & NSWindowCollectionBehaviorFullScreenAuxiliary) { return; - if (allow_fullscreen) + } + if (allow_fullscreen) { behavior |= NSWindowCollectionBehaviorFullScreenPrimary; - else + } else { behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; + } window.collectionBehavior = behavior; } void SetNSWindowVisibleOnAllWorkspaces(NSWindow* window, bool always_visible) { NSWindowCollectionBehavior behavior = window.collectionBehavior; - if (always_visible) + if (always_visible) { behavior |= NSWindowCollectionBehaviorCanJoinAllSpaces; - else + } else { behavior &= ~NSWindowCollectionBehaviorCanJoinAllSpaces; + } window.collectionBehavior = behavior; } @@ -51,18 +38,24 @@ const gfx::Size& max_size, bool can_resize, bool can_fullscreen) { - window.contentMinSize = NSMakeSize(min_size.width(), min_size.height()); + const int kUnboundedSize = 0; CGFloat max_width = max_size.width() == kUnboundedSize ? CGFLOAT_MAX : max_size.width(); CGFloat max_height = max_size.height() == kUnboundedSize ? CGFLOAT_MAX : max_size.height(); - window.contentMaxSize = NSMakeSize(max_width, max_height); - SetResizableStyleMask(window, can_resize); - window.showsResizeIndicator = can_resize; + window.contentMinSize = min_size.ToCGSize(); + window.contentMaxSize = CGSizeMake(max_width, max_height); - // Set the window to participate in Lion Fullscreen mode. + NSUInteger style_mask = window.styleMask; + if (can_resize) { + style_mask |= NSWindowStyleMaskResizable; + } else { + style_mask &= ~NSWindowStyleMaskResizable; + } + window.styleMask = style_mask; + SetNSWindowCanFullscreen(window, can_fullscreen); [window standardWindowButton:NSWindowZoomButton].enabled = can_fullscreen;
diff --git a/ui/gl/gl_features.cc b/ui/gl/gl_features.cc index ddd12a8f..56a95551 100644 --- a/ui/gl/gl_features.cc +++ b/ui/gl/gl_features.cc
@@ -65,7 +65,7 @@ kPassthroughCommandDecoderBlockListByGPUVendorId{ &kDefaultPassthroughCommandDecoder, "BlockListByGPUVendorId", ""}; -bool IsDeviceBlocked(const char* field, const std::string& block_list) { +bool IsDeviceBlocked(const std::string& field, const std::string& block_list) { auto disable_patterns = base::SplitString( block_list, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& disable_pattern : disable_patterns) {
diff --git a/ui/gtk/gtk_color_mixers.cc b/ui/gtk/gtk_color_mixers.cc index f8bf83d..7992f395 100644 --- a/ui/gtk/gtk_color_mixers.cc +++ b/ui/gtk/gtk_color_mixers.cc
@@ -51,6 +51,15 @@ accent_color.value_or(GetBgColor("treeview.view " "treeview.view.cell:selected:focus")); + static constexpr char kTextFocused[] = + "textview.view:focus:focus-within text:focus:focus-within"; + static constexpr char kSelectionFocused[] = + "textview.view:focus:focus-within text:focus:focus-within " + "selection:focus:focus-within"; + const SkColor kSelectedTextBackground = GetBgColor(kSelectionFocused); + const SkColor kSelectedTextForeground = + GetFgColor(GtkCheckVersion(4) ? kTextFocused : kSelectionFocused); + // Core colors mixer[ui::kColorAccent] = {accent_bg}; mixer[ui::kColorAlertHighSeverity] = {SelectBasedOnDarkInput( @@ -72,8 +81,8 @@ mixer[ui::kColorPrimaryBackground] = {primary_bg}; mixer[ui::kColorPrimaryForeground] = {label_fg}; mixer[ui::kColorSecondaryForeground] = {label_fg_disabled}; - mixer[ui::kColorTextSelectionBackground] = {GetBgColor("label selection")}; - mixer[ui::kColorTextSelectionForeground] = {GetFgColor("label selection")}; + mixer[ui::kColorTextSelectionBackground] = {kSelectedTextBackground}; + mixer[ui::kColorTextSelectionForeground] = {kSelectedTextForeground}; // UI element colors mixer[ui::kColorAvatarHeaderArt] = @@ -183,15 +192,8 @@ mixer[ui::kColorTextfieldForegroundDisabled] = { GetFgColor("textview.view:disabled text")}; mixer[ui::kColorTextfieldForegroundPlaceholder] = {GtkCheckVersion(4)}; - static constexpr char kTextFocused[] = - "textview.view:focus:focus-within text:focus:focus-within"; - static constexpr char kSelectionFocused[] = - "textview.view:focus:focus-within text:focus:focus-within " - "selection:focus:focus-within"; - mixer[ui::kColorTextfieldSelectionBackground] = { - GetBgColor(kSelectionFocused)}; - mixer[ui::kColorTextfieldSelectionForeground] = { - GetFgColor(GtkCheckVersion(4) ? kTextFocused : kSelectionFocused)}; + mixer[ui::kColorTextfieldSelectionBackground] = {kSelectedTextBackground}; + mixer[ui::kColorTextfieldSelectionForeground] = {kSelectedTextForeground}; mixer[ui::kColorThrobber] = {GetFgColor("spinner")}; mixer[ui::kColorThrobberPreconnect] = {GetFgColor("spinner:disabled")}; mixer[ui::kColorToggleButtonTrackOff] = {
diff --git a/ui/views/view.cc b/ui/views/view.cc index 466f59b..8764194b 100644 --- a/ui/views/view.cc +++ b/ui/views/view.cc
@@ -1835,6 +1835,10 @@ return true; } +base::span<const ui::Accelerator> View::GetAccelerators() const { + return accelerators_ ? *accelerators_ : base::span<const ui::Accelerator>(); +} + // Focus ----------------------------------------------------------------------- bool View::HasFocus() const {
diff --git a/ui/views/view.h b/ui/views/view.h index ae7eab3..0dbbd79 100644 --- a/ui/views/view.h +++ b/ui/views/view.h
@@ -1339,6 +1339,8 @@ // IsDrawn() bool CanHandleAccelerators() const override; + base::span<const ui::Accelerator> GetAccelerators() const; + // Focus --------------------------------------------------------------------- // Returns whether this view currently has the focus.
diff --git a/v8 b/v8 index 54cfc678..340b396 160000 --- a/v8 +++ b/v8
@@ -1 +1 @@ -Subproject commit 54cfc6784891b42308c9936263c13ca552f95ee9 +Subproject commit 340b39612320d80ca2afbf0467b472941aa031a6