diff --git a/AUTHORS b/AUTHORS index 7a324d6..80e149f 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -1449,6 +1449,7 @@ Suyambulingam R M <suyambu.rm@samsung.com> Suyash Nayan <suyashnyn1@gmail.com> Suyash Sengar <suyash.s@samsung.com> +Suyeon Ji <zeesuyeon@gmail.com> Swarali Raut <swarali.sr@samsung.com> Swati Jaiswal <swa.jaiswal@samsung.com> Syed Wajid <syed.wajid@samsung.com>
diff --git a/DEPS b/DEPS index 7bdc90e..e7df693 100644 --- a/DEPS +++ b/DEPS
@@ -299,7 +299,7 @@ # 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': 'dc1958e7118c351cef5adae548337f7f11746b95', + 'skia_revision': 'f8cd9fe75f21d3be759cbf9491ddc582efcf1e2a', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -307,7 +307,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': 'c53c908c41a10167fabdf4f49c98b9d610d43216', + 'angle_revision': '79ec8b3400ceeafc3e69b9bec29fa39a0e1a9a16', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # 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': 'd7cb23eac0fda3c06441034ccea0c4bb9c947add', + 'devtools_frontend_revision': 'fd0c28cbf06b695f677c525b6630f9f9e4a90eee', # 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. @@ -415,7 +415,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. - 'dawn_revision': '9635124871e8ab6d38d416e78759a447e68c35e5', + 'dawn_revision': '223318d090e36d901f7c7a83b764e0a336b19385', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -519,7 +519,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling llvm-libc # and whatever else without interference from each other. - 'llvm_libc_revision': '1f64d055dbb79dfcef1a63588341d171f327cd73', + 'llvm_libc_revision': 'ff75100b45027c70e17770cb80e720193b742c76', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling llvm-libc # and whatever else without interference from each other. @@ -1521,7 +1521,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - 'b62d38ffc9bd9bfc6011eabe6143a4a40e45bd9a', + '4e7cdb1917bb21338750038cdc8389b643f96cd5', 'condition': 'checkout_android and checkout_src_internal', }, @@ -1680,7 +1680,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'I9DcrpteFBl2d2LwjrGGN07Kcsom-8r_VGyqAfakS2cC', + 'version': 'Qnm0rlf-dVJZJzD3cigXJErvHBVBFNFvsTmMgSPAosUC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1773,7 +1773,7 @@ 'packages': [ { 'package': 'chromium/third_party/android_build_tools/lint', - 'version': 'Otd2S_y5ozK3q8Q7eMr1NqNH38ESBnUYT4I842UsU0UC', + 'version': '3bWjs4NjBtTIXoWH03nPx8c--ehZzlDkL8PUE_GaPKUC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1784,7 +1784,7 @@ 'packages': [ { 'package': 'chromium/third_party/android_build_tools/manifest_merger', - 'version': 'UrgRDTQRxa2KqkIGo6gwYOY7uf56hYmH-QAjov2N9NMC', + 'version': 'AFWUMAcwcd0L1DG2-ib4ghtQYFsCvZjWuQkhuTJl4ToC', }, ], 'condition': 'checkout_android and non_git_source', @@ -2559,7 +2559,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '3231268f2960b13cb52788389e88d7932117f57e', + Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'd7984ebb216b56e0eb99ba29adfae91f37819602', 'src/base/tracing/test/data': { 'bucket': 'perfetto', @@ -2878,16 +2878,16 @@ 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@222889ea039509b86fa6dd90fcb6acf47e79cdb9', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@5f9183df4e99d153dcf2de173ddfd47034f72601', 'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@05cfcc1613c28c1274036f53616d66324f7cd383', '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@50daff941d88609b4d2ad076eae558e727f8e5cd', 'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@e0bad2825dacf274578ec6d3c0e64e406d5e4fd7', - 'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@1d6c53f65443ceeb97d3bdc695aaecc7ea6cc441', - 'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@63b05d2b087952662623de7eddd44f2e57d71a1e', - 'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@fbe722654b7173da961398cf78bd4a62d1839b65', - 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@e48ae20a7938b01aee62806bfcdafe8a0883b1e4', - 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@6d6e6ec8e51cd219498359cbc48e4762d1a80616', + 'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@b39ab380a44b6c8df462c34e976ea9ce2d2c336b', + 'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@22c0f133e6675f9313c12fb5e58337f8fa9b9195', + 'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@d671923090e4dc74c0ebdb10c6e09fa0826e1fe9', + 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@54c9baf20802a13279e23fa4cb0528dd5cf16064', + 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@4b8f3caab2cf0ace618ba857364346aed6297d29', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21', @@ -2932,7 +2932,7 @@ Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'), 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'cf714279e2a0d54e03b4065f14748dea1dc24606', + Var('webrtc_git') + '/src.git' + '@' + '70347d915e9b3d74cfbcc1323f35edc834f097e0', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index b293c89e..a3d8bd6 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -314,7 +314,7 @@ // client for Spotlight within the Boca SWA. BASE_FEATURE(kBocaSpotlightRobotRequester, "BocaSpotlightRobotRequester", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); // Enables or disables enforcing sequential execution for Boca insert activity. BASE_FEATURE(kBocaSequentialInsertActivity,
diff --git a/ash/webui/boca_ui/boca_app_page_handler_unittest.cc b/ash/webui/boca_ui/boca_app_page_handler_unittest.cc index acf3da2..9824365 100644 --- a/ash/webui/boca_ui/boca_app_page_handler_unittest.cc +++ b/ash/webui/boca_ui/boca_app_page_handler_unittest.cc
@@ -404,8 +404,11 @@ public: BocaAppPageHandlerTest() = default; void SetUp() override { - scoped_feature_list_.InitWithFeatures({ash::features::kBoca}, - /*disabled_features=*/{}); + scoped_feature_list_.InitWithFeatures( + {ash::features::kBoca}, + // TODO:crbug.com/424867979 - Re-enable feature flag after adding unit + // tests. + /*disabled_features=*/{ash::features::kBocaSpotlightRobotRequester}); // Set up UserManager related modules. user_manager::UserManagerImpl::RegisterPrefs(local_state_.registry()); ash::boca_util::RegisterPrefs(local_state_.registry());
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java index 9459896..356671a 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java +++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
@@ -288,9 +288,9 @@ } if (primary.mTries != null) { - builder.withTimeout(primary.mTries); + builder.withTries(primary.mTries); } else if (secondary.mTries != null) { - builder.withTimeout(secondary.mTries); + builder.withTries(secondary.mTries); } if (primary.mPossiblyAlreadyFulfilled != null) { @@ -339,8 +339,7 @@ * timeout. */ public Builder withRetry() { - mTries = 2; - return this; + return withTries(2); } /** @@ -348,7 +347,11 @@ * finish within the timeout. */ public Builder withNoRetry() { - mTries = 1; + return withTries(1); + } + + private Builder withTries(int tries) { + mTries = tries; return this; }
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto index 4a48f56..759fd6f 100644 --- a/base/tracing/protos/chrome_track_event.proto +++ b/base/tracing/protos/chrome_track_event.proto
@@ -1026,6 +1026,9 @@ V8_USER_VISIBLE_TQ = 56; V8_BEST_EFFORT_TQ = 57; + + NETWORK_SERVICE_THREAD_HIGH_TQ = 58; + NETWORK_SERVICE_THREAD_DEFAULT_TQ = 59; } optional Priority priority = 1;
diff --git a/cc/layers/texture_layer_impl_unittest.cc b/cc/layers/texture_layer_impl_unittest.cc index eeefaf7f..db61203a 100644 --- a/cc/layers/texture_layer_impl_unittest.cc +++ b/cc/layers/texture_layer_impl_unittest.cc
@@ -27,12 +27,9 @@ LayerTreeImplTestBase impl; - auto resource = viz::TransferableResource::MakeGpu( - gpu::Mailbox::Generate(), GL_TEXTURE_2D, - gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x234), 0x456), - layer_bounds, viz::SinglePlaneFormat::kRGBA_8888, - false /* is_overlay_candidate */); + auto resource = viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, gpu::SyncToken()); TextureLayerImpl* layer = impl.AddLayerInActiveTree<TextureLayerImpl>(); layer->SetBounds(layer_bounds); @@ -61,12 +58,9 @@ LayerTreeImplTestBase impl; - auto resource = viz::TransferableResource::MakeGpu( - gpu::Mailbox::Generate(), GL_TEXTURE_2D, - gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x234), 0x456), - layer_size, viz::SinglePlaneFormat::kRGBA_8888, - false /* is_overlay_candidate */); + auto resource = viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, gpu::SyncToken()); TextureLayerImpl* texture_layer_impl = impl.AddLayerInActiveTree<TextureLayerImpl>();
diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc index c94066f..82d26cd 100644 --- a/cc/layers/texture_layer_unittest.cc +++ b/cc/layers/texture_layer_unittest.cc
@@ -77,33 +77,32 @@ namespace cc { namespace { -gpu::Mailbox MailboxFromChar(char value) { - gpu::Mailbox mailbox; - memset(mailbox.name, value, sizeof(mailbox.name)); - return mailbox; +// Compares SyncToken ignoring verified_flush() bit. +MATCHER_P(SameSyncToken, other, "") { + gpu::SyncToken a = arg; + gpu::SyncToken b = other; + a.SetVerifyFlush(); + b.SetVerifyFlush(); + return a == b; } -gpu::SyncToken SyncTokenFromUInt(uint32_t value) { +gpu::SyncToken GenSyncToken() { + static int next_release = 1; return gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x123), value); + gpu::CommandBufferId::FromUnsafeValue(0x234), + next_release++); } -void AllocateSharedImage(gpu::SharedImageInterface* shared_image_interface, - const gfx::Size& size, - scoped_refptr<gpu::ClientSharedImage>& shared_image, - gpu::SyncToken& sync_token) { - CHECK(shared_image_interface); - viz::SharedImageFormat format = viz::SinglePlaneFormat::kBGRA_8888; - shared_image = shared_image_interface->CreateSharedImageForSoftwareCompositor( - {format, size, gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, - "TextureLayerTest"}); - sync_token = shared_image_interface->GenVerifiedSyncToken(); +viz::TransferableResource MakeFakeResource() { + return viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, GenSyncToken()); } -void DeleteSharedImage(scoped_refptr<gpu::ClientSharedImage> shared_image, - const gpu::SyncToken& sync_token, - bool is_lost) { - shared_image->UpdateDestructionSyncToken(sync_token); +viz::TransferableResource MakeFakeSoftwareResource() { + return viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateSoftwareForTesting(), + viz::TransferableResource::ResourceSource::kTest, GenSyncToken()); } class MockLayerTreeHost : public LayerTreeHost { @@ -137,73 +136,53 @@ class MockReleaseCallback { public: - MOCK_METHOD3(Release, - void(const gpu::Mailbox& mailbox, - const gpu::SyncToken& sync_token, - bool lost_resource)); - MOCK_METHOD3(ReleaseSW, - void(scoped_refptr<gpu::ClientSharedImage>, - const gpu::SyncToken& token, - bool lost)); + MOCK_METHOD2(Release, + void(const gpu::SyncToken& sync_token, bool lost_resource)); }; struct CommonResourceObjects { - CommonResourceObjects() - : mailbox_name1_(MailboxFromChar('1')), - mailbox_name2_(MailboxFromChar('2')), - sync_token1_(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x234), - 1), - sync_token2_(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x234), - 2), - sync_token_sw_(gpu::CommandBufferNamespace::GPU_IO, - gpu::CommandBufferId::FromUnsafeValue(0x235), - 1) { - release_callback1_ = - base::BindRepeating(&MockReleaseCallback::Release, - base::Unretained(&mock_callback_), mailbox_name1_); - release_callback2_ = - base::BindRepeating(&MockReleaseCallback::Release, - base::Unretained(&mock_callback_), mailbox_name2_); - const uint32_t arbitrary_target1 = GL_TEXTURE_2D; - const uint32_t arbitrary_target2 = GL_TEXTURE_EXTERNAL_OES; - gfx::Size size(128, 128); - resource1_ = viz::TransferableResource::MakeGpu( - mailbox_name1_, arbitrary_target1, sync_token1_, size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); - resource2_ = viz::TransferableResource::MakeGpu( - mailbox_name2_, arbitrary_target2, sync_token2_, size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + explicit CommonResourceObjects(bool software) { + if (software) { + resource = MakeFakeSoftwareResource(); + } else { + resource = MakeFakeResource(); + } - // For software rendering - shared_image_sw_ = gpu::ClientSharedImage::CreateForTesting(); - resource_sw_ = viz::TransferableResource::MakeSoftwareSharedImage( - shared_image_sw_, sync_token_sw_, size, - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTest); - release_callback_sw_ = base::BindRepeating( - &MockReleaseCallback::ReleaseSW, base::Unretained(&mock_callback_), - shared_image_sw_); + creation_sync_token = resource.sync_token(); + + release_callback = base::BindRepeating(&MockReleaseCallback::Release, + base::Unretained(&mock_callback)); } + CommonResourceObjects& ExpectReleaseWithSyncToken( + const gpu::SyncToken& sync_token, + bool lost) { + EXPECT_CALL(mock_callback, Release(sync_token, lost)).Times(1); + return *this; + } + + CommonResourceObjects& ExpectRelease() { + EXPECT_CALL(mock_callback, + Release(SameSyncToken(creation_sync_token), false)) + .Times(1); + return *this; + } + + CommonResourceObjects& ExpectNoRelease() { + EXPECT_CALL(mock_callback, Release(_, _)).Times(0); + return *this; + } + + void Verify() { Mock::VerifyAndClearExpectations(&mock_callback); } + using RepeatingReleaseCallback = base::RepeatingCallback<void(const gpu::SyncToken& sync_token, bool is_lost)>; - gpu::Mailbox mailbox_name1_; - gpu::Mailbox mailbox_name2_; - scoped_refptr<gpu::ClientSharedImage> shared_image_sw_; - MockReleaseCallback mock_callback_; - RepeatingReleaseCallback release_callback1_; - RepeatingReleaseCallback release_callback2_; - RepeatingReleaseCallback release_callback_sw_; - gpu::SyncToken sync_token1_; - gpu::SyncToken sync_token2_; - gpu::SyncToken sync_token_sw_; - viz::TransferableResource resource1_; - viz::TransferableResource resource2_; - viz::TransferableResource resource_sw_; + RepeatingReleaseCallback release_callback; + viz::TransferableResource resource; + gpu::SyncToken creation_sync_token; + MockReleaseCallback mock_callback; }; class TextureLayerTest : public testing::Test { @@ -240,7 +219,9 @@ TestTaskGraphRunner task_graph_runner_; std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink_; FakeLayerTreeHostImpl host_impl_; - CommonResourceObjects test_data_; + CommonResourceObjects test_resource1_{false}; + CommonResourceObjects test_resource2_{false}; + CommonResourceObjects test_resource_sw_{true}; }; TEST_F(TextureLayerTest, CheckPropertyChangeCausesCorrectBehavior) { @@ -302,13 +283,11 @@ scoped_refptr<TextureLayer> layer = TextureLayer::Create(nullptr); layer->SetIsDrawable(true); layer->SetBounds(gfx::Size(10, 10)); - if (gpu) { - layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); - } else { - layer->SetTransferableResource(test_data_.resource_sw_, - test_data_.release_callback_sw_); - } + + auto& test_resource = gpu ? test_resource1_ : test_resource_sw_; + + layer->SetTransferableResource(test_resource.resource, + test_resource.release_callback); viz::ParentLocalSurfaceIdAllocator allocator; allocator.GenerateId(); @@ -334,16 +313,7 @@ // Our LayerTreeHostClient makes a FakeLayerTreeFrameSink which returns all // resources when its detached, so the resources will not be in use in the // display compositor, and will be returned as not lost. - if (gpu) { - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, _, false)) - .Times(1); - } else { - EXPECT_CALL(test_data_.mock_callback_, - ReleaseSW(test_data_.shared_image_sw_, - test_data_.sync_token_sw_, false)) - .Times(1); - } + test_resource.ExpectRelease(); { base::RunLoop loop; loop.RunUntilIdle(); @@ -362,11 +332,8 @@ class TextureLayerWithResourceTest : public TextureLayerTest { protected: void TearDown() override { - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(1); + test_resource1_.Verify(); + test_resource1_.ExpectRelease(); TextureLayerTest::TearDown(); } }; @@ -380,48 +347,38 @@ Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - test_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(1); - test_layer->SetTransferableResource(test_data_.resource2_, - test_data_.release_callback2_); + test_resource1_.ExpectRelease(); + test_layer->SetTransferableResource(test_resource2_.resource, + test_resource2_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name2_, test_data_.sync_token2_, false)) - .Times(1); + test_resource2_.ExpectRelease(); test_layer->ClearTexture(); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource2_.Verify(); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - test_layer->SetTransferableResource(test_data_.resource_sw_, - test_data_.release_callback_sw_); + test_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - EXPECT_CALL( - test_data_.mock_callback_, - ReleaseSW(test_data_.shared_image_sw_, test_data_.sync_token_sw_, false)) - .Times(1); + test_resource1_.ExpectRelease(); test_layer->ClearTexture(); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); // Test destructor. EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - test_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); } TEST_F(TextureLayerWithResourceTest, AffectedByHdr) { @@ -433,34 +390,28 @@ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); // sRGB is unaffected by HDR parameters. - test_data_.resource1_.color_space = gfx::ColorSpace::CreateSRGB(); - test_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_resource1_.resource.color_space = gfx::ColorSpace::CreateSRGB(); + test_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_FALSE(test_layer->RequiresSetNeedsDisplayOnHdrHeadroomChange()); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(1); + test_resource1_.ExpectRelease(); // HDR10 is affected by HDR parameters. - test_data_.resource2_.color_space = gfx::ColorSpace::CreateHDR10(); - test_layer->SetTransferableResource(test_data_.resource2_, - test_data_.release_callback2_); + test_resource2_.resource.color_space = gfx::ColorSpace::CreateHDR10(); + test_layer->SetTransferableResource(test_resource2_.resource, + test_resource2_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + EXPECT_TRUE(test_layer->RequiresSetNeedsDisplayOnHdrHeadroomChange()); - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name2_, test_data_.sync_token2_, false)) - .Times(1); + test_resource2_.ExpectRelease(); EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); // sRGB with extended range is affected by HDR parameters. - test_data_.resource1_.hdr_metadata.extended_range.emplace(5.f, 5.f); - test_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_resource1_.resource.hdr_metadata.extended_range.emplace(5.f, 5.f); + test_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_TRUE(test_layer->RequiresSetNeedsDisplayOnHdrHeadroomChange()); } @@ -469,6 +420,8 @@ public: TextureLayerMailboxHolderTest() : main_thread_("MAIN") { main_thread_.Start(); + sync_token1_ = GenSyncToken(); + sync_token2_ = GenSyncToken(); } void Wait(const base::Thread& thread) { @@ -481,8 +434,8 @@ } void CreateMainRef() { - resource_holder_ = TestMailboxHolder::Create(test_data_.resource1_, - test_data_.release_callback1_); + resource_holder_ = TestMailboxHolder::Create( + test_resource1_.resource, test_resource1_.release_callback); } void ReleaseMainRef() { resource_holder_ = nullptr; } @@ -498,6 +451,8 @@ protected: scoped_refptr<TextureLayer::TransferableResourceHolder> resource_holder_; base::Thread main_thread_; + gpu::SyncToken sync_token1_; + gpu::SyncToken sync_token2_; }; TEST_F(TextureLayerMailboxHolderTest, TwoCompositors_BothReleaseThenMain) { @@ -527,29 +482,26 @@ main_thread_.task_runner())); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); // The compositors both destroy their impl trees before the main thread layer // is destroyed. - std::move(compositor1).Run(SyncTokenFromUInt(100), false); - std::move(compositor2).Run(SyncTokenFromUInt(200), false); + std::move(compositor1).Run(sync_token1_, false); + std::move(compositor2).Run(sync_token2_, false); Wait(main_thread_); - EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectNoRelease().Verify(); // The main thread ref is the last one, so the resource is released back to // the embedder, with the last sync point provided by the impl trees. - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, SyncTokenFromUInt(200), false)) - .Times(1); + test_resource1_.ExpectReleaseWithSyncToken(sync_token2_, false); main_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&TextureLayerMailboxHolderTest::ReleaseMainRef, base::Unretained(this))); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); } TEST_F(TextureLayerMailboxHolderTest, TwoCompositors_MainReleaseBetween) { @@ -579,10 +531,10 @@ main_thread_.task_runner())); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectNoRelease().Verify(); // One compositor destroys their impl tree. - std::move(compositor1).Run(SyncTokenFromUInt(100), false); + std::move(compositor1).Run(sync_token1_, false); // Then the main thread reference is destroyed. main_thread_.task_runner()->PostTask( @@ -591,18 +543,14 @@ Wait(main_thread_); - EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); - + test_resource1_.ExpectNoRelease().Verify(); // The second impl reference is destroyed last, causing the resource to be // released back to the embedder with the last sync point from the impl tree. - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, SyncTokenFromUInt(200), true)) - .Times(1); + test_resource1_.ExpectReleaseWithSyncToken(sync_token2_, true); - std::move(compositor2).Run(SyncTokenFromUInt(200), true); + std::move(compositor2).Run(sync_token2_, true); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); } TEST_F(TextureLayerMailboxHolderTest, TwoCompositors_MainReleasedFirst) { @@ -632,7 +580,7 @@ main_thread_.task_runner())); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectNoRelease().Verify(); // The main thread reference is destroyed first. main_thread_.task_runner()->PostTask( @@ -640,22 +588,19 @@ base::Unretained(this))); // One compositor destroys their impl tree. - std::move(compositor2).Run(SyncTokenFromUInt(200), false); + std::move(compositor2).Run(sync_token2_, false); Wait(main_thread_); - EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectNoRelease().Verify(); // The second impl reference is destroyed last, causing the resource to be // released back to the embedder with the last sync point from the impl tree. - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, SyncTokenFromUInt(100), true)) - .Times(1); + test_resource1_.ExpectReleaseWithSyncToken(sync_token1_, true); - std::move(compositor1).Run(SyncTokenFromUInt(100), true); + std::move(compositor1).Run(sync_token1_, true); Wait(main_thread_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); } class TextureLayerImplWithMailboxThreadedCallback : public LayerTreeTest { @@ -683,7 +628,7 @@ case 1: // Case #1: change resource before the commit. The old resource should // be released immediately. - SetMailbox('2'); + SetNewFakeResource(); EXPECT_EQ(1, callback_count_); PostSetNeedsCommitToMainThread(); @@ -694,7 +639,7 @@ // Case #2: change resource after the commit (and draw), where the // layer draws. The old resource should be released during the next // commit. - SetMailbox('3'); + SetNewFakeResource(); EXPECT_EQ(1, callback_count_); // Cases 3-5 rely on a callback to advance. @@ -705,7 +650,7 @@ // Case #3: change resource when the layer doesn't draw. The old // resource should be released during the next commit. layer_->SetBounds(gfx::Size()); - SetMailbox('4'); + SetNewFakeResource(); break; case 4: EXPECT_EQ(3, callback_count_); @@ -716,7 +661,7 @@ case 5: EXPECT_EQ(4, callback_count_); // Restore a resource for the next step. - SetMailbox('5'); + SetNewFakeResource(); // Cases 6 and 7 do not rely on callbacks to advance. pending_callback_ = false; @@ -748,9 +693,7 @@ } // Make sure callback is received on main and doesn't block the impl thread. - void ReleaseCallback(char mailbox_char, - const gpu::SyncToken& sync_token, - bool lost_resource) { + void ReleaseCallback(const gpu::SyncToken& sync_token, bool lost_resource) { EXPECT_EQ(true, main_thread_.CalledOnValidThread()); EXPECT_FALSE(lost_resource); ++callback_count_; @@ -768,17 +711,13 @@ } } - void SetMailbox(char mailbox_char) { + void SetNewFakeResource() { EXPECT_EQ(true, main_thread_.CalledOnValidThread()); viz::ReleaseCallback callback = base::BindOnce( &TextureLayerImplWithMailboxThreadedCallback::ReleaseCallback, - base::Unretained(this), mailbox_char); + base::Unretained(this)); - const gfx::Size size(64, 64); - auto resource = viz::TransferableResource::MakeGpu( - MailboxFromChar(mailbox_char), GL_TEXTURE_2D, - SyncTokenFromUInt(static_cast<uint32_t>(mailbox_char)), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + auto resource = MakeFakeResource(); layer_->SetTransferableResource(resource, std::move(callback)); // Damage the layer so we send a new frame with the new resource to the // Display compositor. @@ -800,7 +739,7 @@ layer_tree_host()->SetRootLayer(root_); layer_tree_host()->SetViewportRectAndScale(gfx::Rect(bounds), 1.f, viz::LocalSurfaceId()); - SetMailbox('1'); + SetNewFakeResource(); EXPECT_EQ(0, callback_count_); // Setup is complete - advance to test case 1. @@ -856,19 +795,11 @@ // Test conditions for results of TextureLayerImpl::WillDraw under // different configurations of different mailbox, texture_id, and draw_mode. TEST_F(TextureLayerImplWithResourceTest, TestWillDraw) { - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(AnyNumber()); - EXPECT_CALL( - test_data_.mock_callback_, - ReleaseSW(test_data_.shared_image_sw_, test_data_.sync_token_sw_, false)) - .Times(AnyNumber()); // Hardware mode. { std::unique_ptr<TextureLayerImpl> impl_layer = CreateTextureLayer(); - impl_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + impl_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE)); } @@ -883,8 +814,8 @@ // Software mode. { std::unique_ptr<TextureLayerImpl> impl_layer = CreateTextureLayer(); - impl_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + impl_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE)); } @@ -898,16 +829,16 @@ { // Software resource. std::unique_ptr<TextureLayerImpl> impl_layer = CreateTextureLayer(); - impl_layer->SetTransferableResource(test_data_.resource_sw_, - test_data_.release_callback_sw_); + impl_layer->SetTransferableResource(test_resource_sw_.resource, + test_resource_sw_.release_callback); EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE)); } // Resourceless software mode. { std::unique_ptr<TextureLayerImpl> impl_layer = CreateTextureLayer(); - impl_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + impl_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_RESOURCELESS_SOFTWARE)); } } @@ -922,53 +853,43 @@ pending_layer->CreateLayerImpl(host_impl_.active_tree())); ASSERT_TRUE(active_layer); - pending_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + pending_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); // Test multiple commits without an activation. The resource wasn't used so // the original sync token is returned. - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(1); - pending_layer->SetTransferableResource(test_data_.resource2_, - test_data_.release_callback2_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectRelease(); + pending_layer->SetTransferableResource(test_resource2_.resource, + test_resource2_.release_callback); + test_resource1_.Verify(); // Test callback after activation. pending_layer->PushPropertiesTo(active_layer.get()); active_layer->DidBecomeActive(); - EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0); - pending_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.ExpectNoRelease(); + pending_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); + test_resource1_.Verify(); - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name2_, _, false)) - .Times(1); + test_resource2_.ExpectRelease(); pending_layer->PushPropertiesTo(active_layer.get()); active_layer->DidBecomeActive(); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource2_.Verify(); // Test resetting the mailbox. - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, _, false)) - .Times(1); + test_resource1_.ExpectRelease(); pending_layer->SetTransferableResource(viz::TransferableResource(), viz::ReleaseCallback()); pending_layer->PushPropertiesTo(active_layer.get()); active_layer->DidBecomeActive(); - Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + test_resource1_.Verify(); // Test destructor. The resource wasn't used so the original sync token is // returned. - EXPECT_CALL( - test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, test_data_.sync_token1_, false)) - .Times(1); - pending_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_resource1_.ExpectRelease(); + pending_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); } TEST_F(TextureLayerImplWithResourceTest, @@ -976,11 +897,9 @@ std::unique_ptr<TextureLayerImpl> impl_layer = CreateTextureLayer(); ASSERT_TRUE(impl_layer); - EXPECT_CALL(test_data_.mock_callback_, - Release(test_data_.mailbox_name1_, _, false)) - .Times(1); - impl_layer->SetTransferableResource(test_data_.resource1_, - test_data_.release_callback1_); + test_resource1_.ExpectRelease(); + impl_layer->SetTransferableResource(test_resource1_.resource, + test_resource1_.release_callback); impl_layer->DidBecomeActive(); EXPECT_TRUE(impl_layer->WillDraw( DRAW_MODE_HARDWARE, host_impl_.active_tree()->resource_provider())); @@ -1004,10 +923,7 @@ return true; } - constexpr gfx::Size size(64, 64); - *resource = viz::TransferableResource::MakeGpu( - MailboxFromChar('1'), GL_TEXTURE_2D, SyncTokenFromUInt(0x123), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + *resource = MakeFakeResource(); *release_callback = base::BindOnce( &TextureLayerNoExtraCommitForMailboxTest::ResourceReleased, base::Unretained(this)); @@ -1080,11 +996,7 @@ } viz::TransferableResource MakeResource(char name) { - constexpr gfx::Size size(64, 64); - return viz::TransferableResource::MakeGpu( - MailboxFromChar(name), GL_TEXTURE_2D, - SyncTokenFromUInt(static_cast<uint32_t>(name)), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + return MakeFakeResource(); } void ResourceReleased(const gpu::SyncToken& sync_token, bool lost_resource) { @@ -1239,10 +1151,7 @@ bool PrepareTransferableResource( viz::TransferableResource* resource, viz::ReleaseCallback* release_callback) override { - constexpr gfx::Size size(64, 64); - *resource = viz::TransferableResource::MakeGpu( - MailboxFromChar('1'), GL_TEXTURE_2D, SyncTokenFromUInt(1), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + *resource = MakeFakeResource(); *release_callback = base::BindOnce(&TextureLayerReleaseResourcesBase::ResourceReleased, base::Unretained(this)); @@ -1311,16 +1220,12 @@ EndTest(); } - void SetMailbox(char mailbox_char) { + void SetNewFakeResource() { EXPECT_EQ(true, main_thread_.CalledOnValidThread()); viz::ReleaseCallback callback = base::BindOnce( &TextureLayerWithResourceMainThreadDeleted::ReleaseCallback, base::Unretained(this)); - constexpr gfx::Size size(64, 64); - auto resource = viz::TransferableResource::MakeGpu( - MailboxFromChar(mailbox_char), GL_TEXTURE_2D, - SyncTokenFromUInt(static_cast<uint32_t>(mailbox_char)), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + auto resource = MakeFakeResource(); layer_->SetTransferableResource(resource, std::move(callback)); } @@ -1345,7 +1250,7 @@ callback_count_ = 0; // Set the resource on the main thread. - SetMailbox('1'); + SetNewFakeResource(); EXPECT_EQ(0, callback_count_); PostSetNeedsCommitToMainThread(); @@ -1382,16 +1287,12 @@ EndTest(); } - void SetMailbox(char mailbox_char) { + void SetNewFakeResource() { EXPECT_EQ(true, main_thread_.CalledOnValidThread()); viz::ReleaseCallback callback = base::BindOnce( &TextureLayerWithResourceImplThreadDeleted::ReleaseCallback, base::Unretained(this)); - constexpr gfx::Size size(64, 64); - auto resource = viz::TransferableResource::MakeGpu( - MailboxFromChar(mailbox_char), GL_TEXTURE_2D, - SyncTokenFromUInt(static_cast<uint32_t>(mailbox_char)), size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + auto resource = MakeFakeResource(); layer_->SetTransferableResource(resource, std::move(callback)); } @@ -1416,7 +1317,7 @@ callback_count_ = 0; // Set the resource on the main thread. - SetMailbox('1'); + SetNewFakeResource(); EXPECT_EQ(0, callback_count_); PostSetNeedsCommitToMainThread(); @@ -1554,21 +1455,8 @@ // The test starts by inserting the TextureLayer to the tree. root_->AddChild(texture_layer_); - scoped_refptr<gpu::ClientSharedImage> shared_image_; - gpu::SyncToken sync_token_; - gfx::Size size(1, 1); - AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size, - shared_image_, sync_token_); - auto transferable_resource = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image_, sync_token_, shared_image_->size(), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); - auto release_callback = - base::BindOnce(&DeleteSharedImage, std::move(shared_image_)); - - texture_layer_->SetTransferableResource( - std::move(transferable_resource), std::move(release_callback)); + texture_layer_->SetTransferableResource(MakeFakeSoftwareResource(), + base::DoNothing()); } break; case 2: // When the layer is removed from the tree, the layer should be @@ -1621,21 +1509,8 @@ // The test starts by inserting the TextureLayer to the tree. root_->AddChild(texture_layer_); - scoped_refptr<gpu::ClientSharedImage> shared_image_; - gpu::SyncToken sync_token_; - gfx::Size size(1, 1); - AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size, - shared_image_, sync_token_); - auto transferable_resource = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image_, sync_token_, shared_image_->size(), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); - auto release_callback = - base::BindOnce(&DeleteSharedImage, std::move(shared_image_)); - - texture_layer_->SetTransferableResource( - std::move(transferable_resource), std::move(release_callback)); + texture_layer_->SetTransferableResource(MakeFakeSoftwareResource(), + base::DoNothing()); } break; @@ -1695,38 +1570,10 @@ // The test starts by inserting the TextureLayer to the tree. root_->AddChild(texture_layer_); - scoped_refptr<gpu::ClientSharedImage> shared_image1_; - gpu::SyncToken sync_token1_; - scoped_refptr<gpu::ClientSharedImage> shared_image2_; - gpu::SyncToken sync_token2_; - gfx::Size size(1, 1); - AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size, - shared_image1_, sync_token1_); - AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size, - shared_image2_, sync_token2_); - auto transferable_resource1 = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image1_, sync_token1_, shared_image1_->size(), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); - - auto transferable_resource2 = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image2_, sync_token2_, shared_image2_->size(), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); - - // Give the TextureLayer a resource so it contributes to the frame. It - // doesn't need to register the layer otherwise. - auto release_callback1 = - base::BindOnce(&DeleteSharedImage, std::move(shared_image1_)); - auto release_callback2 = - base::BindOnce(&DeleteSharedImage, std::move(shared_image2_)); - - texture_layer_->SetTransferableResource( - std::move(transferable_resource1), std::move(release_callback1)); - texture_layer_->SetTransferableResource( - std::move(transferable_resource2), std::move(release_callback2)); + texture_layer_->SetTransferableResource(MakeFakeSoftwareResource(), + base::DoNothing()); + texture_layer_->SetTransferableResource(MakeFakeSoftwareResource(), + base::DoNothing()); } break; case 2: // Force a commit and SubmitCompositorFrame so that we can see it. @@ -1779,22 +1626,12 @@ // The test starts by inserting the TextureLayer to the tree. root_->AddChild(texture_layer_); - scoped_refptr<gpu::ClientSharedImage> shared_image_; - gpu::SyncToken sync_token_; - gfx::Size size(1, 1); - AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size, - shared_image_, sync_token_); - auto transferable_resource = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image_, sync_token_, shared_image_->size(), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); auto release_callback = base::BindOnce( &SoftwareTextureLayerLoseFrameSinkTest::ReleaseCallback, - base::Unretained(this), std::move(shared_image_)); + base::Unretained(this)); - texture_layer_->SetTransferableResource( - std::move(transferable_resource), std::move(release_callback)); + texture_layer_->SetTransferableResource(MakeFakeSoftwareResource(), + std::move(release_callback)); EXPECT_FALSE(released_); } break; @@ -1830,14 +1667,11 @@ source_frame_number_ = commit_state.source_frame_number; } - void ReleaseCallback(scoped_refptr<gpu::ClientSharedImage> shared_image, - const gpu::SyncToken& sync_token, - bool lost) { + void ReleaseCallback(const gpu::SyncToken& sync_token, bool lost) { // The software resource is not released when the LayerTreeFrameSink is lost // since software resources are not destroyed by the GPU process dying. It // is released only after we call TextureLayer::ClearClient(). - shared_image->UpdateDestructionSyncToken(sync_token); EXPECT_EQ(source_frame_number_, 3); released_ = true; EndTest();
diff --git a/cc/test/fake_ui_resource_layer_tree_host_impl.cc b/cc/test/fake_ui_resource_layer_tree_host_impl.cc index ee2e863..dd79beb 100644 --- a/cc/test/fake_ui_resource_layer_tree_host_impl.cc +++ b/cc/test/fake_ui_resource_layer_tree_host_impl.cc
@@ -29,10 +29,9 @@ UIResourceData data; data.resource_id_for_export = resource_provider()->ImportResource( - viz::TransferableResource::MakeGpu( - gpu::Mailbox::Generate(), GL_TEXTURE_2D, gpu::SyncToken(), - bitmap.GetSize(), viz::SinglePlaneFormat::kRGBA_8888, - false /* is_overlay_candidate */), + viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, gpu::SyncToken()), base::DoNothing()); data.opaque = bitmap.GetOpaque();
diff --git a/cc/test/pixel_test.cc b/cc/test/pixel_test.cc index 0d76248..100d0622ae 100644 --- a/cc/test/pixel_test.cc +++ b/cc/test/pixel_test.cc
@@ -284,10 +284,9 @@ CHECK_GE(mem.size(), info.computeByteSize(row_bytes)); source.readPixels(info, mem.data(), row_bytes, 0, 0); - auto transferable_resource = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, size, viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); + auto transferable_resource = viz::TransferableResource::Make( + shared_image, viz::TransferableResource::ResourceSource::kTileRasterTask, + sync_token); auto release_callback = base::BindOnce(&DeleteSharedImage, std::move(shared_image));
diff --git a/cc/test/render_pass_test_utils.cc b/cc/test/render_pass_test_utils.cc index 4d30c62..9141b9d 100644 --- a/cc/test/render_pass_test_utils.cc +++ b/cc/test/render_pass_test_utils.cc
@@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - #include "cc/test/render_pass_test_utils.h" #include <stdint.h> @@ -27,6 +26,7 @@ #include "components/viz/common/resources/returned_resource.h" #include "components/viz/common/resources/transferable_resource.h" #include "components/viz/service/display/display_resource_provider.h" +#include "gpu/command_buffer/client/client_shared_image.h" #include "gpu/command_buffer/common/sync_token.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkImageFilter.h" @@ -39,15 +39,12 @@ viz::ResourceId CreateAndImportResource( viz::ClientResourceProvider* resource_provider, - const gpu::SyncToken& sync_token, - gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB()) { - constexpr gfx::Size size(64, 64); - auto transfer_resource = viz::TransferableResource::MakeGpu( - gpu::Mailbox::Generate(), GL_TEXTURE_2D, sync_token, size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); - transfer_resource.color_space = std::move(color_space); - return resource_provider->ImportResource(transfer_resource, - base::DoNothing()); + const gpu::SyncToken& sync_token) { + auto resource = viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, sync_token); + + return resource_provider->ImportResource(resource, base::DoNothing()); } } // anonymous namespace
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc index cae8622..1b6d0a4 100644 --- a/cc/trees/layer_tree_host_impl_unittest.cc +++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -10717,11 +10717,9 @@ gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "BlendStateCheckLayerTest"}); auto sync_token = shared_image_interface_->GenVerifiedSyncToken(); - viz::TransferableResource resource = - viz::TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, gfx::Size(1, 1), - viz::SinglePlaneFormat::kBGRA_8888, - viz::TransferableResource::ResourceSource::kTileRasterTask); + viz::TransferableResource resource = viz::TransferableResource::Make( + shared_image, + viz::TransferableResource::ResourceSource::kTileRasterTask, sync_token); resource_id_ = resource_provider_->ImportResource(std::move(resource), base::DoNothing());
diff --git a/cc/trees/layer_tree_host_perftest.cc b/cc/trees/layer_tree_host_perftest.cc index 87389e63..9a48829 100644 --- a/cc/trees/layer_tree_host_perftest.cc +++ b/cc/trees/layer_tree_host_perftest.cc
@@ -332,10 +332,9 @@ next_fence_sync_); next_sync_token.SetVerifyFlush(); - constexpr gfx::Size size(64, 64); - viz::TransferableResource resource = viz::TransferableResource::MakeGpu( - gpu_mailbox, GL_TEXTURE_2D, next_sync_token, size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + viz::TransferableResource resource = viz::TransferableResource::Make( + gpu::ClientSharedImage::CreateForTesting(), + viz::TransferableResource::ResourceSource::kTest, next_sync_token); next_fence_sync_++; tab_contents_->SetTransferableResource(resource, std::move(callback));
diff --git a/cc/trees/layer_tree_host_unittest_context.cc b/cc/trees/layer_tree_host_unittest_context.cc index 60a6dc3..cd48664 100644 --- a/cc/trees/layer_tree_host_unittest_context.cc +++ b/cc/trees/layer_tree_host_unittest_context.cc
@@ -872,10 +872,9 @@ scoped_refptr<TextureLayer> texture = TextureLayer::Create(nullptr); texture->SetBounds(gfx::Size(10, 10)); texture->SetIsDrawable(true); - constexpr gfx::Size size(64, 64); - auto resource = viz::TransferableResource::MakeGpu( - shared_image, GL_TEXTURE_2D, sync_token, size, - viz::SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); + auto resource = viz::TransferableResource::Make( + shared_image, viz::TransferableResource::ResourceSource::kTest, + sync_token); texture->SetTransferableResource( resource, base::BindOnce(&LayerTreeHostContextTestDontUseLostResources:: EmptyReleaseCallback));
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc index 604cc01..b4340002 100644 --- a/cc/trees/proxy_main.cc +++ b/cc/trees/proxy_main.cc
@@ -13,6 +13,7 @@ #include "base/functional/bind.h" #include "base/metrics/histogram_macros.h" #include "base/notreached.h" +#include "base/profiler/sample_metadata.h" #include "base/synchronization/waitable_event.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_id_helper.h" @@ -143,6 +144,8 @@ perfetto::protos::pbzero::MainFramePipeline::Step:: BEGIN_MAIN_FRAME); }); + base::ScopedSampleMetadata metadata("ProxyMain::BeginMainFrame", 1, + base::SampleMetadataScope::kProcess); // This needs to run unconditionally, so do it before any early-returns. if (layer_tree_host_->scheduling_client())
diff --git a/chrome/VERSION b/chrome/VERSION index a57d8975..2641cf0 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=139 MINOR=0 -BUILD=7238 +BUILD=7239 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java index 6b53c85..050e69f0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -151,7 +151,6 @@ } private @Nullable LayoutStateProvider mLayoutStateProvider; - protected Runnable mAppMenuInvalidator; /** * Construct a new {@link AppMenuPropertiesDelegateImpl}. @@ -216,7 +215,7 @@ return mReadAloudAppMenuResetter; } - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @Nullable public ModelList getModelList() { return mModelList; @@ -268,16 +267,16 @@ } @Override - public final ModelList getMenuItems(AppMenuHandler handler) { + public final ModelList getMenuItems() { mReadAloudPos = -1; mHasReadAloudInserted = false; - mModelList = buildMenuModelList(handler); + mModelList = buildMenuModelList(); return mModelList; } /** Construct the ModelList for the appropriate current state of the menu. */ @VisibleForTesting - public abstract ModelList buildMenuModelList(AppMenuHandler handler); + public abstract ModelList buildMenuModelList(); /** * Builds a property model for a divider item type. @@ -299,8 +298,20 @@ * @return A Builder object that forms the basis for text menu item models. */ public PropertyModel.Builder buildBaseModelForTextItem(@IdRes int id) { - return new PropertyModel.Builder(AppMenuItemProperties.ALL_KEYS) - .with(AppMenuItemProperties.MENU_ITEM_ID, id) + return populateBaseModelForTextItem( + new PropertyModel.Builder(AppMenuItemProperties.ALL_KEYS), id); + } + + /** + * Populates the PropertyModel.Builder with the common properties for a text menu item. + * + * @param builder The builder to populate with data. + * @param id The id of the text menu item. + * @return A Builder object that forms the basis for text menu item models. + */ + public PropertyModel.Builder populateBaseModelForTextItem( + PropertyModel.Builder builder, @IdRes int id) { + return builder.with(AppMenuItemProperties.MENU_ITEM_ID, id) .with(AppMenuItemProperties.ENABLED, true) .with(AppMenuItemProperties.ICON_COLOR_RES, getMenuItemIconColorRes(id)) .with(AppMenuItemProperties.ICON_SHOW_BADGE, shouldShowBadgeOnMenuItemIcon(id)) @@ -1018,6 +1029,34 @@ */ @Nullable protected ListItem maybeBuildPriceTrackingListItem(@Nullable Tab currentTab, boolean showIcon) { + Boolean show = getPriceTrackingMenuItemInfo(currentTab); + if (show == null) return null; + + if (show) { + return new ListItem( + AppMenuItemType.STANDARD, + buildModelForStandardMenuItem( + R.id.enable_price_tracking_menu_id, + R.string.enable_price_tracking_menu_item, + showIcon ? R.drawable.price_tracking_disabled : 0)); + } else { + return new ListItem( + AppMenuItemType.STANDARD, + buildModelForStandardMenuItem( + R.id.disable_price_tracking_menu_id, + R.string.disable_price_tracking_menu_item, + showIcon ? R.drawable.price_tracking_enabled_filled : 0)); + } + } + + /** + * Determine which menu to show for price tracking feature. + * + * @param currentTab The currently selected tab. + * @return {@code true} to show 'enable'. Shows no option if {@code null}. + */ + @Nullable + public Boolean getPriceTrackingMenuItemInfo(@Nullable Tab currentTab) { if (currentTab == null || currentTab.getWebContents() == null) { return null; } @@ -1049,21 +1088,7 @@ showStartPriceTracking = !isSubscribed; } - if (showStartPriceTracking) { - return new ListItem( - AppMenuItemType.STANDARD, - buildModelForStandardMenuItem( - R.id.enable_price_tracking_menu_id, - R.string.enable_price_tracking_menu_item, - showIcon ? R.drawable.price_tracking_disabled : 0)); - } else { - return new ListItem( - AppMenuItemType.STANDARD, - buildModelForStandardMenuItem( - R.id.disable_price_tracking_menu_id, - R.string.disable_price_tracking_menu_item, - showIcon ? R.drawable.price_tracking_enabled_filled : 0)); - } + return showStartPriceTracking; } /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java index 60b50ca..f0aa72e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -444,7 +444,10 @@ mToolbarCoordinator.get().onToolbarInitialized(mToolbarManager, null); CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar); - toolbar.calculateToolbarWidthBeforeMeasure(mActivity, mIntentDataProvider.get()); + toolbar.initVisibilityRule( + mActivity, + () -> mAppMenuCoordinator.getAppMenuHandler(), + mIntentDataProvider.get()); if (ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()) { toolbar.setFeatureOverridesManager(mFeatureOverridesManagerSupplier.get()); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java index 9757b75..9d12067 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java
@@ -120,7 +120,7 @@ @Override @VisibleForTesting - public MVCListAdapter.ModelList buildMenuModelList(AppMenuHandler handler) { + public MVCListAdapter.ModelList buildMenuModelList() { MVCListAdapter.ModelList modelList = new MVCListAdapter.ModelList(); Tab currentTab = mActivityTabProvider.get();
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 a6c922e3..4f66433 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
@@ -68,6 +68,7 @@ import org.chromium.base.task.PostTask; import org.chromium.base.task.TaskTraits; import org.chromium.chrome.R; +import org.chromium.chrome.browser.app.appmenu.AppMenuPropertiesDelegateImpl; import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate; import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider; import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabProfileType; @@ -108,6 +109,7 @@ import org.chromium.chrome.browser.toolbar.LocationBarModel; import org.chromium.chrome.browser.toolbar.ToolbarProgressBar; import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant; +import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures; import org.chromium.chrome.browser.toolbar.menu_button.MenuButton; import org.chromium.chrome.browser.toolbar.optional_button.ButtonData; import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator; @@ -118,6 +120,8 @@ import org.chromium.chrome.browser.toolbar.top.ToolbarPhone; import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference; import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarColorObserver; +import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; +import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver; import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityClient; import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityExtras.ResolutionType; import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; @@ -187,6 +191,7 @@ private boolean mShouldHighlightCookieControlsIcon; private int mBlockingStatus3pcd; private BrowserServicesIntentDataProvider mIntentDataProvider; + private Supplier<AppMenuHandler> mAppMenuHandler; private final Handler mTaskHandler = new Handler(); private final ButtonVisibilityRule mButtonVisibilityRule = @@ -408,15 +413,19 @@ } /** - * Calculate toolbar with before {@link onMeasure(int, int)} is called. + * initialize the toolbar with menu,. * * @param activity The {@link Activity} that the toolbar is attached to. + * @param appMenuHandler Supplief of {@link AppMenuHandler}. * @param intentDataProvider {@link BrowserServicesIntentDataProvider} for accessing CCT intent * data. */ @ExperimentalOpenInBrowser - public void calculateToolbarWidthBeforeMeasure( - Activity activity, BrowserServicesIntentDataProvider intentDataProvider) { + public void initVisibilityRule( + Activity activity, + Supplier<AppMenuHandler> appMenuHandler, + BrowserServicesIntentDataProvider intentDataProvider) { + mAppMenuHandler = appMenuHandler; if (mIntentDataProvider == null) { mIntentDataProvider = intentDataProvider; @CustomTabsButtonState @@ -1520,8 +1529,52 @@ return; } - boolean show = buttonVariant != AdaptiveToolbarButtonVariant.READER_MODE; + boolean show = + AdaptiveToolbarFeatures.isDynamicAction(buttonVariant) + && buttonVariant != AdaptiveToolbarButtonVariant.READER_MODE; mMenuButton.findViewById(R.id.menu_dot).setVisibility(show ? View.VISIBLE : View.GONE); + if (!show) return; + + int menuId = getHighlightMenuId(buttonVariant); + if (menuId < 0) return; + + mAppMenuHandler.get().setMenuHighlight(menuId, false); + AppMenuObserver menuObserver = + new AppMenuObserver() { + @Override + public void onMenuVisibilityChanged(boolean isVisible) { + // TODO(crbug.com/424807997): Do this toggling in MenuButton MVC. Do it + // upon page navigation as well. + if (isVisible) { + mMenuButton.findViewById(R.id.menu_dot).setVisibility(View.GONE); + mAppMenuHandler.get().removeObserver(this); + } + } + + @Override + public void onMenuHighlightChanged(boolean highlighting) {} + }; + mAppMenuHandler.get().addObserver(menuObserver); + } + + private int getHighlightMenuId(@AdaptiveToolbarButtonVariant int buttonVariant) { + return switch (buttonVariant) { + case AdaptiveToolbarButtonVariant.PRICE_TRACKING -> { + // Figure out which of the two menu items (enable/disable) appears and needs + // highlighting. + // TODO(crbug.com/424807997): Avoid casting. + var appMenuDelegate = + (AppMenuPropertiesDelegateImpl) + mAppMenuHandler.get().getMenuPropertiesDelegate(); + var showEnabled = appMenuDelegate.getPriceTrackingMenuItemInfo(getCurrentTab()); + if (showEnabled == null) yield -1; + yield showEnabled + ? R.id.enable_price_tracking_menu_id + : R.id.disable_price_tracking_menu_id; + } + case AdaptiveToolbarButtonVariant.PRICE_INSIGHTS -> R.id.price_insights_menu_id; + default -> -1; + }; } private void updateOptionalButtonTint() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java index 09915bd..375746a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.browser.tabbed_mode; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.SparseArray; import android.view.View; @@ -13,6 +14,8 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.graphics.drawable.DrawableCompat; import org.chromium.base.BuildInfo; import org.chromium.base.CallbackController; @@ -50,6 +53,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tinker_tank.TinkerTankDelegate; import org.chromium.chrome.browser.toolbar.ToolbarManager; +import org.chromium.chrome.browser.toolbar.menu_button.MenuItemState; import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate; import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; import org.chromium.chrome.browser.ui.appmenu.AppMenuItemProperties; @@ -102,6 +106,8 @@ */ private @Nullable IncognitoReauthController mIncognitoReauthController; + private @Nullable Runnable mUpdateStateChangeObserver; + private final CallbackController mIncognitoReauthCallbackController = new CallbackController(); public TabbedAppMenuPropertiesDelegate( @@ -161,11 +167,11 @@ } @Override - public MVCListAdapter.ModelList buildMenuModelList(AppMenuHandler handler) { + public MVCListAdapter.ModelList buildMenuModelList() { int menuGroup = getMenuGroup(); MVCListAdapter.ModelList modelList = new MVCListAdapter.ModelList(); if (menuGroup == MenuGroup.PAGE_MENU) { - populatePageModeMenu(modelList, handler); + populatePageModeMenu(modelList); } else if (menuGroup == MenuGroup.OVERVIEW_MODE_MENU) { populateOverviewModeMenu(modelList); } else if (menuGroup == MenuGroup.TABLET_EMPTY_MODE_MENU) { @@ -174,7 +180,7 @@ return modelList; } - private void populatePageModeMenu(MVCListAdapter.ModelList modelList, AppMenuHandler handler) { + private void populatePageModeMenu(MVCListAdapter.ModelList modelList) { Tab currentTab = mActivityTabProvider.get(); GURL url = currentTab != null ? currentTab.getUrl() : GURL.emptyGURL(); @@ -202,9 +208,9 @@ mUpdateMenuItemVisible = shouldShowUpdateMenuItem(); if (mUpdateMenuItemVisible) { modelList.add(buildUpdateItem()); - mAppMenuInvalidator = handler::invalidateAppMenu; + mUpdateStateChangeObserver = buildUpdateStateChangedObserver(); UpdateMenuItemHelper.getInstance(mTabModelSelector.getModel(false).getProfile()) - .registerObserver(mAppMenuInvalidator); + .registerObserver(mUpdateStateChangeObserver); } // New Tab @@ -357,6 +363,22 @@ } } + private Runnable buildUpdateStateChangedObserver() { + return () -> { + MVCListAdapter.ModelList modelList = getModelList(); + if (modelList == null) { + assert false : "ModelList should not be null"; + return; + } + for (MVCListAdapter.ListItem listItem : getModelList()) { + if (listItem.model.get(AppMenuItemProperties.MENU_ITEM_ID) == R.id.update_menu_id) { + updateUpdateItemData(listItem.model); + return; + } + } + }; + } + private void maybeAddDividerLine(MVCListAdapter.ModelList modelList, @IdRes int id) { if (modelList.get(modelList.size() - 1).type == AppMenuHandler.AppMenuItemType.DIVIDER) { return; @@ -389,14 +411,40 @@ private MVCListAdapter.ListItem buildUpdateItem() { assert shouldShowUpdateMenuItem(); PropertyModel model = - buildModelForStandardMenuItem( - R.id.update_menu_id, R.string.menu_update, R.drawable.menu_update); - model.set( - AppMenuItemProperties.CUSTOM_ITEM_DATA, + populateBaseModelForTextItem( + new PropertyModel.Builder(UpdateMenuItemViewBinder.ALL_KEYS), + R.id.update_menu_id) + .with(AppMenuItemProperties.TITLE, mContext.getString(R.string.menu_update)) + .with( + AppMenuItemProperties.ICON, + AppCompatResources.getDrawable(mContext, R.drawable.menu_update)) + .build(); + updateUpdateItemData(model); + return new MVCListAdapter.ListItem(TabbedAppMenuItemType.UPDATE_ITEM, model); + } + + private void updateUpdateItemData(PropertyModel model) { + MenuItemState itemState = UpdateMenuItemHelper.getInstance(mTabModelSelector.getModel(false).getProfile()) .getUiState() - .itemState); - return new MVCListAdapter.ListItem(TabbedAppMenuItemType.UPDATE_ITEM, model); + .itemState; + if (itemState == null) { + assert false : "The update state should be non-null"; + model.set(AppMenuItemProperties.ENABLED, false); + return; + } + model.set(UpdateMenuItemViewBinder.SUMMARY, itemState.summary); + model.set(AppMenuItemProperties.TITLE, mContext.getString(itemState.title)); + model.set(UpdateMenuItemViewBinder.TITLE_COLOR_ID, itemState.titleColorId); + Drawable icon = null; + if (itemState.icon != 0) { + icon = AppCompatResources.getDrawable(mContext, itemState.icon); + } + if (icon != null && itemState.iconTintId != 0) { + DrawableCompat.setTint(icon, mContext.getColor(itemState.iconTintId)); + } + model.set(AppMenuItemProperties.ICON, icon); + model.set(AppMenuItemProperties.ENABLED, itemState.enabled); } private MVCListAdapter.ListItem buildNewTabItem() { @@ -944,9 +992,9 @@ UpdateMenuItemHelper.getInstance( mTabModelSelector.getModel(false).getProfile()); updateHelper.onMenuDismissed(); - updateHelper.unregisterObserver(mAppMenuInvalidator); + updateHelper.unregisterObserver(mUpdateStateChangeObserver); mUpdateMenuItemVisible = false; - mAppMenuInvalidator = null; + mUpdateStateChangeObserver = null; } } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/UpdateMenuItemViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/UpdateMenuItemViewBinder.java index 5b4d089..d23f1e7 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/UpdateMenuItemViewBinder.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/UpdateMenuItemViewBinder.java
@@ -12,12 +12,9 @@ import android.widget.TextView; import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.graphics.drawable.DrawableCompat; import org.chromium.build.annotations.NullMarked; -import org.chromium.build.annotations.Nullable; import org.chromium.chrome.R; -import org.chromium.chrome.browser.toolbar.menu_button.MenuItemState; import org.chromium.chrome.browser.ui.appmenu.AppMenuItemProperties; import org.chromium.chrome.browser.ui.appmenu.AppMenuUtil; import org.chromium.ui.modelutil.PropertyKey; @@ -26,65 +23,55 @@ /** A custom binder used to bind the update menu item. */ @NullMarked class UpdateMenuItemViewBinder { + /** Summary for the Update menu item. */ + public static final PropertyModel.WritableObjectPropertyKey<String> SUMMARY = + new PropertyModel.WritableObjectPropertyKey<>("SUMMARY"); + + /** The color to be applied to the title text. */ + public static final PropertyModel.WritableIntPropertyKey TITLE_COLOR_ID = + new PropertyModel.WritableIntPropertyKey("TITLE_COLOR_ID"); + + /** All the applicable property keys for the update menu item. */ + public static final PropertyKey[] ALL_KEYS = + PropertyModel.concatKeys( + AppMenuItemProperties.ALL_KEYS, new PropertyKey[] {SUMMARY, TITLE_COLOR_ID}); + /** Handles binding the view and models changes. */ public static void bind(PropertyModel model, View view, PropertyKey key) { AppMenuUtil.bindStandardItemEnterAnimation(model, view, key); - @Nullable MenuItemState itemState = - ((MenuItemState) model.get(AppMenuItemProperties.CUSTOM_ITEM_DATA)); - if (key == AppMenuItemProperties.MENU_ITEM_ID) { int id = model.get(AppMenuItemProperties.MENU_ITEM_ID); assert id == R.id.update_menu_id; view.setId(id); - - if (itemState != null) { - TextView summary = view.findViewById(R.id.menu_item_summary); - if (!TextUtils.isEmpty(itemState.summary)) { - summary.setText(itemState.summary); - summary.setVisibility(View.VISIBLE); - } else { - summary.setText(""); - summary.setVisibility(View.GONE); - } + } else if (key == SUMMARY) { + TextView summary = view.findViewById(R.id.menu_item_summary); + String summaryText = model.get(SUMMARY); + if (!TextUtils.isEmpty(summaryText)) { + summary.setText(summaryText); + summary.setVisibility(View.VISIBLE); + } else { + summary.setText(""); + summary.setVisibility(View.GONE); } } else if (key == AppMenuItemProperties.TITLE) { TextView text = view.findViewById(R.id.menu_item_text); - if (itemState == null) { - text.setText(model.get(AppMenuItemProperties.TITLE)); - } else { - text.setText(itemState.title); - text.setTextColor( - AppCompatResources.getColorStateList( - view.getContext(), itemState.titleColorId)); - } - } else if (key == AppMenuItemProperties.TITLE_CONDENSED) { + text.setText(model.get(AppMenuItemProperties.TITLE)); + text.setContentDescription(model.get(AppMenuItemProperties.TITLE)); + } else if (key == TITLE_COLOR_ID) { TextView text = view.findViewById(R.id.menu_item_text); - if (itemState == null) { - CharSequence titleCondensed = model.get(AppMenuItemProperties.TITLE_CONDENSED); - text.setContentDescription(titleCondensed); - } else { - text.setContentDescription(view.getResources().getString(itemState.title)); - } + text.setTextColor( + AppCompatResources.getColorStateList( + view.getContext(), model.get(TITLE_COLOR_ID))); } else if (key == AppMenuItemProperties.ICON) { ImageView image = view.findViewById(R.id.menu_item_icon); - - if (itemState == null) { - Drawable icon = model.get(AppMenuItemProperties.ICON); - image.setImageDrawable(icon); - image.setVisibility(View.VISIBLE); - return; - } - - image.setImageResource(itemState.icon); - if (itemState.iconTintId != 0) { - DrawableCompat.setTint( - image.getDrawable(), view.getContext().getColor(itemState.iconTintId)); - } + Drawable icon = model.get(AppMenuItemProperties.ICON); + image.setVisibility(icon == null ? View.GONE : View.VISIBLE); + image.setImageDrawable(icon); } else if (key == AppMenuItemProperties.ENABLED) { view.findViewById(R.id.menu_item_text) .setEnabled(model.get(AppMenuItemProperties.ENABLED)); - if (itemState != null) view.setEnabled(itemState.enabled); + view.setEnabled(model.get(AppMenuItemProperties.ENABLED)); } else if (key == AppMenuItemProperties.CLICK_HANDLER) { view.setOnClickListener( v -> model.get(AppMenuItemProperties.CLICK_HANDLER).onItemClick(model));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java index f710140..4f8c3af3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java
@@ -63,10 +63,7 @@ () -> AppMenuTestSupport.getAppMenuPropertiesDelegate( mActivityTestRule.getAppMenuCoordinator()) - .getMenuItems( - mActivityTestRule - .getAppMenuCoordinator() - .getAppMenuHandler())); + .getMenuItems()); MVCListAdapter.ListItem newIncognitoItem = null; for (MVCListAdapter.ListItem item : modelList) { if (item.model.get(AppMenuItemProperties.MENU_ITEM_ID)
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java index d5e481a..e49710a 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -68,7 +68,6 @@ import org.chromium.chrome.browser.toolbar.menu_button.MenuUiState; import org.chromium.chrome.browser.translate.TranslateBridge; import org.chromium.chrome.browser.translate.TranslateBridgeJni; -import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; import org.chromium.chrome.browser.ui.appmenu.AppMenuItemProperties; import org.chromium.chrome.browser.webapps.WebappRegistry; import org.chromium.components.bookmarks.BookmarkId; @@ -211,8 +210,7 @@ mBookmarkModelSupplier, mReadAloudControllerSupplier) { @Override - public MVCListAdapter.ModelList buildMenuModelList( - AppMenuHandler handler) { + public MVCListAdapter.ModelList buildMenuModelList() { return new MVCListAdapter.ModelList(); } }); @@ -466,16 +464,14 @@ public void readAloud_CanBeAddedOnMultipleCreatedMenus() { when(mReadAloudController.isReadable(any(Tab.class))).thenReturn(true); - MVCListAdapter.ModelList modelList = - mAppMenuPropertiesDelegate.getMenuItems(mock(AppMenuHandler.class)); + MVCListAdapter.ModelList modelList = mAppMenuPropertiesDelegate.getMenuItems(); mAppMenuPropertiesDelegate.observeAndMaybeAddReadAloud(modelList, mTab); Assert.assertEquals(1, modelList.size()); Assert.assertEquals( R.id.readaloud_menu_id, modelList.get(0).model.get(AppMenuItemProperties.MENU_ITEM_ID)); - MVCListAdapter.ModelList modelList2 = - mAppMenuPropertiesDelegate.getMenuItems(mock(AppMenuHandler.class)); + MVCListAdapter.ModelList modelList2 = mAppMenuPropertiesDelegate.getMenuItems(); mAppMenuPropertiesDelegate.observeAndMaybeAddReadAloud(modelList2, mTab); Assert.assertEquals(1, modelList2.size()); Assert.assertEquals(
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegateUnitTest.java index 9c608683..82345000 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegateUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegateUnitTest.java
@@ -164,7 +164,7 @@ mReadAloudControllerSupplier, /* contextualPageActionControllerSupplier */ () -> null, /* hasClientPackage= */ false); - MVCListAdapter.ModelList modelList = delegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = delegate.getMenuItems(); assertTrue(isMenuItemPresent(modelList, R.id.enable_price_tracking_menu_id)); assertFalse(isMenuItemPresent(modelList, R.id.disable_price_tracking_menu_id)); } @@ -200,7 +200,7 @@ mReadAloudControllerSupplier, () -> cpac, /* hasClientPackage= */ false); - MVCListAdapter.ModelList modelList = delegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = delegate.getMenuItems(); assertTrue(isMenuItemPresent(modelList, R.id.price_insights_menu_id)); } @@ -231,7 +231,7 @@ mReadAloudControllerSupplier, /* contextualPageActionControllerSupplier */ () -> null, /* hasClientPackage= */ false); - MVCListAdapter.ModelList modelList = delegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = delegate.getMenuItems(); assertTrue(isMenuItemPresent(modelList, R.id.find_in_page_id)); @@ -270,7 +270,7 @@ mReadAloudControllerSupplier, /* contextualPageActionControllerSupplier */ () -> null, /* hasClientPackage= */ false); - MVCListAdapter.ModelList modelList = delegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = delegate.getMenuItems(); assertTrue(isMenuItemPresent(modelList, R.id.find_in_page_id));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java index 644d232..95d1355 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -15,6 +15,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -52,6 +53,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; @@ -98,6 +101,8 @@ import org.chromium.chrome.browser.toolbar.top.NavigationPopup.HistoryDelegate; import org.chromium.chrome.browser.toolbar.top.ToggleTabStackButtonCoordinator; import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference; +import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; +import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver; import org.chromium.chrome.browser.user_education.UserEducationHelper; import org.chromium.components.content_settings.CookieBlocking3pcdStatus; import org.chromium.components.content_settings.CookieControlsState; @@ -145,10 +150,12 @@ @Mock Callback<Integer> mContainerVisibilityChangeObserver; @Mock View mParentView; @Mock WindowAndroid mWindowAndroid; + @Mock AppMenuHandler mAppMenuHandler; private @Mock PageInfoIphController mPageInfoIphController; @Mock private CustomTabFeatureOverridesManager mFeatureOverridesManager; @Mock private BrowserServicesIntentDataProvider mIntentDataProvider; @Mock private CustomTabMinimizeDelegate mMinimizeDelegate; + @Captor ArgumentCaptor<AppMenuObserver> mAppMenuObserverCaptor; private Activity mActivity; private CustomTabToolbar mToolbar; @@ -212,7 +219,7 @@ null, /* homeButtonDisplay= */ null); if (!ChromeFeatureList.sCctToolbarRefactor.isEnabled()) { - mToolbar.calculateToolbarWidthBeforeMeasure(mActivity, mIntentDataProvider); + mToolbar.initVisibilityRule(mActivity, () -> mAppMenuHandler, mIntentDataProvider); mToolbar.setFeatureOverridesManager(mFeatureOverridesManager); } mLocationBar = @@ -662,7 +669,7 @@ var connection = spy(CustomTabsConnection.getInstance()); Mockito.doReturn(true).when(connection).shouldEnableOmniboxForIntent(any()); CustomTabsConnection.setInstanceForTesting(connection); - mToolbar.updateOptionalButton(getDataForTranslateIconButton()); + mToolbar.updateOptionalButton(getDataForPriceInsightsIconButton()); assertNull(mToolbar.getOptionalButtonCoordinatorForTesting()); assertEquals(View.GONE, mToolbar.findViewById(R.id.menu_dot).getVisibility()); } @@ -680,9 +687,17 @@ "bookmark", mock(OnClickListener.class), ButtonType.OTHER); - mToolbar.updateOptionalButton(getDataForTranslateIconButton()); + mToolbar.updateOptionalButton(getDataForPriceInsightsIconButton()); assertNull(mToolbar.getOptionalButtonCoordinatorForTesting()); assertEquals(View.VISIBLE, mToolbar.findViewById(R.id.menu_dot).getVisibility()); + verify(mAppMenuHandler).addObserver(mAppMenuObserverCaptor.capture()); + + // Verify that the corresponding menu item gets highlighted. + verify(mAppMenuHandler).setMenuHighlight(eq(R.id.price_insights_menu_id), eq(false)); + + // Verify the menu dot disappears as the overflow menu show up. + mAppMenuObserverCaptor.getValue().onMenuVisibilityChanged(/* isVisible= */ true); + assertEquals(View.GONE, mToolbar.findViewById(R.id.menu_dot).getVisibility()); } private void assertUrlAndTitleVisible(boolean titleVisible, boolean urlVisible) { @@ -730,11 +745,12 @@ Mockito.doReturn(UrlBarData.forUrl(url)).when(mLocationBarModel).getUrlBarData(); } - private ButtonDataImpl getDataForTranslateIconButton() { - Drawable iconDrawable = AppCompatResources.getDrawable(mActivity, R.drawable.ic_translate); + private ButtonDataImpl getDataForPriceInsightsIconButton() { + Drawable iconDrawable = + AppCompatResources.getDrawable(mActivity, R.drawable.ic_trending_down_24dp); OnClickListener clickListener = mock(OnClickListener.class); OnLongClickListener longClickListener = mock(OnLongClickListener.class); - String contentDescription = mActivity.getString(R.string.menu_translate); + String contentDescription = mActivity.getString(R.string.price_insights_title); // Whether a button is static or dynamic is determined by the button variant. ButtonSpec buttonSpec = @@ -745,7 +761,7 @@ contentDescription, true, null, - /* buttonVariant= */ AdaptiveToolbarButtonVariant.TRANSLATE, + /* buttonVariant= */ AdaptiveToolbarButtonVariant.PRICE_INSIGHTS, /* actionChipLabelResId= */ Resources.ID_NULL, /* tooltipTextResId= */ Resources.ID_NULL, /* hasErrorBadge= */ false);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java index 40b9c39..8a05889 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -80,6 +80,7 @@ import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher; import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; +import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper; import org.chromium.chrome.browser.pdf.PdfPage; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; @@ -97,6 +98,8 @@ import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.toolbar.ToolbarManager; +import org.chromium.chrome.browser.toolbar.menu_button.MenuItemState; +import org.chromium.chrome.browser.toolbar.menu_button.MenuUiState; import org.chromium.chrome.browser.translate.TranslateBridge; import org.chromium.chrome.browser.translate.TranslateBridgeJni; import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate; @@ -205,8 +208,8 @@ @Mock private PrefService mPrefService; @Mock private SyncService mSyncService; @Mock private WebFeedBridge.Natives mWebFeedBridgeJniMock; - @Mock private AppMenuHandler mAppMenuHandler; @Mock private TranslateBridge.Natives mTranslateBridgeJniMock; + @Mock private UpdateMenuItemHelper mUpdateMenuItemHelper; private ShadowPackageManager mShadowPackageManager; @@ -220,6 +223,7 @@ new ObservableSupplierImpl<>(); private TabbedAppMenuPropertiesDelegate mTabbedAppMenuPropertiesDelegate; + private MenuUiState mUpdateAvailableMenuUiState; // Boolean flags to test multi-window menu visibility for various combinations. private boolean mIsMultiInstance; @@ -284,6 +288,15 @@ Mockito.when(mTranslateBridgeJniMock.canManuallyTranslate(any(), anyBoolean())) .thenReturn(false); + UpdateMenuItemHelper.setInstanceForTesting(mUpdateMenuItemHelper); + doReturn(new MenuUiState()).when(mUpdateMenuItemHelper).getUiState(); + + mUpdateAvailableMenuUiState = new MenuUiState(); + mUpdateAvailableMenuUiState.itemState = new MenuItemState(); + mUpdateAvailableMenuUiState.itemState.title = R.string.menu_update; + mUpdateAvailableMenuUiState.itemState.titleColorId = R.color.default_text_color_error; + mUpdateAvailableMenuUiState.itemState.icon = R.drawable.menu_update; + PowerBookmarkUtils.setPriceTrackingEligibleForTesting(false); PowerBookmarkUtils.setPowerBookmarkMetaForTesting(PowerBookmarkMeta.newBuilder().build()); TabbedAppMenuPropertiesDelegate delegate = @@ -462,8 +475,7 @@ .shouldShowTranslateMenuItem(any(Tab.class)); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.icon_row_menu_id, @@ -496,8 +508,7 @@ .shouldShowTranslateMenuItem(any(Tab.class)); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.icon_row_menu_id, @@ -533,8 +544,7 @@ .withAutoDarkEnabled()); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); List<Integer> expectedItems = new ArrayList<>(); List<Integer> expectedTitles = new ArrayList<>(); @@ -607,8 +617,7 @@ .withAutoDarkEnabled()); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); List<Integer> expectedItems = new ArrayList<>(); List<Integer> expectedTitles = new ArrayList<>(); @@ -680,8 +689,7 @@ .withAutoDarkEnabled()); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.icon_row_menu_id, @@ -716,8 +724,7 @@ doReturn(false).when(mTabbedAppMenuPropertiesDelegate).shouldShowIconBeforeItem(); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = {R.id.update_menu_id, R.id.reader_mode_prefs_id}; assertMenuItemsHaveIcons(modelList, expectedItems); @@ -736,8 +743,7 @@ doReturn(true).when(mTabbedAppMenuPropertiesDelegate).shouldShowIconBeforeItem(); assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.update_menu_id, @@ -769,8 +775,7 @@ Assert.assertFalse(mTabbedAppMenuPropertiesDelegate.shouldShowPageMenu()); assertEquals(MenuGroup.OVERVIEW_MODE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.new_tab_menu_id, @@ -792,8 +797,7 @@ Assert.assertFalse(mTabbedAppMenuPropertiesDelegate.shouldShowPageMenu()); assertEquals(MenuGroup.OVERVIEW_MODE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.new_tab_menu_id, @@ -818,8 +822,7 @@ MenuGroup.TABLET_EMPTY_MODE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); Assert.assertFalse(mTabbedAppMenuPropertiesDelegate.shouldShowPageMenu()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); Integer[] expectedItems = { R.id.new_tab_menu_id, @@ -850,8 +853,7 @@ ThreadUtils.hasSubtleSideEffectsSetThreadAssertsDisabledForTesting(true); AccessibilityState.setIsKnownScreenReaderEnabledForTesting(true); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); ArrayList<Integer> expectedItems = new ArrayList<>( @@ -892,7 +894,7 @@ when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID)) .thenReturn(true); - modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertEquals( "Stop image descriptions", findItemById(modelList, R.id.get_image_descriptions_id) @@ -906,7 +908,7 @@ when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI)) .thenReturn(true); - modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertEquals( "Get image descriptions", findItemById(modelList, R.id.get_image_descriptions_id) @@ -924,8 +926,7 @@ .when(mTabbedAppMenuPropertiesDelegate) .shouldShowManagedByMenuItem(any(Tab.class)); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); ArrayList<Integer> expectedItems = new ArrayList<>( @@ -1079,8 +1080,7 @@ .shouldShowManagedByMenuItem(any(Tab.class)); Assert.assertEquals(MenuGroup.PAGE_MENU, mTabbedAppMenuPropertiesDelegate.getMenuGroup()); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertTrue(isMenuVisible(modelList, R.id.managed_by_menu_id)); } @@ -1097,8 +1097,7 @@ doReturn(true).when(mIncognitoReauthControllerMock).isReauthPageShowing(); doReturn(mIncognitoTabModel).when(mTabModelSelector).getCurrentModel(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); verify(mIncognitoReauthControllerMock).isReauthPageShowing(); MVCListAdapter.ListItem item = findItemById(modelList, R.id.new_incognito_tab_menu_id); @@ -1115,8 +1114,7 @@ .withAutoDarkEnabled()); doReturn(mTabModel).when(mTabModelSelector).getCurrentModel(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); verifyNoMoreInteractions(mIncognitoReauthControllerMock); MVCListAdapter.ListItem item = findItemById(modelList, R.id.new_incognito_tab_menu_id); @@ -1130,8 +1128,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); doReturn(mTabModel).when(mTabModelSelector).getCurrentModel(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertFalse(isMenuVisible(modelList, R.id.reader_mode_menu_id)); } @@ -1143,8 +1140,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); doReturn(mTabModel).when(mTabModelSelector).getCurrentModel(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertTrue(isMenuVisible(modelList, R.id.reader_mode_menu_id)); } @@ -1155,8 +1151,7 @@ prepareMocksForGroupTabsOnTabModel(mIncognitoTabModel); doReturn(isShowing).when(mIncognitoReauthControllerMock).isReauthPageShowing(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); verify(mIncognitoReauthControllerMock, atLeastOnce()).isReauthPageShowing(); return modelList; } @@ -1183,8 +1178,7 @@ when(mTabModelSelector.getCurrentModel()).thenReturn(mTabModel); prepareMocksForGroupTabsOnTabModel(mTabModel); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); // Check group tabs enabled decision in regular mode doesn't depend on re-auth. verify(mIncognitoReauthControllerMock, times(0)).isReauthPageShowing(); @@ -1200,8 +1194,7 @@ when(mTabModelSelector.isTabStateInitialized()).thenReturn(false); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); // Check group tabs enabled decision in regular mode doesn't depend on re-auth. verify(mIncognitoReauthControllerMock, times(0)).isReauthPageShowing(); @@ -1217,8 +1210,7 @@ Tab mockTab1 = mock(Tab.class); when(mTabModel.getTabAt(0)).thenReturn(mockTab1); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); // Check group tabs enabled decision in regular mode doesn't depend on re-auth. verify(mIncognitoReauthControllerMock, times(0)).isReauthPageShowing(); @@ -1231,8 +1223,7 @@ setUpMocksForOverviewMenu(); when(mTabModelSelector.getCurrentModel()).thenReturn(mTabModel); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); // Check group tabs enabled decision in regular mode doesn't depend on re-auth. verify(mIncognitoReauthControllerMock, times(0)).isReauthPageShowing(); @@ -1250,8 +1241,7 @@ setMenuOptions(new MenuOptions()); when(mActivityTabProvider.get()).thenReturn(ntpTab); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); MVCListAdapter.ListItem item = findItemById(modelList, R.id.ntp_customization_id); assertTrue(item.model.get(AppMenuItemProperties.ENABLED)); @@ -1538,8 +1528,7 @@ mReadAloudControllerSupplier.set(null); when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); setUpMocksForPageMenu(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertFalse(isMenuVisible(modelList, R.id.readaloud_menu_id)); } @@ -1548,8 +1537,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); when(mReadAloudController.isReadable(any())).thenReturn(false); setUpMocksForPageMenu(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertFalse(isMenuVisible(modelList, R.id.readaloud_menu_id)); } @@ -1558,8 +1546,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); when(mReadAloudController.isReadable(any())).thenReturn(true); setUpMocksForPageMenu(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertTrue(isMenuVisible(modelList, R.id.readaloud_menu_id)); } @@ -1572,8 +1559,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1); setUpMocksForPageMenu(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertTrue( "AI Web menu item should be visible", @@ -1596,8 +1582,7 @@ when(mTab.getNativePage()).thenReturn(pdfNativePage); when(mTab.isNativePage()).thenReturn(true); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertFalse( "AI Web menu item should not be visible", @@ -1613,8 +1598,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1); setUpMocksForPageMenu(); - MVCListAdapter.ModelList modelList = - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); assertFalse( "AI Web menu item should not be visible", @@ -1648,7 +1632,7 @@ when(mTab.getUrl()).thenReturn(JUnitTestGURLs.EXAMPLE_URL); when(mReadAloudController.isReadable(mTab)).thenReturn(initiallyReadable); setUpMocksForPageMenu(); - mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + mTabbedAppMenuPropertiesDelegate.getMenuItems(); // When menu is created, the visibility should match readability state at that time assertEquals(initiallyReadable, hasReadAloudInMenu()); @@ -1810,7 +1794,7 @@ doReturn(mIsMoveToOtherWindowSupported) .when(mMultiWindowModeStateDispatcher) .isMoveToOtherWindowSupported(mTabModelSelector); - return mTabbedAppMenuPropertiesDelegate.getMenuItems(mAppMenuHandler); + return mTabbedAppMenuPropertiesDelegate.getMenuItems(); } private void testWindowMenu( @@ -2082,9 +2066,9 @@ doReturn(options.showTranslate()) .when(mTabbedAppMenuPropertiesDelegate) .shouldShowTranslateMenuItem(any(Tab.class)); - doReturn(options.showUpdate()) - .when(mTabbedAppMenuPropertiesDelegate) - .shouldShowUpdateMenuItem(); + doReturn(options.showUpdate() ? mUpdateAvailableMenuUiState : new MenuUiState()) + .when(mUpdateMenuItemHelper) + .getUiState(); doReturn(options.showMoveToOtherWindow()) .when(mTabbedAppMenuPropertiesDelegate) .shouldShowMoveToOtherWindow();
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 2cd42d2..11b893ea 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -4263,8 +4263,6 @@ "upgrade_detector/build_state.cc", "upgrade_detector/build_state.h", "upgrade_detector/upgrade_detector.cc", - "upgrade_detector/upgrade_detector.h", - "upgrade_detector/upgrade_observer.h", "upgrade_detector/version_history_client.cc", "upgrade_detector/version_history_client.h", "usb/usb_connection_tracker.cc", @@ -4483,6 +4481,7 @@ "//chrome/browser/ui/webui/top_chrome:impl", "//chrome/browser/ui/webui/whats_new", "//chrome/browser/ui/webui/whats_new:impl", + "//chrome/browser/upgrade_detector", "//chrome/browser/upgrade_detector:build_state_observer", "//chrome/browser/web_applications", "//chrome/browser/web_applications:features",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 4f7b349..c61c0d2 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -6880,6 +6880,10 @@ flag_descriptions::kLensOverlayOmniboxEntryPointDescription, kOsDesktop, FEATURE_VALUE_TYPE(lens::features::kLensOverlayOmniboxEntryPoint)}, + {"omnibox-toolbelt", flag_descriptions::kOmniboxToolbeltName, + flag_descriptions::kOmniboxToolbeltDescription, kOsDesktop, + FEATURE_VALUE_TYPE(omnibox_feature_configs::Toolbelt::kOmniboxToolbelt)}, + {"omnibox-domain-suggestions", flag_descriptions::kOmniboxDomainSuggestionsName, flag_descriptions::kOmniboxDomainSuggestionsDescription, kOsDesktop, @@ -6932,7 +6936,7 @@ {"omnibox-url-suggestions-on-focus", flag_descriptions::kOmniboxUrlSuggestionsOnFocus, - flag_descriptions::kOmniboxUrlSuggestionsOnFocusDecription, kOsDesktop, + flag_descriptions::kOmniboxUrlSuggestionsOnFocusDescription, kOsDesktop, FEATURE_WITH_PARAMS_VALUE_TYPE( omnibox_feature_configs::OmniboxUrlSuggestionsOnFocus:: kOmniboxUrlSuggestionsOnFocus,
diff --git a/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.cc b/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.cc index fb05a2d..1e12ebd 100644 --- a/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.cc +++ b/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.cc
@@ -63,7 +63,7 @@ Profile* profile, int resource_size_in_dip, AppIconLoaderDelegate* delegate) - : AppIconLoader(profile, resource_size_in_dip, delegate) { + : AppIconLoader(resource_size_in_dip, delegate), profile_(profile) { app_registry_cache_observer_.Observe( &apps::AppServiceProxyFactory::GetForProfile(profile) ->AppRegistryCache());
diff --git a/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h b/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h index e5af369..ecc96e41 100644 --- a/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h +++ b/chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h
@@ -9,6 +9,7 @@ #include <set> #include <string> +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "chrome/browser/ui/app_icon_loader.h" @@ -59,7 +60,11 @@ // Returns true if the app_id does exist in icon_map_. bool Exist(const std::string& app_id); + Profile* profile() { return profile_; } + private: + const raw_ptr<Profile, DanglingUntriaged> profile_ = nullptr; + // Maps from an app id to shelf app ids. AppIDToShelfAppId shelf_app_id_map_;
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.cc index af1064c0..d00f2cd 100644 --- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.cc +++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.cc
@@ -27,7 +27,7 @@ Profile* profile, int resource_size_in_dip, AppIconLoaderDelegate* delegate) - : AppIconLoader(profile, resource_size_in_dip, delegate) { + : AppIconLoader(resource_size_in_dip, delegate), profile_(profile) { promise_app_registry_cache_observation_.Observe( apps::AppServiceProxyFactory::GetForProfile(profile) ->PromiseAppRegistryCache());
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.h b/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.h index 0ceca93ce..1c54679 100644 --- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.h +++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_icon_loader.h
@@ -8,6 +8,7 @@ #include <map> #include <string> +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "chrome/browser/apps/app_service/promise_apps/promise_app.h" @@ -47,6 +48,8 @@ static bool CanLoadImage(Profile* profile, const std::string& id); + Profile* profile() { return profile_; } + private: base::ScopedObservation<apps::PromiseAppRegistryCache, apps::PromiseAppRegistryCache::Observer> @@ -60,6 +63,8 @@ void OnLoadIcon(const apps::PackageId& package_id, apps::IconValuePtr icon_value); + const raw_ptr<Profile, DanglingUntriaged> profile_ = nullptr; + base::WeakPtrFactory<AppServicePromiseAppIconLoader> weak_ptr_factory_{this}; };
diff --git a/chrome/browser/ash/policy/handlers/BUILD.gn b/chrome/browser/ash/policy/handlers/BUILD.gn index 6194923..68e7e8c 100644 --- a/chrome/browser/ash/policy/handlers/BUILD.gn +++ b/chrome/browser/ash/policy/handlers/BUILD.gn
@@ -68,6 +68,7 @@ "//chrome/browser/ash/settings", "//chrome/browser/ash/tpm", "//chrome/browser/profiles:profile", + "//chrome/browser/upgrade_detector", "//chrome/browser/web_applications", "//chrome/common:chrome_features", "//chrome/common:constants",
diff --git a/chrome/browser/bookmarks/android/BUILD.gn b/chrome/browser/bookmarks/android/BUILD.gn index fa85404..0ede0b5a018 100644 --- a/chrome/browser/bookmarks/android/BUILD.gn +++ b/chrome/browser/bookmarks/android/BUILD.gn
@@ -87,7 +87,6 @@ "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsLayoutManager.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsProvider.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarProperties.java", - "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProvider.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarViewBinder.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java", @@ -205,7 +204,6 @@ "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarCoordinatorTest.java", "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsLayoutManagerTest.java", "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsProviderTest.java", - "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProviderTest.java", "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java", "junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java", ]
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProvider.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProvider.java deleted file mode 100644 index 81c39e0..0000000 --- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProvider.java +++ /dev/null
@@ -1,75 +0,0 @@ -// 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.chrome.browser.bookmarks.bar; - -import org.chromium.base.Callback; -import org.chromium.base.supplier.ObservableSupplier; -import org.chromium.build.annotations.NullMarked; -import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.preferences.PrefServiceUtil; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.components.prefs.PrefChangeRegistrar; - -/** - * A provider which observes and propagates events on changes to the bookmark bar user setting in a - * profile-agnostic way. This circumvents the need for clients to explicitly observe both profile - * and preference change events in order to track the current state of the bookmark bar user - * setting. - */ -@NullMarked -public class BookmarkBarSettingProvider { - - private final Callback<Boolean> mCallback; - private final ObservableSupplier<Profile> mProfileSupplier; - private final Callback<Profile> mProfileSupplierObserver; - - private @Nullable PrefChangeRegistrar mPrefChangeRegistrar; - - /** - * Constructor. - * - * @param profileSupplier The supplier of the profile for which to observe the user setting. - * @param callback The callback to notify of changes to the user setting. - */ - public BookmarkBarSettingProvider( - ObservableSupplier<Profile> profileSupplier, Callback<Boolean> callback) { - mCallback = callback; - - mProfileSupplier = profileSupplier; - mProfileSupplierObserver = this::onProfileChange; - mProfileSupplier.addObserver(mProfileSupplierObserver); - } - - /** Destroys the setting provider. */ - public void destroy() { - destroyPrefChangeRegistrar(); - mProfileSupplier.removeObserver(mProfileSupplierObserver); - } - - private void onProfileChange(@Nullable Profile profile) { - destroyPrefChangeRegistrar(); - - if (profile != null) { - mPrefChangeRegistrar = PrefServiceUtil.createFor(profile); - BookmarkBarUtils.addSettingObserver(mPrefChangeRegistrar, this::updateSetting); - } - - // The callback is now called directly twice - once when registering the observer above, - // and again here to cover null |profile| cases. - // TODO(crbug.com/424456738): Update setting provider to not have extra calls. - updateSetting(); - } - - private void destroyPrefChangeRegistrar() { - if (mPrefChangeRegistrar != null) { - mPrefChangeRegistrar.destroy(); - mPrefChangeRegistrar = null; - } - } - - private void updateSetting() { - mCallback.onResult(BookmarkBarUtils.isSettingEnabled(mProfileSupplier.get())); - } -}
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java index ace061b..008714e3 100644 --- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java +++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java
@@ -9,17 +9,26 @@ import androidx.annotation.NonNull; +import org.chromium.base.Callback; import org.chromium.base.ObserverList; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.bookmarks.R; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver; +import org.chromium.chrome.browser.preferences.Pref; +import org.chromium.chrome.browser.preferences.PrefServiceUtil; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.components.prefs.PrefChangeRegistrar; +import org.chromium.components.prefs.PrefChangeRegistrar.PrefObserver; /** - * A provider which observes changes to device configuration state and the bookmark bar user setting - * in order to propagate visibility change events. + * A provider which observes changes to device configuration state and the Bookmark Bar user setting + * in order to propagate visibility change events. This class allows observers to get notifications + * of changes in visibility in a Profile-agnostic way. This circumvents the need for clients to + * explicitly observe both profile and preference change events, as well as the Configuration, in + * order to track the current visibility state of the Bookmark Bar. */ @NullMarked public class BookmarkBarVisibilityProvider { @@ -49,9 +58,12 @@ private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher; private final ConfigurationChangedObserver mConfigurationChangedListener; private final ObservableSupplier<Profile> mProfileSupplier; - private final BookmarkBarSettingProvider mSettingProvider; + private final Callback<Profile> mProfileSupplierObserver; private final ObserverList<BookmarkBarVisibilityObserver> mObservers; + private @Nullable PrefChangeRegistrar mPrefChangeRegistrar; + private final @Nullable PrefObserver mPrefObserver; + /** * Constructor. * @@ -72,8 +84,16 @@ mConfigurationChangedListener = this::processConfigurationChange; mActivityLifecycleDispatcher.register(mConfigurationChangedListener); - mSettingProvider = - new BookmarkBarSettingProvider(mProfileSupplier, this::processSettingChange); + mProfileSupplierObserver = this::processProfileChange; + mProfileSupplier.addObserver(mProfileSupplierObserver); + + mPrefObserver = + new PrefObserver() { + @Override + public void onPreferenceChange() { + processPrefChange(); + } + }; } /** @@ -98,11 +118,12 @@ /** Destroys the visibility provider. */ public void destroy() { mActivityLifecycleDispatcher.unregister(mConfigurationChangedListener); - mSettingProvider.destroy(); + mProfileSupplier.removeObserver(mProfileSupplierObserver); + destroyPrefChangeRegistrar(); mObservers.clear(); } - private void processSettingChange(Boolean isSettingEnabled) { + private void notifyVisibilityChange() { boolean visibility = BookmarkBarUtils.isFeatureVisible(mActivity, mProfileSupplier.get()); for (BookmarkBarVisibilityObserver observer : mObservers) { observer.onVisibilityChanged(visibility); @@ -117,6 +138,38 @@ } // Configuration changes can also result in visibility changes (e.g. window size change). - processSettingChange(BookmarkBarUtils.isSettingEnabled(mProfileSupplier.get())); + notifyVisibilityChange(); + } + + private void processProfileChange(@Nullable Profile profile) { + // On a profile change, we may have either received a profile for the first time, or we + // have received a new profile, in which case we want to destroy the previous pref change + // registrar and create a new one. + destroyPrefChangeRegistrar(); + + if (profile != null) { + mPrefChangeRegistrar = PrefServiceUtil.createFor(profile); + mPrefChangeRegistrar.addObserver(Pref.SHOW_BOOKMARK_BAR, this::processPrefChange); + } + + // Profile changes can also result in visibility changes (e.g. different setting prefs). + notifyVisibilityChange(); + } + + private void processPrefChange() { + // On any pref change, we need to notify all observers of visibility change. + notifyVisibilityChange(); + } + + private void destroyPrefChangeRegistrar() { + if (mPrefChangeRegistrar != null) { + mPrefChangeRegistrar.removeObserver(Pref.SHOW_BOOKMARK_BAR); + mPrefChangeRegistrar.destroy(); + mPrefChangeRegistrar = null; + } + } + + @Nullable PrefObserver getPrefObserverForTesting() { + return mPrefObserver; } }
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProviderTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProviderTest.java deleted file mode 100644 index 6b8ebac..0000000 --- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSettingProviderTest.java +++ /dev/null
@@ -1,183 +0,0 @@ -// 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.chrome.browser.bookmarks.bar; - -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.filters.SmallTest; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.mockito.stubbing.Answer; -import org.robolectric.Robolectric; - -import org.chromium.base.Callback; -import org.chromium.base.supplier.ObservableSupplierImpl; -import org.chromium.base.test.BaseRobolectricTestRunner; -import org.chromium.chrome.browser.preferences.Pref; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.components.prefs.PrefChangeRegistrar.PrefObserver; -import org.chromium.components.prefs.PrefChangeRegistrarJni; -import org.chromium.components.prefs.PrefService; -import org.chromium.components.user_prefs.UserPrefsJni; - -import java.util.HashSet; -import java.util.Set; - -/** Unit tests for {@link BookmarkBarSettingProvider}. */ -@RunWith(BaseRobolectricTestRunner.class) -public class BookmarkBarSettingProviderTest { - - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - - @Mock private Callback<Boolean> mCallback; - @Mock private PrefChangeRegistrarJni mPrefChangeRegistrarJni; - @Mock private PrefService mPrefService; - @Mock private Profile mProfile; - @Mock private UserPrefsJni mUserPrefsJni; - - private final ObservableSupplierImpl<Profile> mProfileSupplier = new ObservableSupplierImpl<>(); - private final Set<PrefObserver> mSettingObserverCache = new HashSet<>(); - private final ObservableSupplierImpl<Boolean> mSettingSupplier = new ObservableSupplierImpl<>(); - - private @Nullable BookmarkBarSettingProvider mProvider; - - @Before - public void setUp() { - mProfileSupplier.set(mProfile); - - // Set up mocks. - when(mProfile.getOriginalProfile()).thenReturn(mProfile); - when(mUserPrefsJni.get(mProfile)).thenReturn(mPrefService); - - // Set up natives. - PrefChangeRegistrarJni.setInstanceForTesting(mPrefChangeRegistrarJni); - UserPrefsJni.setInstanceForTesting(mUserPrefsJni); - - // Cache setting observers. - BookmarkBarUtils.setSettingObserverCacheForTesting(mSettingObserverCache); - - // Update setting and notify observers when profile changes. - mProfileSupplier.addSyncObserver( - profile -> { - final boolean enabled = profile != null ? mSettingSupplier.get() : false; - BookmarkBarUtils.setSettingEnabledForTesting(enabled); - mSettingObserverCache.stream().forEach(PrefObserver::onPreferenceChange); - }); - - // Update setting and notify observers when supplier changes. - mSettingSupplier.addObserver( - enabled -> { - BookmarkBarUtils.setSettingEnabledForTesting(enabled); - mSettingObserverCache.stream().forEach(PrefObserver::onPreferenceChange); - }); - - // Update supplier when setting changes. - doAnswer(runCallbackWithValueAtIndex(mSettingSupplier::set, 1)) - .when(mPrefService) - .setBoolean(eq(Pref.SHOW_BOOKMARK_BAR), anyBoolean()); - } - - @After - public void tearDown() { - PrefChangeRegistrarJni.setInstanceForTesting(null); - UserPrefsJni.setInstanceForTesting(null); - } - - @Test - @SmallTest - public void testConstructAndDestroy() { - // Case: Construct w/ setting disabled. - mSettingSupplier.set(false); - mProvider = new BookmarkBarSettingProvider(mProfileSupplier, mCallback); - Robolectric.flushForegroundThreadScheduler(); - verify(mCallback).onResult(false); - verifyNoMoreInteractions(mCallback); - clearInvocations(mCallback); - - // Case: Destroy w/ setting disabled. - mProvider.destroy(); - verifyNoMoreInteractions(mCallback); - - // Case: Construct w/ setting enabled. - mSettingSupplier.set(true); - mProvider = new BookmarkBarSettingProvider(mProfileSupplier, mCallback); - Robolectric.flushForegroundThreadScheduler(); - verify(mCallback, times(2)).onResult(true); - clearInvocations(mCallback); - - // Case: Destroy w/ setting enabled. - mProvider.destroy(); - verifyNoMoreInteractions(mCallback); - } - - @Test - @SmallTest - public void testProfileChange() { - // Set up. - mSettingSupplier.set(true); - mProvider = new BookmarkBarSettingProvider(mProfileSupplier, mCallback); - Robolectric.flushForegroundThreadScheduler(); - verify(mCallback).onResult(true); - verifyNoMoreInteractions(mCallback); - clearInvocations(mCallback); - - // Case: Profile changed to `null`. - mProfileSupplier.set(null); - verify(mCallback, times(2)).onResult(false); - clearInvocations(mCallback); - - // Case: Profile changed from `null`. - mProfileSupplier.set(mProfile); - verify(mCallback, times(2)).onResult(true); - } - - @Test - @SmallTest - public void testSettingChange() { - // Set up. - mSettingSupplier.set(false); - mProvider = new BookmarkBarSettingProvider(mProfileSupplier, mCallback); - Robolectric.flushForegroundThreadScheduler(); - verify(mCallback).onResult(false); - verifyNoMoreInteractions(mCallback); - clearInvocations(mCallback); - - // Case: Setting changed to enabled. - mSettingSupplier.set(true); - verify(mCallback).onResult(true); - verifyNoMoreInteractions(mCallback); - clearInvocations(mCallback); - - // Case: Setting changed to disabled. - mSettingSupplier.set(false); - verify(mCallback).onResult(false); - verifyNoMoreInteractions(mCallback); - } - - private @NonNull <T> Answer<Void> runCallbackWithValueAtIndex( - @NonNull Callback<T> callback, int index) { - return invocation -> { - final T value = invocation.getArgument(index); - callback.onResult(value); - return null; - }; - } -}
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java index 22f9e8c7..5585cf9 100644 --- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java +++ b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java
@@ -21,7 +21,7 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; -import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -34,12 +34,15 @@ import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarVisibilityProvider.BookmarkBarVisibilityObserver; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.components.prefs.PrefChangeRegistrar; import org.chromium.components.prefs.PrefChangeRegistrar.PrefObserver; import org.chromium.components.prefs.PrefChangeRegistrarJni; import org.chromium.components.prefs.PrefService; +import org.chromium.components.user_prefs.UserPrefs; import org.chromium.components.user_prefs.UserPrefsJni; import java.util.HashSet; @@ -55,11 +58,11 @@ @Mock private Resources mResources; @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcher; @Mock private Configuration mConfig; - @Mock private PrefChangeRegistrarJni mPrefChangeRegistrarJni; + @Mock private PrefChangeRegistrar.Natives mPrefChangeRegistrarJni; @Mock private PrefService mPrefService; @Mock private Profile mProfile; - @Mock private UserPrefsJni mUserPrefsJni; - @Mock private BookmarkBarVisibilityProvider.BookmarkBarVisibilityObserver mObserver; + @Mock private UserPrefs.Natives mUserPrefsJni; + @Mock private BookmarkBarVisibilityObserver mObserver; private final Set<ConfigurationChangedObserver> mConfigChangeObserverCache = new HashSet<>(); private final ObservableSupplierImpl<Profile> mProfileSupplier = new ObservableSupplierImpl<>(); @@ -91,8 +94,8 @@ BookmarkBarUtils.setSettingObserverCacheForTesting(mSettingObserverCache); } - @After - public void tearDown() { + @AfterClass + public static void tearDown() { PrefChangeRegistrarJni.setInstanceForTesting(null); UserPrefsJni.setInstanceForTesting(null); } @@ -183,7 +186,7 @@ @Test @SmallTest - public void testSettingChange() { + public void testPrefChange() { // Set up. BookmarkBarUtils.setFeatureAllowedForTesting(true); BookmarkBarUtils.setSettingEnabledForTesting(true); @@ -207,6 +210,32 @@ provider.destroy(); } + @Test + @SmallTest + public void testProfileChange() { + // Set up. + BookmarkBarUtils.setFeatureAllowedForTesting(true); + BookmarkBarUtils.setSettingEnabledForTesting(true); + BookmarkBarVisibilityProvider provider = createProvider(); + Robolectric.flushForegroundThreadScheduler(); + + // Case: Profile changed to `null` + BookmarkBarUtils.setSettingEnabledForTesting(false); + mProfileSupplier.set(null); + verify(mObserver, times(1)).onVisibilityChanged(false); + verify(mObserver, never()).onMaxWidthChanged(anyInt()); + clearInvocations(mObserver); + + // Case: Profile changed from `null` + BookmarkBarUtils.setSettingEnabledForTesting(true); + mProfileSupplier.set(mProfile); + verify(mObserver, times(1)).onVisibilityChanged(true); + verify(mObserver, never()).onMaxWidthChanged(anyInt()); + + // Clean up. + provider.destroy(); + } + private @NonNull <T> Answer<Void> addValueAtIndexToSet(@NonNull Set<T> set, int index) { return invocation -> { final T value = invocation.getArgument(index); @@ -220,6 +249,7 @@ new BookmarkBarVisibilityProvider( mActivity, mActivityLifecycleDispatcher, mProfileSupplier); provider.addObserver(mObserver); + mSettingObserverCache.add(provider.getPrefObserverForTesting()); return provider; }
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 964dc0f..a64e293 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
@@ -58,6 +58,9 @@ */ public final boolean showDangerousItems; + /** Whether need to focus on search box at first. */ + public final boolean autoFocusSearchBox; + /** Constructor. */ private DownloadManagerUiConfig(Builder builder) { otrProfileId = builder.mOtrProfileId; @@ -70,6 +73,7 @@ showPaginationHeaders = builder.mShowPaginationHeaders; startWithPrefetchedContent = builder.mStartWithPrefetchedContent; showDangerousItems = builder.mShowDangerousItems; + autoFocusSearchBox = builder.mAutoFocusSearchBox; } /** Helper class for building a {@link DownloadManagerUiConfig}. */ @@ -88,6 +92,7 @@ private boolean mShowPaginationHeaders; private boolean mStartWithPrefetchedContent; private boolean mShowDangerousItems; + private boolean mAutoFocusSearchBox; public Builder() { mSupportFullWidthImages = @@ -146,6 +151,11 @@ return this; } + public Builder setAutoFocusSearchBox(boolean autoFocusSearchBox) { + mAutoFocusSearchBox = autoFocusSearchBox; + return this; + } + public DownloadManagerUiConfig build() { return new DownloadManagerUiConfig(this); }
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfigHelper.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfigHelper.java index ea1bb9c3..d3696a5 100644 --- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfigHelper.java +++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfigHelper.java
@@ -6,6 +6,7 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.ui.base.DeviceFormFactor; /** Helper class to build default or base {@link DownloadManagerUiConfig.Builder} instances. */ @NullMarked @@ -19,6 +20,7 @@ && !ChromeFeatureList.sMaliciousApkDownloadCheckTelemetryOnly.getValue(); return new DownloadManagerUiConfig.Builder() .setShowDangerousItems(showDangerousItems) - .setSupportsGrouping(true); + .setSupportsGrouping(true) + .setAutoFocusSearchBox(DeviceFormFactor.isTablet()); } }
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java index f72a0af..cfc3126a 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
@@ -33,6 +33,7 @@ import android.view.View; import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.test.espresso.Espresso; import androidx.test.espresso.action.ViewActions; import androidx.test.espresso.contrib.RecyclerViewActions; import androidx.test.espresso.matcher.BoundedMatcher; @@ -195,15 +196,16 @@ } private void setUpUi() { - setUpUi(false); + setUpUi(/* showDangerousItems= */ false, /* autoFocusSearchBox= */ false); } - private void setUpUi(boolean showDangerousItems) { + private void setUpUi(boolean showDangerousItems, boolean autoFocusSearchBox) { DownloadManagerUiConfig config = DownloadManagerUiConfigHelper.fromFlags() .setOtrProfileId(null) .setIsSeparateActivity(true) .setShowDangerousItems(showDangerousItems) + .setAutoFocusSearchBox(autoFocusSearchBox) .build(); mAppModalPresenter = new AppModalPresenter(sActivity); @@ -398,7 +400,7 @@ public void testAddRemoveDangerousItem() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> { - setUpUi(/*showDangerousItems*/ true); + setUpUi(/* showDangerousItems= */ true, /* autoFocusSearchBox= */ false); }); String storageHeaderText = "Using 1.10 KB of"; @@ -451,7 +453,7 @@ public void testDangerousItemNotShownDueToConfig() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> { - setUpUi(/*showDangerousItems*/ false); + setUpUi(/* showDangerousItems= */ false, /* autoFocusSearchBox= */ false); }); String storageHeaderText = "Using 1.10 KB of"; @@ -493,7 +495,7 @@ public void testBypassDangerousWarning() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> { - setUpUi(/*showDangerousItems*/ true); + setUpUi(/* showDangerousItems= */ true, /* autoFocusSearchBox= */ false); }); String storageHeaderText = "Using 1.10 KB of"; @@ -549,7 +551,7 @@ public void testWarningBypassDialogLearnMore() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> { - setUpUi(/*showDangerousItems*/ true); + setUpUi(/* showDangerousItems= */ true, /* autoFocusSearchBox= */ false); }); // Add a dangerous item. @@ -849,6 +851,26 @@ }); } + @Test + @MediumTest + public void testDownloadsFocus() throws Exception { + ThreadUtils.runOnUiThreadBlocking( + () -> { + setUpUi(/* showDangerousItems= */ false, /* autoFocusSearchBox= */ true); + }); + + onView(withText("Download")).check(doesNotExist()); + // Check the search field is displayed + onView(withId(R.id.search_text)).check(matches(isDisplayed())); + // Check we can type in search query + onView(withId(R.id.search_text)).perform(ViewActions.typeText("Google")); + // Close keyboard first then press back to Download Page. + Espresso.closeSoftKeyboard(); + Espresso.pressBack(); + // After back to download pagem user should see the search field + onView(withId(R.id.search_text)).check(matches(not(isDisplayed()))); + } + /** * @param items The list (unsorted) of OfflineItems that could be displayed. * @param expectations Whether or not each item (1:1 with {@code items}) is visible.
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerCoordinatorImpl.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerCoordinatorImpl.java index 2c20257..4088e74 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerCoordinatorImpl.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerCoordinatorImpl.java
@@ -98,6 +98,7 @@ /* listContentView= */ mListCoordinator.getView(), mSelectionDelegate, config.isSeparateActivity, + config.autoFocusSearchBox, tracker); initializeView();
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/toolbar/ToolbarCoordinator.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/toolbar/ToolbarCoordinator.java index c4b9bb0..564821a 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/toolbar/ToolbarCoordinator.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/toolbar/ToolbarCoordinator.java
@@ -96,6 +96,7 @@ View listContentView, SelectionDelegate<ListItem> selectionDelegate, boolean hasCloseButton, + boolean autoFocusSearchBox, Tracker tracker) { mDelegate = delegate; mListActionDelegate = listActionDelegate; @@ -115,7 +116,10 @@ mToolbar.setOnMenuItemClickListener(this::onMenuItemClick); mToolbar.setFocusable(true); mToolbar.setListContentView(listContentView); - + if (autoFocusSearchBox) { + // Request focus for the toolbar by default attach state listener + mView.addOnAttachStateChangeListener(new FocusListener()); + } // TODO(crbug.com/41412009): Pass the visible group to the toolbar during initialization. mToolbar.initializeSearchView( mSearchDelegate, R.string.download_manager_search, R.id.search_menu_id); @@ -212,6 +216,21 @@ } } + /** + * A listener in download page that focuses the search view when the toolbar is first attached + * to the window, then removes itself. + */ + private class FocusListener implements View.OnAttachStateChangeListener { + @Override + public void onViewAttachedToWindow(View v) { + mToolbar.showSearchView(true); + v.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewDetachedFromWindow(View v) {} + } + // TODO(shaktisahu): May be merge toolbar shadow logic into the toolbar itself. private void updateShadowVisibility() { boolean show = mShowToolbarShadow || mToolbar.isSearching();
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn index 9bbfd65..d7b86895 100644 --- a/chrome/browser/extensions/BUILD.gn +++ b/chrome/browser/extensions/BUILD.gn
@@ -112,6 +112,8 @@ "api/identity/launch_web_auth_flow_delegate.h", "api/identity/web_auth_flow_info_bar_delegate.cc", "api/identity/web_auth_flow_info_bar_delegate.h", + "api/instance_id/instance_id_api.cc", + "api/instance_id/instance_id_api.h", "api/management/chrome_management_api_delegate.h", "api/messaging/chrome_messaging_delegate.cc", "api/messaging/chrome_messaging_delegate.h", @@ -547,6 +549,7 @@ "//ui/shell_dialogs", ] public_deps = [ + "//chrome/browser/upgrade_detector", "//components/safe_browsing/core/browser/db:util", "//components/signin/core/browser", "//extensions/buildflags", @@ -658,8 +661,6 @@ "api/image_writer_private/xz_extractor.h", "api/image_writer_private/zip_extractor.cc", "api/image_writer_private/zip_extractor.h", - "api/instance_id/instance_id_api.cc", - "api/instance_id/instance_id_api.h", "api/language_settings_private/language_settings_private_api.cc", "api/language_settings_private/language_settings_private_api.h", "api/language_settings_private/language_settings_private_delegate.cc",
diff --git a/chrome/browser/extensions/api/instance_id/instance_id_api.cc b/chrome/browser/extensions/api/instance_id/instance_id_api.cc index 061c30d..d5368d9 100644 --- a/chrome/browser/extensions/api/instance_id/instance_id_api.cc +++ b/chrome/browser/extensions/api/instance_id/instance_id_api.cc
@@ -13,8 +13,11 @@ #include "chrome/common/extensions/api/instance_id.h" #include "components/gcm_driver/instance_id/instance_id_driver.h" #include "components/gcm_driver/instance_id/instance_id_profile_service.h" +#include "extensions/buildflags/buildflags.h" #include "extensions/common/extension.h" +static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); + namespace extensions { namespace {
diff --git a/chrome/browser/extensions/api/instance_id/instance_id_api.h b/chrome/browser/extensions/api/instance_id/instance_id_api.h index 4ef2069..155fc53 100644 --- a/chrome/browser/extensions/api/instance_id/instance_id_api.h +++ b/chrome/browser/extensions/api/instance_id/instance_id_api.h
@@ -7,6 +7,9 @@ #include "components/gcm_driver/instance_id/instance_id.h" #include "extensions/browser/extension_function.h" +#include "extensions/buildflags/buildflags.h" + +static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); namespace extensions {
diff --git a/chrome/browser/extensions/api/instance_id/instance_id_apitest.cc b/chrome/browser/extensions/api/instance_id/instance_id_apitest.cc index f8ae2f7..29b1e809 100644 --- a/chrome/browser/extensions/api/instance_id/instance_id_apitest.cc +++ b/chrome/browser/extensions/api/instance_id/instance_id_apitest.cc
@@ -13,13 +13,16 @@ #include "chrome/browser/gcm/gcm_profile_service_factory.h" #include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/test/base/ui_test_utils.h" #include "components/gcm_driver/fake_gcm_profile_service.h" #include "components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h" #include "components/version_info/version_info.h" +#include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" +#include "extensions/buildflags/buildflags.h" #include "extensions/test/result_catcher.h" +static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); + using extensions::ResultCatcher; namespace extensions { @@ -67,9 +70,13 @@ IN_PROC_BROWSER_TEST_F(InstanceIDApiTest, Incognito) { ResultCatcher catcher; catcher.RestrictToBrowserContext(profile()); + // Android requires a tab to be created for the incognito profile. + auto* incognito_web_contents = + PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); + auto* incognito_context = incognito_web_contents->GetBrowserContext(); + ASSERT_TRUE(incognito_context->IsOffTheRecord()); ResultCatcher incognito_catcher; - incognito_catcher.RestrictToBrowserContext( - profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); + incognito_catcher.RestrictToBrowserContext(incognito_context); ASSERT_TRUE(RunExtensionTest("instance_id/incognito", {}, {.allow_in_incognito = true}));
diff --git a/chrome/browser/extensions/chrome_app_icon_loader.cc b/chrome/browser/extensions/chrome_app_icon_loader.cc index 2b0b612..ebb8c72 100644 --- a/chrome/browser/extensions/chrome_app_icon_loader.cc +++ b/chrome/browser/extensions/chrome_app_icon_loader.cc
@@ -27,7 +27,8 @@ int icon_size_in_dip, const ResizeFunction& resize_function, AppIconLoaderDelegate* delegate) - : AppIconLoader(profile, icon_size_in_dip, delegate), + : AppIconLoader(icon_size_in_dip, delegate), + profile_(profile), resize_function_(resize_function) {} ChromeAppIconLoader::ChromeAppIconLoader(Profile* profile,
diff --git a/chrome/browser/extensions/chrome_app_icon_loader.h b/chrome/browser/extensions/chrome_app_icon_loader.h index 7313cad..1f8a875 100644 --- a/chrome/browser/extensions/chrome_app_icon_loader.h +++ b/chrome/browser/extensions/chrome_app_icon_loader.h
@@ -10,6 +10,7 @@ #include <string> #include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" #include "chrome/browser/extensions/chrome_app_icon_delegate.h" #include "chrome/browser/ui/app_icon_loader.h" @@ -54,6 +55,8 @@ // Sets `extensions_only_` as true to load icons for extensions only. void SetExtensionsOnly(); + Profile* profile() { return profile_; } + private: using ExtensionIDToChromeAppIconMap = std::map<std::string, std::unique_ptr<ChromeAppIcon>>; @@ -61,6 +64,8 @@ // ChromeAppIconDelegate: void OnIconUpdated(ChromeAppIcon* icon) override; + const raw_ptr<Profile, DanglingUntriaged> profile_ = nullptr; + // Maps from extension id to ChromeAppIcon. ExtensionIDToChromeAppIconMap map_;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index b575b14..66e9aa5 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -7469,6 +7469,11 @@ "expiry_milestone": 130 }, { + "name": "omnibox-toolbelt", + "owners": ["orin@google.com", "manukh@google.com", "chrome-omnibox-team@google.com"], + "expiry_milestone": 150 + }, + { "name": "omnibox-ui-max-autocomplete-matches", "owners": [ "jdonnelly@chromium.org", "chrome-omnibox-team@google.com" ], "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 09c4257..bee3758 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -2868,16 +2868,6 @@ "credential mode disallows. Without this flag enabled, Chrome will always " "try sending client certificates regardless of the credential mode."; -const char kOmniboxAdaptiveSuggestionsCountName[] = - "Adaptive Omnibox Suggestions count"; -const char kOmniboxAdaptiveSuggestionsCountDescription[] = - "Dynamically adjust number of presented Omnibox suggestions depending on " - "available space. When enabled, this feature will increase (or decrease) " - "amount of offered Omnibox suggestions to fill in the space between the " - "Omnibox and soft keyboard (if any). See also Max Autocomplete Matches " - "flag to adjust the limit of offered suggestions. The number of shown " - "suggestions will be no less than the platform default limit."; - const char kOmniboxAdjustIndentationName[] = "Adjust Indentation for Omnibox Text and Suggestions"; const char kOmniboxAdjustIndentationDescription[] = @@ -2926,12 +2916,6 @@ "When set, applies certain assets to match Desktop visuals and " "descriptions"; -const char kOmniboxMostVisitedTilesHorizontalRenderGroupName[] = - "Omnibox MV Tiles Horizontal Render Group"; -const char kOmniboxMostVisitedTilesHorizontalRenderGroupDescription[] = - "Updates the logic constructing MV tiles to use horizontal render group. " - "No user-facing changes expected."; - const char kOmniboxNumNtpZpsRecentSearchesName[] = "Omnibox: Recent Searches on new tab page ZPS"; const char kOmniboxNumNtpZpsRecentSearchesDescription[] = @@ -2974,6 +2958,10 @@ "Controls presence/volume of Most Visited URLs shown in zero-prefix " "context on the Web"; +const char kOmniboxToolbeltName[] = "Omnibox toolbelt"; +const char kOmniboxToolbeltDescription[] = + "Adds a row of buttons at the bottom of the omnibox."; + const char kOmniboxZeroSuggestPrefetchDebouncingName[] = "Omnibox Zero Prefix Suggest Prefetch Request Debouncing"; const char kOmniboxZeroSuggestPrefetchDebouncingDescription[] = @@ -3073,10 +3061,6 @@ const char kOmniboxSuggestionAnswerMigrationDescription[] = "Uses protos instead of SuggestionAnswer to hold answer data."; -const char kOmniboxShortcutBoostName[] = "Omnibox shortcut boosting"; -const char kOmniboxShortcutBoostDescription[] = - "Promote shortcuts to be default when available."; - const char kOmniboxMaxZeroSuggestMatchesName[] = "Omnibox Max Zero Suggest Matches"; const char kOmniboxMaxZeroSuggestMatchesDescription[] = @@ -3146,7 +3130,7 @@ const char kOmniboxUrlSuggestionsOnFocus[] = "Omnibox on-focus URL suggestions on web and SRP"; -const char kOmniboxUrlSuggestionsOnFocusDecription[] = +const char kOmniboxUrlSuggestionsOnFocusDescription[] = "Enables zero-prefix URL suggestions on web and SRP when the omnibox is " "focused.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 67d4d92..6824092 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -1661,9 +1661,6 @@ extern const char kOmitCorsClientCertName[]; extern const char kOmitCorsClientCertDescription[]; -extern const char kOmniboxAdaptiveSuggestionsCountName[]; -extern const char kOmniboxAdaptiveSuggestionsCountDescription[]; - extern const char kOmniboxAdjustIndentationName[]; extern const char kOmniboxAdjustIndentationDescription[]; @@ -1676,9 +1673,6 @@ extern const char kOmniboxCalcProviderName[]; extern const char kOmniboxCalcProviderDescription[]; -extern const char kOmniboxConsumesImeInsetsName[]; -extern const char kOmniboxConsumesImeInsetsDescription[]; - extern const char kOmniboxDiagnosticsName[]; extern const char kOmniboxDiagnosticsDescription[]; @@ -1716,12 +1710,6 @@ extern const char kOmniboxMobileParityUpdateName[]; extern const char kOmniboxMobileParityUpdateDescription[]; -extern const char kOmniboxMostVisitedTilesHorizontalRenderGroupName[]; -extern const char kOmniboxMostVisitedTilesHorizontalRenderGroupDescription[]; - -extern const char kOmniboxMostVisitedTilesTitleWrapAroundName[]; -extern const char kOmniboxMostVisitedTilesTitleWrapAroundDescription[]; - extern const char kOmniboxNumNtpZpsRecentSearchesName[]; extern const char kOmniboxNumNtpZpsRecentSearchesDescription[]; @@ -1752,10 +1740,6 @@ extern const char kOmniboxOnDeviceTailSuggestionsName[]; extern const char kOmniboxOnDeviceTailSuggestionsDescription[]; -extern const char kOmniboxSuppressClipboardSuggestionAfterFirstUsedName[]; -extern const char - kOmniboxSuppressClipboardSuggestionAfterFirstUsedDescription[]; - extern const char kOmniboxRichAutocompletionPromisingName[]; extern const char kOmniboxRichAutocompletionPromisingDescription[]; @@ -1765,12 +1749,6 @@ extern const char kOmniboxSuggestionAnswerMigrationName[]; extern const char kOmniboxSuggestionAnswerMigrationDescription[]; -extern const char kOmniboxShortcutBoostName[]; -extern const char kOmniboxShortcutBoostDescription[]; - -extern const char kOmniboxShortcutExpandingName[]; -extern const char kOmniboxShortcutExpandingDescription[]; - extern const char kOmniboxStarterPackExpansionName[]; extern const char kOmniboxStarterPackExpansionDescription[]; @@ -1804,8 +1782,11 @@ extern const char kOmniboxHideSuggestionGroupHeadersName[]; extern const char kOmniboxHideSuggestionGroupHeadersDescription[]; +extern const char kOmniboxToolbeltName[]; +extern const char kOmniboxToolbeltDescription[]; + extern const char kOmniboxUrlSuggestionsOnFocus[]; -extern const char kOmniboxUrlSuggestionsOnFocusDecription[]; +extern const char kOmniboxUrlSuggestionsOnFocusDescription[]; extern const char kOmniboxZeroSuggestPrefetchDebouncingName[]; extern const char kOmniboxZeroSuggestPrefetchDebouncingDescription[];
diff --git a/chrome/browser/page_load_metrics/integration_tests/soft_navigation_metrics_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/soft_navigation_metrics_browsertest.cc index 883776d..79958b6 100644 --- a/chrome/browser/page_load_metrics/integration_tests/soft_navigation_metrics_browsertest.cc +++ b/chrome/browser/page_load_metrics/integration_tests/soft_navigation_metrics_browsertest.cc
@@ -341,7 +341,7 @@ #define MAYBE_LargestContentfulPaint LargestContentfulPaint #endif -IN_PROC_BROWSER_TEST_P(SoftNavigationTest, MAYBE_LargestContentfulPaint) { +IN_PROC_BROWSER_TEST_P(SoftNavigationTest, DISABLED_LargestContentfulPaint) { auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( web_contents()); @@ -558,7 +558,8 @@ #else #define MAYBE_INP_ClickWithPresentation INP_ClickWithPresentation #endif // BUILDFLAG(IS_WIN) -IN_PROC_BROWSER_TEST_P(SoftNavigationTest, MAYBE_INP_ClickWithPresentation) { + +IN_PROC_BROWSER_TEST_P(SoftNavigationTest, DISABLED_INP_ClickWithPresentation) { // Add waiter to wait for the interaction is arrived in browser. auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( web_contents()); @@ -607,7 +608,8 @@ #else #define MAYBE_LayoutShift LayoutShift #endif // BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER) -IN_PROC_BROWSER_TEST_P(SoftNavigationTest, MAYBE_LayoutShift) { + +IN_PROC_BROWSER_TEST_P(SoftNavigationTest, DISABLED_LayoutShift) { auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>( web_contents());
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContent.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContent.java index 062e6838..1ec80bf 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContent.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContent.java
@@ -278,6 +278,16 @@ } } + void setRequestedPlaybackMode(PlaybackMode playbackMode) { + if (playbackMode == PlaybackMode.OVERVIEW) { + mLoadingTextView.setText(mContext.getString(R.string.readaloud_mini_player_loading_ai_playback)); + mLoadingTextView.setContentDescription(mContext.getString(R.string.readaloud_mini_player_loading_ai_playback)); + } else { + mLoadingTextView.setText(mContext.getString(R.string.readaloud_playback_loading)); + mLoadingTextView.setContentDescription(mContext.getString(R.string.readaloud_playback_loading)); + } + } + void showFeedbackButtons() { mThumbUpButton.setVisibility(View.VISIBLE); mThumbDownButton.setVisibility(View.VISIBLE);
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContentUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContentUnitTest.java index e96c245..22b5985 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContentUnitTest.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerSheetContentUnitTest.java
@@ -321,4 +321,15 @@ scrollView.scrollTo(0, 100); assertEquals(100, mContent.getVerticalScrollOffset()); } + + @Test + public void testLoadingTextIsSetCorrectly() { + TextView loadingText = (TextView) mContentView.findViewById(R.id.readaloud_loading_text); + + mContent.setRequestedPlaybackMode(PlaybackMode.OVERVIEW); + assertEquals(loadingText.getText(), mContext.getString(R.string.readaloud_mini_player_loading_ai_playback)); + + mContent.setRequestedPlaybackMode(PlaybackMode.CLASSIC); + assertEquals(loadingText.getText(), mContext.getString(R.string.readaloud_playback_loading)); + } }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerViewBinder.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerViewBinder.java index 67e583a..117611b 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerViewBinder.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/ExpandedPlayerViewBinder.java
@@ -77,6 +77,9 @@ model.get(PlayerProperties.PLAYBACK_MODE_SELECTION_ENABLED))); } else if (key == PlayerProperties.FEEDBACK_TYPE) { content.setSentFeedback(FeedbackType.fromValue(model.get(PlayerProperties.FEEDBACK_TYPE))); + } else if (key == PlayerProperties.REQUESTED_PLAYBACK_MODE) { + content.setRequestedPlaybackMode( + PlaybackMode.fromValue(model.get(PlayerProperties.REQUESTED_PLAYBACK_MODE))); } } }
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java index b41ec1a6..46490c9 100644 --- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java +++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java
@@ -15,6 +15,7 @@ import org.chromium.components.tab_group_sync.OpeningSource; import org.chromium.components.tab_group_sync.SavedTabGroup; import org.chromium.components.tab_group_sync.TabGroupSyncService; +import org.chromium.components.tab_group_sync.VersioningMessageController; import org.chromium.url.GURL; import java.util.ArrayList; @@ -26,6 +27,8 @@ public static final String LOCAL_DEVICE_CACHE_GUID = "LocalDevice"; private final List<SavedTabGroup> mTabGroups = new ArrayList<>(); + private final VersioningMessageController mVersioningMessageController = + new TestVersioningMessageController(); @Override public void addObserver(Observer observer) {} @@ -142,5 +145,21 @@ public void updateArchivalStatus(String syncTabGroupId, boolean archivalStatus) {} @Override + public VersioningMessageController getVersioningMessageController() { + return mVersioningMessageController; + } + + @Override public void setCollaborationAvailableInFinderForTesting(String collaborationId) {} + + private static class TestVersioningMessageController implements VersioningMessageController { + @Override + public void shouldShowMessageUiAsync(int messageType, Callback<Boolean> callback) {} + + @Override + public void onMessageUiShown(int messageType) {} + + @Override + public void onMessageUiDismissed(int messageType) {} + } }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index c17d964..35bcb53 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1763,6 +1763,7 @@ "//components/media_router/common/mojom:media_router", "//components/network_session_configurator/common", "//components/omnibox/browser:mojo_bindings", + "//components/omnibox/composebox", "//components/page_load_metrics/browser", "//components/paint_preview/browser", "//components/paint_preview/common", @@ -4919,6 +4920,7 @@ # and chrome/browser/ui/views/sharing_hub/ get componentized. "//chrome/browser/prefs:util", "//chrome/browser/sharing_hub", + "//chrome/browser/upgrade_detector", "//ui/base/mojom:ui_base_types", ]
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java index d7de06cc..92c811e 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
@@ -16,7 +16,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.SystemClock; -import android.text.TextUtils; import android.util.SparseArray; import android.view.Gravity; import android.view.KeyEvent; @@ -29,17 +28,13 @@ import android.view.ViewParent; import android.view.ViewStub; import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ImageButton; import android.widget.ListView; import android.widget.PopupWindow; -import androidx.annotation.ColorInt; import androidx.annotation.IdRes; import androidx.annotation.VisibleForTesting; import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.content.ContextCompat; import org.chromium.base.Callback; import org.chromium.base.SysUtils; @@ -55,7 +50,6 @@ import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider.ControlsPosition; import org.chromium.chrome.browser.ui.appmenu.internal.R; import org.chromium.components.browser_ui.styles.SemanticColorUtils; -import org.chromium.components.browser_ui.util.motion.MotionEventInfo; import org.chromium.components.browser_ui.widget.chips.ChipView; import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter; import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams; @@ -63,7 +57,6 @@ import org.chromium.ui.modelutil.MVCListAdapter.ModelList; import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.PropertyModel; -import org.chromium.ui.widget.Toast; import java.util.ArrayList; import java.util.List; @@ -81,7 +74,7 @@ * </ul> */ @NullMarked -class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler { +class AppMenu implements OnKeyListener { private static final float LAST_ITEM_SHOW_FRACTION = 0.5f; /** A means of reporting an exception/stack without crashing. */ @@ -335,7 +328,6 @@ mSelectedItemBeforeDismiss = false; mMenuShownTimeMs = SystemClock.elapsedRealtime(); - mListView.setOnItemClickListener(this); mListView.setItemsCanFocus(true); mListView.setOnKeyListener(this); @@ -420,44 +412,9 @@ return position; } - @Override - public void onItemClick(PropertyModel model, @Nullable MotionEventInfo triggeringMotion) { - if (!model.get(AppMenuItemProperties.ENABLED)) return; - - int id = model.get(AppMenuItemProperties.MENU_ITEM_ID); - mSelectedItemBeforeDismiss = true; - dismiss(); - mHandler.onOptionsItemSelected(id, triggeringMotion); - } - - @Override - public boolean onItemLongClick(PropertyModel model, View view) { - if (!model.get(AppMenuItemProperties.ENABLED)) return false; - - mSelectedItemBeforeDismiss = true; - CharSequence titleCondensed = model.get(AppMenuItemProperties.TITLE_CONDENSED); - CharSequence message = - TextUtils.isEmpty(titleCondensed) - ? model.get(AppMenuItemProperties.TITLE) - : titleCondensed; - return showToastForItem(message, view); - } - - @VisibleForTesting - boolean showToastForItem(CharSequence message, View view) { - Context context = view.getContext(); - final @ColorInt int backgroundColor = ContextCompat.getColor(context, R.color.toast_color); - return new Toast.Builder(context) - .withText(message) - .withAnchoredView(view) - .withBackgroundColor(backgroundColor) - .withTextAppearance(R.style.TextAppearance_TextSmall_Primary) - .buildAndShow(); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - onItemClick(mModelList.get(position).model); + /** Marks whether an item was selected prior to dismissal. */ + public void setSelectedItemBeforeDismiss(boolean selected) { + mSelectedItemBeforeDismiss = selected; } @Override @@ -553,11 +510,6 @@ return null; } - /** Invalidate the app menu data. See {@link AppMenuAdapter#notifyDataSetChanged}. */ - void invalidate() { - if (mAdapter != null) mAdapter.notifyDataSetChanged(); - } - @RequiresNonNull("mPopup") private void setMenuHeight( List<Integer> menuItemIds,
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java index 028bf00..b114e03 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
@@ -12,17 +12,19 @@ import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.Rect; +import android.text.TextUtils; import android.util.SparseArray; import android.view.ContextThemeWrapper; import android.view.Display; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.widget.Adapter; +import androidx.annotation.ColorInt; import androidx.annotation.LayoutRes; import androidx.annotation.VisibleForTesting; +import androidx.core.content.ContextCompat; import org.chromium.base.Callback; import org.chromium.base.metrics.RecordUserAction; @@ -47,6 +49,7 @@ import org.chromium.ui.modelutil.MVCListAdapter.ModelList; import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.ui.widget.Toast; import java.util.ArrayList; import java.util.List; @@ -58,7 +61,10 @@ */ @NullMarked class AppMenuHandlerImpl - implements AppMenuHandler, StartStopWithNativeObserver, ConfigurationChangedObserver { + implements AppMenuHandler, + AppMenuClickHandler, + StartStopWithNativeObserver, + ConfigurationChangedObserver { /** An {@link Adapter} for the list of items in the app menu. */ private static final class AppMenuAdapter extends ModelListAdapter { @@ -161,7 +167,7 @@ updateModelForHighlightAndClick( mModelList, mHighlightMenuId, - mAppMenu, + AppMenuHandlerImpl.this, /* startIndex= */ index, /* withAssertions= */ false); } @@ -172,7 +178,7 @@ updateModelForHighlightAndClick( mModelList, mHighlightMenuId, - mAppMenu, + AppMenuHandlerImpl.this, /* startIndex= */ index, /* withAssertions= */ false); } @@ -260,7 +266,7 @@ assert !(isByPermanentButton && startDragging); - mModelList = mDelegate.getMenuItems(this); + mModelList = mDelegate.getMenuItems(); mModelList.addObserver(mListObserver); ContextThemeWrapper wrapper = new ContextThemeWrapper(mContext, R.style.OverflowMenuThemeOverlay); @@ -274,7 +280,7 @@ mAppMenu = new AppMenu(itemRowHeight, this, mContext.getResources()); mAppMenuDragHelper = new AppMenuDragHelper(mContext, mAppMenu, itemRowHeight); } - setupModelForHighlightAndClick(mModelList, mHighlightMenuId, mAppMenu); + setupModelForHighlightAndClick(mModelList, mHighlightMenuId, this); AppMenuAdapter adapter = new AppMenuAdapter(mModelList); mAppMenu.updateMenu(mModelList, adapter); @@ -362,11 +368,6 @@ } @Override - public void invalidateAppMenu() { - if (mAppMenu != null) mAppMenu.invalidate(); - } - - @Override public void addObserver(AppMenuObserver observer) { mObservers.add(observer); } @@ -390,16 +391,15 @@ hideAppMenu(); } - /** - * Called when a menu item is selected. - * - * @param itemId The menu item ID. - * @param triggeringMotion The {@link MotionEventInfo} that triggered the click; it is {@code - * null} if {@link MotionEvent} wasn't available when the click was detected, such as in - * {@link android.view.View.OnClickListener}. - */ - @VisibleForTesting - void onOptionsItemSelected(int itemId, @Nullable MotionEventInfo triggeringMotion) { + @Override + public void onItemClick(PropertyModel model, @Nullable MotionEventInfo triggeringMotion) { + if (mAppMenu == null) return; + if (!model.get(AppMenuItemProperties.ENABLED)) return; + + int itemId = model.get(AppMenuItemProperties.MENU_ITEM_ID); + mAppMenu.setSelectedItemBeforeDismiss(true); + mAppMenu.dismiss(); + if (mTestOptionsItemSelectedListener != null) { mTestOptionsItemSelectedListener.onResult(itemId); return; @@ -409,8 +409,35 @@ itemId, mDelegate.getBundleForMenuItem(itemId), triggeringMotion); } + @Override + public boolean onItemLongClick(PropertyModel model, View view) { + if (mAppMenu == null) return false; + if (!model.get(AppMenuItemProperties.ENABLED)) return false; + + mAppMenu.setSelectedItemBeforeDismiss(true); + + CharSequence titleCondensed = model.get(AppMenuItemProperties.TITLE_CONDENSED); + CharSequence message = + TextUtils.isEmpty(titleCondensed) + ? model.get(AppMenuItemProperties.TITLE) + : titleCondensed; + return showToastForItem(message, view); + } + + private static boolean showToastForItem(CharSequence message, View view) { + Context context = view.getContext(); + final @ColorInt int backgroundColor = ContextCompat.getColor(context, R.color.toast_color); + return new Toast.Builder(context) + .withText(message) + .withAnchoredView(view) + .withBackgroundColor(backgroundColor) + .withTextAppearance(R.style.TextAppearance_TextSmall_Primary) + .buildAndShow(); + } + /** * Called by AppMenu to report that the App Menu visibility has changed. + * * @param isVisible Whether the App Menu is showing. */ void onMenuVisibilityChanged(boolean isVisible) { @@ -462,7 +489,8 @@ mTestOptionsItemSelectedListener = onOptionsItemSelectedListener; } - AppMenuPropertiesDelegate getDelegateForTests() { + @Override + public AppMenuPropertiesDelegate getMenuPropertiesDelegate() { return mDelegate; }
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java index d414238..20beb11 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java
@@ -67,6 +67,8 @@ import org.chromium.ui.test.util.BlankUiTestActivity; import org.chromium.ui.test.util.NightModeTestUtils; import org.chromium.ui.test.util.RenderTestRule; +import org.chromium.ui.widget.Toast; +import org.chromium.ui.widget.ToastManager; import java.io.IOException; import java.util.ArrayList; @@ -333,35 +335,23 @@ @Test @MediumTest - public void testClickMenuItem_UsingPosition() throws TimeoutException { - showMenuAndAssert(); - - ThreadUtils.runOnUiThreadBlocking( - () -> mAppMenuHandler.getAppMenu().onItemClick(null, null, 0, 0)); - - mDelegate.itemSelectedCallbackHelper.waitForCallback(0); - Assert.assertEquals( - "Incorrect id for last selected item.", - R.id.menu_item_one, - mDelegate.lastSelectedItemId); - } - - @Test - @MediumTest public void testLongClickMenuItem_Title() throws TimeoutException { mPropertiesDelegate.enableAppIconRow = true; showMenuAndAssert(); - AppMenu spiedMenu = Mockito.spy(mAppMenuHandler.getAppMenu()); + + ToastManager toastManager = Mockito.mock(ToastManager.class); + ToastManager.setInstanceForTesting(toastManager); View testView = new View(sActivity); ThreadUtils.runOnUiThreadBlocking( () -> { - spiedMenu.onItemLongClick( - mAppMenuHandler.getAppMenu().getMenuItemPropertyModel(R.id.icon_one), - testView); + AppMenuTestSupport.callOnItemLongClick( + mAppMenuCoordinator, R.id.icon_one, testView); }); - Mockito.verify(spiedMenu, Mockito.times(1)).showToastForItem("Icon One", testView); + ArgumentCaptor<Toast> toastCaptor = ArgumentCaptor.forClass(Toast.class); + Mockito.verify(toastManager, Mockito.times(1)).requestShow(toastCaptor.capture()); + Assert.assertEquals("Icon One", toastCaptor.getValue().getText()); } @Test @@ -369,17 +359,20 @@ public void testLongClickMenuItem_TitleCondensed() throws TimeoutException { mPropertiesDelegate.enableAppIconRow = true; showMenuAndAssert(); - AppMenu spiedMenu = Mockito.spy(mAppMenuHandler.getAppMenu()); + + ToastManager toastManager = Mockito.mock(ToastManager.class); + ToastManager.setInstanceForTesting(toastManager); View testView = new View(sActivity); ThreadUtils.runOnUiThreadBlocking( () -> { - spiedMenu.onItemLongClick( - mAppMenuHandler.getAppMenu().getMenuItemPropertyModel(R.id.icon_two), - testView); + AppMenuTestSupport.callOnItemLongClick( + mAppMenuCoordinator, R.id.icon_two, testView); }); - Mockito.verify(spiedMenu, Mockito.times(1)).showToastForItem("2", testView); + ArgumentCaptor<Toast> toastCaptor = ArgumentCaptor.forClass(Toast.class); + Mockito.verify(toastManager, Mockito.times(1)).requestShow(toastCaptor.capture()); + Assert.assertEquals("2", toastCaptor.getValue().getText()); } @Test @@ -387,18 +380,18 @@ public void testLongClickMenuItem_Disabled() throws TimeoutException { mPropertiesDelegate.enableAppIconRow = true; showMenuAndAssert(); - AppMenu spiedMenu = Mockito.spy(mAppMenuHandler.getAppMenu()); + + ToastManager toastManager = Mockito.mock(ToastManager.class); + ToastManager.setInstanceForTesting(toastManager); View testView = new View(sActivity); ThreadUtils.runOnUiThreadBlocking( () -> { - spiedMenu.onItemLongClick( - mAppMenuHandler.getAppMenu().getMenuItemPropertyModel(R.id.icon_three), - testView); + AppMenuTestSupport.callOnItemLongClick( + mAppMenuCoordinator, R.id.icon_three, testView); }); - Mockito.verify(spiedMenu, Mockito.times(0)) - .showToastForItem(Mockito.any(CharSequence.class), Mockito.any(View.class)); + Mockito.verify(toastManager, Mockito.times(0)).requestShow(Mockito.any(Toast.class)); } @Test @@ -594,9 +587,6 @@ .getModelListForTesting() .add(0, new MVCListAdapter.ListItem(AppMenuItemType.STANDARD, model)); }); - // ensure clicking on the newly added item doesn't break anything - ThreadUtils.runOnUiThreadBlocking( - () -> mAppMenuHandler.getAppMenu().onItemClick(null, null, 0, 0)); ThreadUtils.runOnUiThreadBlocking( () -> {
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/TestAppMenuPropertiesDelegate.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/TestAppMenuPropertiesDelegate.java index 80bbc5c..a321ceb 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/TestAppMenuPropertiesDelegate.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/TestAppMenuPropertiesDelegate.java
@@ -34,7 +34,7 @@ public void destroy() {} @Override - public ModelList getMenuItems(AppMenuHandler handler) { + public ModelList getMenuItems() { ModelList modelList = new ModelList(); modelList.add(
diff --git a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java index 4ca5c3f..392a2087 100644 --- a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java +++ b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandler.java
@@ -99,6 +99,8 @@ */ AppMenuButtonHelper createAppMenuButtonHelper(); - /** Call to cause a redraw when an item in the app menu changes. */ - void invalidateAppMenu(); + /** + * @return {@link AppMenuPropertiesDelegate} that builds the menu list. + */ + AppMenuPropertiesDelegate getMenuPropertiesDelegate(); }
diff --git a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemProperties.java b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemProperties.java index 407ab61c..6bd1d50 100644 --- a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemProperties.java +++ b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemProperties.java
@@ -93,14 +93,6 @@ public static final WritableObjectPropertyKey<ModelList> ADDITIONAL_ICONS = new WritableObjectPropertyKey<>("ADDITIONAL_ICONS"); - /** - * A generic key for non-standard data types. - * - * <p>TODO(crbug.com/40145539): Remove this super hacky key. - */ - public static final WritableObjectPropertyKey<Object> CUSTOM_ITEM_DATA = - new WritableObjectPropertyKey<>("CUSTOM_ITEM_DATA"); - public static final PropertyKey[] ALL_ICON_KEYS = new PropertyKey[] { MENU_ITEM_ID, @@ -131,7 +123,6 @@ SUPPORT_ENTER_ANIMATION, CLICK_HANDLER, MENU_ICON_AT_START, - ADDITIONAL_ICONS, - CUSTOM_ITEM_DATA + ADDITIONAL_ICONS }; }
diff --git a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuPropertiesDelegate.java b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuPropertiesDelegate.java index 1324d539..2433f725 100644 --- a/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuPropertiesDelegate.java +++ b/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuPropertiesDelegate.java
@@ -36,10 +36,9 @@ /** * Gets the menu items for app menu. * - * @param handler The {@link AppMenuHandler} associated with {@code menu}. * @return The {@link ModelList} which contains the menu items for app menu. */ - ModelList getMenuItems(AppMenuHandler handler); + ModelList getMenuItems(); /** * Gets a bundle of (optional) extra data associated with the provided MenuItem.
diff --git a/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java b/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java index 14b76bc..f25819e0 100644 --- a/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java +++ b/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java
@@ -40,7 +40,9 @@ public static void onOptionsItemSelected(AppMenuCoordinator coordinator, int itemId) { ((AppMenuCoordinatorImpl) coordinator) .getAppMenuHandlerImplForTesting() - .onOptionsItemSelected(itemId, /* triggeringMotion= */ null); + .onItemClick( + getMenuItemPropertyModel(coordinator, itemId), + /* triggeringMotion= */ null); } /** @@ -72,23 +74,41 @@ ((AppMenuCoordinatorImpl) coordinator) .getAppMenuHandlerImplForTesting() - .getAppMenu() .onItemClick(model, triggeringMotion); } /** + * Simulates a long click on the menu item matching the provided id. + * + * @param coordinator The {@link AppMenuCoordinator} associated with the app menu being tested. + * @param menuItemId The id of the menu item to click. + * @param view The view that was clicked. + */ + public static void callOnItemLongClick( + AppMenuCoordinator coordinator, int menuItemId, View view) { + PropertyModel model = + ((AppMenuCoordinatorImpl) coordinator) + .getAppMenuHandlerImplForTesting() + .getAppMenu() + .getMenuItemPropertyModel(menuItemId); + + ((AppMenuCoordinatorImpl) coordinator) + .getAppMenuHandlerImplForTesting() + .onItemLongClick(model, view); + } + + /** * Show the app menu. + * * @param coordinator The {@link AppMenuCoordinator} associated with the app menu being tested. * @param anchorView Anchor view (usually a menu button) to be used for the popup, if null is - * passed then hardware menu button anchor will be used. + * passed then hardware menu button anchor will be used. * @param startDragging Whether dragging is started. For example, if the app menu is showed by - * tapping on a button, this should be false. If it is showed by start - * dragging down on the menu button, this should be true. Note that if - * anchorView is null, this must be false since we no longer support - * hardware menu button dragging. - * @return True, if the menu is shown, false, if menu is not shown, example - * reasons: the menu is not yet available to be shown, or the menu is - * already showing. + * tapping on a button, this should be false. If it is showed by start dragging down on the + * menu button, this should be true. Note that if anchorView is null, this must be false + * since we no longer support hardware menu button dragging. + * @return True, if the menu is shown, false, if menu is not shown, example reasons: the menu is + * not yet available to be shown, or the menu is already showing. */ public static boolean showAppMenu( AppMenuCoordinator coordinator, View anchorView, boolean startDragging) { @@ -140,7 +160,7 @@ AppMenuCoordinator coordinator) { return ((AppMenuCoordinatorImpl) coordinator) .getAppMenuHandlerImplForTesting() - .getDelegateForTests(); + .getMenuPropertiesDelegate(); } /**
diff --git a/chrome/browser/ui/android/multiwindow/BUILD.gn b/chrome/browser/ui/android/multiwindow/BUILD.gn index 1b4129a..09aa199 100644 --- a/chrome/browser/ui/android/multiwindow/BUILD.gn +++ b/chrome/browser/ui/android/multiwindow/BUILD.gn
@@ -35,6 +35,7 @@ "//chrome/browser/util:java", "//components/browser_ui/modaldialog/android:java", "//components/browser_ui/styles/android:java", + "//components/browser_ui/util/android:java", "//components/browser_ui/widget/android:java", "//components/favicon/android:java", "//components/feature_engagement/public:public_java", @@ -65,6 +66,7 @@ "java/res/layout/instance_switcher_dialog.xml", "java/res/layout/instance_switcher_dialog_v2.xml", "java/res/layout/instance_switcher_item.xml", + "java/res/layout/instance_switcher_item_v2.xml", "java/res/layout/instance_switcher_list.xml", "java/res/layout/target_selector_dialog.xml", "java/res/values/colors.xml", @@ -94,6 +96,7 @@ "//base:base_java", "//base:base_java_test_support", "//base:base_junit_test_support", + "//chrome/browser/flags:java", "//chrome/browser/profiles/android:java", "//chrome/browser/util:java", "//components/favicon/android:java",
diff --git a/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_item_v2.xml b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_item_v2.xml new file mode 100644 index 0000000..b09773e --- /dev/null +++ b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_item_v2.xml
@@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- +Copyright 2021 The Chromium Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/content" + style="@style/ListItemContainer" > + + <org.chromium.ui.widget.ChromeImageView + android:id="@+id/favicon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_alignParentStart="true" + android:layout_marginStart="24dp" + android:layout_marginEnd="16dp" + android:layout_marginVertical="22dp" + android:scaleType="fitCenter" + android:layout_gravity="center_vertical" + android:importantForAccessibility="no" /> + + <LinearLayout + android:id="@+id/text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toEndOf="@id/favicon" + android:layout_toStartOf="@id/last_accessed" + android:layout_centerVertical="true" + android:orientation="vertical" + android:layout_marginEnd="16dp" > + + <org.chromium.ui.widget.TextViewWithLeading + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:maxLines="1" + android:ellipsize="end" + android:textAppearance="@style/TextAppearance.TextLarge.Primary" + android:layout_gravity="center_vertical" + app:leading="@dimen/text_size_large_leading" /> + <TextView + android:id="@+id/desc" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.TextMedium.Secondary" + android:layout_gravity="center_vertical" + app:leading="@dimen/text_size_medium_leading" /> + </LinearLayout> + + <TextView + android:id="@+id/last_accessed" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.TextMedium.Secondary" + android:layout_centerVertical="true" + android:layout_toStartOf="@id/close_button" + android:layout_marginEnd="16dp" + android:gravity="end" + app:leading="@dimen/text_size_medium_leading" /> + + <ImageView + android:id="@+id/close_button" + android:src="@drawable/material_ic_close_24dp" + android:contentDescription="@string/close" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_marginHorizontal="16dp" + android:scaleType="fitCenter"/> +</RelativeLayout>
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java index 6a50f8d7..d4ebae23 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
@@ -32,9 +32,9 @@ import org.chromium.build.annotations.MonotonicNonNull; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; +import org.chromium.components.browser_ui.util.TimeTextResolver; import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils; import org.chromium.components.favicon.LargeIconBridge; import org.chromium.ui.listmenu.BasicListMenu; @@ -56,8 +56,8 @@ import java.util.List; /** - * Coordinator to construct the instance switcher dialog. - * TODO: Resolve various inconsistencies that can be caused by Ui from multiple instances. + * Coordinator to construct the instance switcher dialog. TODO: Resolve various inconsistencies that + * can be caused by Ui from multiple instances. */ @NullMarked public class InstanceSwitcherCoordinator { @@ -143,9 +143,9 @@ mNewWindowAction = newWindowAction; mMaxInstanceCount = maxInstanceCount; - if (isInstanceSwitcherV2Enabled()) { - var activeListAdapter = getInstanceListAdapter(/* active= */ true); - var inactiveListAdapter = getInstanceListAdapter(/* active= */ false); + if (UiUtils.isInstanceSwitcherV2Enabled()) { + var activeListAdapter = getInstanceListV2Adapter(/* active= */ true); + var inactiveListAdapter = getInstanceListV2Adapter(/* active= */ false); mDialogView = LayoutInflater.from(context) @@ -213,13 +213,13 @@ } } - private SimpleRecyclerViewAdapter getInstanceListAdapter(boolean active) { + private SimpleRecyclerViewAdapter getInstanceListV2Adapter(boolean active) { var adapter = new SimpleRecyclerViewAdapter(active ? mActiveModelList : mInactiveModelList); adapter.registerType( EntryType.INSTANCE, parentView -> LayoutInflater.from(mContext) - .inflate(R.layout.instance_switcher_item, null), + .inflate(R.layout.instance_switcher_item_v2, null), InstanceSwitcherItemViewBinder::bind); adapter.registerType( EntryType.COMMAND, @@ -237,7 +237,7 @@ // An active instance should have an associated live task. boolean isActiveInstance = items.get(i).taskId != INVALID_TASK_ID; PropertyModel itemModel = generateListItem(items.get(i)); - if (isInstanceSwitcherV2Enabled()) { + if (UiUtils.isInstanceSwitcherV2Enabled()) { if (isActiveInstance) { mActiveModelList.add( new ModelListAdapter.ListItem(EntryType.INSTANCE, itemModel)); @@ -296,17 +296,32 @@ private PropertyModel generateListItem(InstanceInfo item) { String title = mUiUtils.getItemTitle(item); String desc = mUiUtils.getItemDesc(item); - boolean currentIndicator = item.type == InstanceInfo.Type.CURRENT; + boolean isCurrentWindow = item.type == InstanceInfo.Type.CURRENT; PropertyModel.Builder builder = new PropertyModel.Builder(InstanceSwitcherItemProperties.ALL_KEYS) .with(InstanceSwitcherItemProperties.TITLE, title) .with(InstanceSwitcherItemProperties.DESC, desc) - .with(InstanceSwitcherItemProperties.CURRENT, currentIndicator) .with(InstanceSwitcherItemProperties.INSTANCE_ID, item.instanceId) .with( InstanceSwitcherItemProperties.CLICK_LISTENER, (view) -> switchToInstance(item)); - if (!currentIndicator) buildMoreMenu(builder, item); + + if (!UiUtils.isInstanceSwitcherV2Enabled()) { + builder.with(InstanceSwitcherItemProperties.CURRENT, isCurrentWindow); + if (!isCurrentWindow) { + buildMoreMenu(builder, item); + } + } else { + String lastAccessedString = + isCurrentWindow + ? mContext.getString(R.string.instance_last_accessed_current) + : TimeTextResolver.resolveTimeAgoText( + mContext.getResources(), item.lastAccessedTime); + builder.with(InstanceSwitcherItemProperties.LAST_ACCESSED, lastAccessedString) + .with( + InstanceSwitcherItemProperties.CLOSE_BUTTON_CLICK_LISTENER, + v -> closeWindow(item)); + } PropertyModel model = builder.build(); mUiUtils.setFavicon(model, InstanceSwitcherItemProperties.FAVICON, item); return model; @@ -335,11 +350,7 @@ (model) -> { int textId = model.get(ListMenuItemProperties.TITLE_ID); if (textId == R.string.instance_switcher_close_window) { - if (canSkipConfirm(item)) { - removeInstance(item); - } else { - showConfirmationMessage(item); - } + closeWindow(item); } }; BasicListMenu listMenu = @@ -351,6 +362,14 @@ builder.with(InstanceSwitcherItemProperties.MORE_MENU, () -> listMenu); } + private void closeWindow(InstanceInfo item) { + if (canSkipConfirm(item)) { + removeInstance(item); + } else { + showConfirmationMessage(item); + } + } + private void switchToInstance(InstanceInfo item) { if (item.type == InstanceInfo.Type.CURRENT) { Toast.makeText( @@ -381,7 +400,7 @@ * </ul> */ private void updateCommandUiState() { - if (!isInstanceSwitcherV2Enabled()) return; + if (!UiUtils.isInstanceSwitcherV2Enabled()) return; int numActiveInstances = mActiveModelList.size(); int numInactiveInstances = mInactiveModelList.size(); if (mNewWindowEnabled) { @@ -427,7 +446,7 @@ } private int getTotalInstanceCount() { - if (!isInstanceSwitcherV2Enabled()) { + if (!UiUtils.isInstanceSwitcherV2Enabled()) { // Exclude COMMAND item from list size. return mModelList.size() - 1; } @@ -443,7 +462,7 @@ private void removeInstance(InstanceInfo item) { int instanceId = item.instanceId; - if (isInstanceSwitcherV2Enabled()) { + if (UiUtils.isInstanceSwitcherV2Enabled()) { removeItemFromModelList( instanceId, item.taskId == INVALID_TASK_ID ? mInactiveModelList : mActiveModelList); @@ -529,8 +548,4 @@ inactiveTab.setText( mContext.getString(R.string.instance_switcher_tabs_inactive, numInactiveInstances)); } - - private static boolean isInstanceSwitcherV2Enabled() { - return ChromeFeatureList.isEnabled(ChromeFeatureList.INSTANCE_SWITCHER_V2); - } }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java index acc6fec..9001c3b 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
@@ -14,6 +14,7 @@ import static androidx.test.espresso.matcher.RootMatchers.withDecorView; import static androidx.test.espresso.matcher.ViewMatchers.Visibility.GONE; import static androidx.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withClassName; @@ -28,10 +29,13 @@ import android.view.View; +import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; +import androidx.test.espresso.matcher.BoundedMatcher; import androidx.test.filters.SmallTest; +import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -42,6 +46,7 @@ import org.chromium.base.Callback; import org.chromium.base.ThreadUtils; +import org.chromium.base.TimeUtils; import org.chromium.base.test.BaseActivityTestRule; import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CommandLineFlags; @@ -373,7 +378,6 @@ final CallbackHelper closeCallbackHelper = new CallbackHelper(); Callback<InstanceInfo> closeCallback = (item) -> closeCallbackHelper.notifyCalled(); - InstanceSwitcherCoordinator.setSkipCloseConfirmation(); ThreadUtils.runOnUiThreadBlocking( () -> { @@ -507,6 +511,57 @@ }); } + @Test + @SmallTest + @EnableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2) + public void testLastAccessedStringsForInstances() { + final String expectedCurrentString = "Current window"; + final String expectedOtherString = "2 days ago"; + + InstanceInfo[] instances = + createPersistedInstances( + /* numActiveInstances= */ 3, /* numInactiveInstances= */ 0); + + ThreadUtils.runOnUiThreadBlocking( + () -> { + InstanceSwitcherCoordinator.showDialog( + mActivityTestRule.getActivity(), + mModalDialogManager, + mIconBridge, + null, + null, + null, + MAX_INSTANCE_COUNT, + Arrays.asList(instances)); + }); + + // Verify that the "Current window" string is at position 0. + onView(withId(R.id.active_instance_list)) + .inRoot(isDialog()) + .check( + matches( + atPosition( + 0, + hasDescendant( + allOf( + withId(R.id.last_accessed), + withText(expectedCurrentString), + isDisplayed()))))); + + // Verify that the "2 days ago" string is at position 2. + onView(withId(R.id.active_instance_list)) + .inRoot(isDialog()) + .check( + matches( + atPosition( + 2, + hasDescendant( + allOf( + withId(R.id.last_accessed), + withText(expectedOtherString), + isDisplayed()))))); + } + private InstanceInfo[] createPersistedInstances( int numActiveInstances, int numInactiveInstances) { int totalInstances = numActiveInstances + numInactiveInstances; @@ -530,7 +585,7 @@ 1, 0, false, - 0); + getDaysAgoMillis(i)); } // Create inactive instances. @@ -543,6 +598,11 @@ return instances; } + /* Returns the millisecond timestamp for a given number of days in the past. */ + private long getDaysAgoMillis(int lastAccessedDaysAgo) { + return TimeUtils.currentTimeMillis() - lastAccessedDaysAgo * 24L * 60L * 60L * 1000L; + } + /* For use in instance switcher v1. */ private void closeInstanceAt(int position, CallbackHelper closeCallbackHelper) throws TimeoutException { @@ -576,18 +636,40 @@ @Override public String getDescription() { - return "Click on the more menu."; + return "Click on the close button."; } @Override public void perform(UiController uiController, View view) { - View v = view.findViewById(R.id.more); + View v = view.findViewById(R.id.close_button); v.performClick(); } })); - onView(withText(R.string.instance_switcher_close_window)) - .inRoot(withDecorView(withClassName(containsString("Popup")))) + onView(withText(R.string.close)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) .perform(click()); closeCallbackHelper.waitForCallback(closeCallbackCount); } + + private static Matcher<View> atPosition(final int position, final Matcher<View> itemMatcher) { + return new BoundedMatcher<>(RecyclerView.class) { + @Override + public void describeTo(Description description) { + description.appendText("has item at position " + position + ": "); + itemMatcher.describeTo(description); + } + + @Override + protected boolean matchesSafely(final RecyclerView view) { + RecyclerView.ViewHolder viewHolder = + view.findViewHolderForAdapterPosition(position); + if (viewHolder == null) { + // Has no item at this position. + return false; + } + return itemMatcher.matches(viewHolder.itemView); + } + }; + } }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemProperties.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemProperties.java index a28dd24..0c99ddd0 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemProperties.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemProperties.java
@@ -39,6 +39,12 @@ public static final PropertyModel.WritableObjectPropertyKey<ListMenuDelegate> MORE_MENU = new PropertyModel.WritableObjectPropertyKey<>(); + public static final PropertyModel.WritableObjectPropertyKey<String> LAST_ACCESSED = + new PropertyModel.WritableObjectPropertyKey<>(); + + public static final PropertyModel.WritableObjectPropertyKey<View.OnClickListener> + CLOSE_BUTTON_CLICK_LISTENER = new PropertyModel.WritableObjectPropertyKey<>(); + public static final PropertyKey[] ALL_KEYS = new PropertyKey[] { CURRENT, @@ -48,6 +54,8 @@ DESC, INSTANCE_ID, CLICK_LISTENER, - MORE_MENU + MORE_MENU, + LAST_ACCESSED, + CLOSE_BUTTON_CLICK_LISTENER }; }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemViewBinder.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemViewBinder.java index cc2dcf4..7d71009 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemViewBinder.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherItemViewBinder.java
@@ -46,7 +46,6 @@ view.findViewById(R.id.current).setVisibility(current ? View.VISIBLE : View.INVISIBLE); // Do not show 3-dot submenu for the current instance. view.findViewById(R.id.more).setVisibility(current ? View.INVISIBLE : View.VISIBLE); - } else if (InstanceSwitcherItemProperties.CLICK_LISTENER == propertyKey) { view.setOnClickListener(model.get(InstanceSwitcherItemProperties.CLICK_LISTENER)); @@ -62,6 +61,15 @@ View maxInfo = view.findViewById(R.id.max_info); maxInfo.setVisibility(enabled ? View.GONE : View.VISIBLE); } + + } else if (InstanceSwitcherItemProperties.LAST_ACCESSED == propertyKey) { + TextView lastAccessedView = view.findViewById(R.id.last_accessed); + String text = model.get(InstanceSwitcherItemProperties.LAST_ACCESSED); + lastAccessedView.setText(text); + } else if (InstanceSwitcherItemProperties.CLOSE_BUTTON_CLICK_LISTENER == propertyKey) { + ImageView closeButton = view.findViewById(R.id.close_button); + closeButton.setOnClickListener( + model.get(InstanceSwitcherItemProperties.CLOSE_BUTTON_CLICK_LISTENER)); } } }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinatorTest.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinatorTest.java index 08a1695..0b20dc0 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinatorTest.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinatorTest.java
@@ -24,6 +24,8 @@ import org.chromium.base.test.BaseActivityTestRule; import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; @@ -72,6 +74,7 @@ @Test @SmallTest + @DisableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2) public void testTargetSelectorCoordinatorTest_moveWindow() throws Exception { InstanceInfo[] instances = new InstanceInfo[] {
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java index fb26066..8cf273a 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java
@@ -16,6 +16,7 @@ import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.ui.favicon.FaviconUtils; import org.chromium.components.browser_ui.widget.RoundedIconGenerator; import org.chromium.components.favicon.LargeIconBridge; @@ -84,9 +85,9 @@ int totalTabCount = totalTabCount(item); String desc; Resources res = mContext.getResources(); - if (item.type == InstanceInfo.Type.CURRENT) { + if (item.type == InstanceInfo.Type.CURRENT && !isInstanceSwitcherV2Enabled()) { desc = res.getString(R.string.instance_switcher_current_window); - } else if (item.type == InstanceInfo.Type.ADJACENT) { + } else if (item.type == InstanceInfo.Type.ADJACENT && !isInstanceSwitcherV2Enabled()) { desc = res.getString(R.string.instance_switcher_adjacent_window); } else if (totalTabCount == 0) { // <ex>No tabs</ex> desc = res.getString(R.string.instance_switcher_tab_count_zero); @@ -168,6 +169,7 @@ /** * Set the favicon for the given instance. + * * @param model {@link PropertyModel} that represents the instance entry. * @param faviconKey Property key for favicon item in the model. * @param item {@link InstanceInfo} object for the given instance. @@ -229,4 +231,13 @@ DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE); } } + + /** + * Checks whether the Instance Switcher V2 feature is enabled. + * + * @return {@code true} if the Instance Switcher V2 feature is enabled, {@code false} otherwise. + */ + public static boolean isInstanceSwitcherV2Enabled() { + return ChromeFeatureList.isEnabled(ChromeFeatureList.INSTANCE_SWITCHER_V2); + } }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtilsUnitTest.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtilsUnitTest.java index d8d328c..407f8b4 100644 --- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtilsUnitTest.java +++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtilsUnitTest.java
@@ -17,6 +17,9 @@ import androidx.annotation.DrawableRes; +import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.base.test.util.Features.EnableFeatures; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,6 +41,7 @@ // Description private static final String NO_TABS = "No tabs"; + private static final String TWO_TABS_ONE_INCOGNITO = "2 tabs, 1 incognito"; private static final String CURRENT = "Current"; private static final String OPEN = "Window is open"; @@ -58,7 +62,14 @@ .getString(R.string.instance_switcher_entry_empty_window); doReturn(INCOGNITO).when(mResources).getString(R.string.notification_incognito_tab); doReturn(NO_TABS).when(mResources).getString(R.string.instance_switcher_tab_count_zero); - + doReturn(TWO_TABS_ONE_INCOGNITO) + .when(mResources) + .getQuantityString( + R.plurals.instance_switcher_desc_mixed, + 2, + 1, + 2, + 1); doReturn(CURRENT).when(mResources).getString(R.string.instance_switcher_current_window); doReturn(OPEN).when(mResources).getString(R.string.instance_switcher_adjacent_window); mUiUtils = @@ -107,7 +118,108 @@ } @Test - public void testItemDescription() { + @EnableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2) + public void testItemDescriptionWithInstanceSwitcherV2() { + // Empty window -> No tabs + assertEquals( + "Instance with no tabs has a wrong description", + NO_TABS, + mUiUtils.getItemDesc(mockInstance(57, 0, 0, false))); + + // Current instance -> 2 tabs, 1 incognito + assertEquals( + "Current instance has a wrong description", + TWO_TABS_ONE_INCOGNITO, + mUiUtils.getItemDesc(mockInstance(InstanceInfo.Type.CURRENT))); + + // Other visible instance -> 2 tabs, 1 incognito + assertEquals( + "Visible instance has a wrong description", + TWO_TABS_ONE_INCOGNITO, + mUiUtils.getItemDesc(mockInstance(InstanceInfo.Type.ADJACENT))); + + // Normal tabs only -> # of tabs + int normalTabCount = 3; + int incognitoTabCount = 0; + int totalTabCount = 3; + InstanceInfo item = mockInstance(57, normalTabCount, incognitoTabCount, false); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_tab_count_nonzero, + item.tabCount, + item.tabCount); + clearInvocations(mResources); + + // Mixed tabs -> # tabs, # incognito + normalTabCount = 3; + incognitoTabCount = 2; + totalTabCount = 5; + item = mockInstance(57, normalTabCount, incognitoTabCount, false); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_desc_mixed, + totalTabCount, + incognitoTabCount, + totalTabCount, + incognitoTabCount); + clearInvocations(mResources); + + // Incognito-selected, incognito tab only -> # incognito tabs + incognitoTabCount = 4; + item = mockInstance(57, 0, incognitoTabCount, true); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_desc_incognito, + incognitoTabCount, + incognitoTabCount); + clearInvocations(mResources); + + // Incognito-selected, mixed tabs -> # tabs, # incognito + normalTabCount = 7; + incognitoTabCount = 13; + totalTabCount = 7 + 13; + item = mockInstance(57, normalTabCount, incognitoTabCount, true); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_desc_mixed, + totalTabCount, + incognitoTabCount, + totalTabCount, + incognitoTabCount); + clearInvocations(mResources); + + // Disregard incognito tab count for a killed task -> # tabs + normalTabCount = 3; + incognitoTabCount = 2; + item = mockInstance(UiUtils.INVALID_TASK_ID, normalTabCount, incognitoTabCount, false); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_tab_count_nonzero, + normalTabCount, + normalTabCount); + + clearInvocations(mResources); + + // Incognito-selected, mixed tabs, killed task -> # tabs + normalTabCount = 2; + incognitoTabCount = 2; + item = mockInstance(UiUtils.INVALID_TASK_ID, normalTabCount, incognitoTabCount, true); + mUiUtils.getItemDesc(item); + verify(mResources) + .getQuantityString( + R.plurals.instance_switcher_tab_count_nonzero, + normalTabCount, + normalTabCount); + } + + @Test + @DisableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2) + public void testItemDescriptionWithoutInstanceSwitcherV2() { // Empty window -> No tabs assertEquals( "Instance with no tabs has a wrong description",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd index 89be366d..7aafe2f 100644 --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5821,6 +5821,9 @@ =1 {<ph name="INCOGNITO_TAB_COUNT">%1$s<ex>2</ex></ph> incognito and <ph name="TAB_COUNT_ONE">%2$s<ex>1</ex></ph> more tab will be closed} other {<ph name="INCOGNITO_TAB_COUNT">%1$s<ex>4</ex></ph> incognito and <ph name="TAB_COUNT_MANY">%2$s<ex>4</ex></ph> more tabs will be closed}} </message> + <message name="IDS_INSTANCE_LAST_ACCESSED_CURRENT" desc="The current window indicator text."> + Current window + </message> <!-- Multi-instance message strings --> <message name="IDS_MULTI_INSTANCE_RESTORATION_MESSAGE_TITLE" desc="Title of the message shown when the number of persisted instances exceeds the max instance count after an instance limit downgrade.">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_LAST_ACCESSED_CURRENT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_LAST_ACCESSED_CURRENT.png.sha1 new file mode 100644 index 0000000..ee5a7a4 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_LAST_ACCESSED_CURRENT.png.sha1
@@ -0,0 +1 @@ +b3daa96cffc5182fbc74d859d12970f3874d09f3 \ No newline at end of file
diff --git a/chrome/browser/ui/app_icon_loader.cc b/chrome/browser/ui/app_icon_loader.cc index b8b85ab5..965f4f9 100644 --- a/chrome/browser/ui/app_icon_loader.cc +++ b/chrome/browser/ui/app_icon_loader.cc
@@ -6,11 +6,8 @@ AppIconLoader::AppIconLoader() = default; -AppIconLoader::AppIconLoader(Profile* profile, - int icon_size_in_dip, +AppIconLoader::AppIconLoader(int icon_size_in_dip, AppIconLoaderDelegate* delegate) - : profile_(profile), - icon_size_in_dip_(icon_size_in_dip), - delegate_(delegate) {} + : icon_size_in_dip_(icon_size_in_dip), delegate_(delegate) {} AppIconLoader::~AppIconLoader() = default;
diff --git a/chrome/browser/ui/app_icon_loader.h b/chrome/browser/ui/app_icon_loader.h index 4f4c727d..7d4fbef 100644 --- a/chrome/browser/ui/app_icon_loader.h +++ b/chrome/browser/ui/app_icon_loader.h
@@ -10,8 +10,6 @@ #include "base/memory/raw_ptr.h" #include "chrome/browser/ui/app_icon_loader_delegate.h" -class Profile; - // Base class that loads and updates Chrome app's icons. // TODO(khmel): Switch to using ChromeAppIconService instead ChromeAppIconLoader // and ArcAppIconLoader. @@ -40,16 +38,12 @@ protected: AppIconLoader(); - AppIconLoader(Profile* profile, - int icon_size_in_dip, - AppIconLoaderDelegate* delegate); + AppIconLoader(int icon_size_in_dip, AppIconLoaderDelegate* delegate); - Profile* profile() { return profile_; } int icon_size_in_dip() const { return icon_size_in_dip_; } AppIconLoaderDelegate* delegate() { return delegate_; } private: - const raw_ptr<Profile, DanglingUntriaged> profile_ = nullptr; const int icon_size_in_dip_ = 0; // The delegate object which receives the icon images. No ownership.
diff --git a/chrome/browser/ui/ash/quick_answers/BUILD.gn b/chrome/browser/ui/ash/quick_answers/BUILD.gn index 520bf74..606acc62 100644 --- a/chrome/browser/ui/ash/quick_answers/BUILD.gn +++ b/chrome/browser/ui/ash/quick_answers/BUILD.gn
@@ -12,6 +12,10 @@ "quick_answers_state_ash.h", "quick_answers_ui_controller.cc", "quick_answers_ui_controller.h", + "ui/magic_boost_header.cc", + "ui/magic_boost_header.h", + "ui/magic_boost_user_consent_view.cc", + "ui/magic_boost_user_consent_view.h", "ui/quick_answers_text_label.cc", "ui/quick_answers_text_label.h", "ui/quick_answers_util.cc",
diff --git a/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc b/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc index 733e6fb7..aa84946 100644 --- a/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc +++ b/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc
@@ -20,6 +20,7 @@ #include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h" #include "chromeos/components/quick_answers/quick_answers_client.h" #include "chromeos/components/quick_answers/quick_answers_model.h" +#include "chromeos/constants/chromeos_features.h" #include "chromeos/strings/grit/chromeos_strings.h" #include "components/prefs/pref_service.h" #include "components/user_manager/user_manager.h" @@ -87,23 +88,22 @@ return true; } - // If feature type is `kQuickAnswers`, return `true` for the case `kUnknown` - // to show a consent UI. if (QuickAnswersState::GetFeatureType() == - QuickAnswersState::FeatureType::kQuickAnswers) { - base::expected<quick_answers::prefs::ConsentStatus, - QuickAnswersState::Error> - maybe_consent_status = QuickAnswersState::GetConsentStatus(); - if (!maybe_consent_status.has_value()) { - return false; - } - - if (maybe_consent_status.value() == - quick_answers::prefs::ConsentStatus::kUnknown) { - return true; - } + QuickAnswersState::FeatureType::kHmr && + !chromeos::features::IsMagicBoostRevampForQuickAnswersEnabled()) { + return false; } + base::expected<quick_answers::prefs::ConsentStatus, QuickAnswersState::Error> + maybe_consent_status = QuickAnswersState::GetConsentStatus(); + if (!maybe_consent_status.has_value()) { + return false; + } + + if (maybe_consent_status.value() == + quick_answers::prefs::ConsentStatus::kUnknown) { + return true; + } return false; } @@ -575,10 +575,9 @@ bool QuickAnswersControllerImpl::MaybeShowUserConsent( IntentType intent_type, const std::u16string& intent_text) { - // For non-QuickAnswers case (i.e., HMR), user consent is handled outside of - // QuickAnswers code. - if (QuickAnswersState::GetFeatureType() != - QuickAnswersState::FeatureType::kQuickAnswers) { + if (QuickAnswersState::GetFeatureType() == + QuickAnswersState::FeatureType::kHmr && + !chromeos::features::IsMagicBoostRevampForQuickAnswersEnabled()) { return false; }
diff --git a/chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.cc b/chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.cc index cefe4f5..c0bba2c 100644 --- a/chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.cc +++ b/chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.cc
@@ -18,6 +18,7 @@ #include "base/strings/stringprintf.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.h" +#include "chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.h" #include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h" #include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_view.h" #include "chrome/browser/ui/ash/quick_answers/ui/rich_answers_definition_view.h" @@ -280,23 +281,36 @@ CHECK_EQ(controller_->GetQuickAnswersVisibility(), QuickAnswersVisibility::kPending); - auto* view = GetReadWriteCardsUiController().SetQuickAnswersUi( - views::Builder<quick_answers::UserConsentView>( - std::make_unique<quick_answers::UserConsentView>( - use_refreshed_design, GetReadWriteCardsUiController())) - .SetIntentType(intent_type) - .SetIntentText(intent_text) - // It is safe to do `base::Unretained(this)`. UIs are destructed - // before a UI controller gets destructed. See - // `~QuickAnswersUiController`. - .SetNoThanksButtonPressed(base::BindRepeating( - &QuickAnswersUiController::OnUserConsentNoThanksPressed, - base::Unretained(this))) - .SetAllowButtonPressed(base::BindRepeating( - &QuickAnswersUiController::OnUserConsentAllowPressed, - base::Unretained(this))) - .Build()); - user_consent_view_.SetView(view); + if (chromeos::features::IsMagicBoostRevampForQuickAnswersEnabled() && + QuickAnswersState::GetFeatureType() == + QuickAnswersState::FeatureType::kHmr) { + user_consent_view_.SetView( + GetReadWriteCardsUiController().SetQuickAnswersUi( + views::Builder<quick_answers::MagicBoostUserConsentView>( + std::make_unique<quick_answers::MagicBoostUserConsentView>( + // TODO: crbug.com/414391121 - Populate the button label + // with the correct text. + intent_text, GetReadWriteCardsUiController())) + .Build())); + } else { + user_consent_view_.SetView( + GetReadWriteCardsUiController().SetQuickAnswersUi( + views::Builder<quick_answers::UserConsentView>( + std::make_unique<quick_answers::UserConsentView>( + use_refreshed_design, GetReadWriteCardsUiController())) + .SetIntentType(intent_type) + .SetIntentText(intent_text) + // It is safe to do `base::Unretained(this)`. UIs are destructed + // before a UI controller gets destructed. See + // `~QuickAnswersUiController`. + .SetNoThanksButtonPressed(base::BindRepeating( + &QuickAnswersUiController::OnUserConsentNoThanksPressed, + base::Unretained(this))) + .SetAllowButtonPressed(base::BindRepeating( + &QuickAnswersUiController::OnUserConsentAllowPressed, + base::Unretained(this))) + .Build())); + } // `ViewAccessibility::AnnounceText` requires a root view. Announce text after // a view gets attached to a widget.
diff --git a/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.cc b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.cc new file mode 100644 index 0000000..dbafd82 --- /dev/null +++ b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.cc
@@ -0,0 +1,60 @@ +// 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/ash/quick_answers/ui/magic_boost_header.h" + +#include "ash/style/typography.h" +#include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h" +#include "chromeos/components/magic_boost/public/cpp/views/experiment_badge.h" +#include "chromeos/components/quick_answers/public/cpp/constants.h" +#include "chromeos/strings/grit/chromeos_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/layout/layout_provider.h" +#include "ui/views/layout/layout_types.h" +#include "ui/views/style/typography_provider.h" +#include "ui/views/view.h" +#include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" + +namespace quick_answers { + +views::Builder<views::BoxLayoutView> GetMagicBoostHeader() { + int line_height = ash::TypographyProvider::Get()->ResolveLineHeight( + ash::TypographyToken::kCrosAnnotation1); + int vertical_padding = std::max(0, (20 - line_height) / 2); + + return views::Builder<views::BoxLayoutView>() + .SetProperty( + views::kMarginsKey, + gfx::Insets::TLBR( + 0, 0, + views::LayoutProvider::Get()->GetDistanceMetric( + views::DistanceMetric::DISTANCE_RELATED_CONTROL_VERTICAL), + GetButtonsViewOcclusion(Design::kMagicBoost))) + .SetOrientation(views::LayoutOrientation::kHorizontal) + .SetCrossAxisAlignment(views::LayoutAlignment::kCenter) + .SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred, + views::MaximumFlexSizeRule::kPreferred)) + .SetBetweenChildSpacing(views::LayoutProvider::Get()->GetDistanceMetric( + views::DistanceMetric::DISTANCE_RELATED_BUTTON_HORIZONTAL)) + .AddChild( + views::Builder<views::Label>() + .SetText(l10n_util::GetStringUTF16(IDS_ASH_MAHI_MENU_TITLE)) + .SetLineHeight(line_height) + .SetProperty(views::kMarginsKey, + gfx::Insets::VH(vertical_padding, 0)) + .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT) + .SetFontList(ash::TypographyProvider::Get() + ->ResolveTypographyToken( + ash::TypographyToken::kCrosAnnotation1) + .DeriveWithWeight(gfx::Font::Weight::MEDIUM))) + .AddChild(views::Builder<chromeos::ExperimentBadge>()); +} + +} // namespace quick_answers
diff --git a/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.h b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.h new file mode 100644 index 0000000..92cab710 --- /dev/null +++ b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.h
@@ -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. + +#ifndef CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_HEADER_H_ +#define CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_HEADER_H_ + +#include "ui/views/layout/box_layout_view.h" + +namespace quick_answers { + +views::Builder<views::BoxLayoutView> GetMagicBoostHeader(); + +} + +#endif // CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_HEADER_H_
diff --git a/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.cc b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.cc new file mode 100644 index 0000000..c58db4ec --- /dev/null +++ b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.cc
@@ -0,0 +1,194 @@ +// 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/ash/quick_answers/ui/magic_boost_user_consent_view.h" + +#include <memory> +#include <string> + +#include "base/functional/bind.h" +#include "base/notreached.h" +#include "chrome/browser/ui/ash/editor_menu/utils/pre_target_handler.h" +#include "chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.h" +#include "chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.h" +#include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h" +#include "chrome/browser/ui/ash/quick_answers/ui/typography.h" +#include "chrome/browser/ui/ash/read_write_cards/read_write_cards_ui_controller.h" +#include "chrome/browser/ui/ash/read_write_cards/read_write_cards_view.h" +#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h" +#include "chromeos/strings/grit/chromeos_strings.h" +#include "chromeos/ui/vector_icons/vector_icons.h" +#include "components/vector_icons/vector_icons.h" +#include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/base/models/image_model.h" +#include "ui/base/ui_base_types.h" +#include "ui/color/color_id.h" +#include "ui/events/event_handler.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/gfx/text_constants.h" +#include "ui/views/accessibility/view_accessibility.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/menu/menu_config.h" +#include "ui/views/controls/menu/menu_controller.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/layout/flex_layout_types.h" +#include "ui/views/layout/flex_layout_view.h" +#include "ui/views/layout/layout_provider.h" +#include "ui/views/layout/layout_types.h" +#include "ui/views/metadata/view_factory.h" +#include "ui/views/metadata/view_factory_internal.h" +#include "ui/views/style/typography.h" +#include "ui/views/view_class_properties.h" +#include "ui/views/widget/tooltip_manager.h" +#include "ui/views/widget/widget.h" +#include "ui/wm/core/coordinate_conversion.h" + +namespace quick_answers { + +namespace { + +// Main view (or common) specs. +constexpr auto kContentViewInsets = gfx::Insets::TLBR(0, 8, 0, 0); +constexpr auto kButtonPadding = gfx::Insets::VH(6, 8); +constexpr auto kSettingsViewInsets = gfx::Insets::TLBR(12, 16, 16, 16); +constexpr auto kChipMargin = gfx::Insets::TLBR(0, 0, 0, 10); + +constexpr int kButtonBorderThickness = 1; +constexpr int kButtonCornerRadius = 8; +constexpr int kSettingsButtonSizeDip = 14; +constexpr int kSettingsButtonBorderDip = 3; + +// TODO: crbug.com/414391121 - Updates the accessibility name and description. +constexpr char kConsentViewAccessibilityName[] = "Magic Boost"; +constexpr char kConsentViewAccessibilityDescription[] = "Magic Boost"; + +// Icon. +constexpr gfx::Insets kIntentIconInsets = gfx::Insets(8); + +} // namespace + +// MagicBoostUserConsentView +// ------------------------------------------------------------- + +MagicBoostUserConsentView::MagicBoostUserConsentView( + const std::u16string& chip_label, + chromeos::ReadWriteCardsUiController& read_write_cards_ui_controller) + : chromeos::ReadWriteCardsView(read_write_cards_ui_controller), + focus_search_( + this, + base::BindRepeating(&MagicBoostUserConsentView::GetFocusableViews, + base::Unretained(this))) { + SetUseDefaultFillLayout(true); + SetBackground(views::CreateSolidBackground(ui::kColorPrimaryBackground)); + + AddChildView( + views::Builder<views::FlexLayoutView>() + .SetOrientation(views::LayoutOrientation::kHorizontal) + .SetInteriorMargin(kMainViewInsets) + .SetCrossAxisAlignment(views::LayoutAlignment::kStart) + .AddChild(views::Builder<views::ImageView>().SetImage( + ui::ImageModel::FromVectorIcon(chromeos::kInfoSparkIcon, + ui::ColorIds::kColorSysOnSurface, + kGoogleIconSizeDip))) + .AddChild( + views::Builder<views::FlexLayoutView>() + .SetInteriorMargin(kContentViewInsets) + .SetProperty(views::kFlexBehaviorKey, + views::FlexSpecification( + views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kPreferred, + /*adjust_height_for_width=*/true)) + .SetOrientation(views::LayoutOrientation::kVertical) + .AddChild(GetMagicBoostHeader()) + .AddChild( + views::Builder<views::FlexLayoutView>() + .SetInteriorMargin(kIntentIconInsets) + .AddChild( + views::Builder<views::LabelButton>() + .CopyAddressTo(&intent_chip_) + .SetText(chip_label) + .SetProperty(views::kMarginsKey, kChipMargin) + .SetLabelStyle( + views::style::STYLE_BODY_4_EMPHASIS) + .SetTextColor(views::LabelButton:: + ButtonState::STATE_NORMAL, + ui::kColorSysOnSurface) + .SetTextColor(views::LabelButton:: + ButtonState::STATE_DISABLED, + ui::kColorSysStateDisabled) + .SetImageLabelSpacing(4) + .SetBorder(views::CreatePaddedBorder( + views::CreateRoundedRectBorder( + kButtonBorderThickness, + kButtonCornerRadius, + ui::kColorSysTonalOutline), + kButtonPadding)) + .SetEnabled(true)))) + .Build()); + + AddChildView( + views::Builder<views::BoxLayoutView>() + .SetOrientation(views::LayoutOrientation::kHorizontal) + .SetMainAxisAlignment(views::LayoutAlignment::kEnd) + .SetCrossAxisAlignment(views::LayoutAlignment::kStart) + .SetInsideBorderInsets(kSettingsViewInsets) + .AddChild( + views::Builder<views::ImageButton>() + .SetTooltipText(l10n_util::GetStringUTF16( + IDS_RICH_ANSWERS_VIEW_SETTINGS_BUTTON_A11Y_NAME_TEXT)) + .SetImageModel( + views::Button::ButtonState::STATE_NORMAL, + ui::ImageModel::FromVectorIcon( + vector_icons::kSettingsOutlineIcon, + ui::kColorSysSecondary, kSettingsButtonSizeDip)) + .SetProperty(views::kMarginsKey, + gfx::Insets(kSettingsButtonBorderDip))) + .Build()); + + GetViewAccessibility().SetRole(ax::mojom::Role::kDialog); + GetViewAccessibility().SetDescription(kConsentViewAccessibilityName); + GetViewAccessibility().SetName(kConsentViewAccessibilityDescription); + + // Focus should cycle to each of the buttons the view contains and back to it. + SetFocusBehavior(FocusBehavior::ALWAYS); + set_suppress_default_focus_handling(); + views::FocusRing::Install(this); +} + +MagicBoostUserConsentView::~MagicBoostUserConsentView() = default; + +void MagicBoostUserConsentView::OnFocus() {} + +views::FocusTraversable* MagicBoostUserConsentView::GetPaneFocusTraversable() { + return &focus_search_; +} + +void MagicBoostUserConsentView::UpdateBoundsForQuickAnswers() {} + +std::u16string MagicBoostUserConsentView::chip_label_for_testing() { + return intent_chip_ == nullptr ? u"" : intent_chip_->GetText().data(); +} + +std::vector<views::View*> MagicBoostUserConsentView::GetFocusableViews() { + std::vector<views::View*> focusable_views; + // The view itself is not included in focus loop, unless screen-reader is on. + if (QuickAnswersState::Get()->spoken_feedback_enabled()) { + focusable_views.push_back(this); + } + focusable_views.push_back(intent_chip_); + return focusable_views; +} + +BEGIN_METADATA(MagicBoostUserConsentView) +END_METADATA + +} // namespace quick_answers
diff --git a/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.h b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.h new file mode 100644 index 0000000..9cb09fb --- /dev/null +++ b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view.h
@@ -0,0 +1,76 @@ +// 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_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_USER_CONSENT_VIEW_H_ +#define CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_USER_CONSENT_VIEW_H_ + +#include <memory> +#include <string> + +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/ui/ash/editor_menu/utils/focus_search.h" +#include "chrome/browser/ui/ash/read_write_cards/read_write_cards_ui_controller.h" +#include "chrome/browser/ui/ash/read_write_cards/read_write_cards_view.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/layout/flex_layout_view.h" +#include "ui/views/metadata/view_factory.h" +#include "ui/views/view.h" +#include "ui/views/widget/unique_widget_ptr.h" + +namespace views { +class LabelButton; +} // namespace views + +class QuickAnswersUiController; + +namespace quick_answers { + +class MagicBoostUserConsentView : public chromeos::ReadWriteCardsView { + METADATA_HEADER(MagicBoostUserConsentView, chromeos::ReadWriteCardsView) + + public: + static constexpr char kWidgetName[] = "MagicBoostUserConsentViewWidget"; + + // TODO: crbug.com/340628664 - remove `read_write_cards_ui_controller` arg + // once we stop extending `ReadWriteCardsView`. + explicit MagicBoostUserConsentView( + const std::u16string& chip_label, + chromeos::ReadWriteCardsUiController& read_write_cards_ui_controller); + + // Disallow copy and assign. + MagicBoostUserConsentView(const MagicBoostUserConsentView&) = delete; + MagicBoostUserConsentView& operator=(const MagicBoostUserConsentView&) = + delete; + + ~MagicBoostUserConsentView() override; + + // chromeos::ReadWriteCardsView: + void OnFocus() override; + views::FocusTraversable* GetPaneFocusTraversable() override; + void UpdateBoundsForQuickAnswers() override; + + std::u16string chip_label_for_testing(); + + private: + // FocusSearch::GetFocusableViewsCallback to poll currently focusable views. + std::vector<views::View*> GetFocusableViews(); + + base::WeakPtr<QuickAnswersUiController> controller_; + chromeos::editor_menu::FocusSearch focus_search_; + raw_ptr<views::LabelButton> intent_chip_ = nullptr; +}; + +BEGIN_VIEW_BUILDER(/* no export */, + MagicBoostUserConsentView, + chromeos::ReadWriteCardsView) +END_VIEW_BUILDER + +} // namespace quick_answers + +DEFINE_VIEW_BUILDER(/* no export */, quick_answers::MagicBoostUserConsentView) + +#endif // CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_MAGIC_BOOST_USER_CONSENT_VIEW_H_
diff --git a/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view_unittest.cc b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view_unittest.cc new file mode 100644 index 0000000..6d1cbd00c --- /dev/null +++ b/chrome/browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view_unittest.cc
@@ -0,0 +1,23 @@ +// 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/ash/quick_answers/ui/magic_boost_user_consent_view.h" + +#include "chrome/browser/ui/ash/read_write_cards/read_write_cards_ui_controller.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/views/test/test_layout_provider.h" + +namespace quick_answers { + +TEST(MagicBoostUserConsentViewTest, ButtonTextLabel) { + views::test::TestLayoutProvider test_layout_provider; + chromeos::ReadWriteCardsUiController read_write_cards_ui_controller; + MagicBoostUserConsentView magic_boost_user_consent_view( + /*chip_label=*/u"testing label", read_write_cards_ui_controller); + + EXPECT_EQ(u"testing label", + magic_boost_user_consent_view.chip_label_for_testing()); +} + +} // namespace quick_answers
diff --git a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.cc b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.cc index 5cb7f45..8f21d61 100644 --- a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.cc +++ b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.cc
@@ -22,6 +22,7 @@ #include "ui/views/layout/box_layout_view.h" #include "ui/views/layout/flex_layout.h" #include "ui/views/layout/flex_layout_view.h" +#include "ui/views/layout/layout_provider.h" namespace { @@ -236,4 +237,41 @@ tts_controller->SpeakOrEnqueue(std::move(tts_utterance)); } +const gfx::Insets GetMainViewInsets(Design design) { + switch (design) { + case Design::kCurrent: + return kMainViewInsets; + case Design::kRefresh: + case Design::kMagicBoost: + return gfx::Insets::TLBR(12, 16, 16, 16); + } + + NOTREACHED() << "Invalid design enum value provided"; +} + +const gfx::Insets GetButtonsViewInsets(Design design) { + switch (design) { + case Design::kCurrent: + return gfx::Insets(kButtonsViewMarginDip); + case Design::kRefresh: + case Design::kMagicBoost: + // Buttons view is rendered as a layer on top of main view. For `kRefresh` + // and `kMagicBoost`, they share the same insets. + return GetMainViewInsets(design); + } + + NOTREACHED() << "Invalid design enum value provided"; +} + +// TODO: crbug.com/340629098 - A temporary solution until buttons view is merged +// into headers. See another comment for buttons view in +// `QuickAnswersView::QuickAnswersView` about details. +int GetButtonsViewOcclusion(Design design) { + gfx::Insets insets_icon_button = + views::LayoutProvider::Get()->GetInsetsMetric( + views::InsetsMetric::INSETS_ICON_BUTTON); + return insets_icon_button.left() + kGoogleIconSizeDip + + insets_icon_button.right() + GetButtonsViewInsets(design).right(); +} + } // namespace quick_answers
diff --git a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h index 1917a26..109e783b 100644 --- a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h +++ b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h
@@ -25,6 +25,14 @@ inline constexpr int kSubContentTextWidth = kContentTextWidth - kSubContentViewIndent; +inline constexpr int kButtonsViewMarginDip = 4; + +// Google icon. +inline constexpr int kGoogleIconSizeDip = 16; + +// Other icons +inline constexpr int kIconSizeDip = 16; + // Spacing constants. inline constexpr int kContentSingleSpacing = 8; inline constexpr int kContentDoubleSpacing = 16; @@ -37,6 +45,7 @@ inline constexpr int kRichAnswersIconBorderDip = 4; inline constexpr gfx::Insets kSubContentViewInsets = gfx::Insets::TLBR(0, kSubContentViewIndent, 0, 0); +inline constexpr gfx::Insets kMainViewInsets = gfx::Insets::TLBR(12, 8, 12, 16); // Font constants. inline constexpr char kGoogleSansFont[] = "Google Sans"; @@ -115,6 +124,12 @@ const std::string& text, const std::string& locale); +const gfx::Insets GetMainViewInsets(Design design); + +const gfx::Insets GetButtonsViewInsets(Design design); + +int GetButtonsViewOcclusion(Design design); + } // namespace quick_answers #endif // CHROME_BROWSER_UI_ASH_QUICK_ANSWERS_UI_QUICK_ANSWERS_UTIL_H_
diff --git a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_view.cc b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_view.cc index c8c8ce00..2f811fca 100644 --- a/chrome/browser/ui/ash/quick_answers/ui/quick_answers_view.cc +++ b/chrome/browser/ui/ash/quick_answers/ui/quick_answers_view.cc
@@ -19,6 +19,7 @@ #include "chrome/browser/ui/ash/editor_menu/utils/pre_target_handler.h" #include "chrome/browser/ui/ash/quick_answers/quick_answers_ui_controller.h" #include "chrome/browser/ui/ash/quick_answers/ui/loading_view.h" +#include "chrome/browser/ui/ash/quick_answers/ui/magic_boost_header.h" #include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_stage_button.h" #include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_text_label.h" #include "chrome/browser/ui/ash/quick_answers/ui/quick_answers_util.h" @@ -99,35 +100,18 @@ using views::Label; using views::View; -constexpr auto kMainViewInsets = gfx::Insets::TLBR(12, 8, 12, 16); constexpr auto kContentViewInsets = gfx::Insets::TLBR(0, 8, 0, 0); -// Google icon. -constexpr int kIconSizeDip = 16; - // Spacing between lines in the main view. constexpr int kLineSpacingDip = 4; constexpr int kDefaultLineHeightDip = 20; // Buttons view. -constexpr int kButtonsViewMarginDip = 4; constexpr int kButtonsSpacingDip = 4; constexpr int kDogfoodButtonSizeDip = 20; constexpr int kSettingsButtonSizeDip = 14; constexpr int kSettingsButtonBorderDip = 3; -const gfx::Insets GetMainViewInsets(Design design) { - switch (design) { - case Design::kCurrent: - return kMainViewInsets; - case Design::kRefresh: - case Design::kMagicBoost: - return gfx::Insets::TLBR(12, 16, 16, 16); - } - - NOTREACHED() << "Invalid design enum value provided"; -} - const gfx::Insets GetIconInsets(Design design) { switch (design) { case Design::kCurrent: @@ -141,20 +125,6 @@ NOTREACHED() << "Invalid design enum value provided"; } -const gfx::Insets GetButtonsViewInsets(Design design) { - switch (design) { - case Design::kCurrent: - return gfx::Insets(kButtonsViewMarginDip); - case Design::kRefresh: - case Design::kMagicBoost: - // Buttons view is rendered as a layer on top of main view. For `kRefresh` - // and `kMagicBoost`, they share the same insets. - return GetMainViewInsets(design); - } - - NOTREACHED() << "Invalid design enum value provided"; -} - const gfx::VectorIcon& GetVectorIcon(std::optional<Intent> intent) { if (!intent) { return omnibox::kAnswerDefaultIcon; @@ -175,8 +145,9 @@ ui::ImageModel GetIcon(Design design, std::optional<Intent> intent) { switch (design) { case Design::kCurrent: - return ui::ImageModel::FromVectorIcon( - vector_icons::kGoogleColorIcon, gfx::kPlaceholderColor, kIconSizeDip); + return ui::ImageModel::FromVectorIcon(vector_icons::kGoogleColorIcon, + gfx::kPlaceholderColor, + kGoogleIconSizeDip); case Design::kRefresh: return ui::ImageModel::FromVectorIcon( GetVectorIcon(intent), ui::kColorSysOnSurface, kIconSizeDip); @@ -263,17 +234,6 @@ NOTREACHED() << "Invalid intent enum value specified"; } -// TODO(b/340629098): A temporary solution until buttons view is merged into -// headers. See another comment for buttons view in -// `QuickAnswersView::QuickAnswersView` about details. -int GetButtonsViewOcclusion(Design design) { - gfx::Insets insets_icon_button = - views::LayoutProvider::Get()->GetInsetsMetric( - views::InsetsMetric::INSETS_ICON_BUTTON); - return insets_icon_button.left() + kIconSizeDip + insets_icon_button.right() + - GetButtonsViewInsets(design).right(); -} - views::Builder<views::Label> GetRefreshUiHeader() { int line_height = ash::TypographyProvider::Get()->ResolveLineHeight( ash::TypographyToken::kCrosAnnotation1); @@ -300,41 +260,6 @@ views::MaximumFlexSizeRule::kPreferred)); } -views::Builder<views::BoxLayoutView> GetMagicBoostHeader() { - int line_height = ash::TypographyProvider::Get()->ResolveLineHeight( - ash::TypographyToken::kCrosAnnotation1); - int vertical_padding = std::max(0, (20 - line_height) / 2); - - return views::Builder<views::BoxLayoutView>() - .SetProperty( - views::kMarginsKey, - gfx::Insets::TLBR( - 0, 0, - views::LayoutProvider::Get()->GetDistanceMetric( - views::DistanceMetric::DISTANCE_RELATED_CONTROL_VERTICAL), - GetButtonsViewOcclusion(Design::kMagicBoost))) - .SetOrientation(views::LayoutOrientation::kHorizontal) - .SetCrossAxisAlignment(views::LayoutAlignment::kCenter) - .SetProperty( - views::kFlexBehaviorKey, - views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred, - views::MaximumFlexSizeRule::kPreferred)) - .SetBetweenChildSpacing(views::LayoutProvider::Get()->GetDistanceMetric( - views::DistanceMetric::DISTANCE_RELATED_BUTTON_HORIZONTAL)) - .AddChild( - views::Builder<views::Label>() - .SetText(l10n_util::GetStringUTF16(IDS_ASH_MAHI_MENU_TITLE)) - .SetLineHeight(line_height) - .SetProperty(views::kMarginsKey, - gfx::Insets::VH(vertical_padding, 0)) - .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT) - .SetFontList(ash::TypographyProvider::Get() - ->ResolveTypographyToken( - ash::TypographyToken::kCrosAnnotation1) - .DeriveWithWeight(gfx::Font::Weight::MEDIUM))) - .AddChild(views::Builder<chromeos::ExperimentBadge>()); -} - std::string GetResultA11yDescription(ResultView* result_view, std::optional<Intent> intent, Design design) {
diff --git a/chrome/browser/ui/ash/quick_answers/ui/rich_answers_view.cc b/chrome/browser/ui/ash/quick_answers/ui/rich_answers_view.cc index 5bb5e3d7..e187c95 100644 --- a/chrome/browser/ui/ash/quick_answers/ui/rich_answers_view.cc +++ b/chrome/browser/ui/ash/quick_answers/ui/rich_answers_view.cc
@@ -56,9 +56,6 @@ constexpr int kMinimumRichCardHeight = 120; constexpr int kMaximumRichCardHeight = 464; -// View dimensions. -constexpr auto kMainViewInsets = gfx::Insets::TLBR(20, 20, 16, 20); - // Buttons view. constexpr int kSettingsButtonSizeDip = 20;
diff --git a/chrome/browser/ui/ash/quick_answers/ui/user_consent_view.cc b/chrome/browser/ui/ash/quick_answers/ui/user_consent_view.cc index f006cf2..bf77b3b05 100644 --- a/chrome/browser/ui/ash/quick_answers/ui/user_consent_view.cc +++ b/chrome/browser/ui/ash/quick_answers/ui/user_consent_view.cc
@@ -62,12 +62,10 @@ // Main view (or common) specs. constexpr int kLineHeightDip = 20; constexpr int kContentSpacingDip = 8; -constexpr auto kMainViewInsets = gfx::Insets::TLBR(16, 12, 16, 16); constexpr auto kContentInsets = gfx::Insets::TLBR(0, 12, 0, 0); constexpr auto kContentInsetsRefresh = gfx::Insets::TLBR(0, 16, 0, 0); // Icon. -constexpr int kGoogleIconSizeDip = 16; constexpr int kIntentIconSizeDip = 20; constexpr int kIconBackgroundCornerRadiusDip = 12; constexpr gfx::Insets kIntentIconInsets = gfx::Insets(8);
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_unittest.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_unittest.cc index 5e95425..3cc193c 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_unittest.cc +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_unittest.cc
@@ -97,6 +97,14 @@ parent_window_ = widget->GetNativeWindow(); } + void TearDown() override { + if (bubble_delegate_) { + bubble_delegate_->CloseBubble(::sharesheet::SharesheetResult::kCancel); + } + ASSERT_FALSE(sharesheet_widget_); + ChromeAshTestBase::TearDown(); + } + void ShowAndVerifyBubble(apps::IntentPtr intent, ::sharesheet::LaunchSource source, int num_actions_to_add = 0) { @@ -105,7 +113,10 @@ sharesheet_service->ShowBubbleForTesting( parent_window_, std::move(intent), source, /*delivered_callback=*/base::DoNothing(), - /*close_callback=*/base::DoNothing(), num_actions_to_add); + /*close_callback=*/ + base::BindOnce(&SharesheetBubbleViewTest::OnClose, + base::Unretained(this)), + num_actions_to_add); bubble_delegate_ = static_cast<SharesheetBubbleViewDelegate*>( sharesheet_service->GetUiDelegateForTesting(parent_window_)); EXPECT_NE(bubble_delegate_, nullptr); @@ -121,26 +132,26 @@ void CloseBubble() { bubble_delegate_->CloseBubble(::sharesheet::SharesheetResult::kCancel); - // |bubble_delegate_| and |sharesheet_bubble_view_| destruct on close. - bubble_delegate_ = nullptr; - sharesheet_bubble_view_ = nullptr; - - ASSERT_FALSE(IsSharesheetVisible()); + ASSERT_FALSE(sharesheet_widget_); } void CloseBubbleWithEscKey() { GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE); - // |bubble_delegate_| and |sharesheet_bubble_view_| destruct on close. - bubble_delegate_ = nullptr; - sharesheet_bubble_view_ = nullptr; - - ASSERT_FALSE(IsSharesheetVisible()); + ASSERT_FALSE(sharesheet_widget_); } - bool IsSharesheetVisible() { return sharesheet_widget_->IsVisible(); } + bool IsSharesheetVisible() { + return sharesheet_widget_ && sharesheet_widget_->IsVisible(); + } views::Widget* sharesheet_widget() { return sharesheet_widget_; } + void OnClose(views::Widget::ClosedReason reason) { + sharesheet_bubble_view_ = nullptr; + sharesheet_widget_ = nullptr; + bubble_delegate_ = nullptr; + } + SharesheetBubbleView* sharesheet_bubble_view() { return sharesheet_bubble_view_; } @@ -162,9 +173,9 @@ private: gfx::NativeWindow parent_window_; std::unique_ptr<TestingProfile> profile_; - raw_ptr<SharesheetBubbleViewDelegate, DanglingUntriaged> bubble_delegate_; - raw_ptr<SharesheetBubbleView, DanglingUntriaged> sharesheet_bubble_view_; - raw_ptr<views::Widget, DanglingUntriaged> sharesheet_widget_; + raw_ptr<SharesheetBubbleViewDelegate> bubble_delegate_; + raw_ptr<SharesheetBubbleView> sharesheet_bubble_view_; + raw_ptr<views::Widget> sharesheet_widget_; }; TEST_F(SharesheetBubbleViewTest, BubbleDoesOpenAndClose) {
diff --git a/chrome/browser/ui/ash/system/BUILD.gn b/chrome/browser/ui/ash/system/BUILD.gn index f94ff0b..3674457 100644 --- a/chrome/browser/ui/ash/system/BUILD.gn +++ b/chrome/browser/ui/ash/system/BUILD.gn
@@ -10,7 +10,10 @@ "system_tray_client_impl.h", ] - public_deps = [ "//chrome/browser:browser_public_dependencies" ] + public_deps = [ + "//chrome/browser:browser_public_dependencies", + "//chrome/browser/upgrade_detector", + ] deps = [ "//ash/constants",
diff --git a/chrome/browser/ui/dialogs/BUILD.gn b/chrome/browser/ui/dialogs/BUILD.gn index 7b20f77..70a44468 100644 --- a/chrome/browser/ui/dialogs/BUILD.gn +++ b/chrome/browser/ui/dialogs/BUILD.gn
@@ -23,6 +23,7 @@ "//chrome/app:generated_resources", "//chrome/browser:browser_process", "//chrome/browser/ui:browser_element_identifiers", + "//chrome/browser/upgrade_detector", "//chrome/common:channel_info", "//chrome/common:constants", "//content/public/browser",
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc index 5eb06c4..70a7ee7 100644 --- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc +++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -2613,6 +2613,7 @@ // panel browsertest file. IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, SidePanel_SearchURLClickWithTextDirective) { + base::HistogramTester histogram_tester; WaitForPaint(); // State should start in off. @@ -2678,6 +2679,12 @@ // Verify the loading state was never set. EXPECT_EQ(test_side_panel_coordinator->side_panel_loading_set_to_true_, 0); EXPECT_EQ(test_side_panel_coordinator->side_panel_loading_set_to_false_, 0); + + // Record the text directive result. + histogram_tester.ExpectTotalCount("Lens.Overlay.TextDirectiveResult", 1); + histogram_tester.ExpectUniqueSample( + "Lens.Overlay.TextDirectiveResult", + lens::LensOverlayTextDirectiveResult::kOpenedInNewTab, 1); } // TODO(crbug.com/413042395): This test is not testing overlay logic, but @@ -2685,6 +2692,7 @@ // panel browsertest file. IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, SidePanel_LinkClickWithTextDirective_TextIsPresent) { + base::HistogramTester histogram_tester; WaitForPaint(); // State should start in off. @@ -2774,6 +2782,15 @@ EXPECT_TRUE(highlighter->GetTextDirective() == "select" || highlighter->GetTextDirective() == "element"); } + EXPECT_TRUE(base::test::RunUntil([&]() { + return histogram_tester.GetBucketCount( + "Lens.Overlay.TextDirectiveResult", + lens::LensOverlayTextDirectiveResult::kFoundOnPage) == 1; + })); + histogram_tester.ExpectTotalCount("Lens.Overlay.TextDirectiveResult", 1); + histogram_tester.ExpectUniqueSample( + "Lens.Overlay.TextDirectiveResult", + lens::LensOverlayTextDirectiveResult::kFoundOnPage, 1); } // TODO(crbug.com/413042395): This test is not testing overlay logic, but @@ -2790,6 +2807,7 @@ IN_PROC_BROWSER_TEST_F( LensOverlayControllerBrowserTest, MAYBE_SidePanel_LinkClickWithTextDirective_TextIsMissing) { + base::HistogramTester histogram_tester; WaitForPaint(); // State should start in off. @@ -2869,6 +2887,10 @@ kCheckSidePanelToastShownScript) .ExtractBool(); })); + histogram_tester.ExpectTotalCount("Lens.Overlay.TextDirectiveResult", 1); + histogram_tester.ExpectUniqueSample( + "Lens.Overlay.TextDirectiveResult", + lens::LensOverlayTextDirectiveResult::kNotFoundOnPage, 1); } // TODO(crbug.com/413042395): This test is not testing overlay logic, but @@ -2885,6 +2907,7 @@ IN_PROC_BROWSER_TEST_F( LensOverlayControllerBrowserTest, MAYBE_SidePanel_LinkClickWithTextDirective_TextIsIncomplete) { + base::HistogramTester histogram_tester; WaitForPaint(); // State should start in off. @@ -2964,6 +2987,10 @@ kCheckSidePanelToastShownScript) .ExtractBool(); })); + histogram_tester.ExpectTotalCount("Lens.Overlay.TextDirectiveResult", 1); + histogram_tester.ExpectUniqueSample( + "Lens.Overlay.TextDirectiveResult", + lens::LensOverlayTextDirectiveResult::kNotFoundOnPage, 1); } // TODO(crbug.com/413042395): This test is not testing overlay logic, but
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 1e8fe69..4452104 100644 --- a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc +++ b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
@@ -226,6 +226,8 @@ if (page_url.host() != nav_url.host() || page_url.path() != nav_url.path() || page_url_text_query != nav_url_text_query) { + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kOpenedInNewTab); lens_search_controller_->GetTabInterface() ->GetBrowserWindowInterface() ->OpenGURL(nav_url, WindowOpenDisposition::NEW_FOREGROUND_TAB); @@ -883,11 +885,15 @@ ->GetLastCommittedURL(); if (lookup_results.empty()) { if (URLsMatchWithoutTextFragment(page_url, nav_url)) { + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kNotFoundOnPage); ShowToast(l10n_util::GetStringUTF8( IDS_LENS_OVERLAY_TOAST_PAGE_CONTENT_NOT_FOUND_MESSAGE)); return; } + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kOpenedInNewTab); lens_search_controller_->GetTabInterface() ->GetBrowserWindowInterface() ->OpenGURL(nav_url, WindowOpenDisposition::NEW_FOREGROUND_TAB); @@ -899,11 +905,15 @@ // If any of the text fragments are not found, then open in a new tab. if (!pair.second) { if (URLsMatchWithoutTextFragment(page_url, nav_url)) { + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kNotFoundOnPage); ShowToast(l10n_util::GetStringUTF8( IDS_LENS_OVERLAY_TOAST_PAGE_CONTENT_NOT_FOUND_MESSAGE)); return; } + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kOpenedInNewTab); lens_search_controller_->GetTabInterface() ->GetBrowserWindowInterface() ->OpenGURL(nav_url, WindowOpenDisposition::NEW_FOREGROUND_TAB); @@ -923,6 +933,8 @@ // If every text fragment was found, then create a text highlighter manager to // render the text highlights. Focus the main tab first. + lens::RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult::kFoundOnPage); lens_search_controller_->GetTabInterface()->GetContents()->Focus(); companion::TextHighlighterManager* text_highlighter_manager = companion::TextHighlighterManager::GetOrCreateForPage(page);
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.cc b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.cc index 21861d5..9f4ad6f 100644 --- a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.cc +++ b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.cc
@@ -26,6 +26,7 @@ #include "components/saved_tab_groups/public/saved_tab_group_tab.h" #include "components/saved_tab_groups/public/tab_group_sync_service.h" #include "components/saved_tab_groups/public/types.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" #include "components/sync/base/user_selectable_type.h" #include "components/sync/service/sync_service.h" #include "components/sync/service/sync_user_settings.h" @@ -382,6 +383,11 @@ return false; } +VersioningMessageController* +TabGroupSyncServiceProxy::GetVersioningMessageController() { + return nullptr; +} + void TabGroupSyncServiceProxy::OnLastTabClosed( const SavedTabGroup& saved_tab_group) {}
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.h b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.h index ceb238a..096b77a 100644 --- a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.h +++ b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_proxy.h
@@ -139,6 +139,7 @@ std::unique_ptr<std::vector<SavedTabGroup>> TakeSharedTabGroupsAvailableAtStartupForMessaging() override; bool HadSharedTabGroupsLastSession(bool open_shared_tab_groups) override; + VersioningMessageController* GetVersioningMessageController() override; void OnLastTabClosed(const SavedTabGroup& saved_tab_group) override; void AddObserver(Observer* observer) override;
diff --git a/chrome/browser/ui/views/location_bar/lens_overlay_page_action_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/lens_overlay_page_action_icon_view_interactive_uitest.cc index cd6ccfbe..8828de78 100644 --- a/chrome/browser/ui/views/location_bar/lens_overlay_page_action_icon_view_interactive_uitest.cc +++ b/chrome/browser/ui/views/location_bar/lens_overlay_page_action_icon_view_interactive_uitest.cc
@@ -110,12 +110,17 @@ LensOverlayPageActionIconViewTest() { if (IsMigrationEnabled()) { scoped_feature_list_.InitWithFeaturesAndParameters( - {{lens::features::kLensOverlay, {}}, - {lens::features::kLensOverlayOmniboxEntryPoint, {}}, - {::features::kPageActionsMigration, - { - {::features::kPageActionsMigrationLensOverlay.name, "true"}, - }}}, + { + {lens::features::kLensOverlay, {}}, + {lens::features::kLensOverlayOmniboxEntryPoint, {}}, + { + ::features::kPageActionsMigration, + { + {::features::kPageActionsMigrationLensOverlay.name, + "true"}, + }, + }, + }, {}); } else { scoped_feature_list_.InitWithFeatures( @@ -169,8 +174,9 @@ scoped_feature_list_.InitWithFeaturesAndParameters( {base::test::FeatureRefAndParams(lens::features::kLensOverlay, {{"omnibox-entry-point", "false"}}), - base::test::FeatureRefAndParams(::features::kPageActionsMigration, - {})}, + base::test::FeatureRefAndParams( + ::features::kPageActionsMigration, + {{::features::kPageActionsMigrationLensOverlay.name, "true"}})}, {}); } else { scoped_feature_list_.InitWithFeaturesAndParameters(
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc b/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc index 88bc5779..592164a5 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc +++ b/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc
@@ -502,8 +502,11 @@ : public InProcessBrowserTest { public: LocationBarViewPageActionHideWhileEditingTests() { - scoped_feature_list_.InitWithFeaturesAndParameters( - {{::features::kPageActionsMigration, {}}}, {}); + scoped_feature_list_.InitAndEnableFeatureWithParameters( + ::features::kPageActionsMigration, + { + {features::kPageActionsMigrationZoom.name, "true"}, + }); } void SetUpOnMainThread() override {
diff --git a/chrome/browser/ui/views/page_action/BUILD.gn b/chrome/browser/ui/views/page_action/BUILD.gn index dfd648d..d463474 100644 --- a/chrome/browser/ui/views/page_action/BUILD.gn +++ b/chrome/browser/ui/views/page_action/BUILD.gn
@@ -97,6 +97,7 @@ deps = [ ":page_action", ":test_support", + "//chrome/browser/ui:ui_features", "//chrome/test:test_support", "//components/tabs:test_support", "//ui/events:test_support",
diff --git a/chrome/browser/ui/views/page_action/page_action_container_view.cc b/chrome/browser/ui/views/page_action/page_action_container_view.cc index 87510e0e..925597d 100644 --- a/chrome/browser/ui/views/page_action/page_action_container_view.cc +++ b/chrome/browser/ui/views/page_action/page_action_container_view.cc
@@ -7,6 +7,7 @@ #include <memory> #include "base/functional/bind.h" +#include "chrome/browser/ui/page_action/page_action_icon_type.h" #include "chrome/browser/ui/views/page_action/page_action_controller.h" #include "chrome/browser/ui/views/page_action/page_action_properties_provider.h" #include "chrome/browser/ui/views/page_action/page_action_view.h" @@ -42,9 +43,16 @@ int initial_index = 0; for (actions::ActionItem* action_item : action_items) { const auto action_item_id = action_item->GetActionId().value(); + const auto& properties = properties_provider.GetProperties(action_item_id); + + // When the page action migration is not enabled, the view should not be + // created to avoid conflicting with the old framework version identifier. + if (!IsPageActionMigrated(properties.type)) { + continue; + } + PageActionView* view = AddChildView(std::make_unique<PageActionView>( - action_item, params, - properties_provider.GetProperties(action_item_id).element_identifier)); + action_item, params, properties.element_identifier)); page_action_views_[action_item_id] = view; chip_state_changed_callbacks_.push_back(
diff --git a/chrome/browser/ui/views/page_action/page_action_container_view_unittest.cc b/chrome/browser/ui/views/page_action/page_action_container_view_unittest.cc index b1324ee..ed8af13 100644 --- a/chrome/browser/ui/views/page_action/page_action_container_view_unittest.cc +++ b/chrome/browser/ui/views/page_action/page_action_container_view_unittest.cc
@@ -4,6 +4,9 @@ #include "chrome/browser/ui/views/page_action/page_action_container_view.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/ui/actions/chrome_action_id.h" +#include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/page_action/page_action_view.h" #include "chrome/browser/ui/views/page_action/page_action_view_params.h" #include "chrome/browser/ui/views/page_action/test_support/test_page_action_properties_provider.h" @@ -17,12 +20,13 @@ constexpr int kDefaultBetweenIconSpacing = 8; constexpr int kDefaultIconSize = 16; -static constexpr actions::ActionId kTestPageActionId = 0; +static constexpr actions::ActionId kTestPageActionId = kActionZoomNormal; static const PageActionPropertiesMap kTestProperties = PageActionPropertiesMap{ { kTestPageActionId, PageActionProperties{ - .histogram_name = "Test", + .histogram_name = "TestZoom", + .type = PageActionIconType::kZoom, }, }, }; @@ -41,7 +45,12 @@ class PageActionContainerViewTest : public views::ViewsTestBase { public: - PageActionContainerViewTest() = default; + PageActionContainerViewTest() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + features::kPageActionsMigration, + {{features::kPageActionsMigrationZoom.name, "true"}}); + } + ~PageActionContainerViewTest() override = default; void TearDown() override { @@ -57,6 +66,7 @@ } private: + base::test::ScopedFeatureList scoped_feature_list_; MockIconLabelViewDelegate icon_label_view_delegate_; }; @@ -74,12 +84,14 @@ TestPageActionPropertiesProvider(kTestProperties), DefaultViewParams()); PageActionView* page_action_view = - page_action_container->GetPageActionView(0); + page_action_container->GetPageActionView(kTestPageActionId); ASSERT_TRUE(!!page_action_view); - EXPECT_EQ(0, page_action_view->GetActionId()); + EXPECT_EQ(kTestPageActionId, page_action_view->GetActionId()); // Returns null if the action ID is not found. - EXPECT_EQ(nullptr, page_action_container->GetPageActionView(1)); + static constexpr actions::ActionId kNonExistantPageActionId = 1; + EXPECT_EQ(nullptr, + page_action_container->GetPageActionView(kNonExistantPageActionId)); } } // namespace
diff --git a/chrome/browser/ui/views/page_action/page_action_interactive_uitest.cc b/chrome/browser/ui/views/page_action/page_action_interactive_uitest.cc index 0d4fca1..c7406a6 100644 --- a/chrome/browser/ui/views/page_action/page_action_interactive_uitest.cc +++ b/chrome/browser/ui/views/page_action/page_action_interactive_uitest.cc
@@ -118,10 +118,22 @@ // TODO(crbug.com/424806660): These tests should not be reliant on // kLensOverlayOmniboxEntryPoint being enabled, but disabling it causes them // to fail. - feature_list_.InitWithFeatures( - {features::kPageActionsMigration, - lens::features::kLensOverlayOmniboxEntryPoint}, - {lens::features::kLensOverlay}); + feature_list_.InitWithFeaturesAndParameters( + /*enabled_features=*/ + { + { + features::kPageActionsMigration, + { + {features::kPageActionsMigrationZoom.name, "true"}, + {features::kPageActionsMigrationTranslate.name, "true"}, + {features::kPageActionsMigrationMemorySaver.name, "true"}, + }, + }, + {lens::features::kLensOverlayOmniboxEntryPoint, {}}, + }, + /*disabled_features=*/{ + lens::features::kLensOverlay, + }); } virtual ~PageActionUiTestBase() = default; @@ -613,13 +625,7 @@ class PageActionMetricsInteractiveUiTest : public InteractiveBrowserTest, public PageActionUiTestBase { public: - PageActionMetricsInteractiveUiTest() { - scoped_feature_list_.InitAndEnableFeatureWithParameters( - features::kPageActionsMigration, - { - {features::kPageActionsMigrationZoom.name, "true"}, - }); - } + PageActionMetricsInteractiveUiTest() = default; PageActionMetricsInteractiveUiTest( const PageActionMetricsInteractiveUiTest&) = delete; @@ -642,9 +648,6 @@ auto DoZoomOut() { return Do([&]() { SetZoomLevel(content::PAGE_ZOOM_OUT); }); } - - private: - base::test::ScopedFeatureList scoped_feature_list_; }; IN_PROC_BROWSER_TEST_F(PageActionMetricsInteractiveUiTest, ClickHistogramLogs) {
diff --git a/chrome/browser/ui/views/toolbar/BUILD.gn b/chrome/browser/ui/views/toolbar/BUILD.gn index fcd0bf4..dc2d80a 100644 --- a/chrome/browser/ui/views/toolbar/BUILD.gn +++ b/chrome/browser/ui/views/toolbar/BUILD.gn
@@ -132,6 +132,7 @@ "//chrome/browser/ui/views/page_action:page_action", "//chrome/browser/ui/views/side_panel:side_panel_enums", "//chrome/browser/ui/web_applications", + "//chrome/browser/upgrade_detector", "//chrome/browser/web_applications:features", "//chrome/common:buildflags", "//chrome/common:chrome_features",
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc index 3e7eab1..f71f2a4d 100644 --- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
@@ -20,6 +20,7 @@ #include "base/test/gmock_expected_support.h" #include "base/test/icu_test_util.h" #include "base/test/run_until.h" +#include "base/test/scoped_feature_list.h" #include "base/test/test_future.h" #include "base/test/test_timeouts.h" #include "build/build_config.h" @@ -34,6 +35,7 @@ #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/download/download_display.h" +#include "chrome/browser/ui/page_action/page_action_icon_type.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/view_ids.h" @@ -49,6 +51,7 @@ #include "chrome/browser/ui/views/infobars/infobar_container_view.h" #include "chrome/browser/ui/views/infobars/infobar_view.h" #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h" +#include "chrome/browser/ui/views/page_action/page_action_properties_provider.h" #include "chrome/browser/ui/views/page_action/page_action_view.h" #include "chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h" #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_test_helper.h" @@ -239,12 +242,23 @@ helper()->web_app_frame_toolbar()); std::vector<const views::View*> page_action_views = {}; + const auto& properties_provider = + page_actions::PageActionPropertiesProvider(); for (auto action_id : helper() ->app_browser() ->GetAppBrowserController() ->GetTitleBarPageActions()) { auto* page_action_view = helper()->web_app_frame_toolbar()->GetPageActionView(action_id); + + const auto& properties = properties_provider.GetProperties(action_id); + + // When the page action migration is not enabled, the view should not be + // created to avoid conflicting with the old framework version identifier. + if (!IsPageActionMigrated(properties.type)) { + continue; + } + ASSERT_NE(nullptr, page_action_view); EXPECT_EQ(page_action_view->parent(), toolbar_right_container->page_action_container());
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/BUILD.gn b/chrome/browser/ui/webui/new_tab_page/composebox/BUILD.gn index dd3efeab..82099a9 100644 --- a/chrome/browser/ui/webui/new_tab_page/composebox/BUILD.gn +++ b/chrome/browser/ui/webui/new_tab_page/composebox/BUILD.gn
@@ -22,3 +22,13 @@ sources = [ "composebox.mojom" ] webui_module_path = "/" } + +source_set("unit_tests") { + testonly = true + sources = [ "composebox_handler_unittest.cc" ] + deps = [ + ":composebox", + "//base/test:test_support", + "//components/omnibox/composebox", + ] +}
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc index fc29e75..179ec68 100644 --- a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc +++ b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.cc
@@ -4,11 +4,11 @@ #include "chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.h" -// TODO(420700441) Add unittest coverage. ComposeboxHandler::ComposeboxHandler( - mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler> handler) + mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler> handler, + std::unique_ptr<ComposeboxQueryController> query_controller) : handler_(this, std::move(handler)), - query_controller_{std::make_unique<ComposeboxQueryController>()} {} + query_controller_(std::move(query_controller)) {} ComposeboxHandler::~ComposeboxHandler() = default;
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.h b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.h index 396b236..7d0bbe2 100644 --- a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.h +++ b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler.h
@@ -13,7 +13,8 @@ class ComposeboxHandler : public composebox::mojom::ComposeboxPageHandler { public: explicit ComposeboxHandler( - mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler> handler); + mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler> handler, + std::unique_ptr<ComposeboxQueryController> query_controller); ~ComposeboxHandler() override; // composebox::mojom::ComposeboxPageHandler:
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler_unittest.cc b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler_unittest.cc new file mode 100644 index 0000000..f79567dd --- /dev/null +++ b/chrome/browser/ui/webui/new_tab_page/composebox/composebox_handler_unittest.cc
@@ -0,0 +1,45 @@ +// 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_page/composebox/composebox_handler.h" + +#include "chrome/browser/ui/webui/new_tab_page/composebox/composebox.mojom.h" +#include "components/omnibox/composebox/composebox_query_controller.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +class MockQueryController : public ComposeboxQueryController { + public: + MockQueryController() = default; + ~MockQueryController() override = default; + + MOCK_METHOD(void, NotifySessionStarted, ()); + MOCK_METHOD(void, NotifySessionAbandoned, ()); +}; + +class ComposeboxHandlerTest : public testing::Test { + public: + ComposeboxHandlerTest() = default; + ~ComposeboxHandlerTest() override = default; + + void SetUp() override { + auto query_controller_ptr = std::make_unique<MockQueryController>(); + query_controller_ = query_controller_ptr.get(); + handler_ = std::make_unique<ComposeboxHandler>( + mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler>(), + std::move(query_controller_ptr)); + } + + ComposeboxHandler& handler() { return *handler_; } + MockQueryController& query_controller() { return *query_controller_; } + + private: + std::unique_ptr<ComposeboxHandler> handler_; + raw_ptr<MockQueryController> query_controller_; +}; + +TEST_F(ComposeboxHandlerTest, NotifySessionStarted) { + EXPECT_CALL(query_controller(), NotifySessionStarted).Times(1); + handler().NotifySessionStarted(); +}
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc index 466a778..10b4dbfc 100644 --- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc +++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -75,6 +75,7 @@ #include "components/grit/components_scaled_resources.h" #include "components/history_clusters/core/features.h" #include "components/omnibox/browser/omnibox_prefs.h" +#include "components/omnibox/composebox/composebox_query_controller.h" #include "components/page_image_service/image_service.h" #include "components/page_image_service/image_service_handler.h" #include "components/prefs/pref_registry_simple.h" @@ -735,8 +736,9 @@ void NewTabPageUI::BindInterface( mojo::PendingReceiver<composebox::mojom::ComposeboxPageHandler> pending_receiver) { - composebox_handler_ = - std::make_unique<ComposeboxHandler>(std::move(pending_receiver)); + composebox_handler_ = std::make_unique<ComposeboxHandler>( + std::move(pending_receiver), + std::make_unique<ComposeboxQueryController>()); } void NewTabPageUI::BindInterface(
diff --git a/chrome/browser/upgrade_detector/BUILD.gn b/chrome/browser/upgrade_detector/BUILD.gn index f2fa29b..0fe53010 100644 --- a/chrome/browser/upgrade_detector/BUILD.gn +++ b/chrome/browser/upgrade_detector/BUILD.gn
@@ -7,3 +7,16 @@ public_deps = [ "//base" ] } + +source_set("upgrade_detector") { + sources = [ "upgrade_observer.h" ] + public_deps = [] + + if (!is_android) { + sources += [ "upgrade_detector.h" ] + public_deps = [ + "//base", + "//components/prefs", + ] + } +}
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt index d8af2af..8ba3835a 100644 --- a/chrome/build/android-arm32.pgo.txt +++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@ -chrome-android32-main-1749837587-dc74434184fd38f82c739e0bce155bc22605b7a3-a841b09d34eaaae88f2d5d1c3133bc31f6644401.profdata +chrome-android32-main-1749859065-3a39a0a8c5909a0ecd3659f2b686d601766e8944-2547103072959024f14c03c5e63b282d70732547.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index 782e772..c44cb035 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1749838109-993cb23ef058f9c53d3f078c25ca1bd80885b51f-b4b2f68779c4f8ced8b70999f6f308a50c00aa50.profdata +chrome-android64-main-1749873206-3ba42b7b98b1e49b18ba3b93006b81affc74d4c0-ca82c71509e66359013536d1f746da84e624584c.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 96271e0..bc6ff56 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1749837587-dfd4560370021fdc9be0d898832572f40a4abef5-a841b09d34eaaae88f2d5d1c3133bc31f6644401.profdata +chrome-linux-main-1749859065-6b7219937452527d2ea4d10e8890d3a841d6b1c8-2547103072959024f14c03c5e63b282d70732547.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 49465378..57ea0094 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1749844660-a31350277cc3f3bb603b7603310cfa6e7abecbbb-b0219b4ce2b576512bd7698922697c75e7ddb569.profdata +chrome-mac-arm-main-1749873206-588f4cac38cdfc0135540e0f967469f86b7e68d6-ca82c71509e66359013536d1f746da84e624584c.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 5ed2165b..9f16c94 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1749837587-682f385701cb67c2306078d62b477beea6ae34bd-a841b09d34eaaae88f2d5d1c3133bc31f6644401.profdata +chrome-mac-main-1749859065-387941b4719e71ad8482b82d23f1e1d6b0c4036e-2547103072959024f14c03c5e63b282d70732547.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt index e0c9eaf..492e9d9 100644 --- a/chrome/build/win-arm64.pgo.txt +++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@ -chrome-win-arm64-main-1749837587-0d75c735f64ac784762067f38317d80af0897482-a841b09d34eaaae88f2d5d1c3133bc31f6644401.profdata +chrome-win-arm64-main-1749859065-221bd629013277b9011d1dbcf131371e9e815223-2547103072959024f14c03c5e63b282d70732547.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 58c34c7..34a4985 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1749826705-78c2b5ce6ec32fc1cbc26855aaa8a025beeb4583-ec1b13fe44cea60c605fa6193cedd87f2e43821b.profdata +chrome-win32-main-1749847676-4d20a04a5bd7f83ecf3bd29cbfa216b2dd8d81f8-0393af2ee7d050c595324df6afb1fc93a27242c2.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index b0824eb..07f83b34 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1749815741-51068494ebe1454ff4f94d6ce9f526c03829c3d3-41d4f9a106e824de554d27e7ce69ba8e9af88d7d.profdata +chrome-win64-main-1749826705-22d1a4c3e4b2d1b2d0566cf18d69e0cbd6aceecf-ec1b13fe44cea60c605fa6193cedd87f2e43821b.profdata
diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni index 48557a0..1baac29d 100644 --- a/chrome/common/extensions/api/api_sources.gni +++ b/chrome/common/extensions/api/api_sources.gni
@@ -20,6 +20,7 @@ "font_settings.json", "history.json", "identity.idl", + "instance_id.json", "notifications.idl", "permissions.json", "processes.idl", @@ -63,7 +64,6 @@ "enterprise_reporting_private.idl", "gcm.json", "image_writer_private.idl", - "instance_id.json", "language_settings_private.idl", "omnibox.json", "page_capture.json",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 8fca949..da901b2 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -1420,7 +1420,7 @@ "//components/metrics:test_support", "//components/metrics_services_manager", "//components/network_session_configurator/common", - "//components/optimization_guide/core:model_executor", + "//components/optimization_guide/core", "//components/optimization_guide/core:test_support", "//components/policy:generated", "//components/policy:policy_code_generate", @@ -1535,6 +1535,7 @@ "../browser/extensions/api/history/history_apitest.cc", "../browser/extensions/api/i18n/i18n_apitest.cc", "../browser/extensions/api/idle/idle_apitest.cc", + "../browser/extensions/api/instance_id/instance_id_apitest.cc", "../browser/extensions/api/messaging/messaging_apitest.cc", "../browser/extensions/api/messaging/service_worker_messaging_apitest.cc", "../browser/extensions/api/module/module_apitest.cc", @@ -4453,7 +4454,6 @@ "../browser/extensions/api/image_writer_private/extractor_browsertest.cc", "../browser/extensions/api/image_writer_private/test_utils.cc", "../browser/extensions/api/image_writer_private/test_utils.h", - "../browser/extensions/api/instance_id/instance_id_apitest.cc", "../browser/extensions/api/management/management_api_browsertest.cc", "../browser/extensions/api/management/management_api_non_persistent_apitest.cc", "../browser/extensions/api/management/management_apitest.cc", @@ -8222,6 +8222,7 @@ "//chrome/browser/ui/webui/access_code_cast:unit_tests", "//chrome/browser/ui/webui/app_management:unit_tests", "//chrome/browser/ui/webui/new_tab_footer:unit_tests", + "//chrome/browser/ui/webui/new_tab_page/composebox:unit_tests", "//chrome/browser/ui/webui/privacy_sandbox:unit_tests", "//chrome/browser/ui/webui/settings", "//chrome/browser/ui/webui/signin:unit_tests", @@ -8681,6 +8682,7 @@ "../browser/ui/ash/quick_answers/quick_answers_controller_unittest.cc", "../browser/ui/ash/quick_answers/quick_answers_state_ash_unittest.cc", "../browser/ui/ash/quick_answers/quick_answers_ui_controller_unittest.cc", + "../browser/ui/ash/quick_answers/ui/magic_boost_user_consent_view_unittest.cc", "../browser/ui/ash/quick_answers/ui/quick_answers_view_unittest.cc", "../browser/ui/ash/quick_answers/ui/user_consent_view_unittest.cc", "../browser/ui/views/frame/immersive_mode_controller_chromeos_unittest.cc",
diff --git a/chrome/test/base/ash/extension_js_browser_test.cc b/chrome/test/base/ash/extension_js_browser_test.cc index a2cea46..1d32863 100644 --- a/chrome/test/base/ash/extension_js_browser_test.cc +++ b/chrome/test/base/ash/extension_js_browser_test.cc
@@ -5,8 +5,14 @@ #include "chrome/test/base/ash/extension_js_browser_test.h" #include <memory> +#include <optional> +#include <string> +#include <utility> #include <vector> +#include "base/command_line.h" +#include "base/containers/contains.h" +#include "base/files/file_path.h" #include "base/functional/callback.h" #include "base/json/json_reader.h" #include "base/strings/strcat.h" @@ -117,7 +123,10 @@ } if (!libs_loaded_) { - BuildJavascriptLibraries(&scripts); + if (!BuildJavascriptLibraries(&scripts)) { + ADD_FAILURE() << "Failed to build JavaScript libraries"; + return false; + } libs_loaded_ = true; }
diff --git a/chrome/test/base/ash/javascript_browser_test.cc b/chrome/test/base/ash/javascript_browser_test.cc index 949f4ad..7cadc93 100644 --- a/chrome/test/base/ash/javascript_browser_test.cc +++ b/chrome/test/base/ash/javascript_browser_test.cc
@@ -4,7 +4,11 @@ #include "chrome/test/base/ash/javascript_browser_test.h" +#include <optional> + +#include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/logging.h" #include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" @@ -44,59 +48,78 @@ library_search_paths_.push_back(source_root_directory); } -// TODO(dtseng): Make this return bool (success/failure) and remove ASSERt_TRUE -// calls. -void JavaScriptBrowserTest::BuildJavascriptLibraries( +bool JavaScriptBrowserTest::BuildJavascriptLibraries( std::vector<std::u16string>* libraries) { base::ScopedAllowBlockingForTesting allow_blocking; - ASSERT_TRUE(libraries != nullptr); - std::vector<base::FilePath>::iterator user_libraries_iterator; - for (user_libraries_iterator = user_libraries_.begin(); - user_libraries_iterator != user_libraries_.end(); - ++user_libraries_iterator) { + if (!libraries) { + LOG(ERROR) << "BuildJavascriptLibraries called with null libraries pointer"; + return false; + } + + // Path processing logic. + auto resolve_mapped_or_absolute_path = + [&](const base::FilePath& path) -> std::optional<base::FilePath> { + const auto components = path.GetComponents(); + if (components.front() == FILE_PATH_LITERAL("ROOT_GEN_DIR")) { + base::FilePath exe_dir; + if (!base::PathService::Get(base::DIR_EXE, &exe_dir)) { + LOG(ERROR) << "Failed to get base::DIR_EXE for ROOT_GEN_DIR: " + << path.value(); + return std::nullopt; + } + base::FilePath abs_path = exe_dir.AppendASCII("gen"); + for (size_t i = 1; i < components.size(); ++i) { + abs_path = abs_path.Append(components[i]); + } + return abs_path.NormalizePathSeparators(); + } + return path.IsAbsolute() ? std::optional<base::FilePath>(path) + : std::nullopt; + }; + + for (const auto& library_path : user_libraries_) { std::string library_content; base::FilePath library_absolute_path; - std::vector<base::FilePath::StringType> components = - user_libraries_iterator->GetComponents(); - if (components[0] == FILE_PATH_LITERAL("ROOT_GEN_DIR")) { - base::FilePath exe_dir; - base::PathService::Get(base::DIR_EXE, &exe_dir); - library_absolute_path = exe_dir.AppendASCII("gen"); - for (size_t i = 1; i < components.size(); i++) - library_absolute_path = library_absolute_path.Append(components[i]); - library_absolute_path = library_absolute_path.NormalizePathSeparators(); - ASSERT_TRUE( - base::ReadFileToString(library_absolute_path, &library_content)) - << user_libraries_iterator->value(); - } else if (user_libraries_iterator->IsAbsolute()) { - library_absolute_path = *user_libraries_iterator; - ASSERT_TRUE( - base::ReadFileToString(library_absolute_path, &library_content)) - << user_libraries_iterator->value(); - } else { - bool ok = false; - std::vector<base::FilePath>::iterator library_search_path_iterator; - for (library_search_path_iterator = library_search_paths_.begin(); - library_search_path_iterator != library_search_paths_.end(); - ++library_search_path_iterator) { - library_absolute_path = base::MakeAbsoluteFilePath( - library_search_path_iterator->Append(*user_libraries_iterator)); - ok = base::ReadFileToString(library_absolute_path, &library_content); - if (ok) - break; + bool found = false; + // Resolve mapped paths or absolute paths. + if (auto resolved_path = resolve_mapped_or_absolute_path(library_path)) { + if (base::ReadFileToString(*resolved_path, &library_content)) { + library_absolute_path = *resolved_path; + found = true; + } else { + LOG(ERROR) << "Failed to read resolved library file: " + << resolved_path->value(); } - ASSERT_TRUE(ok) << "User library not found: " - << user_libraries_iterator->value(); + } else { + // Handling relative paths. + for (const auto& search_path : library_search_paths_) { + base::FilePath candidate = + base::MakeAbsoluteFilePath(search_path.Append(library_path)); + if (base::ReadFileToString(candidate, &library_content)) { + library_absolute_path = candidate; + found = true; + break; + } + } } - library_content.append(";\n"); + + if (!found) { + LOG(ERROR) << "Failed to load JS library: " << library_path.value(); + return false; + } // This magic code puts filenames in stack traces. - library_content.append("//# sourceURL="); - library_content.append( - net::FilePathToFileURL(library_absolute_path).spec()); - library_content.append("\n"); + std::string source_url = + "//# sourceURL=" + + net::FilePathToFileURL(library_absolute_path).spec() + "\n"; + + library_content.reserve(library_content.size() + source_url.size() + 3); + library_content += ";\n"; + library_content += source_url; + libraries->push_back(base::UTF8ToUTF16(library_content)); } + return true; } std::u16string JavaScriptBrowserTest::BuildRunTestJSCall(
diff --git a/chrome/test/base/ash/javascript_browser_test.h b/chrome/test/base/ash/javascript_browser_test.h index e7abe59..42ee60f3 100644 --- a/chrome/test/base/ash/javascript_browser_test.h +++ b/chrome/test/base/ash/javascript_browser_test.h
@@ -8,6 +8,7 @@ #include <string> #include <vector> +#include "base/files/file_path.h" #include "base/values.h" #include "chrome/test/base/in_process_browser_test.h" @@ -29,9 +30,9 @@ // InProcessBrowserTest overrides. void SetUpOnMainThread() override; - // Builds a vector of strings of all added javascript libraries suitable for - // execution by subclasses. - void BuildJavascriptLibraries(std::vector<std::u16string>* libraries); + // Builds a vector of javascript library contents for execution. Returns true + // on success, false if any library fails to load or if `libraries` is null. + bool BuildJavascriptLibraries(std::vector<std::u16string>* libraries); // Builds a string with a call to the runTest JS function, passing the // given |is_async|, |test_name| and its |args|.
diff --git a/chrome/test/base/ash/web_ui_browser_test.cc b/chrome/test/base/ash/web_ui_browser_test.cc index 71cb380f..ff565509 100644 --- a/chrome/test/base/ash/web_ui_browser_test.cc +++ b/chrome/test/base/ash/web_ui_browser_test.cc
@@ -7,10 +7,12 @@ #include <stddef.h> #include <string> +#include <utility> #include <vector> #include "base/command_line.h" #include "base/containers/contains.h" +#include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/lazy_instance.h" @@ -451,7 +453,10 @@ : test_handler_->GetRenderFrameHostForTest(); if (!base::Contains(libraries_preloaded_for_frames_, frame_for_libraries->GetGlobalId())) { - BuildJavascriptLibraries(&libraries); + if (!BuildJavascriptLibraries(&libraries)) { + ADD_FAILURE() << "Failed to build JavaScript libraries"; + return false; + } if (!preload_frame) { content = base::JoinString(libraries, u"\n"); libraries.clear();
diff --git a/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js b/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js index 51954b7..2c5fd85 100644 --- a/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js +++ b/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js
@@ -86,6 +86,13 @@ } function deleteTokenBeforeGetToken() { + if (/Android/.test(navigator.userAgent)) { + // Skip this test on Android because the underlying call to + // com.google.android.gms.iid.InstanceID.deleteToken() always succeeds, + // even for non-existent tokens. + chrome.test.succeed("skipped"); + return; + } chrome.instanceID.deleteToken( {"authorizedEntity": "1", "scope": "GCM"}, function() {
diff --git a/chrome/test/data/webui/settings/settings_focus_test.cc b/chrome/test/data/webui/settings/settings_focus_test.cc index 90cbc09..9197b1ae 100644 --- a/chrome/test/data/webui/settings/settings_focus_test.cc +++ b/chrome/test/data/webui/settings/settings_focus_test.cc
@@ -75,7 +75,13 @@ base::test::ScopedFeatureList scoped_feature_list_; }; -IN_PROC_BROWSER_TEST_F(SettingsGlicPageFocusTest, GlicPageFocus) { +// TODO(crbug.com/424864547): Investigate flakiness and enable on Mac64. +#if BUILDFLAG(IS_MAC) +#define MAYBE_GlicPageFocus DISABLED_GlicPageFocus +#else +#define MAYBE_GlicPageFocus GlicPageFocus +#endif // BUILDFLAG(IS_MAC) +IN_PROC_BROWSER_TEST_F(SettingsGlicPageFocusTest, MAYBE_GlicPageFocus) { RunTest("settings/glic_page_focus_test.js", "mocha.run()"); } #endif
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index 20e8e106..1577052 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -16318.0.0-1069562 \ No newline at end of file +16318.0.0-1069568 \ No newline at end of file
diff --git a/clank b/clank index b62d38f..4e7cdb1 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit b62d38ffc9bd9bfc6011eabe6143a4a40e45bd9a +Subproject commit 4e7cdb1917bb21338750038cdc8389b643f96cd5
diff --git a/components/autofill/ios/common/features.h b/components/autofill/ios/common/features.h index 589101cb..4f978c5 100644 --- a/components/autofill/ios/common/features.h +++ b/components/autofill/ios/common/features.h
@@ -65,6 +65,9 @@ // expanding forms. BASE_DECLARE_FEATURE(kAutofillRefillForFormsIos); +// Reports JS errors that occur upon handling form submission in the renderer. +BASE_DECLARE_FEATURE(kAutofillReportFormSubmissionErrors); + // Makes the autofill and password infobars sticky on iOS. The sticky infobar // sticks there until navigating from an explicit user gesture (e.g. reload or // load a new page from the omnibox). This includes the infobar UI and the
diff --git a/components/autofill/ios/common/features.mm b/components/autofill/ios/common/features.mm index 41c5adee..82003ee 100644 --- a/components/autofill/ios/common/features.mm +++ b/components/autofill/ios/common/features.mm
@@ -64,6 +64,12 @@ "AutofillRefillForFormsIos", base::FEATURE_DISABLED_BY_DEFAULT); +// LINT.IfChange(autofill_report_form_submission_errors) +BASE_FEATURE(kAutofillReportFormSubmissionErrors, + "AutofillReportFormSubmissionErrors", + base::FEATURE_DISABLED_BY_DEFAULT); +// LINT.ThenChange(/components/autofill/ios/form_util/resources/autofill_form_features.ts:autofill_report_form_submission_errors) + BASE_FEATURE(kAutofillStickyInfobarIos, "AutofillStickyInfobarIos", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/autofill/ios/form_util/autofill_form_features_injector.mm b/components/autofill/ios/form_util/autofill_form_features_injector.mm index 92542b4..60724a7c 100644 --- a/components/autofill/ios/form_util/autofill_form_features_injector.mm +++ b/components/autofill/ios/form_util/autofill_form_features_injector.mm
@@ -63,6 +63,11 @@ ->SetAutofillDedupeFormSubmission( web_frame, base::FeatureList::IsEnabled(kAutofillDedupeFormSubmission)); + + AutofillFormFeaturesJavaScriptFeature::GetInstance() + ->SetAutofillReportFormSubmissionErrors( + web_frame, + base::FeatureList::IsEnabled(kAutofillReportFormSubmissionErrors)); } AutofillFormFeaturesInjector::~AutofillFormFeaturesInjector() = default;
diff --git a/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm b/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm index a20323a..df07fb8 100644 --- a/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm +++ b/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm
@@ -63,7 +63,8 @@ autofill::features::kAutofillAcrossIframesIos, autofill::features::kAutofillAcrossIframesIosThrottling, kAutofillCorrectUserEditedBitInParsedField, - kAutofillAllowDefaultPreventedSubmission, kAutofillDedupeFormSubmission}, + kAutofillAllowDefaultPreventedSubmission, kAutofillDedupeFormSubmission, + kAutofillReportFormSubmissionErrors}, /* disabled_features= */ {}); AutofillFormFeaturesInjector injector(&fake_web_state_, @@ -86,7 +87,9 @@ u"__gCrWeb.autofill_form_features." u"setAutofillAllowDefaultPreventedSubmission(true);", u"__gCrWeb.autofill_form_features." - u"setAutofillDedupeFormSubmission(true);")); + u"setAutofillDedupeFormSubmission(true);", + u"__gCrWeb.autofill_form_features." + u"setAutofillReportFormSubmissionErrors(true);")); } }
diff --git a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h index 8051bba..a05a8af 100644 --- a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h +++ b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h
@@ -49,6 +49,10 @@ // Enables/disables deduping form submission for Autofill. void SetAutofillDedupeFormSubmission(web::WebFrame* frame, bool enabled); + // Enables/disables reporting form submission errors. + void SetAutofillReportFormSubmissionErrors(web::WebFrame* frame, + bool enabled); + private: friend class base::NoDestructor<AutofillFormFeaturesJavaScriptFeature>;
diff --git a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm index 41747cb..6c37edb 100644 --- a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm +++ b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm
@@ -97,4 +97,12 @@ base::Value::List().Append(enabled)); } +// Enables/disables reporting form submission errors. +void AutofillFormFeaturesJavaScriptFeature:: + SetAutofillReportFormSubmissionErrors(web::WebFrame* frame, bool enabled) { + frame->CallJavaScriptFunction( + "autofill_form_features.setAutofillReportFormSubmissionErrors", + base::Value::List().Append(enabled)); +} + } // namespace autofill
diff --git a/components/autofill/ios/form_util/form_activity_tab_helper.h b/components/autofill/ios/form_util/form_activity_tab_helper.h index b33a4cab..909c4b47 100644 --- a/components/autofill/ios/form_util/form_activity_tab_helper.h +++ b/components/autofill/ios/form_util/form_activity_tab_helper.h
@@ -24,7 +24,7 @@ "Autofill.iOS.FormSubmission.IsProgrammatic"; inline constexpr char kFormSubmissionOutcomeHistogram[] = - "Autofill.iOS.FormSubmission.Outcome"; + "Autofill.iOS.FormSubmission.OutcomeV2"; inline constexpr char kInvalidSubmittedFormReasonHistogram[] = "Autofill.iOS.FormSubmission.Outcome.InvalidFormReason";
diff --git a/components/autofill/ios/form_util/form_activity_tab_helper.mm b/components/autofill/ios/form_util/form_activity_tab_helper.mm index bdd78504..89259de 100644 --- a/components/autofill/ios/form_util/form_activity_tab_helper.mm +++ b/components/autofill/ios/form_util/form_activity_tab_helper.mm
@@ -72,7 +72,13 @@ // Autofill.iOS.FormSubmission.Outcome.InvalidFormReason to investigate the // cause. kFormExtractionFailure = 7, - kMaxValue = kFormExtractionFailure, + // There was an error while handling the form submission event in the + // renderer. + kRendererError = 8, + // There was an error while handling the form submission event in the renderer + // but the error couldn't be parsed. + kUnparsedRendererError = 9, + kMaxValue = kUnparsedRendererError, }; // LINT.ThenChange(//tools/metrics/histograms/metadata/autofill/enums.xml:FormSubmissionOutcomeIOS) @@ -219,6 +225,25 @@ observers_.RemoveObserver(observer); } +void HandleSubmissionError(const base::Value::Dict& message) { + const std::string* error_stack = message.FindString("errorStack"); + const std::string* error_message = message.FindString("errorMessage"); + std::optional<bool> is_programmatic = + message.FindBool("programmaticSubmission"); + + if (!error_stack || !error_message || !is_programmatic) { + RecordFormSubmissionOutcome(FormSubmissionOutcome::kUnparsedRendererError); + return; + } + + SCOPED_CRASH_KEY_STRING256("FormSubmissionError", "msg", *error_message); + SCOPED_CRASH_KEY_STRING1024("FormSubmissionError", "stack", *error_stack); + + base::debug::DumpWithoutCrashing(); + + RecordFormSubmissionOutcome(FormSubmissionOutcome::kRendererError); +} + void FormActivityTabHelper::OnFormMessageReceived( web::WebState* web_state, const web::ScriptMessage& message) { @@ -227,9 +252,11 @@ return; } - RecordMetrics(message.body()->GetDict()); + const auto& message_body = message.body()->GetDict(); - const std::string* command = message.body()->GetDict().FindString("command"); + RecordMetrics(message_body); + + const std::string* command = message_body.FindString("command"); if (!command) { DLOG(WARNING) << "JS message parameter not found: command"; } else if (*command == "form.submit") { @@ -238,6 +265,8 @@ HandleFormActivity(web_state, message); } else if (*command == "form.removal") { HandleFormRemoval(web_state, message); + } else if (*command == "form.submit.error") { + HandleSubmissionError(message_body); } }
diff --git a/components/autofill/ios/form_util/resources/autofill_form_features.ts b/components/autofill/ios/form_util/resources/autofill_form_features.ts index 36f532d3..561e7b92 100644 --- a/components/autofill/ios/form_util/resources/autofill_form_features.ts +++ b/components/autofill/ios/form_util/resources/autofill_form_features.ts
@@ -55,6 +55,13 @@ let autofillDedupeFormSubmission: boolean = false; // LINT.ThenChange(//components/autofill/ios/common/features.mm:autofill_dedupe_form_submission) +// LINT.IfChange(autofill_report_form_submission_errors) +/** + * Reports JS errors that occur upon handling form submission in the renderer. + */ +let autofillReportFormSubmissionErrors: boolean = false; +// LINT.ThenChange(//components/autofill/ios/common/features.mm:autofill_report_form_submission_errors) + /** * @see autofillAcrossIframes */ @@ -140,6 +147,20 @@ return autofillDedupeFormSubmission; } +/** + * @see autofillReportFormSubmissionErrors + */ +function setAutofillReportFormSubmissionErrors(enabled: boolean): void { + autofillReportFormSubmissionErrors = enabled; +} + +/** + * @see autofillReportFormSubmissionErrors + */ +function isAutofillReportFormSubmissionErrorsEnabled(): boolean { + return autofillReportFormSubmissionErrors; +} + // Expose globally via `gCrWeb` instead of `export` to ensure state (feature // on/off) is maintained across imports. gCrWebLegacy.autofill_form_features = { @@ -155,4 +176,6 @@ isAutofillAllowDefaultPreventedSubmission, setAutofillDedupeFormSubmission, isAutofillDedupeFormSubmissionEnabled, + setAutofillReportFormSubmissionErrors, + isAutofillReportFormSubmissionErrorsEnabled, };
diff --git a/components/autofill/ios/form_util/resources/form.ts b/components/autofill/ios/form_util/resources/form.ts index a678639..32c6ef7 100644 --- a/components/autofill/ios/form_util/resources/form.ts +++ b/components/autofill/ios/form_util/resources/form.ts
@@ -325,7 +325,7 @@ } // Send the form data to the browser. -function formSubmitted( +function formSubmittedInternal( form: HTMLFormElement, messageHandler: string, programmaticSubmission: boolean, @@ -358,6 +358,65 @@ sendWebKitMessage(messageHandler, message); } +/** + * Sends the form data to the browser. Errors that are caught via the try/catch + * are reported to the browser. This is done before the error bubbles above + * `formSubmitted()` so the generic JS errors wrapper doesn't intercept the + * error before this custom error handler. + * + * @param form The form that was submitted. + * @param messageHandler The name of the message handler to send the message to. + * @param programmaticSubmission True if the form submission is programmatic. + * @includeRemoteFrameToken True if the remote frame token should be included + * in the payload of the message sent to the browser. + */ +function formSubmitted( + form: HTMLFormElement, + messageHandler: string, + programmaticSubmission: boolean, + includeRemoteFrameToken: boolean = false, + ): void { + try { + formSubmittedInternal( + form, messageHandler, programmaticSubmission, includeRemoteFrameToken); + } catch (error) { + if (gCrWebLegacy.autofill_form_features + .isAutofillReportFormSubmissionErrorsEnabled()) { + reportFormSubmissionError(error, programmaticSubmission, messageHandler); + } else { + // Just let the error go through if not reported. + throw error; + } + } +} + +/** + * Reports a form submission error to the browser. + * @param error Object that holds information on the error. + * @param programmaticSubmission True if the submission that errored was + * programmatic. + * @param handler The name of the handler to send the error message to. + */ +function reportFormSubmissionError( + error: any, programmaticSubmission: boolean, handler: string) { + let errorMessage = ''; + let errorStack = ''; + if (error && error instanceof Error) { + errorMessage = error.message; + if (error.stack) { + errorStack = error.stack; + } + } + + const message = { + command: 'form.submit.error', + errorStack, + errorMessage, + programmaticSubmission, + }; + sendWebKitMessage(handler, message); +} + gCrWebLegacy.form = { wasEditedByUser, isFormControlElement, @@ -370,4 +429,5 @@ getFormElementFromRendererId, fieldWasEditedByUser, formSubmitted, + reportFormSubmissionError, };
diff --git a/components/autofill/ios/form_util/resources/form_handlers.ts b/components/autofill/ios/form_util/resources/form_handlers.ts index d087a66..d9e167b 100644 --- a/components/autofill/ios/form_util/resources/form_handlers.ts +++ b/components/autofill/ios/form_util/resources/form_handlers.ts
@@ -207,6 +207,26 @@ } /** + * A wrapper around `submitHandler()` that catches and reports errors that + * happen before calling gCrWebLegacy.form.formSubmitted(). + */ +function submitHandlerWithErrorWrapper(evt: Event): void { + try { + submitHandler(evt); + } catch (error) { + if (gCrWebLegacy.autofill_form_features + .isAutofillReportFormSubmissionErrorsEnabled()) { + gCrWebLegacy.form.reportFormSubmissionError( + error, /*programmaticSubmission=*/ false, + /*handler=*/ NATIVE_MESSAGE_HANDLER); + } else { + // Just let the error go through if not reported. + throw error; + } + } +} + +/** * Schedules `messages` to be sent back-to-back after `delay` and with a `delay` * between them. * @@ -279,7 +299,7 @@ */ document.addEventListener('keyup', formActivity, false); document.addEventListener( - 'submit', submitHandler, + 'submit', submitHandlerWithErrorWrapper, shouldListenToFormSubmissionEventsInCaptureMode()); /**
diff --git a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolver.java b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolver.java index 7a5e2eba..7596394 100644 --- a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolver.java +++ b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolver.java
@@ -4,12 +4,12 @@ package org.chromium.components.browser_ui.util; -import android.content.res.Resources; import android.util.Pair; +import org.chromium.base.TimeUtils; import org.chromium.build.annotations.NullMarked; +import android.content.res.Resources; -import java.time.Clock; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.concurrent.TimeUnit; @@ -25,38 +25,27 @@ new Pair<>(ChronoUnit.MONTHS, R.plurals.time_last_accessed_months), new Pair<>(ChronoUnit.WEEKS, R.plurals.time_last_accessed_weeks), new Pair<>(ChronoUnit.DAYS, R.plurals.time_last_accessed_days)); - private final Resources mResources; - private final Clock mClock; - - /** - * @param resources Used to resolve strings against. - * @param clock Used to get the current time. - */ - public TimeTextResolver(Resources resources, Clock clock) { - mResources = resources; - mClock = clock; - } /** * @param timestamp The timestamp in milliseconds. - * @return Simple text representing the time since a given timestamp. - * Represented as one of: Today > Yesterday > x days ago > x weeks ago > x months ago. + * @return Simple text representing the time since a given timestamp. Represented as one of: + * Today > Yesterday > x days ago > x weeks ago > x months ago. */ // duration.getSeconds should be toSeconds after api 31. @SuppressWarnings("JavaDurationGetSecondsToToSeconds") - public String resolveTimeAgoText(long timestamp) { + public static String resolveTimeAgoText(Resources resources, long timestamp) { List<Pair<ChronoUnit, Integer>> selectedChronoUnitAndPluralRes = LAST_ACCESSED_CHRONO_UNIT_AND_PLURAL_RES; - long nowMillis = mClock.millis(); + long nowMillis = TimeUtils.currentTimeMillis(); int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(nowMillis - timestamp); for (Pair<ChronoUnit, Integer> pair : selectedChronoUnitAndPluralRes) { int count = (int) (seconds / pair.first.getDuration().getSeconds()); if (count >= 1) { - return mResources.getQuantityString(pair.second, count, count); + return resources.getQuantityString(pair.second, count, count); } } - return mResources.getString(R.string.time_last_accessed_today); + return resources.getString(R.string.time_last_accessed_today); } }
diff --git a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolverUnitTest.java b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolverUnitTest.java index 0e6a04a..277718b 100644 --- a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolverUnitTest.java +++ b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/TimeTextResolverUnitTest.java
@@ -5,7 +5,6 @@ package org.chromium.components.browser_ui.util; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; import android.app.Activity; @@ -13,33 +12,29 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; - -import org.chromium.base.test.BaseRobolectricTestRunner; - -import java.time.Clock; import org.robolectric.Robolectric; +import org.chromium.base.TimeUtils; +import org.chromium.base.test.BaseRobolectricTestRunner; + /** Unit tests for {@link TimeTextResolver}. */ @RunWith(BaseRobolectricTestRunner.class) public class TimeTextResolverUnitTest { private Activity mActivity; @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Mock private Clock mClock; TimeTextResolver mResolver; @Before public void setup() { - when(mClock.millis()).thenReturn(0L); mActivity = Robolectric.setupActivity(Activity.class); - mResolver = new TimeTextResolver(mActivity.getResources(), mClock); } private String fromSecondsAgo(long secondsDelta) { - return mResolver.resolveTimeAgoText(0 - 1000 * secondsDelta); + return TimeTextResolver.resolveTimeAgoText( + mActivity.getResources(), TimeUtils.currentTimeMillis() - 1000 * secondsDelta); } @Test
diff --git a/components/data_sharing/public/features.cc b/components/data_sharing/public/features.cc index 88c2e879..e519b66 100644 --- a/components/data_sharing/public/features.cc +++ b/components/data_sharing/public/features.cc
@@ -10,11 +10,15 @@ namespace data_sharing::features { namespace { const char kDataSharingDefaultUrl[] = "https://www.google.com/chrome/tabshare/"; -const char kLearnMoreSharedTabGroupPageDefaultUrl[] = "https://support.google.com/chrome/?p=chrome_collaboration"; -const char kLearnAboutBlockedAccountsDefaultUrl[] = "https://support.google.com/accounts/answer/6388749"; -const char kActivityLogsDefaultUrl[] = "https://myactivity.google.com/product/chrome_shared_tab_group_activity?utm_source=chrome_collab"; +const char kLearnMoreSharedTabGroupPageDefaultUrl[] = + "https://support.google.com/chrome/?p=chrome_collaboration"; +const char kLearnAboutBlockedAccountsDefaultUrl[] = + "https://support.google.com/accounts/answer/6388749"; +const char kActivityLogsDefaultUrl[] = + "https://myactivity.google.com/product/" + "chrome_shared_tab_group_activity?utm_source=chrome_collab"; -} +} // namespace BASE_FEATURE(kCollaborationAutomotive, "CollaborationAutomotive", @@ -32,6 +36,10 @@ "DataSharingJoinOnly", base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kDataSharingEnableUpdateChromeUI, + "DataSharingEnableUpdateChromeUI", + base::FEATURE_DISABLED_BY_DEFAULT); + bool IsDataSharingFunctionalityEnabled() { return base::FeatureList::IsEnabled( data_sharing::features::kDataSharingFeature) ||
diff --git a/components/data_sharing/public/features.h b/components/data_sharing/public/features.h index 26270b4..334e9d76 100644 --- a/components/data_sharing/public/features.h +++ b/components/data_sharing/public/features.h
@@ -29,6 +29,13 @@ // autopush server environment is set. BASE_DECLARE_FEATURE(kDataSharingNonProductionEnvironment); +// Feature flag to show UI that prompts the user to update Chrome when a version +// mismatch is detected. +// Note: Do not clean up this feature as it is meant to be used in unforeseen +// situations as a kill switch in future from finch when the shared tab groups +// feature becomes incompatible for the current chrome client. +BASE_DECLARE_FEATURE(kDataSharingEnableUpdateChromeUI); + extern const base::FeatureParam<std::string> kDataSharingURL; extern const base::FeatureParam<std::string> kLearnMoreSharedTabGroupPageURL; extern const base::FeatureParam<std::string> kLearnAboutBlockedAccountsURL;
diff --git a/components/input/child_frame_input_helper.cc b/components/input/child_frame_input_helper.cc index fd95023..fcd8c03 100644 --- a/components/input/child_frame_input_helper.cc +++ b/components/input/child_frame_input_helper.cc
@@ -4,6 +4,7 @@ #include "components/input/child_frame_input_helper.h" +#include "base/trace_event/trace_event.h" #include "components/input/features.h" #include "components/input/render_input_router.h" #include "components/input/render_widget_host_input_event_router.h"
diff --git a/components/input/mouse_wheel_event_queue.cc b/components/input/mouse_wheel_event_queue.cc index b3b56de..a30575b 100644 --- a/components/input/mouse_wheel_event_queue.cc +++ b/components/input/mouse_wheel_event_queue.cc
@@ -6,6 +6,7 @@ #include "base/functional/bind.h" #include "base/metrics/histogram_macros.h" +#include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "ui/events/base_event_utils.h" #include "ui/events/blink/web_input_event_traits.h"
diff --git a/components/input/render_input_router.cc b/components/input/render_input_router.cc index fea9f03..a9e2404 100644 --- a/components/input/render_input_router.cc +++ b/components/input/render_input_router.cc
@@ -12,6 +12,7 @@ #include "base/location.h" #include "base/memory/ptr_util.h" #include "base/task/single_thread_task_runner.h" +#include "base/trace_event/trace_event.h" #include "base/tracing/protos/chrome_track_event.pbzero.h" #include "cc/input/browser_controls_offset_tag_modifications.h" #include "components/input/input_constants.h"
diff --git a/components/input/render_widget_targeter.cc b/components/input/render_widget_targeter.cc index 091c237ab..5bf5c12 100644 --- a/components/input/render_widget_targeter.cc +++ b/components/input/render_widget_targeter.cc
@@ -11,6 +11,7 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/rand_util.h" +#include "base/trace_event/trace_event.h" #include "components/input/render_widget_host_view_input.h" #include "third_party/blink/public/common/input/web_input_event.h" #include "ui/events/blink/blink_event_util.h"
diff --git a/components/lens/lens_overlay_metrics.cc b/components/lens/lens_overlay_metrics.cc index 5a557ea..009aedf5 100644 --- a/components/lens/lens_overlay_metrics.cc +++ b/components/lens/lens_overlay_metrics.cc
@@ -522,4 +522,9 @@ "Lens.Overlay.SidePanel.SelectedMoreInfoMenuOption", menu_option); } +void RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult result) { + base::UmaHistogramEnumeration("Lens.Overlay.TextDirectiveResult", result); +} + } // namespace lens
diff --git a/components/lens/lens_overlay_metrics.h b/components/lens/lens_overlay_metrics.h index e37e63a..95eb54b 100644 --- a/components/lens/lens_overlay_metrics.h +++ b/components/lens/lens_overlay_metrics.h
@@ -55,6 +55,19 @@ bool searchbox_shown_ = false; }; +// LINT.IfChange(LensOverlayTextDirectiveResult) +enum class LensOverlayTextDirectiveResult { + // The text directive was found on the page. + kFoundOnPage = 0, + // The URL with a text directive was opened in a new tab because it did not + // match the current page. + kOpenedInNewTab = 1, + // The text directive was not found on the page. + kNotFoundOnPage = 2, + kMaxValue = kNotFoundOnPage, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/lens/enums.xml:LensOverlayTextDirectiveResult) + // Returns the string representation of the invocation source. std::string InvocationSourceToString( LensOverlayInvocationSource invocation_source); @@ -174,6 +187,10 @@ void RecordSidePanelMenuOptionSelected( lens::LensOverlaySidePanelMenuOption menu_option); +// Records the result of handling a text directive in the Lens Overlay. +void RecordHandleTextDirectiveResult( + lens::LensOverlayTextDirectiveResult result); + } // namespace lens #endif // COMPONENTS_LENS_LENS_OVERLAY_METRICS_H_
diff --git a/components/omnibox/composebox/composebox_query_controller.h b/components/omnibox/composebox/composebox_query_controller.h index 670947d1..53b37da 100644 --- a/components/omnibox/composebox/composebox_query_controller.h +++ b/components/omnibox/composebox/composebox_query_controller.h
@@ -19,9 +19,9 @@ ComposeboxQueryController() = default; virtual ~ComposeboxQueryController(); - // Session management. - void NotifySessionStarted(); - void NotifySessionAbandoned(); + // Session management. Virtual for testing. + virtual void NotifySessionStarted(); + virtual void NotifySessionAbandoned(); SessionState session_state() { return session_state_; }
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn index 903587e..cc06cd3f 100644 --- a/components/optimization_guide/content/browser/BUILD.gn +++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -33,15 +33,6 @@ "//services/metrics/public/cpp:ukm_builders", "//services/network/public/cpp", ] - if (build_with_tflite_lib) { - public_deps += [ - "//components/optimization_guide/core:machine_learning", - "//third_party/tflite", - "//third_party/tflite:tflite_public_headers", - "//third_party/tflite_support", - "//third_party/tflite_support:tflite_support_proto", - ] - } configs += [ "//build/config/compiler:wexit_time_destructors" ] }
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn index c1f1c62d..28bcc7c2 100644 --- a/components/optimization_guide/core/BUILD.gn +++ b/components/optimization_guide/core/BUILD.gn
@@ -7,10 +7,7 @@ if (is_android) { import("//build/config/android/rules.gni") } -import("//build/buildflag_header.gni") import("//components/optimization_guide/features.gni") -import("//third_party/mediapipe/features.gni") -import("//third_party/tflite/features.gni") # bloomfilter is isolated because of the allowlisted murmurhash3 dep. source_set("bloomfilter") { @@ -50,98 +47,12 @@ visibility = [ ":*" ] } -static_library("model_executor") { - sources = [ - "inference/execution_status.cc", - "inference/execution_status.h", - "inference/model_executor.h", - "inference/model_handler.h", - ] - if (build_with_tflite_lib) { - sources += [ - "inference/base_model_executor.h", - "inference/base_model_executor_helpers.h", - "inference/bert_model_executor.cc", - "inference/bert_model_executor.h", - "inference/bert_model_handler.cc", - "inference/bert_model_handler.h", - "inference/model_execution_timeout_watchdog.cc", - "inference/model_execution_timeout_watchdog.h", - "inference/model_validator.cc", - "inference/model_validator.h", - "inference/signature_model_executor.h", - "inference/tflite_model_executor.h", - ] - } - - if (build_with_mediapipe_lib) { - assert(build_with_tflite_lib) - - sources += [ - "inference/mediapipe_text_model_executor.cc", - "inference/mediapipe_text_model_executor.h", - ] - } - - public_deps = [ - "//components/optimization_guide:machine_learning_tflite_buildflags", - "//components/optimization_guide/proto:optimization_guide_proto", - "//third_party/re2", - ] - - deps = [ - ":core_util", - ":features", - ":prediction", - "//base", - "//net", - "//url", - ] - - configs += [ "//build/config/compiler:wexit_time_destructors" ] - - if (build_with_tflite_lib) { - public_deps += [ - "//components/optimization_guide/core:machine_learning", - "//third_party/abseil-cpp:absl", - "//third_party/tflite", - "//third_party/tflite:tflite_public_headers", - "//third_party/tflite_support", - "//third_party/tflite_support:tflite_support_proto", - ] - deps += [ ":machine_learning" ] - } - - if (build_with_mediapipe_lib) { - assert(build_with_tflite_lib) - - public_deps += [ "//third_party/mediapipe" ] - } -} - mojom("interfaces") { sources = [ "optimization_guide_common.mojom" ] webui_module_path = "/" public_deps = [ "//mojo/public/mojom/base" ] } -if (build_with_tflite_lib) { - static_library("machine_learning") { - sources = [ - "tflite_op_resolver.cc", - "tflite_op_resolver.h", - ] - deps = [ - ":features", - "//components/optimization_guide:machine_learning_tflite_buildflags", - "//third_party/tflite", - "//third_party/tflite:buildflags", - "//third_party/tflite:tflite_public_headers", - ] - configs += [ "//build/config/compiler:wexit_time_destructors" ] - } -} - component("features") { sources = [ "feature_registry/enterprise_policy_registry.cc", @@ -271,7 +182,7 @@ "//ui/accessibility:ax_enums_mojo", "//url", ] - visibility = [ ":*" ] + visibility = [ "./*" ] } source_set("model_execution") { @@ -385,7 +296,6 @@ ] deps += [ ":core_util", - ":model_executor", ":on_device_model_execution_proto_generator", ":prediction", "//components/component_updater", @@ -417,7 +327,6 @@ ":filters", ":hints", ":model_execution", - ":model_executor", ":prediction", "//components/autofill/core/common", "//components/leveldb_proto", @@ -428,12 +337,9 @@ ] if (build_with_tflite_lib) { public_deps += [ - "//components/optimization_guide/core:machine_learning", - "//third_party/abseil-cpp:absl", - "//third_party/tflite", - "//third_party/tflite:tflite_public_headers", - "//third_party/tflite_support", - "//third_party/tflite_support:tflite_support_proto", + "//components/optimization_guide/core/inference", + + # TODO: crbug.com/421262905 - Don't provide compression_utils "//third_party/zlib/google:compression_utils", ] } @@ -550,27 +456,7 @@ "//base", "//components/optimization_guide/proto:optimization_guide_proto", ] -} - -static_library("inference_test_support") { - testonly = true - sources = [ - "inference/test_model_executor.cc", - "inference/test_model_executor.h", - "inference/test_model_handler.h", - ] - deps = [ - ":model_executor", - "//base", - ] - if (build_with_tflite_lib) { - sources += [ - "inference/test_tflite_model_executor.cc", - "inference/test_tflite_model_executor.h", - "inference/test_tflite_model_handler.h", - ] - deps += [ "//third_party/abseil-cpp:absl" ] - } + visibility = [ "./*" ] } static_library("model_execution_test_support") { @@ -631,9 +517,12 @@ ":delivery_test_support", ":filters_test_support", ":hints_test_support", - ":inference_test_support", ":model_execution_test_support", ] + if (build_with_tflite_lib) { + public_deps += + [ "//components/optimization_guide/core/inference:test_support" ] + } } if (is_ios) { @@ -745,43 +634,6 @@ ] } -source_set("inference_unit_tests") { - testonly = true - sources = [ "inference/model_handler_unittest.cc" ] - deps = [ - ":delivery_test_support", - ":inference_test_support", - ":model_executor", - "//base", - "//base/test:test_support", - "//components/optimization_guide/proto:optimization_guide_proto", - "//testing/gtest", - ] - if (build_with_tflite_lib) { - sources += [ - "inference/bert_model_executor_unittest.cc", - "inference/model_validator_unittest.cc", - "inference/tflite_model_executor_unittest.cc", - ] - deps += [ - ":core_util", - ":features", - ":prediction", - "//third_party/abseil-cpp:absl", - "//third_party/tflite", - "//third_party/tflite:tflite_public_headers", - "//third_party/tflite_support", - "//third_party/tflite_support:tflite_support_proto", - ] - } - if (build_with_mediapipe_lib) { - assert(build_with_tflite_lib) - - sources += [ "mediapipe_text_model_executor_unittest.cc" ] - deps += [ "//third_party/mediapipe" ] - } -} - source_set("model_execution_unit_tests") { testonly = true sources = [ @@ -865,14 +717,12 @@ "optimization_guide_switches_unittest.cc", "optimization_guide_util_unittest.cc", ] - deps = [ ":core", ":delivery_unit_tests", ":features", ":filters_unit_tests", ":hints_unit_tests", - ":inference_unit_tests", ":model_execution_unit_tests", "//base", "//base/test:test_support", @@ -884,6 +734,9 @@ "//testing/gmock", "//testing/gtest", ] + if (build_with_tflite_lib) { + deps += [ "//components/optimization_guide/core/inference:unit_tests" ] + } } bundle_data("unit_tests_bundle_data") {
diff --git a/components/optimization_guide/core/inference/BUILD.gn b/components/optimization_guide/core/inference/BUILD.gn new file mode 100644 index 0000000..9f98ca3 --- /dev/null +++ b/components/optimization_guide/core/inference/BUILD.gn
@@ -0,0 +1,123 @@ +# 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/optimization_guide/features.gni") +import("//third_party/mediapipe/features.gni") + +assert(build_with_tflite_lib) + +visibility = [ "//components/optimization_guide/core/*" ] + +static_library("op_resolver") { + # TODO: crbug.com/422803641 - Move these files to this directory. + sources = [ + "../tflite_op_resolver.cc", + "../tflite_op_resolver.h", + ] + deps = [ + "//components/optimization_guide:machine_learning_tflite_buildflags", + "//components/optimization_guide/core:features", + "//third_party/tflite", + "//third_party/tflite:buildflags", + "//third_party/tflite:tflite_public_headers", + ] + configs += [ "//build/config/compiler:wexit_time_destructors" ] + visibility += [ "//services/passage_embeddings/*" ] +} + +static_library("inference") { + sources = [ + "base_model_executor.h", + "base_model_executor_helpers.h", + "bert_model_executor.cc", + "bert_model_executor.h", + "bert_model_handler.cc", + "bert_model_handler.h", + "execution_status.cc", + "execution_status.h", + "model_execution_timeout_watchdog.cc", + "model_execution_timeout_watchdog.h", + "model_executor.h", + "model_handler.h", + "model_validator.cc", + "model_validator.h", + "signature_model_executor.h", + "tflite_model_executor.h", + ] + public_deps = [ + ":op_resolver", + "//components/optimization_guide:machine_learning_tflite_buildflags", + "//components/optimization_guide/proto:optimization_guide_proto", + "//third_party/abseil-cpp:absl", + "//third_party/re2", + "//third_party/tflite", + "//third_party/tflite:tflite_public_headers", + "//third_party/tflite_support", + "//third_party/tflite_support:tflite_support_proto", + ] + deps = [ + "//base", + "//components/optimization_guide/core:core_util", + "//components/optimization_guide/core:features", + "//components/optimization_guide/core:prediction", + "//net", + "//url", + ] + configs += [ "//build/config/compiler:wexit_time_destructors" ] + if (build_with_mediapipe_lib) { + sources += [ + "mediapipe_text_model_executor.cc", + "mediapipe_text_model_executor.h", + ] + public_deps += [ "//third_party/mediapipe" ] + } +} + +static_library("test_support") { + testonly = true + sources = [ + "test_model_executor.cc", + "test_model_executor.h", + "test_model_handler.h", + "test_tflite_model_executor.cc", + "test_tflite_model_executor.h", + "test_tflite_model_handler.h", + ] + deps = [ + ":inference", + "//base", + "//third_party/abseil-cpp:absl", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "bert_model_executor_unittest.cc", + "model_handler_unittest.cc", + "model_validator_unittest.cc", + "tflite_model_executor_unittest.cc", + ] + deps = [ + ":inference", + ":test_support", + "//base", + "//base/test:test_support", + "//components/optimization_guide/core:core_util", + "//components/optimization_guide/core:delivery_test_support", + "//components/optimization_guide/core:features", + "//components/optimization_guide/core:prediction", + "//components/optimization_guide/proto:optimization_guide_proto", + "//testing/gtest", + "//third_party/abseil-cpp:absl", + "//third_party/tflite", + "//third_party/tflite:tflite_public_headers", + "//third_party/tflite_support", + "//third_party/tflite_support:tflite_support_proto", + ] + if (build_with_mediapipe_lib) { + sources += [ "mediapipe_text_model_executor_unittest.cc" ] + deps += [ "//third_party/mediapipe" ] + } +}
diff --git a/components/page_content_annotations/core/BUILD.gn b/components/page_content_annotations/core/BUILD.gn index 8b707fb..5176040 100644 --- a/components/page_content_annotations/core/BUILD.gn +++ b/components/page_content_annotations/core/BUILD.gn
@@ -56,7 +56,6 @@ "//components/omnibox/common", "//components/optimization_guide/core", "//components/optimization_guide/core:features", - "//components/optimization_guide/core:model_executor", "//components/search", "//components/search_engines", "//services/metrics/public/cpp:metrics_cpp",
diff --git a/components/safe_browsing/content/browser/BUILD.gn b/components/safe_browsing/content/browser/BUILD.gn index 1fc1f8d..2c5ebe0 100644 --- a/components/safe_browsing/content/browser/BUILD.gn +++ b/components/safe_browsing/content/browser/BUILD.gn
@@ -173,7 +173,6 @@ "//base:base", "//base/test:test_support", "//components/optimization_guide/core", - "//components/optimization_guide/core:model_executor", "//components/optimization_guide/core:test_support", "//components/optimization_guide/proto:optimization_guide_proto", "//components/safe_browsing:buildflags",
diff --git a/components/safe_browsing/content/browser/notification_content_detection/BUILD.gn b/components/safe_browsing/content/browser/notification_content_detection/BUILD.gn index 4bee9255..f84f0be 100644 --- a/components/safe_browsing/content/browser/notification_content_detection/BUILD.gn +++ b/components/safe_browsing/content/browser/notification_content_detection/BUILD.gn
@@ -60,7 +60,6 @@ "//base:base", "//base/test:test_support", "//components/optimization_guide/core", - "//components/optimization_guide/core:model_executor", "//components/optimization_guide/core:test_support", "//components/optimization_guide/proto:optimization_guide_proto", "//components/permissions:test_support",
diff --git a/components/saved_tab_groups/components_unittests.filter b/components/saved_tab_groups/components_unittests.filter index ba4f59c4..1e8dee1 100644 --- a/components/saved_tab_groups/components_unittests.filter +++ b/components/saved_tab_groups/components_unittests.filter
@@ -13,3 +13,4 @@ *TabGroupSyncMetricsLoggerTest.* *TabGroupSyncServiceAndroidTest.* *TabGroupSyncServiceTest.* +*VersioningMessageControllerImplTest.*
diff --git a/components/saved_tab_groups/internal/BUILD.gn b/components/saved_tab_groups/internal/BUILD.gn index 27a9ed71..dc3d2e2 100644 --- a/components/saved_tab_groups/internal/BUILD.gn +++ b/components/saved_tab_groups/internal/BUILD.gn
@@ -22,6 +22,8 @@ "tab_group_sync_service_impl.h", "tab_group_type_observer.cc", "tab_group_type_observer.h", + "versioning_message_controller_impl.cc", + "versioning_message_controller_impl.h", ] deps = [ ":tab_group_sync_bridge", @@ -48,6 +50,8 @@ sources += [ "android/tab_group_sync_service_android.cc", "android/tab_group_sync_service_android.h", + "android/versioning_message_controller_android.cc", + "android/versioning_message_controller_android.h", ] deps += [ @@ -81,7 +85,10 @@ if (is_android) { android_library("java") { - sources = [ "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java" ] + sources = [ + "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java", + "android/java/src/org/chromium/components/tab_group_sync/VersioningMessageControllerImpl.java", + ] deps = [ "//base:base_java", @@ -103,7 +110,10 @@ "//chrome/browser", ] - sources = [ "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java" ] + sources = [ + "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java", + "android/java/src/org/chromium/components/tab_group_sync/VersioningMessageControllerImpl.java", + ] } android_library("native_java_unittests_java") { @@ -168,6 +178,7 @@ "tab_group_sync_metrics_logger_unittest.cc", "tab_group_sync_service_unittest.cc", "tab_group_type_observer_unittest.cc", + "versioning_message_controller_impl_unittest.cc", ] deps = [ ":internal",
diff --git a/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java index 30c55c0..0de6851 100644 --- a/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java +++ b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java
@@ -16,6 +16,7 @@ import org.junit.Assert; import org.mockito.ArgumentCaptor; +import org.chromium.base.Callback; import org.chromium.base.ThreadUtils; import org.chromium.base.Token; import org.chromium.components.tab_groups.TabGroupColorId; @@ -41,11 +42,11 @@ private static final int LOCAL_TAB_ID_1 = 2; private static final int LOCAL_TAB_ID_2 = 4; private static final int TAB_POSITION = 3; - private TabGroupSyncService mService; private TabGroupSyncService.Observer mObserver; private final ArgumentCaptor<SavedTabGroup> mTabGroupCaptor = ArgumentCaptor.forClass(SavedTabGroup.class); + private Callback<Boolean> mCallback; @CalledByNative private TabGroupSyncServiceAndroidUnitTest() { @@ -257,4 +258,25 @@ public void testUpdateArchivalStatus(String uuid, boolean archivalStatus) { mService.updateArchivalStatus(uuid, archivalStatus); } + + @CalledByNative + public void testShouldShowMessageUiAsync() { + mCallback = mock(Callback.class); + mService.getVersioningMessageController() + .shouldShowMessageUiAsync( + MessageType.VERSION_OUT_OF_DATE_INSTANT_MESSAGE, mCallback); + verify(mCallback).onResult(true); + } + + @CalledByNative + public void testOnMessageUiShown() { + mService.getVersioningMessageController() + .onMessageUiShown(MessageType.VERSION_OUT_OF_DATE_INSTANT_MESSAGE); + } + + @CalledByNative + public void testOnMessageUiDismissed() { + mService.getVersioningMessageController() + .onMessageUiDismissed(MessageType.VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE); + } }
diff --git a/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java index 42e0d6e..e6bbc87 100644 --- a/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java +++ b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
@@ -257,6 +257,11 @@ } @Override + public VersioningMessageController getVersioningMessageController() { + return TabGroupSyncServiceImplJni.get().getVersioningMessageController(mNativePtr, this); + } + + @Override public void setCollaborationAvailableInFinderForTesting(String collaborationId) { if (mNativePtr == 0) return; TabGroupSyncServiceImplJni.get() @@ -454,6 +459,9 @@ String syncTabGroupId, boolean archivalStatus); + VersioningMessageController getVersioningMessageController( + long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller); + void setCollaborationAvailableInFinderForTesting( long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller,
diff --git a/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageControllerImpl.java b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageControllerImpl.java new file mode 100644 index 0000000..bafaf89 --- /dev/null +++ b/components/saved_tab_groups/internal/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageControllerImpl.java
@@ -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. + +package org.chromium.components.tab_group_sync; + +import org.jni_zero.CalledByNative; +import org.jni_zero.JNINamespace; +import org.jni_zero.NativeMethods; + +import org.chromium.base.Callback; +import org.chromium.build.annotations.NullMarked; + +@JNINamespace("tab_groups") +@NullMarked +public class VersioningMessageControllerImpl implements VersioningMessageController { + private long mNativePtr; + + @CalledByNative + private static VersioningMessageControllerImpl create(long nativePtr) { + return new VersioningMessageControllerImpl(nativePtr); + } + + private VersioningMessageControllerImpl(long nativePtr) { + mNativePtr = nativePtr; + } + + @Override + public void shouldShowMessageUiAsync(@MessageType int messageType, Callback<Boolean> callback) { + if (mNativePtr == 0) { + callback.onResult(false); + return; + } + VersioningMessageControllerImplJni.get() + .shouldShowMessageUiAsync(mNativePtr, this, messageType, callback); + } + + @Override + public void onMessageUiShown(@MessageType int messageType) { + if (mNativePtr == 0) return; + VersioningMessageControllerImplJni.get().onMessageUiShown(mNativePtr, this, messageType); + } + + @Override + public void onMessageUiDismissed(@MessageType int messageType) { + if (mNativePtr == 0) return; + VersioningMessageControllerImplJni.get() + .onMessageUiDismissed(mNativePtr, this, messageType); + } + + @CalledByNative + private void clearNativePtr() { + mNativePtr = 0; + } + + @NativeMethods + interface Natives { + void shouldShowMessageUiAsync( + long nativeVersioningMessageControllerAndroid, + VersioningMessageControllerImpl caller, + int messageType, + Callback<Boolean> callback); + + void onMessageUiShown( + long nativeVersioningMessageControllerAndroid, + VersioningMessageControllerImpl caller, + int messageType); + + void onMessageUiDismissed( + long nativeVersioningMessageControllerAndroid, + VersioningMessageControllerImpl caller, + int messageType); + } +}
diff --git a/components/saved_tab_groups/internal/android/tab_group_sync_service_android.cc b/components/saved_tab_groups/internal/android/tab_group_sync_service_android.cc index a13c26ef..137e1bf 100644 --- a/components/saved_tab_groups/internal/android/tab_group_sync_service_android.cc +++ b/components/saved_tab_groups/internal/android/tab_group_sync_service_android.cc
@@ -12,6 +12,7 @@ #include "base/android/scoped_java_ref.h" #include "base/memory/ptr_util.h" #include "base/memory/scoped_refptr.h" +#include "components/saved_tab_groups/internal/android/versioning_message_controller_android.h" #include "components/saved_tab_groups/public/android/tab_group_sync_conversions_bridge.h" #include "components/saved_tab_groups/public/android/tab_group_sync_conversions_utils.h" #include "components/saved_tab_groups/public/collaboration_finder.h" @@ -59,6 +60,9 @@ : tab_group_sync_service_(tab_group_sync_service) { DCHECK(tab_group_sync_service_); JNIEnv* env = base::android::AttachCurrentThread(); + versioning_messaging_controller_android_ = + std::make_unique<VersioningMessageControllerAndroid>( + tab_group_sync_service_->GetVersioningMessageController()); java_obj_.Reset(env, Java_TabGroupSyncServiceImpl_create( env, reinterpret_cast<int64_t>(this)) .obj()); @@ -443,6 +447,13 @@ j_archival_status); } +ScopedJavaLocalRef<jobject> +TabGroupSyncServiceAndroid::GetVersioningMessageController( + JNIEnv* env, + const JavaParamRef<jobject>& j_caller) { + return versioning_messaging_controller_android_->GetJavaObject(env); +} + void TabGroupSyncServiceAndroid::SetCollaborationAvailableInFinderForTesting( JNIEnv* env, const JavaParamRef<jobject>& j_caller,
diff --git a/components/saved_tab_groups/internal/android/tab_group_sync_service_android.h b/components/saved_tab_groups/internal/android/tab_group_sync_service_android.h index e4be94b7..eecc47c1 100644 --- a/components/saved_tab_groups/internal/android/tab_group_sync_service_android.h +++ b/components/saved_tab_groups/internal/android/tab_group_sync_service_android.h
@@ -9,6 +9,7 @@ #include "base/android/scoped_java_ref.h" #include "base/memory/raw_ptr.h" #include "base/supports_user_data.h" +#include "components/saved_tab_groups/internal/android/versioning_message_controller_android.h" #include "components/saved_tab_groups/public/tab_group_sync_service.h" using base::android::JavaParamRef; @@ -167,6 +168,10 @@ const JavaParamRef<jstring>& j_sync_group_id, const jboolean j_archival_status); + ScopedJavaLocalRef<jobject> GetVersioningMessageController( + JNIEnv* env, + const JavaParamRef<jobject>& j_caller); + void SetCollaborationAvailableInFinderForTesting( JNIEnv* env, const JavaParamRef<jobject>& j_caller, @@ -177,6 +182,10 @@ // TabGroupSyncServiceImpl.java. ScopedJavaGlobalRef<jobject> java_obj_; + // Owns the JNI bridge for VersioningMessageController. + std::unique_ptr<VersioningMessageControllerAndroid> + versioning_messaging_controller_android_; + // Not owned. This is safe because the JNI bridge is destroyed and the // native pointer in Java is cleared whenever TabGroupSyncService is // destroyed. Hence there will be no subsequent unsafe calls to native.
diff --git a/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc b/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc index 6f442a2..6d7790d 100644 --- a/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc +++ b/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc
@@ -13,6 +13,7 @@ #include "base/test/task_environment.h" #include "components/saved_tab_groups/public/android/tab_group_sync_conversions_bridge.h" #include "components/saved_tab_groups/public/android/tab_group_sync_conversions_utils.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" #include "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h" #include "components/saved_tab_groups/test_support/saved_tab_group_test_utils.h" #include "components/sync/test/test_matchers.h" @@ -51,6 +52,19 @@ static_cast<int>(arg.position()) == position; } +class MockVersioningMessageController : public VersioningMessageController { + public: + MockVersioningMessageController() = default; + ~MockVersioningMessageController() override = default; + + MOCK_METHOD(void, + ShouldShowMessageUiAsync, + (MessageType, base::OnceCallback<void(bool)>), + (override)); + MOCK_METHOD(void, OnMessageUiShown, (MessageType), (override)); + MOCK_METHOD(void, OnMessageUiDismissed, (MessageType), (override)); +}; + } // namespace class TabGroupSyncServiceAndroidTest : public testing::Test { @@ -66,6 +80,8 @@ } void CreateBridge() { + EXPECT_CALL(tab_group_sync_service_, GetVersioningMessageController()) + .WillOnce(Return(&versioning_message_controller_)); EXPECT_CALL(tab_group_sync_service_, AddObserver(_)); bridge_ = std::make_unique<TabGroupSyncServiceAndroid>(&tab_group_sync_service_); @@ -87,6 +103,7 @@ base::android::ScopedJavaLocalRef<jobject> j_service_; base::android::ScopedJavaGlobalRef<jobject> j_test_; LocalTabGroupID test_tab_group_id_ = base::Token(4, 5); + MockVersioningMessageController versioning_message_controller_; }; TEST_F(TabGroupSyncServiceAndroidTest, OnInitialized) { @@ -433,4 +450,38 @@ env, j_test_, j_uuid, true); } +TEST_F(TabGroupSyncServiceAndroidTest, ShouldShowMessageUiAsync) { + auto* env = AttachCurrentThread(); + EXPECT_CALL( + versioning_message_controller_, + ShouldShowMessageUiAsync(VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + _)) + .WillOnce( + testing::WithArg<1>([](base::OnceCallback<void(bool)> callback) { + std::move(callback).Run(true); + })); + Java_TabGroupSyncServiceAndroidUnitTest_testShouldShowMessageUiAsync(env, + j_test_); +} + +TEST_F(TabGroupSyncServiceAndroidTest, OnMessageUiShown) { + auto* env = AttachCurrentThread(); + EXPECT_CALL(versioning_message_controller_, + OnMessageUiShown(VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_INSTANT_MESSAGE)) + .Times(1); + Java_TabGroupSyncServiceAndroidUnitTest_testOnMessageUiShown(env, j_test_); +} + +TEST_F(TabGroupSyncServiceAndroidTest, OnMessageUiDismissed) { + auto* env = AttachCurrentThread(); + EXPECT_CALL(versioning_message_controller_, + OnMessageUiDismissed(VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE)) + .Times(1); + Java_TabGroupSyncServiceAndroidUnitTest_testOnMessageUiDismissed(env, + j_test_); +} + } // namespace tab_groups
diff --git a/components/saved_tab_groups/internal/android/versioning_message_controller_android.cc b/components/saved_tab_groups/internal/android/versioning_message_controller_android.cc new file mode 100644 index 0000000..8e9f38f --- /dev/null +++ b/components/saved_tab_groups/internal/android/versioning_message_controller_android.cc
@@ -0,0 +1,67 @@ +// 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 "components/saved_tab_groups/internal/android/versioning_message_controller_android.h" + +#include "base/android/callback_android.h" +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "components/saved_tab_groups/internal/jni_headers/VersioningMessageControllerImpl_jni.h" + +using base::android::JavaParamRef; + +namespace tab_groups { +using MessageType = VersioningMessageController::MessageType; + +VersioningMessageControllerAndroid::VersioningMessageControllerAndroid( + VersioningMessageController* versioning_message_controller) + : versioning_message_controller_(versioning_message_controller) { + JNIEnv* env = base::android::AttachCurrentThread(); + java_obj_.Reset(env, Java_VersioningMessageControllerImpl_create( + env, reinterpret_cast<int64_t>(this)) + .obj()); +} + +VersioningMessageControllerAndroid::~VersioningMessageControllerAndroid() { + Java_VersioningMessageControllerImpl_clearNativePtr( + base::android::AttachCurrentThread(), java_obj_); +} + +base::android::ScopedJavaLocalRef<jobject> +VersioningMessageControllerAndroid::GetJavaObject(JNIEnv* env) { + return base::android::ScopedJavaLocalRef<jobject>(java_obj_); +} + +void VersioningMessageControllerAndroid::ShouldShowMessageUiAsync( + JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type, + const JavaParamRef<jobject>& j_callback) { + MessageType message_type = static_cast<MessageType>(j_message_type); + base::OnceCallback<void(bool)> callback = base::BindOnce( + [](const base::android::JavaRef<jobject>& j_callback, bool result) { + base::android::RunBooleanCallbackAndroid(j_callback, result); + }, + base::android::ScopedJavaGlobalRef<jobject>(j_callback)); + versioning_message_controller_->ShouldShowMessageUiAsync(message_type, + std::move(callback)); +} + +void VersioningMessageControllerAndroid::OnMessageUiShown( + JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type) { + MessageType message_type = static_cast<MessageType>(j_message_type); + versioning_message_controller_->OnMessageUiShown(message_type); +} + +void VersioningMessageControllerAndroid::OnMessageUiDismissed( + JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type) { + MessageType message_type = static_cast<MessageType>(j_message_type); + versioning_message_controller_->OnMessageUiDismissed(message_type); +} + +} // namespace tab_groups
diff --git a/components/saved_tab_groups/internal/android/versioning_message_controller_android.h b/components/saved_tab_groups/internal/android/versioning_message_controller_android.h new file mode 100644 index 0000000..495557f --- /dev/null +++ b/components/saved_tab_groups/internal/android/versioning_message_controller_android.h
@@ -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. + +#ifndef COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_ANDROID_VERSIONING_MESSAGE_CONTROLLER_ANDROID_H_ +#define COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_ANDROID_VERSIONING_MESSAGE_CONTROLLER_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/memory/raw_ptr.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" + +using base::android::JavaParamRef; + +namespace tab_groups { + +// Helper class responsible for bridging the VersioningMessageController between +// C++ and Java. +class VersioningMessageControllerAndroid { + public: + explicit VersioningMessageControllerAndroid( + VersioningMessageController* versioning_message_controller); + ~VersioningMessageControllerAndroid(); + + base::android::ScopedJavaLocalRef<jobject> GetJavaObject(JNIEnv* env); + + void ShouldShowMessageUiAsync(JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type, + const JavaParamRef<jobject>& j_callback); + void OnMessageUiShown(JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type); + void OnMessageUiDismissed(JNIEnv* env, + const JavaParamRef<jobject>& j_caller, + jint j_message_type); + + private: + // A reference to the Java counterpart of this class. See + // VersioningMessageControllerImpl.java. + base::android::ScopedJavaGlobalRef<jobject> java_obj_; + + // Not owned. This is safe because the JNI bridge is destroyed and the + // native pointer in Java is cleared whenever native is destroyed. Hence there + // will be no subsequent unsafe calls to native. + raw_ptr<VersioningMessageController> versioning_message_controller_; +}; + +} // namespace tab_groups + +#endif // COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_ANDROID_VERSIONING_MESSAGE_CONTROLLER_ANDROID_H_
diff --git a/components/saved_tab_groups/internal/tab_group_sync_service_impl.cc b/components/saved_tab_groups/internal/tab_group_sync_service_impl.cc index 428f16c..0423827f 100644 --- a/components/saved_tab_groups/internal/tab_group_sync_service_impl.cc +++ b/components/saved_tab_groups/internal/tab_group_sync_service_impl.cc
@@ -31,6 +31,7 @@ #include "components/saved_tab_groups/internal/sync_data_type_configuration.h" #include "components/saved_tab_groups/internal/tab_group_sync_bridge_mediator.h" #include "components/saved_tab_groups/internal/tab_group_sync_coordinator_impl.h" +#include "components/saved_tab_groups/internal/versioning_message_controller_impl.h" #include "components/saved_tab_groups/public/collaboration_finder.h" #include "components/saved_tab_groups/public/features.h" #include "components/saved_tab_groups/public/pref_names.h" @@ -191,7 +192,10 @@ collaboration_finder_(std::move(collaboration_finder)), logger_(logger), pref_service_(pref_service), - opt_guide_(optimization_guide_decider) { + opt_guide_(optimization_guide_decider), + versioning_message_controller_( + std::make_unique<VersioningMessageControllerImpl>(pref_service_, + this)) { if (shared_tab_group_account_configuration) { shared_tab_group_account_data_bridge_ = std::make_unique<SharedTabGroupAccountDataSyncBridge>( @@ -259,6 +263,12 @@ : had_shared_tab_groups_on_startup_; } +VersioningMessageController* +TabGroupSyncServiceImpl::GetVersioningMessageController() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return versioning_message_controller_.get(); +} + void TabGroupSyncServiceImpl::AddObserver( TabGroupSyncService::Observer* observer) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/components/saved_tab_groups/internal/tab_group_sync_service_impl.h b/components/saved_tab_groups/internal/tab_group_sync_service_impl.h index f1fdcfcf..d1e6ce2 100644 --- a/components/saved_tab_groups/internal/tab_group_sync_service_impl.h +++ b/components/saved_tab_groups/internal/tab_group_sync_service_impl.h
@@ -33,6 +33,7 @@ #include "components/saved_tab_groups/public/tab_group_sync_metrics_logger.h" #include "components/saved_tab_groups/public/tab_group_sync_service.h" #include "components/saved_tab_groups/public/types.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/sync/base/collaboration_id.h" @@ -187,6 +188,7 @@ std::unique_ptr<std::vector<SavedTabGroup>> TakeSharedTabGroupsAvailableAtStartupForMessaging() override; bool HadSharedTabGroupsLastSession(bool open_shared_tab_groups) override; + VersioningMessageController* GetVersioningMessageController() override; void OnLastTabClosed(const SavedTabGroup& saved_tab_group) override; void AddObserver(TabGroupSyncService::Observer* observer) override; @@ -464,6 +466,10 @@ std::map<base::Uuid, TabGroupSharingTimeoutInfo> tab_group_sharing_timeout_info_; + // The versioning message controller which is responsible for business logic + // related to shared tab groups versioning related messages. + std::unique_ptr<VersioningMessageController> versioning_message_controller_; + base::ScopedObservation<signin::IdentityManager, signin::IdentityManager::Observer> identity_manager_observation_{this};
diff --git a/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc b/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc index deba3efa..5bd55468 100644 --- a/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc +++ b/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc
@@ -204,6 +204,13 @@ base::Value::Dict()); pref_service_.registry()->RegisterDictionaryPref( prefs::kLocallyClosedRemoteTabGroupIds, base::Value::Dict()); + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionUpdatedMessage, false); + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, false); + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + false); auto metrics_logger = std::make_unique<TabGroupSyncMetricsLoggerImpl>(&device_info_tracker_); @@ -406,6 +413,7 @@ TEST_F(TabGroupSyncServiceTest, ServiceConstruction) { EXPECT_TRUE(tab_group_sync_service_->GetSavedTabGroupControllerDelegate()); + EXPECT_TRUE(tab_group_sync_service_->GetVersioningMessageController()); } TEST_F(TabGroupSyncServiceTest, GetAllGroups) {
diff --git a/components/saved_tab_groups/internal/versioning_message_controller_impl.cc b/components/saved_tab_groups/internal/versioning_message_controller_impl.cc new file mode 100644 index 0000000..0d51344 --- /dev/null +++ b/components/saved_tab_groups/internal/versioning_message_controller_impl.cc
@@ -0,0 +1,149 @@ +// 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 "components/saved_tab_groups/internal/versioning_message_controller_impl.h" + +#include "base/functional/bind.h" +#include "components/data_sharing/public/features.h" +#include "components/prefs/pref_service.h" +#include "components/saved_tab_groups/public/pref_names.h" +#include "components/saved_tab_groups/public/tab_group_sync_service.h" + +namespace tab_groups { +namespace { +bool IsVersionOutOfDate() { + return base::FeatureList::IsEnabled( + data_sharing::features::kDataSharingEnableUpdateChromeUI); +} + +bool MeetsEligibilityCriteria( + PrefService* pref_service, + VersioningMessageController::MessageType message_type) { + switch (message_type) { + case VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_INSTANT_MESSAGE: + return !pref_service->GetBoolean( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage); + case VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE: + return !pref_service->GetBoolean( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage); + case VersioningMessageController::MessageType::VERSION_UPDATED_MESSAGE: + return !pref_service->GetBoolean( + prefs::kDataSharingHasShownVersionUpdatedMessage); + default: + return false; + } +} + +} // namespace + +VersioningMessageControllerImpl::VersioningMessageControllerImpl( + PrefService* pref_service, + TabGroupSyncService* tab_group_sync_service) + : pref_service_(pref_service), + tab_group_sync_service_(tab_group_sync_service) { + ResetMessagePrefsOnStartup(); + tab_group_sync_service_->AddObserver(this); +} + +VersioningMessageControllerImpl::~VersioningMessageControllerImpl() { + tab_group_sync_service_->RemoveObserver(this); +} + +void VersioningMessageControllerImpl::ResetMessagePrefsOnStartup() { + // On startup, reset the message prefs based on the state of the + // feature flag. + if (IsVersionOutOfDate()) { + // If versioning is disabled, reset the pref used for showing the re-enabled + // message shown in the enabled state. + pref_service_->SetBoolean(prefs::kDataSharingHasShownVersionUpdatedMessage, + false); + } else { + // If versioning is enabled, reset the prefs used for showing the instant + // and persistent messages shown in the disabled state. + pref_service_->SetBoolean( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, false); + pref_service_->SetBoolean( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + false); + } +} + +void VersioningMessageControllerImpl::ShouldShowMessageUiAsync( + MessageType message_type, + base::OnceCallback<void(bool)> callback) { + if (!is_initialized_) { + pending_callbacks_.push_back(base::BindOnce( + &VersioningMessageControllerImpl::ShouldShowMessageUiAsync, + weak_ptr_factory_.GetWeakPtr(), message_type, std::move(callback))); + + return; + } + + std::move(callback).Run(ShouldShowMessageUi(message_type)); +} + +bool VersioningMessageControllerImpl::ShouldShowMessageUi( + MessageType message_type) { + CHECK(is_initialized_); + bool is_version_out_of_date = IsVersionOutOfDate(); + bool had_open_shared_tab_groups = + tab_group_sync_service_->HadSharedTabGroupsLastSession( + /*open_shared_tab_groups=*/true); + bool had_shared_tab_groups = + tab_group_sync_service_->HadSharedTabGroupsLastSession( + /*open_shared_tab_groups=*/false); + // Determines if the message can be shown, considering past displays or + // dismissals. + bool meets_eligibility_criteria = + MeetsEligibilityCriteria(pref_service_, message_type); + + switch (message_type) { + case MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE: + return is_version_out_of_date && had_open_shared_tab_groups && + meets_eligibility_criteria; + case MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE: + return is_version_out_of_date && had_shared_tab_groups && + meets_eligibility_criteria; + case MessageType::VERSION_UPDATED_MESSAGE: + return !is_version_out_of_date && meets_eligibility_criteria; + default: + return false; + } +} + +void VersioningMessageControllerImpl::OnMessageUiShown( + MessageType message_type) { + switch (message_type) { + case MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE: + pref_service_->SetBoolean( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, true); + break; + case MessageType::VERSION_UPDATED_MESSAGE: + pref_service_->SetBoolean( + prefs::kDataSharingHasShownVersionUpdatedMessage, true); + break; + default: + break; + } +} + +void VersioningMessageControllerImpl::OnMessageUiDismissed( + MessageType message_type) { + if (message_type == MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE) { + pref_service_->SetBoolean( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, true); + } +} + +void VersioningMessageControllerImpl::OnInitialized() { + is_initialized_ = true; + for (auto& callback : pending_callbacks_) { + std::move(callback).Run(); + } + pending_callbacks_.clear(); +} + +} // namespace tab_groups
diff --git a/components/saved_tab_groups/internal/versioning_message_controller_impl.h b/components/saved_tab_groups/internal/versioning_message_controller_impl.h new file mode 100644 index 0000000..dd9137f --- /dev/null +++ b/components/saved_tab_groups/internal/versioning_message_controller_impl.h
@@ -0,0 +1,56 @@ +// 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 COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_VERSIONING_MESSAGE_CONTROLLER_IMPL_H_ +#define COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_VERSIONING_MESSAGE_CONTROLLER_IMPL_H_ + +#include "base/functional/callback.h" +#include "base/functional/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list_types.h" +#include "components/prefs/pref_service.h" +#include "components/saved_tab_groups/public/tab_group_sync_service.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" + +namespace tab_groups { + +class TabGroupSyncService; + +class VersioningMessageControllerImpl : public VersioningMessageController, + public TabGroupSyncService::Observer { + public: + VersioningMessageControllerImpl(PrefService* pref_service, + TabGroupSyncService* tab_group_sync_service); + ~VersioningMessageControllerImpl() override; + + // VersioningMessageController implementation. + void ShouldShowMessageUiAsync( + MessageType message_type, + base::OnceCallback<void(bool)> callback) override; + void OnMessageUiShown(MessageType message_type) override; + void OnMessageUiDismissed(MessageType message_type) override; + + // TabGroupSyncService::Observer implementation. + void OnInitialized() override; + + private: + bool ShouldShowMessageUi(MessageType message_type); + void ResetMessagePrefsOnStartup(); + + raw_ptr<PrefService> pref_service_; + raw_ptr<TabGroupSyncService> tab_group_sync_service_; + + // Callbacks that are waiting for initialization to complete. + std::vector<base::OnceClosure> pending_callbacks_; + + // Whether the TabGroupSyncService has been initialized. + bool is_initialized_ = false; + + base::WeakPtrFactory<VersioningMessageControllerImpl> weak_ptr_factory_{this}; +}; + +} // namespace tab_groups + +#endif // COMPONENTS_SAVED_TAB_GROUPS_INTERNAL_VERSIONING_MESSAGE_CONTROLLER_IMPL_H_
diff --git a/components/saved_tab_groups/internal/versioning_message_controller_impl_unittest.cc b/components/saved_tab_groups/internal/versioning_message_controller_impl_unittest.cc new file mode 100644 index 0000000..01934d4 --- /dev/null +++ b/components/saved_tab_groups/internal/versioning_message_controller_impl_unittest.cc
@@ -0,0 +1,271 @@ +// 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 "components/saved_tab_groups/internal/versioning_message_controller_impl.h" + +#include "base/functional/callback.h" +#include "base/run_loop.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "components/data_sharing/public/features.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "components/saved_tab_groups/public/pref_names.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" +#include "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tab_groups { +using MessageType = VersioningMessageController::MessageType; + +namespace { + +class VersioningMessageControllerImplTest : public testing::Test { + public: + VersioningMessageControllerImplTest() { + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, false); + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + false); + pref_service_.registry()->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionUpdatedMessage, false); + + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/false, + /*has_open_shared_tab_groups=*/false); + controller_ = std::make_unique<VersioningMessageControllerImpl>( + &pref_service_, &mock_tab_group_sync_service_); + } + + void SetPref(const std::string& pref_name, bool value) { + pref_service_.SetBoolean(pref_name, value); + } + + bool GetPref(const std::string& pref_name) { + return pref_service_.GetBoolean(pref_name); + } + + void SetTabGroupSyncServiceExpectation(bool had_shared_tab_groups, + bool had_open_shared_tab_groups) { + EXPECT_CALL(mock_tab_group_sync_service_, + HadSharedTabGroupsLastSession(/*open_shared_tab_groups=*/false)) + .WillRepeatedly(testing::Return(had_shared_tab_groups)); + EXPECT_CALL(mock_tab_group_sync_service_, + HadSharedTabGroupsLastSession(/*open_shared_tab_groups=*/true)) + .WillRepeatedly(testing::Return(had_open_shared_tab_groups)); + } + + void InitializeController() { controller_->OnInitialized(); } + + void ExpectMessageUiShouldBeShown(MessageType message_type, + bool expected_value) { + base::RunLoop run_loop; + controller_->ShouldShowMessageUiAsync( + message_type, base::BindOnce( + [](base::RunLoop* run_loop, bool expected_value, + bool actual_value) { + EXPECT_EQ(expected_value, actual_value); + run_loop->Quit(); + }, + &run_loop, expected_value)); + run_loop.Run(); + } + + protected: + base::test::TaskEnvironment task_environment_; + TestingPrefServiceSimple pref_service_; + MockTabGroupSyncService mock_tab_group_sync_service_; + std::unique_ptr<VersioningMessageControllerImpl> controller_; + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(VersioningMessageControllerImplTest, + TestPrefs_Startup_VersionOutOfDate) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, true); + SetPref(prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + true); + SetPref(prefs::kDataSharingHasShownVersionUpdatedMessage, true); + InitializeController(); + controller_ = std::make_unique<VersioningMessageControllerImpl>( + &pref_service_, &mock_tab_group_sync_service_); + + EXPECT_TRUE( + GetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage)); + EXPECT_TRUE(GetPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage)); + EXPECT_FALSE(GetPref(prefs::kDataSharingHasShownVersionUpdatedMessage)); +} + +TEST_F(VersioningMessageControllerImplTest, TestPrefs_Startup_VersionUpdated) { + scoped_feature_list_.InitAndDisableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, true); + SetPref(prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + true); + SetPref(prefs::kDataSharingHasShownVersionUpdatedMessage, true); + InitializeController(); + controller_ = std::make_unique<VersioningMessageControllerImpl>( + &pref_service_, &mock_tab_group_sync_service_); + + EXPECT_FALSE( + GetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage)); + EXPECT_FALSE(GetPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage)); + EXPECT_TRUE(GetPref(prefs::kDataSharingHasShownVersionUpdatedMessage)); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_InstantMessage_VersionOutOfDate) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/true, + /*has_open_shared_tab_groups=*/true); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + true); +} + +TEST_F( + VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_InstantMessage_VersionOutOfDate_NoSharedGroups) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/false, + /*has_open_shared_tab_groups=*/false); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + false); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_InstantMessage_VersionOutOfDate_PrefSet) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, true); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/true, + /*has_open_shared_tab_groups=*/true); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + false); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_InstantMessage_VersionUpdated) { + scoped_feature_list_.InitAndDisableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + false); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_PersistentMessage_VersionOutOfDate) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/true, + /*has_open_shared_tab_groups=*/false); + InitializeController(); + ExpectMessageUiShouldBeShown( + MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE, true); +} + +TEST_F( + VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_PersistentMessage_VersionOutOfDate_NoSharedGroups) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/false, + /*has_open_shared_tab_groups=*/false); + InitializeController(); + ExpectMessageUiShouldBeShown( + MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE, false); +} + +TEST_F( + VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_PersistentMessage_VersionOutOfDate_PrefSet) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetPref(prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, + true); + SetTabGroupSyncServiceExpectation(/*has_shared_tab_groups=*/true, + /*has_open_shared_tab_groups=*/false); + InitializeController(); + ExpectMessageUiShouldBeShown( + MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE, false); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_PersistentMessage_VersionUpdated) { + scoped_feature_list_.InitAndDisableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + InitializeController(); + ExpectMessageUiShouldBeShown( + MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE, false); +} + +TEST_F(VersioningMessageControllerImplTest, + ExpectMessageUiShouldBeShown_ReenabledMessage_VersionOutOfDate) { + scoped_feature_list_.InitAndEnableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_UPDATED_MESSAGE, false); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_ReenabledMessage_VersionUpdated) { + scoped_feature_list_.InitAndDisableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_UPDATED_MESSAGE, true); +} + +TEST_F(VersioningMessageControllerImplTest, + QueryIfMessageUiShouldBeShown_ReenabledMessage_VersionUpdated_PrefSet) { + scoped_feature_list_.InitAndDisableFeature( + data_sharing::features::kDataSharingEnableUpdateChromeUI); + SetPref(prefs::kDataSharingHasShownVersionUpdatedMessage, true); + InitializeController(); + ExpectMessageUiShouldBeShown(MessageType::VERSION_UPDATED_MESSAGE, false); +} + +TEST_F(VersioningMessageControllerImplTest, OnMessageUiShown_InstantMessage) { + InitializeController(); + controller_->OnMessageUiShown( + MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE); + EXPECT_TRUE( + GetPref(prefs::kDataSharingHasShownVersionOutOfDateInstantMessage)); +} + +TEST_F(VersioningMessageControllerImplTest, OnMessageUiShown_ReenabledMessage) { + controller_->OnMessageUiShown(MessageType::VERSION_UPDATED_MESSAGE); + EXPECT_TRUE(GetPref(prefs::kDataSharingHasShownVersionUpdatedMessage)); +} + +TEST_F(VersioningMessageControllerImplTest, + OnMessageUiDismissed_PersistentMessage) { + InitializeController(); + controller_->OnMessageUiDismissed( + MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE); + EXPECT_TRUE(GetPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage)); +} + +TEST_F(VersioningMessageControllerImplTest, + Initialization_QueuedCallbacksFlushed) { + bool callback_called = false; + controller_->ShouldShowMessageUiAsync( + MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + base::BindOnce( + [](bool* callback_called, bool result) { *callback_called = true; }, + &callback_called)); + EXPECT_FALSE(callback_called); + InitializeController(); + EXPECT_TRUE(callback_called); +} + +} // namespace +} // namespace tab_groups
diff --git a/components/saved_tab_groups/public/BUILD.gn b/components/saved_tab_groups/public/BUILD.gn index 46d507f..3b95b71 100644 --- a/components/saved_tab_groups/public/BUILD.gn +++ b/components/saved_tab_groups/public/BUILD.gn
@@ -25,6 +25,7 @@ "types.h", "utils.cc", "utils.h", + "versioning_message_controller.h", ] deps = [ "//base", @@ -164,6 +165,7 @@ "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java", "android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncUtils.java", "android/java/src/org/chromium/components/tab_group_sync/TabGroupUiActionHandler.java", + "android/java/src/org/chromium/components/tab_group_sync/VersioningMessageController.java", ] deps = [ @@ -184,7 +186,10 @@ ":*", "//components/saved_tab_groups:*", ] - sources = [ "types.h" ] + sources = [ + "types.h", + "versioning_message_controller.h", + ] } android_library("test_support_java") {
diff --git a/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java b/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java index 625c37f..f6c2f01 100644 --- a/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java +++ b/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
@@ -328,4 +328,10 @@ * @param collaborationId Collaboration ID with which the collaboration group is associated. */ void setCollaborationAvailableInFinderForTesting(String collaborationId); + + /** + * @return The {@link VersioningMessageController} which is responsible for business logic + * related to shared tab groups versioning related messages. + */ + VersioningMessageController getVersioningMessageController(); }
diff --git a/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageController.java b/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageController.java new file mode 100644 index 0000000..63fa9dad --- /dev/null +++ b/components/saved_tab_groups/public/android/java/src/org/chromium/components/tab_group_sync/VersioningMessageController.java
@@ -0,0 +1,40 @@ +// 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.components.tab_group_sync; + +import org.chromium.base.Callback; +import org.chromium.build.annotations.NullMarked; + +/** + * The central controller for versioning messages. Determines which versioning messages should be + * shown based on whether the current chrome client is able to support shared tab groups. The UI + * layer should query this class before showing any versioning messages. + */ +@NullMarked +public interface VersioningMessageController { + /** + * Invoke this method to query if the given message UI should be shown. This will internally + * wait for TabGroupSyncService initialization. + * + * @param messageType The {@link MessageType} to query. + * @param callback The callback to be invoked with the result. + */ + void shouldShowMessageUiAsync(@MessageType int messageType, Callback<Boolean> callback); + + /** + * Invoke this after a MessageType.VERSION_OUT_OF_DATE_INSTANT_MESSAGE or + * MessageType.VERSION_UPDATED_MESSAGE has been displayed. + * + * @param messageType The {@link MessageType} that was shown. + */ + void onMessageUiShown(@MessageType int messageType); + + /** + * Invoke this after a user dismisses a MessageType.VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE. + * + * @param messageType The {@link MessageType} that was dismissed. + */ + void onMessageUiDismissed(@MessageType int messageType); +}
diff --git a/components/saved_tab_groups/public/pref_names.cc b/components/saved_tab_groups/public/pref_names.cc index 18e4f17..a7e6cbf 100644 --- a/components/saved_tab_groups/public/pref_names.cc +++ b/components/saved_tab_groups/public/pref_names.cc
@@ -45,6 +45,12 @@ registry->RegisterBooleanPref(prefs::kDidSyncTabGroupsInLastSession, false); registry->RegisterBooleanPref(prefs::kDidEnableSharedTabGroupsInLastSession, false); + registry->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionOutOfDateInstantMessage, false); + registry->RegisterBooleanPref( + prefs::kDataSharingHasDismissedVersionOutOfDatePersistentMessage, false); + registry->RegisterBooleanPref( + prefs::kDataSharingHasShownVersionUpdatedMessage, false); } void KeepAccountSettingsPrefsOnlyForUsers(
diff --git a/components/saved_tab_groups/public/pref_names.h b/components/saved_tab_groups/public/pref_names.h index a1518302..013379a0 100644 --- a/components/saved_tab_groups/public/pref_names.h +++ b/components/saved_tab_groups/public/pref_names.h
@@ -74,6 +74,21 @@ inline constexpr char kDidEnableSharedTabGroupsInLastSession[] = "saved_tab_groups.did_enable_shared_tab_groups_in_last_session"; +// Prefs for Data Sharing (Versioning). +// Stores whether the instant message prompting users to update chrome to +// continue using shared tab groups has been shown. +inline constexpr char kDataSharingHasShownVersionOutOfDateInstantMessage[] = + "data_sharing.has_shown_out_of_date_instant_message"; +// Stores whether the persistent message prompting users to update chrome to +// continue using shared tab groups has been shown and dismissed by the user. +inline constexpr char + kDataSharingHasDismissedVersionOutOfDatePersistentMessage[] = + "data_sharing.has_dismissed_out_of_date_persistent_message"; +// Stores whether the message that chrome has been updated to support shared tab +// groups has been shown. +inline constexpr char kDataSharingHasShownVersionUpdatedMessage[] = + "data_sharing.has_shown_version_updated_message"; + // Registers the Clear Browsing Data UI prefs. void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
diff --git a/components/saved_tab_groups/public/tab_group_sync_service.h b/components/saved_tab_groups/public/tab_group_sync_service.h index 5486451..8fdf8c1 100644 --- a/components/saved_tab_groups/public/tab_group_sync_service.h +++ b/components/saved_tab_groups/public/tab_group_sync_service.h
@@ -34,6 +34,7 @@ class CollaborationFinder; class TabGroupSyncDelegate; class TabGroupSyncMetricsLogger; +class VersioningMessageController; // A RAII class that pauses local tab model observers when required. class ScopedLocalObservationPauser { @@ -398,6 +399,10 @@ virtual void AddObserver(Observer* observer) = 0; virtual void RemoveObserver(Observer* observer) = 0; + // Returns the versioning message controller which is responsible for business + // logic related to shared tab groups versioning related messages. + virtual VersioningMessageController* GetVersioningMessageController() = 0; + // For testing only. This is needed to test the API calls received before // service init as we need to explicitly un-initialize the service for these // scenarios. When calling this method the MessagingBackendService will need
diff --git a/components/saved_tab_groups/public/versioning_message_controller.h b/components/saved_tab_groups/public/versioning_message_controller.h new file mode 100644 index 0000000..3b9a4d7 --- /dev/null +++ b/components/saved_tab_groups/public/versioning_message_controller.h
@@ -0,0 +1,60 @@ +// 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 COMPONENTS_SAVED_TAB_GROUPS_PUBLIC_VERSIONING_MESSAGE_CONTROLLER_H_ +#define COMPONENTS_SAVED_TAB_GROUPS_PUBLIC_VERSIONING_MESSAGE_CONTROLLER_H_ + +#include "base/functional/callback_forward.h" + +namespace tab_groups { + +// The central controller for versioning messages. Determines which versioning +// messages should be shown based on whether the current chrome client is +// able to support shared tab groups. The UI layer should query this class +// before showing any versioning messages. Owned by TabGroupSyncService. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.tab_group_sync +class VersioningMessageController { + public: + // Enum representing the different types of messages that needs to be + // displayed to the user. + enum class MessageType { + // Instant message displayed to prompt the user to update chrome to be able + // to continue using their shared tab groups. Shown one time only on + // startup. + // Invoke OnMessageUiShown after displaying the message. + VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + // Persistent message displayed to prompt the user to update chrome to be + // able to continue using their shared tab groups. Continues to be shown on + // every session until user explicitly dismisses it. + // Invoke OnMessageUiShown after displaying the message. + // Invoke OnMessageUiDismissed if the user dismisses the message. + VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE, + // IPH displayed to notify the user that shared tab groups are available + // again. Shown one time only. + // Invoke OnMessageUiShown after displaying the message. + VERSION_UPDATED_MESSAGE + }; + + virtual ~VersioningMessageController() = default; + + // Invoke this method to query if the given message UI should be shown. This + // will internally wait for TabGroupSyncService initialization. See comments + // on MessageType for when the UI should inform this class about display / + // dismissed events. + virtual void ShouldShowMessageUiAsync( + MessageType message_type, + base::OnceCallback<void(bool)> callback) = 0; + + // Invoke this after a MessageType::VERSION_OUT_OF_DATE_INSTANT_MESSAGE or + // MessageType::VERSION_UPDATED_MESSAGE has been displayed. + virtual void OnMessageUiShown(MessageType message_type) = 0; + + // Invoke this after a user dismisses a + // MessageType::VERSION_OUT_OF_DATE_PERSISTENT_MESSAGE. + virtual void OnMessageUiDismissed(MessageType message_type) = 0; +}; + +} // namespace tab_groups + +#endif // COMPONENTS_SAVED_TAB_GROUPS_PUBLIC_VERSIONING_MESSAGE_CONTROLLER_H_
diff --git a/components/saved_tab_groups/test_support/fake_tab_group_sync_service.cc b/components/saved_tab_groups/test_support/fake_tab_group_sync_service.cc index 7fc6dc1d..946cc4c6 100644 --- a/components/saved_tab_groups/test_support/fake_tab_group_sync_service.cc +++ b/components/saved_tab_groups/test_support/fake_tab_group_sync_service.cc
@@ -17,6 +17,7 @@ #include "components/saved_tab_groups/public/saved_tab_group_tab.h" #include "components/saved_tab_groups/public/tab_group_sync_service.h" #include "components/saved_tab_groups/public/types.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" #include "components/sync/base/collaboration_id.h" namespace tab_groups { @@ -456,6 +457,11 @@ return false; } +VersioningMessageController* +FakeTabGroupSyncService::GetVersioningMessageController() { + return nullptr; +} + void FakeTabGroupSyncService::OnLastTabClosed( const SavedTabGroup& saved_tab_group) {}
diff --git a/components/saved_tab_groups/test_support/fake_tab_group_sync_service.h b/components/saved_tab_groups/test_support/fake_tab_group_sync_service.h index a094b506..eda117e 100644 --- a/components/saved_tab_groups/test_support/fake_tab_group_sync_service.h +++ b/components/saved_tab_groups/test_support/fake_tab_group_sync_service.h
@@ -115,6 +115,7 @@ std::unique_ptr<std::vector<SavedTabGroup>> TakeSharedTabGroupsAvailableAtStartupForMessaging() override; bool HadSharedTabGroupsLastSession(bool open_shared_tab_groups) override; + VersioningMessageController* GetVersioningMessageController() override; void OnLastTabClosed(const SavedTabGroup& saved_tab_group) override; void AddObserver(Observer* observer) override; void RemoveObserver(Observer* observer) override;
diff --git a/components/saved_tab_groups/test_support/mock_tab_group_sync_service.h b/components/saved_tab_groups/test_support/mock_tab_group_sync_service.h index 8e5718de..706004fc 100644 --- a/components/saved_tab_groups/test_support/mock_tab_group_sync_service.h +++ b/components/saved_tab_groups/test_support/mock_tab_group_sync_service.h
@@ -8,6 +8,7 @@ #include "components/saved_tab_groups/delegate/tab_group_sync_delegate.h" #include "components/saved_tab_groups/public/tab_group_sync_service.h" #include "components/saved_tab_groups/public/types.h" +#include "components/saved_tab_groups/public/versioning_message_controller.h" #include "components/sync/model/data_type_sync_bridge.h" #include "testing/gmock/include/gmock/gmock.h" @@ -143,6 +144,10 @@ TakeSharedTabGroupsAvailableAtStartupForMessaging, ()); MOCK_METHOD(bool, HadSharedTabGroupsLastSession, (bool), (override)); + MOCK_METHOD(VersioningMessageController*, + GetVersioningMessageController, + (), + (override)); MOCK_METHOD(void, OnLastTabClosed, (const SavedTabGroup&)); MOCK_METHOD(void, AddObserver, (Observer*));
diff --git a/components/services/storage/dom_storage/async_dom_storage_database.cc b/components/services/storage/dom_storage/async_dom_storage_database.cc index bb11ef8..99b3c64 100644 --- a/components/services/storage/dom_storage/async_dom_storage_database.cc +++ b/components/services/storage/dom_storage/async_dom_storage_database.cc
@@ -4,24 +4,12 @@ #include "components/services/storage/dom_storage/async_dom_storage_database.h" -#include <inttypes.h> - -#include <algorithm> -#include <map> -#include <optional> -#include <string> -#include <utility> - #include "base/debug/alias.h" #include "base/feature_list.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" -#include "base/rand_util.h" -#include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/task/sequenced_task_runner.h" -#include "third_party/leveldatabase/env_chromium.h" -#include "third_party/leveldatabase/src/include/leveldb/db.h" #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" namespace storage {
diff --git a/components/services/storage/dom_storage/async_dom_storage_database.h b/components/services/storage/dom_storage/async_dom_storage_database.h index 55b64d7..2fc14ca 100644 --- a/components/services/storage/dom_storage/async_dom_storage_database.h +++ b/components/services/storage/dom_storage/async_dom_storage_database.h
@@ -8,6 +8,7 @@ #include <memory> #include <optional> #include <set> +#include <string> #include <tuple> #include <vector> @@ -15,11 +16,8 @@ #include "base/memory/scoped_refptr.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/sequence_bound.h" -#include "base/unguessable_token.h" #include "components/services/storage/dom_storage/dom_storage_database.h" #include "components/services/storage/dom_storage/features.h" -#include "third_party/leveldatabase/src/include/leveldb/cache.h" -#include "third_party/leveldatabase/src/include/leveldb/db.h" namespace storage {
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc index e3341f2..acec916 100644 --- a/components/viz/common/features.cc +++ b/components/viz/common/features.cc
@@ -267,11 +267,6 @@ base::FEATURE_DISABLED_BY_DEFAULT); #endif -// If enabled, complex occluders are generated for quads with rounded corners, -BASE_FEATURE(kComplexOccluderForQuadsWithRoundedCorners, - "ComplexOccluderForQuadsWithRoundedCorners", - base::FEATURE_ENABLED_BY_DEFAULT); - // Allow SurfaceAggregator to merge render passes when they contain quads that // require overlay (e.g. protected video). See usage in |EmitSurfaceContent|. BASE_FEATURE(kAllowForceMergeRenderPassWithRequireOverlayQuads, @@ -539,12 +534,6 @@ return base::FeatureList::IsEnabled(features::kOnBeginFrameThrottleVideo); } -bool IsComplexOccluderForQuadsWithRoundedCornersEnabled() { - static bool enabled = base::FeatureList::IsEnabled( - features::kComplexOccluderForQuadsWithRoundedCorners); - return enabled; -} - bool ShouldDrawImmediatelyWhenInteractive() { return base::FeatureList::IsEnabled( features::kDrawImmediatelyWhenInteractive);
diff --git a/components/viz/common/features.h b/components/viz/common/features.h index b5eec88..3f970ab 100644 --- a/components/viz/common/features.h +++ b/components/viz/common/features.h
@@ -162,7 +162,6 @@ VIZ_COMMON_EXPORT bool UseSurfaceLayerForVideo(); VIZ_COMMON_EXPORT int MaxOverlaysConsidered(); VIZ_COMMON_EXPORT bool ShouldOnBeginFrameThrottleVideo(); -VIZ_COMMON_EXPORT bool IsComplexOccluderForQuadsWithRoundedCornersEnabled(); VIZ_COMMON_EXPORT bool ShouldDrawImmediatelyWhenInteractive(); VIZ_COMMON_EXPORT bool IsVSyncAlignedPresentEnabled(); VIZ_COMMON_EXPORT bool ShouldLogFrameQuadInfo();
diff --git a/components/viz/service/display/display_resource_provider_software_unittest.cc b/components/viz/service/display/display_resource_provider_software_unittest.cc index 9ae61e64..794e64f9 100644 --- a/components/viz/service/display/display_resource_provider_software_unittest.cc +++ b/components/viz/service/display/display_resource_provider_software_unittest.cc
@@ -98,10 +98,9 @@ std::fill(span.begin(), span.end(), value); - auto transferable_resource = TransferableResource::MakeSoftwareSharedImage( - shared_image, shared_image_interface->GenVerifiedSyncToken(), size, - SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + auto transferable_resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image_interface->GenVerifiedSyncToken()); auto callback = base::BindOnce(&MockReleaseCallback::Released, base::Unretained(&release_callback),
diff --git a/components/viz/service/display/occlusion_culler.cc b/components/viz/service/display/occlusion_culler.cc index 9174b4f9..10d1bba2c 100644 --- a/components/viz/service/display/occlusion_culler.cc +++ b/components/viz/service/display/occlusion_culler.cc
@@ -136,7 +136,6 @@ lower_left == lower_right || lower_left.IsZero() || lower_right.IsZero(); const bool should_generate_complex_occluder = - features::IsComplexOccluderForQuadsWithRoundedCornersEnabled() && generate_complex_occluder_for_rounded_corners && uniform_top_corners && uniform_bottom_corners && bounds_f.size().GetArea() >= minumum_quad_size_with_rounded_corners;
diff --git a/components/viz/service/display/occlusion_culler_unittest.cc b/components/viz/service/display/occlusion_culler_unittest.cc index ca47cf6..077346b 100644 --- a/components/viz/service/display/occlusion_culler_unittest.cc +++ b/components/viz/service/display/occlusion_culler_unittest.cc
@@ -3371,10 +3371,6 @@ }; TEST_P(QuadsWithComplexOccluderTest, OcclusionCullingWithRoundedCorner) { - if (!features::IsComplexOccluderForQuadsWithRoundedCornersEnabled()) { - GTEST_SKIP(); - } - RendererSettings::OcclusionCullerSettings settings; settings.generate_complex_occluder_for_rounded_corners = true; settings.minimum_fragments_reduced = 0; @@ -3507,10 +3503,6 @@ // inscribed rectangle within the rounded rectangle, rather than a more complex // occluder. TEST_F(OcclusionCullerTest, OcclusionCullingWithRoundedCornerOnSmallQuads) { - if (!features::IsComplexOccluderForQuadsWithRoundedCornersEnabled()) { - GTEST_SKIP(); - } - RendererSettings::OcclusionCullerSettings settings; settings.generate_complex_occluder_for_rounded_corners = true; settings.quad_split_limit = 10;
diff --git a/components/viz/service/display/renderer_perftest.cc b/components/viz/service/display/renderer_perftest.cc index 42f09c3..3393a02 100644 --- a/components/viz/service/display/renderer_perftest.cc +++ b/components/viz/service/display/renderer_perftest.cc
@@ -165,10 +165,9 @@ base::as_byte_span(base::allow_nonunique_obj, pixels)); gpu::SyncToken sync_token = sii->GenVerifiedSyncToken(); - TransferableResource gl_resource = TransferableResource::MakeGpu( - client_shared_image, GL_TEXTURE_2D, sync_token, size, - SinglePlaneFormat::kRGBA_8888, false /* is_overlay_candidate */); - gl_resource.color_space = gfx::ColorSpace(); + TransferableResource gl_resource = TransferableResource::Make( + client_shared_image, TransferableResource::ResourceSource::kTest, + sync_token); auto release_callback = base::BindOnce(&DeleteSharedImage, std::move(child_context_provider), std::move(client_shared_image));
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc index e3988e1d..15758d49 100644 --- a/components/viz/service/display/renderer_pixeltest.cc +++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -100,22 +100,6 @@ bitmap.computeByteSize()); } -void AllocateAndRegisterSharedBitmapMemory( - scoped_refptr<RasterContextProvider> context_provider, - const gfx::Size& size, - scoped_refptr<gpu::ClientSharedImage>& shared_image, - gpu::SyncToken& sync_token) { - DCHECK(context_provider); - gpu::SharedImageInterface* shared_image_interface = - context_provider->SharedImageInterface(); - shared_image = shared_image_interface->CreateSharedImageForSoftwareCompositor( - {SinglePlaneFormat::kBGRA_8888, size, gfx::ColorSpace(), - gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "PixelTestSharedBitmap"}); - - sync_token = shared_image_interface->GenVerifiedSyncToken(); - CHECK(shared_image); -} - void DeleteSharedImage(scoped_refptr<gpu::ClientSharedImage> shared_image, const gpu::SyncToken& sync_token, bool is_lost) { @@ -301,25 +285,28 @@ const GrSurfaceOrigin origin = flipped_texture_quad ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin; + const SkAlphaType alpha_type = + premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; ResourceId resource; if (gpu_resource) { resource = CreateGpuResource( child_context_provider, child_resource_provider, rect.size(), - SinglePlaneFormat::kBGRA_8888, - premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType, - gfx::ColorSpace(), MakePixelSpan(pixels), origin); + SinglePlaneFormat::kBGRA_8888, alpha_type, gfx::ColorSpace(), + MakePixelSpan(pixels), origin); } else { - scoped_refptr<gpu::ClientSharedImage> shared_image; - gpu::SyncToken sync_token; - AllocateAndRegisterSharedBitmapMemory(child_context_provider, rect.size(), - shared_image, sync_token); + auto shared_image = + child_context_provider->SharedImageInterface() + ->CreateSharedImageForSoftwareCompositor( + {SinglePlaneFormat::kBGRA_8888, rect.size(), gfx::ColorSpace(), + origin, alpha_type, gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, + "PixelTest"}); + auto mapping = shared_image->Map(); - auto transferable_resource = TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, rect.size(), SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); - transferable_resource.origin = origin; + auto transferable_resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); auto release_callback = base::BindOnce(&DeleteSharedImage, std::move(shared_image)); @@ -372,23 +359,26 @@ size_t num_pixels = static_cast<size_t>(rect.width()) * rect.height(); std::vector<uint32_t> pixels(num_pixels, pixel_color); + const SkAlphaType alpha_type = + premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; ResourceId resource; if (gpu_resource) { - resource = CreateGpuResource( - child_context_provider, child_resource_provider, rect.size(), - SinglePlaneFormat::kRGBA_8888, - premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType, - gfx::ColorSpace(), MakePixelSpan(pixels)); + resource = + CreateGpuResource(child_context_provider, child_resource_provider, + rect.size(), SinglePlaneFormat::kRGBA_8888, + alpha_type, gfx::ColorSpace(), MakePixelSpan(pixels)); } else { - scoped_refptr<gpu::ClientSharedImage> shared_image; - gpu::SyncToken sync_token; - AllocateAndRegisterSharedBitmapMemory(child_context_provider, rect.size(), - shared_image, sync_token); + auto shared_image = + child_context_provider->SharedImageInterface() + ->CreateSharedImageForSoftwareCompositor( + {SinglePlaneFormat::kBGRA_8888, rect.size(), gfx::ColorSpace(), + kTopLeft_GrSurfaceOrigin, alpha_type, + gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "PixelTest"}); auto mapping = shared_image->Map(); - auto transferable_resource = TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, rect.size(), SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + auto transferable_resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); auto release_callback = base::BindOnce(&DeleteSharedImage, std::move(shared_image));
diff --git a/components/viz/service/display/software_renderer_unittest.cc b/components/viz/service/display/software_renderer_unittest.cc index f2badb35..10962049 100644 --- a/components/viz/service/display/software_renderer_unittest.cc +++ b/components/viz/service/display/software_renderer_unittest.cc
@@ -105,10 +105,9 @@ source.readPixels(info, mapping->GetMemoryForPlane(0).data(), info.minRowBytes(), 0, 0); - auto transferable_resource = TransferableResource::MakeSoftwareSharedImage( - shared_image, shared_image_interface->GenVerifiedSyncToken(), size, - SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + auto transferable_resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); auto release_callback = base::BindOnce(&DeleteSharedImage, std::move(shared_image));
diff --git a/components/viz/service/display/surface_aggregator_perftest.cc b/components/viz/service/display/surface_aggregator_perftest.cc index 4e5fa0f..44c7215 100644 --- a/components/viz/service/display/surface_aggregator_perftest.cc +++ b/components/viz/service/display/surface_aggregator_perftest.cc
@@ -119,11 +119,9 @@ {SinglePlaneFormat::kBGRA_8888, size, gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "SurfaceAggregatorPerfTest"}); - auto sync_token = shared_image_interface->GenVerifiedSyncToken(); - TransferableResource resource = - TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, size, SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + TransferableResource resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); resource.id = ResourceId(j); frame_builder.AddTransferableResource(resource); @@ -245,13 +243,11 @@ {SinglePlaneFormat::kBGRA_8888, quad->rect.size(), gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "SurfaceAggregatorPerfTest"}); - auto sync_token = shared_image_interface->GenVerifiedSyncToken(); - created_resources[resource_id] = - TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, quad->rect.size(), - SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + created_resources[resource_id] = TransferableResource::Make( + shared_image, + TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); created_resources[resource_id].id = resource_id; }
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc index adb0401..ba5abd2 100644 --- a/components/viz/service/display/surface_aggregator_unittest.cc +++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -5858,11 +5858,9 @@ {SinglePlaneFormat::kBGRA_8888, gfx::Size(1, 1), gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "SurfaceAggregatorWithResourcesTest"}); - auto sync_token = shared_image_interface->GenVerifiedSyncToken(); - auto resource = TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, gfx::Size(1, 1), - SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask); + auto resource = TransferableResource::Make( + shared_image, TransferableResource::ResourceSource::kTileRasterTask, + shared_image->creation_sync_token()); resource.id = resource_id; if (!valid) {
diff --git a/components/viz/service/input/render_input_router_support_child_frame.cc b/components/viz/service/input/render_input_router_support_child_frame.cc index 0a047dc..28f1dc566 100644 --- a/components/viz/service/input/render_input_router_support_child_frame.cc +++ b/components/viz/service/input/render_input_router_support_child_frame.cc
@@ -4,6 +4,7 @@ #include "components/viz/service/input/render_input_router_support_child_frame.h" +#include "base/trace_event/trace_event.h" #include "components/input/features.h" #include "components/input/render_widget_host_input_event_router.h" #include "components/viz/service/input/render_input_router_support_base.h"
diff --git a/components/viz/test/compositor_frame_helpers.cc b/components/viz/test/compositor_frame_helpers.cc index 33de905..3c4b70a 100644 --- a/components/viz/test/compositor_frame_helpers.cc +++ b/components/viz/test/compositor_frame_helpers.cc
@@ -601,16 +601,15 @@ if (quad->resource_id != kInvalidResourceId) { // Adds a TransferableResource the first time seeing a ResourceId. if (resources_added.insert(quad->resource_id).second) { - auto shared_image = gpu::ClientSharedImage::CreateForTesting( - SinglePlaneFormat::kBGRA_8888, GL_TEXTURE_2D); + auto shared_image = + gpu::ClientSharedImage::CreateSoftwareForTesting(); gpu::SyncToken sync_token; sync_token.Set(gpu::GPU_IO, gpu::CommandBufferId::FromUnsafeValue(1), 1); - frame.resource_list.push_back( - TransferableResource::MakeSoftwareSharedImage( - shared_image, sync_token, quad->rect.size(), - SinglePlaneFormat::kBGRA_8888, - TransferableResource::ResourceSource::kTileRasterTask)); + frame.resource_list.push_back(TransferableResource::Make( + shared_image, + TransferableResource::ResourceSource::kTileRasterTask, + sync_token)); frame.resource_list.back().id = quad->resource_id; } }
diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm index 5bed786c..97e95c0 100644 --- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm +++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
@@ -26,6 +26,7 @@ #include "base/numerics/safe_conversions.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" +#include "base/trace_event/trace_event.h" #include "components/input/web_input_event_builders_mac.h" #include "components/remote_cocoa/app_shim/ns_view_ids.h" #import "content/browser/cocoa/system_hotkey_helper_mac.h"
diff --git a/content/browser/accessibility/ax_style_data.h b/content/browser/accessibility/ax_style_data.h index 7b8ee80..db1be34 100644 --- a/content/browser/accessibility/ax_style_data.h +++ b/content/browser/accessibility/ax_style_data.h
@@ -58,7 +58,7 @@ if (!field) { field.emplace(); } - (*field)[value].emplace_back(start, end); + (*field)[std::move(value)].emplace_back(start, end); } // Returns a string representation of the style data for testing.
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc index 5b9e4e7..75e3c07 100644 --- a/content/browser/accessibility/browser_accessibility_android.cc +++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -9,12 +9,14 @@ #include "base/check_deref.h" #include "base/containers/contains.h" #include "base/functional/bind.h" +#include "base/functional/callback_helpers.h" #include "base/i18n/break_iterator.h" #include "base/memory/ptr_util.h" #include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/ax_style_data.h" #include "content/browser/accessibility/browser_accessibility_manager_android.h" #include "content/public/common/content_client.h" #include "content/public/common/content_features.h" @@ -23,6 +25,7 @@ #include "third_party/skia/include/core/SkColor.h" #include "ui/accessibility/android/accessibility_state.h" #include "ui/accessibility/ax_assistant_structure.h" +#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_position.h" #include "ui/accessibility/ax_role_properties.h" #include "ui/accessibility/ax_selection.h" @@ -94,6 +97,8 @@ ANDROID_VIEW_ACCESSIBILITY_CHECKED_STATE_PARTIAL = 2, }; +using AXStyleData = content::AXStyleData; + using UniqueIdMap = absl::flat_hash_map<int32_t, content::BrowserAccessibilityAndroid*>; using LeafMap = @@ -112,6 +117,76 @@ return *leaf_map; } +// Populates the computed style data from `node` into `style_data`. +// Non-opaque colors also are blended from ancestors. +void PopulateStyleData(const content::BrowserAccessibilityAndroid& node, + const std::u16string& parent_text, + const std::u16string& text, + AXStyleData* style_data) { + if (!style_data) { + return; + } + + int start = parent_text.size(); + int end = start + text.size(); + + if (node.IsTextField()) { + std::vector<int> suggestion_starts; + std::vector<int> suggestion_ends; + node.GetSuggestions(&suggestion_starts, &suggestion_ends); + CHECK_EQ(suggestion_starts.size(), suggestion_ends.size()); + for (size_t i = 0; i < suggestion_starts.size(); ++i) { + // Currently we don't retrieve the text of each suggestion, so store a + // blank string for now. + AXStyleData::AddRange(style_data->suggestions, std::u16string(), + start + suggestion_starts[i], + start + suggestion_ends[i]); + } + } + + if (ui::IsLink(node.GetRole())) { + AXStyleData::AddRange(style_data->links, node.GetTargetUrl(), start, end); + } + + if (node.GetRole() == ax::mojom::Role::kStaticText) { + if (float size = node.GetTextSize(); size != 0) { + AXStyleData::AddRange(style_data->text_sizes, size, start, end); + } + if (node.GetTextStyle() != 0) { + // GetTextStyle returns a bit field shifted by ax::mojom::TextStyle enum + // values, so we need to parse out the individual enum values. See: + // https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/ax_node_data.cc?q=HasTextStyle + for (int i = static_cast<int>(ax::mojom::TextStyle::kMinValue); + i <= static_cast<int>(ax::mojom::TextStyle::kMaxValue); ++i) { + ax::mojom::TextStyle style = static_cast<ax::mojom::TextStyle>(i); + if (style != ax::mojom::TextStyle::kNone && node.HasTextStyle(style)) { + AXStyleData::AddRange(style_data->text_styles, style, start, end); + } + } + } + if (auto pos = static_cast<ax::mojom::TextPosition>(node.GetTextPosition()); + pos != ax::mojom::TextPosition::kNone) { + AXStyleData::AddRange(style_data->text_positions, pos, start, end); + } + // GetColor() gets the blended color. + AXStyleData::AddRange(style_data->foreground_colors, + static_cast<int>(node.GetColor()), start, end); + // GetBackgroundColor() gets blended background color. + AXStyleData::AddRange(style_data->background_colors, + static_cast<int>(node.GetBackgroundColor()), start, + end); + if (const auto& family = node.GetInheritedFontFamilyName(); + !family.empty()) { + AXStyleData::AddRange(style_data->font_families, std::move(family), start, + end); + } + // GetLanguage() gets the inherited language locale. + if (const auto& lang = node.GetLanguage(); !lang.empty()) { + AXStyleData::AddRange(style_data->locales, std::move(lang), start, end); + } + } +} + } // namespace namespace ui { @@ -187,11 +262,11 @@ } if (string->empty()) { - *string = extra_text; + *string = std::move(extra_text); return; } - *string += std::u16string(u", ") + extra_text; + base::StrAppend(string, {u", ", std::move(extra_text)}); } bool BrowserAccessibilityAndroid::IsCheckable() const { @@ -379,14 +454,12 @@ } bool BrowserAccessibilityAndroid::IsSubscript() const { - return static_cast<ax::mojom::TextPosition>( - GetIntAttribute(ax::mojom::IntAttribute::kTextPosition)) == + return static_cast<ax::mojom::TextPosition>(GetTextPosition()) == ax::mojom::TextPosition::kSubscript; } bool BrowserAccessibilityAndroid::IsSuperscript() const { - return static_cast<ax::mojom::TextPosition>( - GetIntAttribute(ax::mojom::IntAttribute::kTextPosition)) == + return static_cast<ax::mojom::TextPosition>(GetTextPosition()) == ax::mojom::TextPosition::kSuperscript; } @@ -782,16 +855,37 @@ } std::u16string BrowserAccessibilityAndroid::GetSubstringTextContentUTF16( - std::optional<size_t> min_length) const { + std::optional<size_t> min_length, + AXStyleData* style_data) const { + std::u16string text; + AccumulateSubstringTextContentUTF16(&text, min_length, style_data); + return text; +} + +void BrowserAccessibilityAndroid::AccumulateSubstringTextContentUTF16( + std::u16string* accumulated_text, + std::optional<size_t> min_length, + AXStyleData* style_data) const { + CHECK(accumulated_text); + std::u16string text; + base::ScopedClosureRunner accumulate_text_and_styling(base::BindOnce( + [](const BrowserAccessibilityAndroid& node, std::u16string* dest, + std::u16string& src, AXStyleData* style_data) { + PopulateStyleData(node, *dest, src, style_data); + base::StrAppend(dest, {std::move(src)}); + }, + std::cref(*this), base::Unretained(accumulated_text), std::ref(text), + base::Unretained(style_data))); + if (ui::IsIframe(GetRole())) { - return std::u16string(); + return; } - // First, always return the |value| attribute if this is an - // input field. + // First, always return the `value` attribute if this is an input field. std::u16string value = GetValueForControl(); if (ShouldExposeValueAsName(value)) { - return value; + text = std::move(value); + return; } // For color wells, the color is stored in separate attributes. @@ -799,14 +893,14 @@ if (GetRole() == ax::mojom::Role::kColorWell) { unsigned int color = static_cast<unsigned int>( GetIntAttribute(ax::mojom::IntAttribute::kColorValue)); - return base::UTF8ToUTF16(skia::SkColorToHexString(color)); + text = base::UTF8ToUTF16(skia::SkColorToHexString(color)); + return; } // In the case of accessible name from kAttribute, the aria-label will be // mapped to one of the container title, content description or supplemental // description, we should exclude aria-label from mapping to text. - std::u16string text = - IsAccessibleNameFromAttribute() ? u"" : GetNameAsString16(); + text = IsAccessibleNameFromAttribute() ? u"" : GetNameAsString16(); if (ui::IsRangeValueSupported(GetRole())) { // For controls that support range values such as sliders, when a non-empty // name is present (e.g. a label), append this to the value so both the @@ -831,13 +925,13 @@ // For almost all focusable nodes we try to get text from contents, but for // the root node that's redundant and often way too verbose. if (ui::IsPlatformDocument(GetRole())) { - return text; + return; } // A role="separator" is a leaf, and cannot get name from contents, even if // author appends text children. if (GetRole() == ax::mojom::Role::kSplitter) { - return text; + return; } // Append image description strings to the text. @@ -869,38 +963,29 @@ } } - size_t text_length = text.size(); - std::vector<std::u16string> inner_text({std::move(text)}); // This is called from IsLeaf, so don't call PlatformChildCount // from within this! // Only for roles that do not support naming with child content, we loop // through the children, in order to populate the visual content (use Android // text API), in addition to populating the aria label information. - if (text_length == 0 && !ui::SupportsNamingWithChildContent(GetRole()) && + if (text.size() == 0 && !ui::SupportsNamingWithChildContent(GetRole()) && ((HasOnlyTextChildren() && !HasListMarkerChild()) || (IsFocusable() && HasOnlyTextAndImageChildren()))) { for (auto it = InternalChildrenBegin(); it != InternalChildrenEnd(); ++it) { - std::u16string child_text = - static_cast<BrowserAccessibilityAndroid*>(it.get()) - ->GetSubstringTextContentUTF16(min_length); - text_length += child_text.size(); - inner_text.push_back(std::move(child_text)); - if (min_length && text_length >= *min_length) { + static_cast<BrowserAccessibilityAndroid*>(it.get()) + ->AccumulateSubstringTextContentUTF16(&text, min_length, style_data); + if (min_length && text.size() >= *min_length) { break; } } } - text = base::JoinString(inner_text, u""); - if (text.empty() && (ui::IsLink(GetRole()) || ui::IsImageOrVideo(GetRole())) && !HasExplicitlyEmptyName()) { std::u16string url = GetString16Attribute(ax::mojom::StringAttribute::kUrl); text = ui::AXUrlBaseText(url); } - - return text; } BrowserAccessibilityAndroid::EarlyExitPredicate @@ -1455,6 +1540,10 @@ return GetIntAttribute(ax::mojom::IntAttribute::kTextStyle); } +int BrowserAccessibilityAndroid::GetTextPosition() const { + return GetIntAttribute(ax::mojom::IntAttribute::kTextPosition); +} + int BrowserAccessibilityAndroid::GetTextColor() const { return GetIntAttribute(ax::mojom::IntAttribute::kColor); }
diff --git a/content/browser/accessibility/browser_accessibility_android.h b/content/browser/accessibility/browser_accessibility_android.h index 38ba1a1..c03d0e0 100644 --- a/content/browser/accessibility/browser_accessibility_android.h +++ b/content/browser/accessibility/browser_accessibility_android.h
@@ -8,6 +8,7 @@ #include <stddef.h> #include <stdint.h> +#include <optional> #include <string> #include <vector> @@ -20,6 +21,8 @@ namespace content { +struct AXStyleData; + class CONTENT_EXPORT BrowserAccessibilityAndroid : public ui::BrowserAccessibility { public: @@ -133,8 +136,11 @@ typedef base::RepeatingCallback<bool(const std::u16string& partial)> EarlyExitPredicate; + // Gets the text content of this node, up to at least `min_length` if given. + // If `style_data` is provided, it's populated with styling information. std::u16string GetSubstringTextContentUTF16( - std::optional<size_t> min_length) const; + std::optional<size_t> min_length, + AXStyleData* style_data = nullptr) const; static EarlyExitPredicate NonEmptyPredicate(); static EarlyExitPredicate LengthAtLeast(size_t length); @@ -178,6 +184,7 @@ // superscript from the methods above. float GetTextSize() const; int GetTextStyle() const; + int GetTextPosition() const; int GetTextColor() const; int GetTextBackgroundColor() const; std::string GetFontFamily() const; @@ -308,6 +315,11 @@ // Returns tree if any child has kSelect action verb. bool HasSelectActionVerbChildren() const; + // Helper function that accumulates the text content for the node. + void AccumulateSubstringTextContentUTF16(std::u16string* accumulated_text, + std::optional<size_t> min_length, + AXStyleData* style_data) const; + std::u16string cached_text_; std::u16string old_value_; std::u16string new_value_;
diff --git a/content/browser/accessibility/browser_accessibility_android_unittest.cc b/content/browser/accessibility/browser_accessibility_android_unittest.cc index b559c084..ef1618ac 100644 --- a/content/browser/accessibility/browser_accessibility_android_unittest.cc +++ b/content/browser/accessibility/browser_accessibility_android_unittest.cc
@@ -8,15 +8,19 @@ #include "base/test/scoped_feature_list.h" #include "build/build_config.h" +#include "content/browser/accessibility/ax_style_data.h" #include "content/browser/accessibility/browser_accessibility_manager_android.h" #include "content/browser/accessibility/web_contents_accessibility_android.h" #include "content/public/test/browser_task_environment.h" #include "content/test/test_content_client.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/accessibility/ax_enums.mojom-data-view.h" #include "ui/accessibility/platform/browser_accessibility_manager.h" #include "ui/accessibility/platform/test_ax_node_id_delegate.h" #include "ui/accessibility/platform/test_ax_platform_tree_manager_delegate.h" #include "ui/strings/grit/auto_image_annotation_strings.h" + namespace content { namespace { @@ -27,6 +31,11 @@ } // namespace using RetargetEventType = ui::AXTreeManager::RetargetEventType; +using RangePairs = AXStyleData::RangePairs; + +using ::testing::Eq; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; class MockContentClient : public TestContentClient { public: @@ -67,6 +76,8 @@ ~BrowserAccessibilityAndroidTest() override; protected: + static const ui::AXNodeID ROOT_ID = 100; + std::unique_ptr<ui::TestAXPlatformTreeManagerDelegate> test_browser_accessibility_delegate_; ui::TestAXNodeIdDelegate node_id_delegate_; @@ -700,6 +711,656 @@ image_succeeded_with_name->GetSupplementalDescription()); } +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_Suggestions) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101}; + last_node->role = ax::mojom::Role::kTextField; + last_node->SetValue(u"Some very wrrrongly spelled words"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->child_ids = {201, 202, 203}; + last_node->role = ax::mojom::Role::kGenericContainer; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 201; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 202; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("very wrrrongly spelled"); + last_node->AddIntListAttribute( + ax::mojom::IntListAttribute::kMarkerTypes, + {static_cast<int>(ax::mojom::MarkerType::kSuggestion)}); + last_node->AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts, + {5}); + last_node->AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds, + {14}); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 203; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" words"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some very wrrrongly spelled words")); + ASSERT_TRUE(style_data.suggestions); + EXPECT_THAT(*style_data.suggestions, + UnorderedElementsAre(Pair(u"", RangePairs{{10, 19}}))); +} + +// TODO: aluh - Enable once link nodes are merged into text content. +TEST_F(BrowserAccessibilityAndroidTest, DISABLED_TextStyling_Links) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("A "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->child_ids = {201}; + last_node->role = ax::mojom::Role::kLink; + last_node->AddStringAttribute(ax::mojom::StringAttribute::kUrl, + "https://www.example.com/"); + last_node->SetName("simple"); + last_node->SetNameFrom(ax::mojom::NameFrom::kContents); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 201; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("simple"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" link"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"A simple link")); + ASSERT_TRUE(style_data.links); + EXPECT_THAT(*style_data.links, + UnorderedElementsAre( + Pair(u"https://www.example.com/", RangePairs{{2, 8}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_NestedStyle) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("bold"); + last_node->AddTextStyle(ax::mojom::TextStyle::kBold); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some bold text")); + ASSERT_TRUE(style_data.text_styles); + EXPECT_THAT(*style_data.text_styles, + UnorderedElementsAre( + Pair(ax::mojom::TextStyle::kBold, RangePairs{{5, 9}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_MixedStyles) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103, 104, 105}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("bold "); + last_node->AddTextStyle(ax::mojom::TextStyle::kBold); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("and"); + last_node->AddTextStyle(ax::mojom::TextStyle::kBold); + last_node->AddTextStyle(ax::mojom::TextStyle::kItalic); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 104; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" italic"); + last_node->AddTextStyle(ax::mojom::TextStyle::kItalic); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 105; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some bold and italic text")); + ASSERT_TRUE(style_data.text_styles); + EXPECT_THAT( + *style_data.text_styles, + UnorderedElementsAre( + Pair(ax::mojom::TextStyle::kBold, RangePairs{{5, 10}, {10, 13}}), + Pair(ax::mojom::TextStyle::kItalic, RangePairs{{10, 13}, {13, 20}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_TextSizes) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("big"); + last_node->AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 24.0f); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some big text")); + ASSERT_TRUE(style_data.text_sizes); + EXPECT_THAT(*style_data.text_sizes, + UnorderedElementsAre(Pair(24.0f, RangePairs{{5, 8}}))); +} + +// TODO: aluh - Enable once super/subscript nodes are merged into text content. +TEST_F(BrowserAccessibilityAndroidTest, DISABLED_TextStyling_TextPositions) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 104}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kSuperscript; + last_node->SetTextPosition(ax::mojom::TextPosition::kSuperscript); + last_node->child_ids = {103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("superscript"); + last_node->SetTextPosition(ax::mojom::TextPosition::kSuperscript); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 104; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some superscript text")); + ASSERT_TRUE(style_data.text_positions); + EXPECT_THAT(*style_data.text_positions, + UnorderedElementsAre(Pair(ax::mojom::TextPosition::kSuperscript, + RangePairs{{5, 16}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_ForegroundColors) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("red"); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFFF0000); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some red text")); + ASSERT_TRUE(style_data.foreground_colors); + EXPECT_THAT( + *style_data.foreground_colors, + UnorderedElementsAre(Pair(0x00000000, RangePairs{{0, 5}, {8, 13}}), + Pair(0xFFFF0000, RangePairs{{5, 8}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_BackgroundColors) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("highlighted"); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, + 0xFF00FF00); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some highlighted text")); + ASSERT_TRUE(style_data.background_colors); + EXPECT_THAT( + *style_data.background_colors, + UnorderedElementsAre(Pair(0x00000000, RangePairs{{0, 5}, {16, 21}}), + Pair(0xFF00FF00, RangePairs{{5, 16}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_BlendedColors) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + last_node->role = ax::mojom::Role::kGenericContainer; + last_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFFF0000); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, + 0xFFFFFF00); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("blended color"); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, 0x55007788); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, + 0x8800FFFF); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some blended color text")); + ASSERT_TRUE(style_data.foreground_colors); + EXPECT_THAT( + *style_data.foreground_colors, + UnorderedElementsAre(Pair(0xFFFF0000, RangePairs{{0, 5}, {18, 23}}), + Pair(0xFFAA282D, RangePairs{{5, 18}}))); + ASSERT_TRUE(style_data.background_colors); + EXPECT_THAT( + *style_data.background_colors, + UnorderedElementsAre(Pair(0xFFFFFF00, RangePairs{{0, 5}, {18, 23}}), + Pair(0xFF77FF88, RangePairs{{5, 18}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_FontFamilies) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + last_node->AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, + "serif"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("sans serif"); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, + "sans-serif"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some sans serif text")); + ASSERT_TRUE(style_data.font_families); + EXPECT_THAT(*style_data.font_families, + UnorderedElementsAre(Pair("serif", RangePairs{{0, 5}, {15, 20}}), + Pair("sans-serif", RangePairs{{5, 15}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_Locales) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + last_node->AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en-US"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("繁體中文"); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "zh-TW"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some 繁體中文 text")); + ASSERT_TRUE(style_data.locales); + EXPECT_THAT(*style_data.locales, + UnorderedElementsAre(Pair("en-US", RangePairs{{0, 5}, {9, 14}}), + Pair("zh-TW", RangePairs{{5, 9}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_ManyAttributes) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("fancy"); + last_node->AddTextStyle(ax::mojom::TextStyle::kBold); + last_node->AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 32.0f); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFFF0000); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, + 0xFF0000FF); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, + "serif"); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "ja-JP"); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some fancy text")); + ASSERT_TRUE(style_data.text_styles); + EXPECT_THAT(*style_data.text_styles, + UnorderedElementsAre( + Pair(ax::mojom::TextStyle::kBold, RangePairs{{5, 10}}))); + ASSERT_TRUE(style_data.text_sizes); + EXPECT_THAT(*style_data.text_sizes, + UnorderedElementsAre(Pair(32.0f, RangePairs{{5, 10}}))); + ASSERT_TRUE(style_data.foreground_colors); + EXPECT_THAT( + *style_data.foreground_colors, + UnorderedElementsAre(Pair(0x00000000, RangePairs{{0, 5}, {10, 15}}), + Pair(0xFFFF0000, RangePairs{{5, 10}}))); + ASSERT_TRUE(style_data.background_colors); + EXPECT_THAT( + *style_data.background_colors, + UnorderedElementsAre(Pair(0x00000000, RangePairs{{0, 5}, {10, 15}}), + Pair(0xFF0000FF, RangePairs{{5, 10}}))); + ASSERT_TRUE(style_data.font_families); + EXPECT_THAT(*style_data.font_families, + UnorderedElementsAre(Pair("serif", RangePairs{{5, 10}}))); + ASSERT_TRUE(style_data.locales); + EXPECT_THAT(*style_data.locales, + UnorderedElementsAre(Pair("ja-JP", RangePairs{{5, 10}}))); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_IgnoreInvalidValues) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("normal"); + last_node->AddIntAttribute(ax::mojom::IntAttribute::kTextStyle, 0); + last_node->AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 0); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, ""); + last_node->AddStringAttribute(ax::mojom::StringAttribute::kLanguage, ""); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some normal text")); + EXPECT_FALSE(style_data.text_styles); + EXPECT_FALSE(style_data.text_sizes); + EXPECT_FALSE(style_data.font_families); + EXPECT_FALSE(style_data.locales); +} + +TEST_F(BrowserAccessibilityAndroidTest, TextStyling_EmptyStyledText) { + ui::AXTreeUpdate tree; + tree.root_id = ROOT_ID; + + ui::AXNodeData* last_node = &tree.nodes.emplace_back(); + last_node->id = ROOT_ID; + last_node->child_ids = {101, 102, 103}; + + last_node = &tree.nodes.emplace_back(); + last_node->id = 101; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName("Some "); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 102; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(""); + last_node->AddTextStyle(ax::mojom::TextStyle::kBold); + + last_node = &tree.nodes.emplace_back(); + last_node->id = 103; + last_node->role = ax::mojom::Role::kStaticText; + last_node->SetName(" text"); + + std::unique_ptr<ui::BrowserAccessibilityManager> manager( + BrowserAccessibilityManagerAndroid::Create( + tree, node_id_delegate_, test_browser_accessibility_delegate_.get())); + + AXStyleData style_data; + BrowserAccessibilityAndroid* container = + static_cast<BrowserAccessibilityAndroid*>(manager->GetFromID(ROOT_ID)); + EXPECT_THAT( + container->GetSubstringTextContentUTF16(std::nullopt, &style_data), + Eq(u"Some text")); + ASSERT_TRUE(style_data.text_styles); + EXPECT_THAT(*style_data.text_styles, + UnorderedElementsAre( + Pair(ax::mojom::TextStyle::kBold, RangePairs{{5, 5}}))); +} + TEST_F(BrowserAccessibilityAndroidTest, TestJavaNodeCache_AttributeChange) { ui::AXTreeUpdate tree; tree.root_id = 1;
diff --git a/content/browser/accessibility/browser_accessibility_state_impl.cc b/content/browser/accessibility/browser_accessibility_state_impl.cc index b3382fa..6798112 100644 --- a/content/browser/accessibility/browser_accessibility_state_impl.cc +++ b/content/browser/accessibility/browser_accessibility_state_impl.cc
@@ -57,6 +57,7 @@ const char kAXModeBundleBasic[] = "basic"; const char kAXModeBundleFormControls[] = "form-controls"; const char kAXModeBundleComplete[] = "complete"; +const char kAXModeBundleOnScreen[] = "on-screen"; // A data holder attached to a WebContents while it is hidden and has // accessibility enabled. Used only when the disable_on_hide feature of @@ -355,13 +356,15 @@ } else { // Support // --force-renderer-accessibility=[basic|form-controls|complete| - // screen-reader] + // screen-reader|on-screen] if (ax_mode_bundle.compare(kAXModeBundleBasic) == 0) { initial_mode = ui::kAXModeBasic; } else if (ax_mode_bundle.compare(kAXModeBundleFormControls) == 0) { initial_mode = ui::kAXModeFormControls; } else if (ax_mode_bundle.compare(kAXModeBundleComplete) == 0) { initial_mode = ui::kAXModeComplete; + } else if (ax_mode_bundle.compare(kAXModeBundleOnScreen) == 0) { + initial_mode = ui::kAXModeOnScreen; } else { // If 'screen-reader', or invalid, default to screen reader bundle, // which is the most useful in development and testing scenarios.
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc index 0df9739..bdbbee5 100644 --- a/content/browser/navigation_browsertest.cc +++ b/content/browser/navigation_browsertest.cc
@@ -5025,8 +5025,16 @@ EXPECT_NE(origin_to_commit, origin_committed); } +// TODO(crbug.com/424764870): Fix flakiness. +#if BUILDFLAG(IS_FUCHSIA) +#define MAYBE_NavigateToAboutBlankWhileFirstNavigationPending \ + DISABLED_NavigateToAboutBlankWhileFirstNavigationPending +#else +#define MAYBE_NavigateToAboutBlankWhileFirstNavigationPending \ + NavigateToAboutBlankWhileFirstNavigationPending +#endif // BUILDFLAG(IS_FUCHSIA) IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, - NavigateToAboutBlankWhileFirstNavigationPending) { + MAYBE_NavigateToAboutBlankWhileFirstNavigationPending) { GURL url_a = embedded_test_server()->GetURL("a.com", "/empty.html"); GURL url_b = embedded_test_server()->GetURL("b.com", "/empty.html");
diff --git a/content/browser/renderer_host/input/synthetic_gesture_target_base.cc b/content/browser/renderer_host/input/synthetic_gesture_target_base.cc index 2fb56ef..8fabbea 100644 --- a/content/browser/renderer_host/input/synthetic_gesture_target_base.cc +++ b/content/browser/renderer_host/input/synthetic_gesture_target_base.cc
@@ -4,6 +4,7 @@ #include "content/browser/renderer_host/input/synthetic_gesture_target_base.h" +#include "base/trace_event/trace_event.h" #include "components/input/events_helper.h" #include "components/input/render_widget_host_input_event_router.h" #include "content/browser/renderer_host/render_widget_host_impl.h"
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.cc b/content/browser/renderer_host/render_widget_host_view_event_handler.cc index 41b34a2..838636b4 100644 --- a/content/browser/renderer_host/render_widget_host_view_event_handler.cc +++ b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
@@ -8,6 +8,7 @@ #include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics_action.h" #include "base/numerics/safe_conversions.h" +#include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "components/input/render_widget_host_input_event_router.h" #include "components/input/switches.h"
diff --git a/content/browser/renderer_host/text_input_manager.cc b/content/browser/renderer_host/text_input_manager.cc index 4f1d98e9..705a2ee 100644 --- a/content/browser/renderer_host/text_input_manager.cc +++ b/content/browser/renderer_host/text_input_manager.cc
@@ -9,6 +9,7 @@ #include "base/numerics/clamped_math.h" #include "base/observer_list.h" #include "base/strings/string_number_conversions.h" +#include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/render_widget_host_view_base.h"
diff --git a/gpu/command_buffer/client/client_shared_image.cc b/gpu/command_buffer/client/client_shared_image.cc index 92fe26cb..f3509f1 100644 --- a/gpu/command_buffer/client/client_shared_image.cc +++ b/gpu/command_buffer/client/client_shared_image.cc
@@ -581,12 +581,19 @@ return CreateForTesting(viz::SinglePlaneFormat::kRGBA_8888, GL_TEXTURE_2D); } +scoped_refptr<ClientSharedImage> ClientSharedImage::CreateSoftwareForTesting() { + auto shared_image = CreateForTesting(); // IN-TEST + shared_image->is_software_ = true; + return shared_image; +} + // static scoped_refptr<ClientSharedImage> ClientSharedImage::CreateForTesting( viz::SharedImageFormat format, uint32_t texture_target) { SharedImageMetadata metadata; metadata.format = format; + metadata.size = gfx::Size(64, 64); metadata.color_space = gfx::ColorSpace::CreateSRGB(); metadata.surface_origin = kTopLeft_GrSurfaceOrigin; metadata.alpha_type = kOpaque_SkAlphaType; @@ -600,6 +607,7 @@ SharedImageUsageSet usage) { SharedImageMetadata metadata; metadata.format = viz::SinglePlaneFormat::kRGBA_8888; + metadata.size = gfx::Size(64, 64); metadata.color_space = gfx::ColorSpace::CreateSRGB(); metadata.surface_origin = kTopLeft_GrSurfaceOrigin; metadata.alpha_type = kOpaque_SkAlphaType;
diff --git a/gpu/command_buffer/client/client_shared_image.h b/gpu/command_buffer/client/client_shared_image.h index 29b26a7..ee7fc1e 100644 --- a/gpu/command_buffer/client/client_shared_image.h +++ b/gpu/command_buffer/client/client_shared_image.h
@@ -201,6 +201,7 @@ // Creates a ClientSharedImage that is not associated with any // SharedImageInterface for testing. static scoped_refptr<ClientSharedImage> CreateForTesting(); + static scoped_refptr<ClientSharedImage> CreateSoftwareForTesting(); static scoped_refptr<ClientSharedImage> CreateForTesting( viz::SharedImageFormat format, uint32_t texture_target);
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc index ad5d40c..ab4af0d 100644 --- a/gpu/config/gpu_finch_features.cc +++ b/gpu/config/gpu_finch_features.cc
@@ -222,7 +222,12 @@ "EnableDrDc", #if BUILDFLAG(IS_ANDROID) base::FEATURE_ENABLED_BY_DEFAULT +#elif BUILDFLAG(IS_MAC) + // DrDC will not be running if Graphite is disabled on Mac. + // Feature incomplete. DO NOT ENABLE! + base::FEATURE_DISABLED_BY_DEFAULT #else + // NOT SUPPORTED. DO NOT ENABLE! base::FEATURE_DISABLED_BY_DEFAULT #endif ); @@ -526,60 +531,6 @@ #endif } -bool IsDrDcEnabled() { -#if BUILDFLAG(IS_ANDROID) - // Enabled on android P+. - if (base::android::BuildInfo::GetInstance()->sdk_int() < - base::android::SDK_VERSION_P) { - return false; - } - - // DrDc is supported on android MediaPlayer and MCVD path only when - // AImageReader is enabled. Also DrDc requires AImageReader max size to be - // at least 2 for each gpu thread. Hence DrDc is disabled on devices which has - // only 1 image. - if (!base::android::EnableAndroidImageReader() || - LimitAImageReaderMaxSizeToOne()) { - return false; - } - - // Check block list against build info. - const auto* build_info = base::android::BuildInfo::GetInstance(); - if (IsDeviceBlocked(build_info->device(), kDrDcBlockListByDevice.Get())) - return false; - if (IsDeviceBlocked(build_info->model(), kDrDcBlockListByModel.Get())) - return false; - if (IsDeviceBlocked(build_info->hardware(), kDrDcBlockListByHardware.Get())) - return false; - if (IsDeviceBlocked(build_info->brand(), kDrDcBlockListByBrand.Get())) - return false; - if (IsDeviceBlocked(build_info->android_build_id(), - kDrDcBlockListByAndroidBuildId.Get())) - return false; - if (IsDeviceBlocked(build_info->manufacturer(), - kDrDcBlockListByManufacturer.Get())) - return false; - if (IsDeviceBlocked(build_info->board(), kDrDcBlockListByBoard.Get())) - return false; - if (IsDeviceBlocked(build_info->android_build_fp(), - kDrDcBlockListByAndroidBuildFP.Get())) - return false; - - // Chrome on Android desktop aims to be Vulkan-only, which can result - // in crashes when enabled together with DrDc. Re-enable DrDc after - // crbug.com/380295059 is fixed if it is shown beneficial on desktop. - if (build_info->is_desktop()) - return false; - - if (!base::FeatureList::IsEnabled(kEnableDrDc)) - return false; - - return true; -#else - return false; -#endif -} - bool IsUsingThreadSafeMediaForWebView() { #if BUILDFLAG(IS_ANDROID) // SurfaceTexture can't be thread-safe. Also thread safe media code currently @@ -715,6 +666,69 @@ return base::FeatureList::IsEnabled(features::kSkiaGraphite); } +bool IsDrDcEnabled() { +#if BUILDFLAG(IS_ANDROID) + // Enabled on android P+. + if (base::android::BuildInfo::GetInstance()->sdk_int() < + base::android::SDK_VERSION_P) { + return false; + } + + // DrDc is supported on android MediaPlayer and MCVD path only when + // AImageReader is enabled. Also DrDc requires AImageReader max size to be + // at least 2 for each gpu thread. Hence DrDc is disabled on devices which has + // only 1 image. + if (!base::android::EnableAndroidImageReader() || + LimitAImageReaderMaxSizeToOne()) { + return false; + } + + // Check block list against build info. + const auto* build_info = base::android::BuildInfo::GetInstance(); + if (IsDeviceBlocked(build_info->device(), kDrDcBlockListByDevice.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->model(), kDrDcBlockListByModel.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->hardware(), kDrDcBlockListByHardware.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->brand(), kDrDcBlockListByBrand.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->android_build_id(), + kDrDcBlockListByAndroidBuildId.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->manufacturer(), + kDrDcBlockListByManufacturer.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->board(), kDrDcBlockListByBoard.Get())) { + return false; + } + if (IsDeviceBlocked(build_info->android_build_fp(), + kDrDcBlockListByAndroidBuildFP.Get())) { + return false; + } + + // Chrome on Android desktop aims to be Vulkan-only, which can result + // in crashes when enabled together with DrDc. Re-enable DrDc after + // crbug.com/380295059 is fixed if it is shown beneficial on desktop. + if (build_info->is_desktop()) { + return false; + } + +#elif BUILDFLAG(IS_MAC) + if (!IsSkiaGraphiteEnabled(base::CommandLine::ForCurrentProcess())) { + return false; + } +#endif + + return base::FeatureList::IsEnabled(kEnableDrDc); +} + bool IsSkiaGraphitePrecompilationEnabled( const base::CommandLine* command_line) { if (!IsSkiaGraphiteEnabled(command_line)) { @@ -877,11 +891,15 @@ // graphite::context as well as its wrapper class GraphiteSharedContext between // GpuMain and CompositorGpuThread. Note: When this feature is disabled, // each thread creates its own graphite::context and the context wrapper. -// -// Feature incomplete. DO NOT ENABLE! BASE_FEATURE(kGraphiteContextIsThreadSafe, "GraphiteContextIsThreadSafe", +#if BUILDFLAG(IS_MAC) + // DrDC needs a thread-safe graphite context to work correctly. + base::FEATURE_ENABLED_BY_DEFAULT); +#else + // Feature incomplete. DO NOT ENABLE! base::FEATURE_DISABLED_BY_DEFAULT); +#endif bool IsGraphiteContextThreadSafe() { return base::FeatureList::IsEnabled(features::kGraphiteContextIsThreadSafe) &&
diff --git a/infra/config/generated/builder-owners/~unowned.txt b/infra/config/generated/builder-owners/~unowned.txt index d061eee..c92f36f 100644 --- a/infra/config/generated/builder-owners/~unowned.txt +++ b/infra/config/generated/builder-owners/~unowned.txt
@@ -33,7 +33,7 @@ ci/chromeos-js-code-coverage ci/fuchsia-code-coverage ci/fuchsia-x64-accessibility-rel -ci/ios-blink-dbg-fyi +ci/ios-blink-rel-fyi ci/ios-catalyst ci/ios-device ci/ios-fieldtrial-rel @@ -166,7 +166,7 @@ try/fuchsia-x64-cast-receiver-rel-compilator try/ios-angle-try-intel try/ios-asan -try/ios-blink-dbg-fyi +try/ios-blink-rel-fyi try/ios-catalyst try/ios-device try/ios-fieldtrial-rel
diff --git a/infra/config/generated/builders/ci/android-16-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/ci/android-16-x64-fyi-rel/targets/chromium.android.fyi.json index a5e7d915..a6a1369 100644 --- a/infra/config/generated/builders/ci/android-16-x64-fyi-rel/targets/chromium.android.fyi.json +++ b/infra/config/generated/builders/ci/android-16-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -98,6 +98,56 @@ }, { "args": [ + "--disable-field-trial-config", + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "ci_only": true, + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "android_browsertests_no_fieldtrial" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "android_browsertests_no_fieldtrial", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 3 + }, + "test": "android_browsertests", + "test_id_prefix": "ninja://chrome/test:android_browsertests/" + }, + { + "args": [ "--test-launcher-batch-limit=1", "--emulator-debug-tags=all", "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", @@ -670,6 +720,55 @@ "--gs-results-bucket=chromium-result-details", "--recover-devices" ], + "ci_only": true, + "description": "Run with android_36_google_apis_x64", + "experiment_percentage": 100, + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "chrome_public_apk_profile_tests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "chrome_public_apk_profile_tests", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "chrome_public_apk_baseline_profile_generator", + "test_id_prefix": "ninja://chrome/test/android:chrome_public_apk_baseline_profile_generator/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], "description": "Run with android_36_google_apis_x64", "merge": { "args": [ @@ -1829,6 +1928,53 @@ "--bucket", "chromium-result-details", "--test-name", + "jni_zero_sample_apk_test" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "jni_zero_sample_apk_test", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "jni_zero_sample_apk_test", + "test_id_prefix": "ninja://third_party/jni_zero/sample:jni_zero_sample_apk_test/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "latency_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -2113,6 +2259,53 @@ "--bucket", "chromium-result-details", "--test-name", + "minidump_uploader_test" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "minidump_uploader_test", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "minidump_uploader_test", + "test_id_prefix": "ninja://components/minidump_uploader:minidump_uploader_test/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "mojo_test_apk" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/gn-args.json b/infra/config/generated/builders/ci/ios-blink-rel-fyi/gn-args.json similarity index 100% rename from infra/config/generated/builders/ci/ios-blink-dbg-fyi/gn-args.json rename to infra/config/generated/builders/ci/ios-blink-rel-fyi/gn-args.json
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json b/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json similarity index 88% rename from infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json rename to infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json index 84aaa03..a37b511 100644 --- a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json +++ b/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json
@@ -2,14 +2,14 @@ "$build/chromium_tests_builder_config": { "builder_config": { "additional_exclusions": [ - "infra/config/generated/builders/ci/ios-blink-dbg-fyi/gn-args.json" + "infra/config/generated/builders/ci/ios-blink-rel-fyi/gn-args.json" ], "builder_db": { "entries": [ { "builder_id": { "bucket": "ci", - "builder": "ios-blink-dbg-fyi", + "builder": "ios-blink-rel-fyi", "project": "chromium" }, "builder_spec": { @@ -36,17 +36,17 @@ "builder_ids": [ { "bucket": "ci", - "builder": "ios-blink-dbg-fyi", + "builder": "ios-blink-rel-fyi", "project": "chromium" } ], "mirroring_builder_group_and_names": [ { - "builder": "ios-blink-dbg-fyi", + "builder": "ios-blink-rel-fyi", "group": "tryserver.chromium.mac" } ], - "targets_spec_directory": "src/infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets" + "targets_spec_directory": "src/infra/config/generated/builders/ci/ios-blink-rel-fyi/targets" } }, "$build/siso": {
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/shadow-properties.json b/infra/config/generated/builders/ci/ios-blink-rel-fyi/shadow-properties.json similarity index 100% rename from infra/config/generated/builders/ci/ios-blink-dbg-fyi/shadow-properties.json rename to infra/config/generated/builders/ci/ios-blink-rel-fyi/shadow-properties.json
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json b/infra/config/generated/builders/ci/ios-blink-rel-fyi/targets/chromium.fyi.json similarity index 99% rename from infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json rename to infra/config/generated/builders/ci/ios-blink-rel-fyi/targets/chromium.fyi.json index cc3315f..ef7fcf9 100644 --- a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json +++ b/infra/config/generated/builders/ci/ios-blink-rel-fyi/targets/chromium.fyi.json
@@ -1,5 +1,5 @@ { - "ios-blink-dbg-fyi": { + "ios-blink-rel-fyi": { "additional_compile_targets": [ "all" ],
diff --git a/infra/config/generated/builders/gn_args_locations.json b/infra/config/generated/builders/gn_args_locations.json index 04d1096..70edb45 100644 --- a/infra/config/generated/builders/gn_args_locations.json +++ b/infra/config/generated/builders/gn_args_locations.json
@@ -308,7 +308,7 @@ "Win 10 Fast Ring": "ci/Win 10 Fast Ring/gn-args.json", "android-fieldtrial-rel": "ci/android-fieldtrial-rel/gn-args.json", "android-perfetto-rel": "ci/android-perfetto-rel/gn-args.json", - "ios-blink-dbg-fyi": "ci/ios-blink-dbg-fyi/gn-args.json", + "ios-blink-rel-fyi": "ci/ios-blink-rel-fyi/gn-args.json", "ios-fieldtrial-rel": "ci/ios-fieldtrial-rel/gn-args.json", "ios-vm": "ci/ios-vm/gn-args.json", "ios-webkit-tot": "ci/ios-webkit-tot/gn-args.json", @@ -846,7 +846,7 @@ "gpu-try-mac-amd-retina-dbg": "try/gpu-try-mac-amd-retina-dbg/gn-args.json", "gpu-try-mac-intel-dbg": "try/gpu-try-mac-intel-dbg/gn-args.json", "ios-asan": "try/ios-asan/gn-args.json", - "ios-blink-dbg-fyi": "try/ios-blink-dbg-fyi/gn-args.json", + "ios-blink-rel-fyi": "try/ios-blink-rel-fyi/gn-args.json", "ios-catalyst": "try/ios-catalyst/gn-args.json", "ios-device": "try/ios-device/gn-args.json", "ios-fieldtrial-rel": "try/ios-fieldtrial-rel/gn-args.json",
diff --git a/infra/config/generated/builders/try/android-16-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/try/android-16-x64-fyi-rel/targets/chromium.android.fyi.json index a5e7d915..a6a1369 100644 --- a/infra/config/generated/builders/try/android-16-x64-fyi-rel/targets/chromium.android.fyi.json +++ b/infra/config/generated/builders/try/android-16-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -98,6 +98,56 @@ }, { "args": [ + "--disable-field-trial-config", + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "ci_only": true, + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "android_browsertests_no_fieldtrial" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "android_browsertests_no_fieldtrial", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 3 + }, + "test": "android_browsertests", + "test_id_prefix": "ninja://chrome/test:android_browsertests/" + }, + { + "args": [ "--test-launcher-batch-limit=1", "--emulator-debug-tags=all", "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", @@ -670,6 +720,55 @@ "--gs-results-bucket=chromium-result-details", "--recover-devices" ], + "ci_only": true, + "description": "Run with android_36_google_apis_x64", + "experiment_percentage": 100, + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "chrome_public_apk_profile_tests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "chrome_public_apk_profile_tests", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "chrome_public_apk_baseline_profile_generator", + "test_id_prefix": "ninja://chrome/test/android:chrome_public_apk_baseline_profile_generator/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], "description": "Run with android_36_google_apis_x64", "merge": { "args": [ @@ -1829,6 +1928,53 @@ "--bucket", "chromium-result-details", "--test-name", + "jni_zero_sample_apk_test" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "jni_zero_sample_apk_test", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "jni_zero_sample_apk_test", + "test_id_prefix": "ninja://third_party/jni_zero/sample:jni_zero_sample_apk_test/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "latency_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -2113,6 +2259,53 @@ "--bucket", "chromium-result-details", "--test-name", + "minidump_uploader_test" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "minidump_uploader_test", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "dimensions": { + "cores": "8", + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "os": "Ubuntu-22.04", + "pool": "chromium.tests.avd" + }, + "named_caches": [ + { + "name": "android_36_google_apis_x64", + "path": ".android_emulator/android_36_google_apis_x64" + } + ], + "optional_dimensions": { + "60": { + "caches": "android_36_google_apis_x64" + } + }, + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "minidump_uploader_test", + "test_id_prefix": "ninja://components/minidump_uploader:minidump_uploader_test/" + }, + { + "args": [ + "--emulator-debug-tags=all", + "--avd-config=../../tools/android/avd/proto/android_36_google_apis_x64.textpb", + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "description": "Run with android_36_google_apis_x64", + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "mojo_test_apk" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/infra/config/generated/builders/try/ios-blink-dbg-fyi/targets/chromium.fyi.json b/infra/config/generated/builders/try/ios-blink-dbg-fyi/targets/chromium.fyi.json deleted file mode 100644 index cc3315f..0000000 --- a/infra/config/generated/builders/try/ios-blink-dbg-fyi/targets/chromium.fyi.json +++ /dev/null
@@ -1,2747 +0,0 @@ -{ - "ios-blink-dbg-fyi": { - "additional_compile_targets": [ - "all" - ], - "isolated_scripts": [ - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "absl_hardening_tests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "absl_hardening_tests", - "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "angle_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "angle_unittests", - "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/", - "use_isolated_scripts_api": true, - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.base_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "base_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "base_unittests", - "test_id_prefix": "ninja://base:base_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "blink_common_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "blink_common_unittests", - "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "blink_fuzzer_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "blink_fuzzer_unittests", - "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_fuzzer_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "blink_heap_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "blink_heap_unittests", - "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--git-revision=${got_revision}", - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.blink_platform_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "blink_platform_unittests iPhone 15 18.4", - "precommit_args": [ - "--gerrit-issue=${patch_issue}", - "--gerrit-patchset=${patch_set}", - "--buildbucket-id=${buildbucket_build_id}" - ], - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "blink_platform_unittests", - "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "boringssl_crypto_tests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "boringssl_crypto_tests", - "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "boringssl_ssl_tests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "boringssl_ssl_tests", - "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "capture_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "capture_unittests", - "test_id_prefix": "ninja://media/capture:capture_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "cast_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "cast_unittests", - "test_id_prefix": "ninja://media/cast:cast_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.cc_unittests.filter", - "--use-gpu-in-tests", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "cc_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "cc_unittests", - "test_id_prefix": "ninja://cc:cc_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.use_blink.components_browsertests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "components_browsertests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "components_browsertests", - "test_id_prefix": "ninja://components:components_browsertests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.use_blink.components_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "components_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "components_unittests", - "test_id_prefix": "ninja://components:components_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.compositor_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "compositor_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "compositor_unittests", - "test_id_prefix": "ninja://ui/compositor:compositor_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.content_browsertests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "content_browsertests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", - "shards": 10 - }, - "test": "content_browsertests", - "test_id_prefix": "ninja://content/test:content_browsertests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.content_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "content_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "content_unittests", - "test_id_prefix": "ninja://content/test:content_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "crashpad_tests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "crashpad_tests", - "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "crypto_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "crypto_unittests", - "test_id_prefix": "ninja://crypto:crypto_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "device_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "device_unittests", - "test_id_prefix": "ninja://device:device_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "display_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "display_unittests", - "test_id_prefix": "ninja://ui/display:display_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "env_chromium_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "env_chromium_unittests", - "test_id_prefix": "ninja://third_party/leveldatabase:env_chromium_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "events_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "events_unittests", - "test_id_prefix": "ninja://ui/events:events_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gcm_unit_tests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gcm_unit_tests", - "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.gfx_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gfx_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gfx_unittests", - "test_id_prefix": "ninja://ui/gfx:gfx_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gin_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gin_unittests", - "test_id_prefix": "ninja://gin:gin_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gl_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gl_unittests", - "test_id_prefix": "ninja://ui/gl:gl_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "google_apis_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "google_apis_unittests", - "test_id_prefix": "ninja://google_apis:google_apis_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.gpu_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gpu_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gpu_unittests", - "test_id_prefix": "ninja://gpu:gpu_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "gwp_asan_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "gwp_asan_unittests", - "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "latency_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "latency_unittests", - "test_id_prefix": "ninja://ui/latency:latency_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "leveldb_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "leveldb_unittests", - "test_id_prefix": "ninja://third_party/leveldatabase:leveldb_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "libjingle_xmpp_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "libjingle_xmpp_unittests", - "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "liburlpattern_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "liburlpattern_unittests", - "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.media_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "media_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "media_unittests", - "test_id_prefix": "ninja://media:media_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--enable-features=SkiaGraphite", - "--skia-graphite-backend=dawn", - "--use-gpu-in-tests", - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.media_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "media_unittests_skia_graphite_dawn iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "media_unittests", - "test_id_prefix": "ninja://media:media_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--enable-features=SkiaGraphite", - "--skia-graphite-backend=metal", - "--use-gpu-in-tests", - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.media_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "media_unittests_skia_graphite_metal iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "media_unittests", - "test_id_prefix": "ninja://media:media_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "midi_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "midi_unittests", - "test_id_prefix": "ninja://media/midi:midi_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.mojo_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "mojo_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "mojo_unittests", - "test_id_prefix": "ninja://mojo:mojo_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "native_theme_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "native_theme_unittests", - "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "net_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "net_unittests", - "test_id_prefix": "ninja://net:net_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "perfetto_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "perfetto_unittests", - "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "printing_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "printing_unittests", - "test_id_prefix": "ninja://printing:printing_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "sandbox_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "sandbox_unittests", - "test_id_prefix": "ninja://sandbox:sandbox_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-filter-file=testing/buildbot/filters/ios.services_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "services_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "services_unittests", - "test_id_prefix": "ninja://services:services_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "shell_dialogs_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "shell_dialogs_unittests", - "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "skia_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "skia_unittests", - "test_id_prefix": "ninja://skia:skia_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "sql_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "sql_unittests", - "test_id_prefix": "ninja://sql:sql_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "storage_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "storage_unittests", - "test_id_prefix": "ninja://storage:storage_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-filter-file=testing/buildbot/filters/ios.ui_base_unittests.filter", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "ui_base_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "ui_base_unittests", - "test_id_prefix": "ninja://ui/base:ui_base_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "ui_touch_selection_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "ui_touch_selection_unittests", - "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "ui_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "ui_unittests", - "test_id_prefix": "ninja://ui/tests:ui_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "url_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "url_unittests", - "test_id_prefix": "ninja://url:url_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--test-launcher-bot-mode", - "--test-launcher-filter-file=testing/buildbot/filters/ios.viz_unittests.filter", - "--use-gpu-in-tests", - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "viz_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "viz_unittests", - "test_id_prefix": "ninja://components/viz:viz_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "wtf_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "wtf_unittests", - "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/", - "variant_id": "iPhone 15 18.4" - }, - { - "args": [ - "--platform", - "iPhone 15", - "--version", - "18.4", - "--out-dir", - "${ISOLATED_OUTDIR}", - "--xcode-build-version", - "16f6", - "--xctest" - ], - "merge": { - "script": "//testing/merge_scripts/standard_isolated_script_merge.py" - }, - "name": "zlib_unittests iPhone 15 18.4", - "resultdb": { - "enable": true, - "has_native_resultdb_integration": true - }, - "swarming": { - "cipd_packages": [ - { - "cipd_package": "infra/tools/mac_toolchain/${platform}", - "location": ".", - "revision": "git_revision:a18b7d95d26f3c6bf9591978b19cf0ca8268ac7d" - } - ], - "dimensions": { - "cpu": "arm64", - "os": "Mac-15.5" - }, - "named_caches": [ - { - "name": "runtime_ios_18_4", - "path": "Runtime-ios-18.4" - }, - { - "name": "xcode_ios_16f6", - "path": "Xcode.app" - } - ], - "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" - }, - "test": "zlib_unittests", - "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/", - "variant_id": "iPhone 15 18.4" - } - ] - } -} \ No newline at end of file
diff --git a/infra/config/generated/builders/try/ios-blink-dbg-fyi/gn-args.json b/infra/config/generated/builders/try/ios-blink-rel-fyi/gn-args.json similarity index 100% rename from infra/config/generated/builders/try/ios-blink-dbg-fyi/gn-args.json rename to infra/config/generated/builders/try/ios-blink-rel-fyi/gn-args.json
diff --git a/infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json b/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json similarity index 89% rename from infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json rename to infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json index 0cc5a80..e07bdc6f 100644 --- a/infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json +++ b/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json
@@ -2,14 +2,14 @@ "$build/chromium_tests_builder_config": { "builder_config": { "additional_exclusions": [ - "infra/config/generated/builders/try/ios-blink-dbg-fyi/gn-args.json" + "infra/config/generated/builders/try/ios-blink-rel-fyi/gn-args.json" ], "builder_db": { "entries": [ { "builder_id": { "bucket": "ci", - "builder": "ios-blink-dbg-fyi", + "builder": "ios-blink-rel-fyi", "project": "chromium" }, "builder_spec": { @@ -36,11 +36,11 @@ "builder_ids": [ { "bucket": "ci", - "builder": "ios-blink-dbg-fyi", + "builder": "ios-blink-rel-fyi", "project": "chromium" } ], - "targets_spec_directory": "src/infra/config/generated/builders/try/ios-blink-dbg-fyi/targets" + "targets_spec_directory": "src/infra/config/generated/builders/try/ios-blink-rel-fyi/targets" } }, "$build/siso": {
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json b/infra/config/generated/builders/try/ios-blink-rel-fyi/targets/chromium.fyi.json similarity index 99% copy from infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json copy to infra/config/generated/builders/try/ios-blink-rel-fyi/targets/chromium.fyi.json index cc3315f..ef7fcf9 100644 --- a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/targets/chromium.fyi.json +++ b/infra/config/generated/builders/try/ios-blink-rel-fyi/targets/chromium.fyi.json
@@ -1,5 +1,5 @@ { - "ios-blink-dbg-fyi": { + "ios-blink-rel-fyi": { "additional_compile_targets": [ "all" ],
diff --git a/infra/config/generated/health-specs/health-specs.json b/infra/config/generated/health-specs/health-specs.json index 0dc3524..3894ca36 100644 --- a/infra/config/generated/health-specs/health-specs.json +++ b/infra/config/generated/health-specs/health-specs.json
@@ -8861,7 +8861,7 @@ } ] }, - "ios-blink-dbg-fyi": { + "ios-blink-rel-fyi": { "problem_specs": [ { "name": "Unhealthy",
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg index bac319f..92587132 100644 --- a/infra/config/generated/luci/commit-queue.cfg +++ b/infra/config/generated/luci/commit-queue.cfg
@@ -4292,7 +4292,7 @@ disable_reuse_footers: "Include-Ci-Only-Tests" } builders { - name: "chromium/try/ios-blink-dbg-fyi" + name: "chromium/try/ios-blink-rel-fyi" includable_only: true disable_reuse_footers: "Include-Ci-Only-Tests" }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg index 9cdcfd1..3350fba 100644 --- a/infra/config/generated/luci/cr-buildbucket.cfg +++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -49465,7 +49465,7 @@ } } builders { - name: "ios-blink-dbg-fyi" + name: "ios-blink-rel-fyi" swarming_host: "chromium-swarm.appspot.com" dimensions: "builderless:1" dimensions: "cpu:arm64" @@ -49489,8 +49489,8 @@ ' }' ' },' ' "$bootstrap/properties": {' - ' "properties_file": "infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json",' - ' "shadow_properties_file": "infra/config/generated/builders/ci/ios-blink-dbg-fyi/shadow-properties.json",' + ' "properties_file": "infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json",' + ' "shadow_properties_file": "infra/config/generated/builders/ci/ios-blink-rel-fyi/shadow-properties.json",' ' "top_level_project": {' ' "ref": "refs/heads/main",' ' "repo": {' @@ -49551,7 +49551,7 @@ use_invocation_timestamp: true } } - description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/ios-blink-dbg-fyi\">ios-blink-dbg-fyi</a></li></ul>" + description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/ios-blink-rel-fyi\">ios-blink-rel-fyi</a></li></ul>" shadow_builder_adjustments { service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com" pool: "luci.chromium.try" @@ -100432,7 +100432,7 @@ } } builders { - name: "ios-blink-dbg-fyi" + name: "ios-blink-rel-fyi" swarming_host: "chromium-swarm.appspot.com" dimensions: "builderless:1" dimensions: "cpu:arm64" @@ -100456,7 +100456,7 @@ ' }' ' },' ' "$bootstrap/properties": {' - ' "properties_file": "infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json",' + ' "properties_file": "infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json",' ' "top_level_project": {' ' "ref": "refs/heads/main",' ' "repo": {' @@ -100524,7 +100524,7 @@ use_invocation_timestamp: true } } - description_html: "This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/ios-blink-dbg-fyi\">ios-blink-dbg-fyi</a></li></ul>" + description_html: "This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/ios-blink-rel-fyi\">ios-blink-rel-fyi</a></li></ul>" custom_metric_definitions { name: "/chrome/infra/browser/builds/cached_count" predicates: "has(build.output.properties.is_cached)"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg index a3696fac3b..396045ae 100644 --- a/infra/config/generated/luci/luci-milo.cfg +++ b/infra/config/generated/luci/luci-milo.cfg
@@ -15426,7 +15426,7 @@ short_name: "dbg" } builders { - name: "buildbucket/luci.chromium.ci/ios-blink-dbg-fyi" + name: "buildbucket/luci.chromium.ci/ios-blink-rel-fyi" category: "iOS" short_name: "ios-blk" } @@ -26765,7 +26765,7 @@ name: "buildbucket/luci.chromium.try/ios-asan" } builders { - name: "buildbucket/luci.chromium.try/ios-blink-dbg-fyi" + name: "buildbucket/luci.chromium.try/ios-blink-rel-fyi" } builders { name: "buildbucket/luci.chromium.try/ios-catalyst" @@ -28657,7 +28657,7 @@ name: "buildbucket/luci.chromium.try/ios-asan" } builders { - name: "buildbucket/luci.chromium.try/ios-blink-dbg-fyi" + name: "buildbucket/luci.chromium.try/ios-blink-rel-fyi" } builders { name: "buildbucket/luci.chromium.try/ios-catalyst"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg index 28a38915..034b855 100644 --- a/infra/config/generated/luci/luci-scheduler.cfg +++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4167,12 +4167,12 @@ } } job { - id: "ios-blink-dbg-fyi" + id: "ios-blink-rel-fyi" realm: "ci" buildbucket { server: "cr-buildbucket.appspot.com" bucket: "ci" - builder: "ios-blink-dbg-fyi" + builder: "ios-blink-rel-fyi" } } job { @@ -6456,7 +6456,7 @@ triggers: "fuchsia-x64-perf-cast-receiver-rel" triggers: "ios-angle-builder" triggers: "ios-asan" - triggers: "ios-blink-dbg-fyi" + triggers: "ios-blink-rel-fyi" triggers: "ios-catalyst" triggers: "ios-device" triggers: "ios-fieldtrial-rel"
diff --git a/infra/config/lib/builder_exemptions.star b/infra/config/lib/builder_exemptions.star index 483c7ec..7d296946 100644 --- a/infra/config/lib/builder_exemptions.star +++ b/infra/config/lib/builder_exemptions.star
@@ -224,7 +224,7 @@ "fuchsia-x64-cast-receiver-rel", "ios-angle-builder", "ios-angle-intel", - "ios-blink-dbg-fyi", + "ios-blink-rel-fyi", "ios-simulator-code-coverage", "ios-webkit-tot", "ios-wpt-fyi-rel", @@ -450,7 +450,7 @@ "fuchsia-x64-cast-receiver-rel-compilator", "ios-angle-try-intel", "ios-asan", - "ios-blink-dbg-fyi", + "ios-blink-rel-fyi", "ios-catalyst", "ios-device", "ios-fieldtrial-rel", @@ -716,7 +716,7 @@ "chromeos-js-code-coverage", "fuchsia-code-coverage", "fuchsia-x64-accessibility-rel", - "ios-blink-dbg-fyi", + "ios-blink-rel-fyi", "ios-catalyst", "ios-device", "ios-fieldtrial-rel", @@ -854,7 +854,7 @@ "fuchsia-x64-cast-receiver-rel-compilator", "ios-angle-try-intel", "ios-asan", - "ios-blink-dbg-fyi", + "ios-blink-rel-fyi", "ios-catalyst", "ios-device", "ios-fieldtrial-rel",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star index 58bbcdfa..c0183b0 100644 --- a/infra/config/subprojects/chromium/ci/chromium.fyi.star +++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1524,7 +1524,7 @@ ) fyi_ios_builder( - name = "ios-blink-dbg-fyi", + name = "ios-blink-rel-fyi", builder_spec = builder_config.builder_spec( gclient_config = builder_config.gclient_config( config = "ios", @@ -1555,7 +1555,7 @@ ), targets = targets.bundle( targets = [ - "ios_blink_dbg_tests", + "ios_blink_rel_tests", ], additional_compile_targets = [ "all",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star index 1434264..8dadf8d 100644 --- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star +++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -594,11 +594,11 @@ ) ios_builder( - name = "ios-blink-dbg-fyi", + name = "ios-blink-rel-fyi", mirrors = [ - "ci/ios-blink-dbg-fyi", + "ci/ios-blink-rel-fyi", ], - gn_args = "ci/ios-blink-dbg-fyi", + gn_args = "ci/ios-blink-rel-fyi", builderless = True, cpu = cpu.ARM64, execution_timeout = 4 * time.hour,
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star index e144d64d..36f3b7f3 100644 --- a/infra/config/targets/bundles.star +++ b/infra/config/targets/bundles.star
@@ -296,7 +296,11 @@ "android_emulator_specific_chrome_public_tests", "android_trichrome_smoke_tests", "android_smoke_tests", + "chrome_profile_generator_tests", "chromium_gtests_for_devices_with_graphical_output", + "fieldtrial_android_tests", + "jni_zero_sample_apk_test", + "minidump_uploader_test", "linux_flavor_specific_chromium_gtests", "system_webview_shell_instrumentation_tests", # Not an experimental test "webview_ui_instrumentation_tests", @@ -5122,7 +5126,7 @@ ) targets.bundle( - name = "ios_blink_dbg_tests", + name = "ios_blink_rel_tests", targets = [ targets.bundle( targets = "ios_blink_tests",
diff --git a/ios/chrome/browser/alert_view/ui_bundled/alert_action.h b/ios/chrome/browser/alert_view/ui_bundled/alert_action.h index ff4d8f12..f78bd41 100644 --- a/ios/chrome/browser/alert_view/ui_bundled/alert_action.h +++ b/ios/chrome/browser/alert_view/ui_bundled/alert_action.h
@@ -23,6 +23,11 @@ // Block to be called when this action is triggered. @property(nonatomic, readonly) void (^handler)(AlertAction* action); +// Determines whether the action is currently enabled. If set to NO, the +// corresponding button in the UI will be disabled (e.g., grayed out and +// non-interactive). Defaults to YES. +@property(nonatomic, assign, getter=isEnabled) BOOL enabled; + // Initializes an action with `title` and `handler`. + (instancetype)actionWithTitle:(NSString*)title style:(UIAlertActionStyle)style
diff --git a/ios/chrome/browser/alert_view/ui_bundled/alert_action.mm b/ios/chrome/browser/alert_view/ui_bundled/alert_action.mm index 360ac0b38..6ca65c3 100644 --- a/ios/chrome/browser/alert_view/ui_bundled/alert_action.mm +++ b/ios/chrome/browser/alert_view/ui_bundled/alert_action.mm
@@ -16,6 +16,7 @@ _title = [title copy]; _handler = handler; _style = style; + _enabled = YES; } return self; }
diff --git a/ios/chrome/browser/alert_view/ui_bundled/alert_view_controller.mm b/ios/chrome/browser/alert_view/ui_bundled/alert_view_controller.mm index 021f212..c6e2403 100644 --- a/ios/chrome/browser/alert_view/ui_bundled/alert_view_controller.mm +++ b/ios/chrome/browser/alert_view/ui_bundled/alert_view_controller.mm
@@ -6,6 +6,7 @@ #import <ostream> +#import "base/apple/foundation_util.h" #import "base/check_op.h" #import "base/ios/ios_util.h" #import "base/notreached.h" @@ -23,6 +24,8 @@ #import "ui/base/l10n/l10n_util.h" namespace { +// Tag for the button stack view. +constexpr NSInteger kButtonStackViewTag = 9998; // Properties of the alert shadow. constexpr CGFloat kShadowOffsetX = 0; @@ -148,19 +151,49 @@ } } +UIColor* ColorForActionStyle(UIAlertActionStyle style, BOOL enabled) { + UIColor* enabledStateDefaultColor = [UIColor colorNamed:kBlueColor]; + UIColor* enabledStateDestructiveColor = [UIColor colorNamed:kRedColor]; + UIColor* disabledStateColor = [UIColor lightGrayColor]; + + if (!enabled) { + return disabledStateColor; + } + + switch (style) { + case UIAlertActionStyleDefault: + return enabledStateDefaultColor; + case UIAlertActionStyleCancel: + return enabledStateDefaultColor; + case UIAlertActionStyleDestructive: + return enabledStateDestructiveColor; + } +} + +// Update the button foreground color depending on its state. +void UpdateButtonColorDependingOnEnabledState(UIAlertActionStyle style, + UIButton* button) { + UIButtonConfiguration* configuration = button.configuration; + if (!configuration) { + return; + } + + UIColor* color = ColorForActionStyle(style, button.enabled); + if (![configuration.baseForegroundColor isEqual:color]) { + configuration = [configuration copy]; + configuration.baseForegroundColor = color; + button.configuration = configuration; + } +} + // Returns a GrayHighlightButton to be added to the alert for `action`. GrayHighlightButton* GetButtonForAction(AlertAction* action) { UIFont* font = nil; - UIColor* textColor = nil; - if (action.style == UIAlertActionStyleDefault) { - font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; - textColor = [UIColor colorNamed:kBlueColor]; - } else if (action.style == UIAlertActionStyleCancel) { + + if (action.style == UIAlertActionStyleCancel) { font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - textColor = [UIColor colorNamed:kBlueColor]; - } else { // Style is UIAlertActionStyleDestructive + } else { font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; - textColor = [UIColor colorNamed:kRedColor]; } UIButtonConfiguration* buttonConfiguration = @@ -173,7 +206,9 @@ [[NSAttributedString alloc] initWithString:action.title attributes:attributes]; buttonConfiguration.attributedTitle = title; - buttonConfiguration.baseForegroundColor = textColor; + buttonConfiguration.baseForegroundColor = + ColorForActionStyle(action.style, action.enabled); + GrayHighlightButton* button = [GrayHighlightButton buttonWithConfiguration:buttonConfiguration primaryAction:nil]; @@ -182,6 +217,13 @@ button.translatesAutoresizingMaskIntoConstraints = NO; button.tag = action.uniqueIdentifier; + button.enabled = action.enabled; + + UIAlertActionStyle style = action.style; + button.configurationUpdateHandler = ^(UIButton* updatedButton) { + UpdateButtonColorDependingOnEnabledState(style, updatedButton); + }; + return button; } @@ -447,6 +489,7 @@ if ([self.actions count] > 0) { UIStackView* buttonStackView = [self createButtonStackView]; + buttonStackView.tag = kButtonStackViewTag; [stackView addArrangedSubview:buttonStackView]; AddSameConstraintsToSides(buttonStackView, self.contentView, LayoutSides::kTrailing | LayoutSides::kLeading); @@ -615,6 +658,56 @@ } } +- (void)setActions:(NSArray<NSArray<AlertAction*>*>*)newActions { + if ([_actions isEqual:newActions]) { + return; + } + + _actions = [newActions copy]; + _buttonAlertActionsDictionary = nil; + + if (!self.isViewLoaded) { + return; + } + + UIStackView* mainContentStackView = [self mainContentStackView]; + + if (!mainContentStackView) { + return; + } + + UIView* oldButtonStackContainer = + [mainContentStackView viewWithTag:kButtonStackViewTag]; + if (oldButtonStackContainer) { + [oldButtonStackContainer removeFromSuperview]; + } + + if (_actions.count > 0) { + UIStackView* newButtonStackContainer = [self createButtonStackView]; + newButtonStackContainer.tag = kButtonStackViewTag; + [mainContentStackView addArrangedSubview:newButtonStackContainer]; + AddSameConstraintsToSides(newButtonStackContainer, self.contentView, + (LayoutSides::kTrailing | LayoutSides::kLeading)); + } +} + +- (UIStackView*)mainContentStackView { + for (UIView* subview_content in self.contentView.subviews) { + if (![subview_content isKindOfClass:[UIScrollView class]]) { + continue; + } + + UIScrollView* scrollView = + base::apple::ObjCCastStrict<UIScrollView>(subview_content); + for (UIView* subview_scroll in scrollView.subviews) { + if ([subview_scroll isKindOfClass:[UIStackView class]]) { + return base::apple::ObjCCastStrict<UIStackView>(subview_scroll); + } + } + } + return nil; +} + - (void)setProgressState:(ProgressIndicatorState)progressState { if (_progressState != progressState) { _progressState = progressState; @@ -764,9 +857,11 @@ GrayHighlightButton* button = GetButtonForAction(action); if (self.actionButtonsAreInitiallyDisabled) { button.enabled = NO; - [self performSelector:@selector(enableActionButton:) + [self performSelector:@selector(updateButtonEnabledState:) withObject:button afterDelay:kEnableActionButtonsDelay]; + } else { + [self updateButtonEnabledState:button]; } [button addTarget:self action:@selector(didSelectActionForButton:) @@ -821,9 +916,11 @@ [self.lastFocusedTextField resignFirstResponder]; } -// Enables `button`. -- (void)enableActionButton:(UIButton*)actionButton { - actionButton.enabled = YES; +- (void)updateButtonEnabledState:(UIButton*)button { + AlertAction* action = self.buttonAlertActionsDictionary[@(button.tag)]; + if (action) { + button.enabled = action.enabled; + } } // Updates the `textFieldStackHolder`'s border color when the view controller's
diff --git a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator.mm index d243e45d..8a442dd 100644 --- a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator.mm +++ b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator.mm
@@ -50,7 +50,16 @@ } [consumer_ setProgressState:ProgressIndicatorStateSuccess]; - [consumer_ setActions:@[]]; + + NSString* cancelTitle = + base::SysUTF16ToNSString(model_controller_->GetCancelButtonLabel()); + AlertAction* disabledCancelAction = + [AlertAction actionWithTitle:cancelTitle + style:UIAlertActionStyleCancel + handler:nil]; + disabledCancelAction.enabled = NO; + [consumer_ setActions:@[ @[ disabledCancelAction ] ]]; + consumer_.confirmationAccessibilityLabel = l10n_util::GetNSString( IDS_IOS_AUTOFILL_PROGRESS_DIALOG_CONFIRMATION_ACCESSIBILITY_ANNOUNCEMENT);
diff --git a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator_unittest.mm index d0c1548..1f41840 100644 --- a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator_unittest.mm +++ b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/autofill_progress_dialog_mediator_unittest.mm
@@ -19,6 +19,39 @@ #import "third_party/ocmock/gtest_support.h" #import "ui/base/l10n/l10n_util.h" +namespace { + +// Helper function to check if the passed value contains a disabled cancel +// action. +bool ValueHasDisabledCancelAction(id value) { + if (![value isKindOfClass:[NSArray class]]) { + return NO; + } + + NSArray* actionsRowsOrActions = (NSArray*)value; + + for (id rowOrActionObject in actionsRowsOrActions) { + if ([rowOrActionObject isKindOfClass:[AlertAction class]]) { + AlertAction* action = (AlertAction*)rowOrActionObject; + if (action.style == UIAlertActionStyleCancel && !action.enabled) { + return YES; + } + } else if ([rowOrActionObject isKindOfClass:[NSArray class]]) { + NSArray* actionsInRow = (NSArray*)rowOrActionObject; + for (id actionObjectInRow in actionsInRow) { + if ([actionObjectInRow isKindOfClass:[AlertAction class]]) { + AlertAction* action = (AlertAction*)actionObjectInRow; + if (action.style == UIAlertActionStyleCancel && !action.enabled) { + return YES; + } + } + } + } + } + return NO; +} +} // namespace + class AutofillProgressDialogMediatorTest : public PlatformTest { protected: AutofillProgressDialogMediatorTest() { @@ -74,7 +107,10 @@ // Expectations for the consumer when showing confirmation. OCMExpect([consumer_ setProgressState:ProgressIndicatorStateSuccess]); - OCMExpect([consumer_ setActions:@[]]); + + OCMExpect([consumer_ setActions:[OCMArg checkWithBlock:^BOOL(id value) { + return ValueHasDisabledCancelAction(value); + }]]); mediator_->Dismiss(/*show_confirmation_before_closing=*/true, /*is_canceled_by_user=*/false);
diff --git a/ios/chrome/browser/autofill/ui_bundled/save_profile_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/save_profile_egtest.mm index 7e380a4..2a537db4 100644 --- a/ios/chrome/browser/autofill/ui_bundled/save_profile_egtest.mm +++ b/ios/chrome/browser/autofill/ui_bundled/save_profile_egtest.mm
@@ -59,6 +59,9 @@ // Email value used by the tests. constexpr char kEmail[] = "foo1@gmail.com"; +// Histogram bucket representing renderer errors. +constexpr int kRendererErrorHistogramBucket = 8; + struct FullAddressFormPageParams { // True if the submission should be default prevented. bool default_prevented = false; @@ -264,6 +267,16 @@ kAutofillFormSubmissionEventsInCaptureMode); } + if ([self isRunningTest:@selector(testSubmissionErrorReporting_Enabled)]) { + config.features_enabled.push_back(kAutofillIsolatedWorldForJavascriptIos); + config.features_enabled.push_back(kAutofillReportFormSubmissionErrors); + } + + if ([self isRunningTest:@selector(testSubmissionErrorReporting_Disabled)]) { + config.features_enabled.push_back(kAutofillIsolatedWorldForJavascriptIos); + config.features_disabled.push_back(kAutofillReportFormSubmissionErrors); + } + return config; } @@ -333,8 +346,7 @@ } // Loads, fills, and submits the full address form. -- (void)loadAndSubmitFullAddressFormWithParams: - (FullAddressFormPageParams)params { +- (void)loadFullAddressFormWithParams:(FullAddressFormPageParams)params { // Start server. GREYAssertTrue(self.testServer->Start(), @"Server did not start."); @@ -365,7 +377,11 @@ // Call the helper function embedded in the page content to fill the form. [ChromeEarlGrey evaluateJavaScriptForSideEffect:@"FillForm();"]; +} +- (void)loadAndSubmitFullAddressFormWithParams: + (FullAddressFormPageParams)params { + [self loadFullAddressFormWithParams:params]; // Submit the form via the dedicated <button>. [ChromeEarlGrey tapWebStateElementWithID:@"submit-button"]; } @@ -912,7 +928,7 @@ ^{ NSError* error = [MetricsAppInterface expectTotalCount:6 - forHistogram:@"Autofill.iOS.FormSubmission.Outcome"]; + forHistogram:@"Autofill.iOS.FormSubmission.OutcomeV2"]; return error == nil; }), @"Timed out waiting for all form submission events."); @@ -944,7 +960,93 @@ // multiple submissions on the same form. chrome_test_util::GREYAssertErrorNil([MetricsAppInterface expectTotalCount:1 - forHistogram:@"Autofill.iOS.FormSubmission.Outcome"]); + forHistogram:@"Autofill.iOS.FormSubmission.OutcomeV2"]); +} + +// Tests that the submission errors that occur in the renderer are reported to +// the browser. +- (void)testSubmissionErrorReporting_Enabled { + // Inject a bug that will trigger error when handling the form submission in + // the renderer. + constexpr char kInjectedBug[] = R"( + // Swizzle autofillSubmissionData() with an erroring function. + gcrweb.gCrWeb.fill.autofillSubmissionData = function() { + throw new Error("Oh no, something bad happened!"); + }; + // This is to give a return value to make the thing handling the JS + // execution happy. + true + )"; + + // Load page without submitting the form. + [self loadFullAddressFormWithParams:{}]; + + // Inject the bug in the submission handler so it triggers an error that will + // be reported to the browser. + [ChromeEarlGrey + evaluateJavaScriptInIsolatedWorldForSideEffect:base::SysUTF8ToNSString( + kInjectedBug)]; + + // Now that the submission handler is buggy, submit the form to trigger the + // error. + [ChromeEarlGrey tapWebStateElementWithID:@"submit-button"]; + + // Verify that no infobar is displayed when there is a submission error. + [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:NO]; + + // Verify that the submission error was reported and recorded. + GREYAssertTrue( + base::test::ios::WaitUntilConditionOrTimeout( + base::Milliseconds(200), + ^{ + NSError* error = [MetricsAppInterface + expectUniqueSampleWithCount:1 + forBucket:kRendererErrorHistogramBucket + forHistogram: + @"Autofill.iOS.FormSubmission.OutcomeV2"]; + return error == nil; + }), + @"Timed out waiting for the submission error uma record."); +} + +// Tests that the submission errors that occur in the renderer are not reported +// to the browser when the feature is disabled. +- (void)testSubmissionErrorReporting_Disabled { + // Inject a bug that will trigger error when handling the form submission in + // the renderer. + constexpr char kInjectedBug[] = R"( + // Swizzle autofillSubmissionData() with an erroring function. + gcrweb.gCrWeb.fill.autofillSubmissionData = function() { + throw new Error("Oh no, something bad happened!"); + }; + // This is to give a return value to make the thing handling the JS + // execution happy. + true + )"; + + // Load page without submitting the form. + [self loadFullAddressFormWithParams:{}]; + + // Inject the bug in the submission handler so it triggers an error that will + // be reported to the browser. + [ChromeEarlGrey + evaluateJavaScriptInIsolatedWorldForSideEffect:base::SysUTF8ToNSString( + kInjectedBug)]; + + // Now that the submission handler is buggy, submit the form to trigger the + // error. + [ChromeEarlGrey tapWebStateElementWithID:@"submit-button"]; + + // Verify for some time that no infobar is displayed when there is a + // submission error. + [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:NO]; + + // Verify that no submission error was not reported and recorded. At this + // point there should have been enough time to hypothetically handle the + // submit event if there was no error. + chrome_test_util::GREYAssertErrorNil([MetricsAppInterface + expectTotalCount:0 + forHistogram:@"Autofill.iOS.FormSubmission.OutcomeV2"]); } // Tests that submission is detected hence the infobar is displayed when the
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h index 0a1c549..1e6bff1 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey.h +++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -698,6 +698,11 @@ // Fails if the execution causes an error. - (void)evaluateJavaScriptForSideEffect:(NSString*)javaScript; +// Same as -evaluateJavaScriptForSideEffect but executes the javascript in the +// isolated world instead of the page content world. This allows interacting +// with the gcrweb objects that are injected there. +- (void)evaluateJavaScriptInIsolatedWorldForSideEffect:(NSString*)javaScript; + // Returns the user agent that should be used for the mobile version. - (NSString*)mobileUserAgentString;
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm index a6eea0dc..e5f86bb 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm +++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -1335,6 +1335,13 @@ result.success, @"An error was produced during the script's execution"); } +- (void)evaluateJavaScriptInIsolatedWorldForSideEffect:(NSString*)javaScript { + JavaScriptExecutionResult* result = + [ChromeEarlGreyAppInterface executeJavaScriptInIsolatedWorld:javaScript]; + EG_TEST_HELPER_ASSERT_TRUE( + result.success, @"An error was produced during the script's execution"); +} + - (NSString*)mobileUserAgentString { return [ChromeEarlGreyAppInterface mobileUserAgentString]; }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h index 182d839..4dd9638 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h +++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -496,6 +496,12 @@ // otherwise returns object representing execution result. + (JavaScriptExecutionResult*)executeJavaScript:(NSString*)javaScript; +// Same as -executeJavaScript but executes the script in the isolated world +// instead of the page content world. This allows interacting with the gcrweb +// objects that are injected there. ++ (JavaScriptExecutionResult*)executeJavaScriptInIsolatedWorld: + (NSString*)javaScript; + // Returns the user agent that should be used for the mobile version. + (NSString*)mobileUserAgentString;
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 6949f21..b33ab156 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
@@ -91,6 +91,7 @@ #import "ios/web/common/features.h" #import "ios/web/js_messaging/web_view_js_utils.h" #import "ios/web/public/browser_state_utils.h" +#import "ios/web/public/js_messaging/content_world.h" #import "ios/web/public/js_messaging/web_frame.h" #import "ios/web/public/js_messaging/web_frames_manager.h" #import "ios/web/public/navigation/navigation_manager.h" @@ -1081,12 +1082,27 @@ #pragma mark - JavaScript Utilities (EG2) + (JavaScriptExecutionResult*)executeJavaScript:(NSString*)javaScript { + return [ChromeEarlGreyAppInterface + executeJavaScript:javaScript + inWorld:static_cast<int>(web::ContentWorld::kPageContentWorld)]; +} + ++ (JavaScriptExecutionResult*)executeJavaScriptInIsolatedWorld: + (NSString*)javaScript { + return [ChromeEarlGreyAppInterface + executeJavaScript:javaScript + inWorld:static_cast<int>(web::ContentWorld::kIsolatedWorld)]; +} + ++ (JavaScriptExecutionResult*)executeJavaScript:(NSString*)javaScript + inWorld:(int)world { __block web::WebFrame* main_frame = nullptr; bool completed = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool { - main_frame = chrome_test_util::GetCurrentWebState() - ->GetPageWorldWebFramesManager() - ->GetMainWebFrame(); + main_frame = + chrome_test_util::GetCurrentWebState() + ->GetWebFramesManager(static_cast<web::ContentWorld>(world)) + ->GetMainWebFrame(); return main_frame != nullptr; });
diff --git a/media/audio/audio_input_unittest.cc b/media/audio/audio_input_unittest.cc index 12b27a4..42c2e33 100644 --- a/media/audio/audio_input_unittest.cc +++ b/media/audio/audio_input_unittest.cc
@@ -277,7 +277,7 @@ } // Test create, open and close of an AudioInputStream without recording audio. -// TODO(crbug.com/40262701): This test is failing on ios-blink-dbg-fyi bot. +// TODO(crbug.com/40262701): This test is failing on ios-blink-rel-fyi bot. #if BUILDFLAG(IS_IOS) #define MAYBE_OpenAndClose DISABLED_OpenAndClose #else @@ -294,7 +294,7 @@ } // Test create, open, stop and close of an AudioInputStream without recording. -// TODO(crbug.com/40262701): This test is failing on ios-blink-dbg-fyi bot. +// TODO(crbug.com/40262701): This test is failing on ios-blink-rel-fyi bot. #if BUILDFLAG(IS_IOS) #define MAYBE_OpenStopAndClose DISABLED_OpenStopAndClose #else @@ -312,7 +312,7 @@ // Test a normal recording sequence using an AudioInputStream. // Very simple test which starts capturing and verifies that recording starts. -// TODO(crbug.com/40262701): This test is failing on ios-blink-dbg-fyi bot. +// TODO(crbug.com/40262701): This test is failing on ios-blink-rel-fyi bot. #if BUILDFLAG(IS_IOS) #define MAYBE_Record DISABLED_Record #else
diff --git a/net/BUILD.gn b/net/BUILD.gn index d2ecebc..d8673bc 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -299,6 +299,8 @@ "base/sockaddr_storage.cc", "base/sockaddr_storage.h", "base/sys_addrinfo.h", + "base/task/task_runner.cc", + "base/task/task_runner.h", "base/trace_event_stub.h", "base/tracing.h", "base/transport_info.cc",
diff --git a/net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java b/net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java index 8da4b54..777ea3d 100644 --- a/net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java +++ b/net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java
@@ -12,6 +12,7 @@ import androidx.annotation.RequiresApi; +import org.chromium.base.TraceEvent; import org.chromium.build.annotations.NullMarked; @NullMarked @@ -25,8 +26,10 @@ @Override @RequiresApi(Build.VERSION_CODES.M) public void onReceive(Context context, final Intent intent) { - if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { - mListener.updateProxyConfigFromConnectivityManager(intent); + try (TraceEvent e = TraceEvent.scoped("ProxyBroadcastReceiver#onReceive")) { + if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { + mListener.updateProxyConfigFromConnectivityManager(intent); + } } } }
diff --git a/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/net/android/java/src/org/chromium/net/ProxyChangeListener.java index 833919e..980e9569 100644 --- a/net/android/java/src/org/chromium/net/ProxyChangeListener.java +++ b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -163,8 +163,10 @@ @Override @UsedByReflection("WebView embedders call this to override proxy settings") public void onReceive(Context context, final Intent intent) { - if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { - runOnThread(() -> proxySettingsChanged(extractNewProxy(intent))); + try (TraceEvent e = TraceEvent.scoped("ProxyChangeListener.ProxyReceiver#onReceive")) { + if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { + runOnThread(() -> proxySettingsChanged(extractNewProxy(intent))); + } } } } @@ -176,119 +178,128 @@ // use system properties). @SuppressWarnings({"PrivateApi", "ObsoleteSdkInt"}) private static @Nullable ProxyConfig extractNewProxy(Intent intent) { - Bundle extras = intent.getExtras(); - if (extras == null) { - return null; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return ProxyConfig.fromProxyInfo( - (ProxyInfo) extras.get("android.intent.extra.PROXY_INFO")); - } - - try { - final String getHostName = "getHost"; - final String getPortName = "getPort"; - final String getPacFileUrl = "getPacFileUrl"; - final String getExclusionList = "getExclusionList"; - final String className = "android.net.ProxyProperties"; - - Object props = extras.get("proxy"); - if (props == null) { + try (TraceEvent e = TraceEvent.scoped("ProxyChangeListener#extractNewProxy")) { + Bundle extras = intent.getExtras(); + if (extras == null) { return null; } - - Class<?> cls = Class.forName(className); - Method getHostMethod = cls.getDeclaredMethod(getHostName); - Method getPortMethod = cls.getDeclaredMethod(getPortName); - Method getExclusionListMethod = cls.getDeclaredMethod(getExclusionList); - - String host = (String) getHostMethod.invoke(props); - int port = (Integer) getPortMethod.invoke(props); - - String[] exclusionList; - String s = (String) getExclusionListMethod.invoke(props); - exclusionList = s.split(","); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Method getPacFileUrlMethod = cls.getDeclaredMethod(getPacFileUrl); - String pacFileUrl = (String) getPacFileUrlMethod.invoke(props); - if (!TextUtils.isEmpty(pacFileUrl)) { - return new ProxyConfig(host, port, pacFileUrl, exclusionList); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return ProxyConfig.fromProxyInfo( + (ProxyInfo) extras.get("android.intent.extra.PROXY_INFO")); } - return new ProxyConfig(host, port, null, exclusionList); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException - | NullPointerException ex) { - Log.e(TAG, "Using no proxy configuration due to exception:" + ex); - return null; + + try { + final String getHostName = "getHost"; + final String getPortName = "getPort"; + final String getPacFileUrl = "getPacFileUrl"; + final String getExclusionList = "getExclusionList"; + final String className = "android.net.ProxyProperties"; + + Object props = extras.get("proxy"); + if (props == null) { + return null; + } + + Class<?> cls = Class.forName(className); + Method getHostMethod = cls.getDeclaredMethod(getHostName); + Method getPortMethod = cls.getDeclaredMethod(getPortName); + Method getExclusionListMethod = cls.getDeclaredMethod(getExclusionList); + + String host = (String) getHostMethod.invoke(props); + int port = (Integer) getPortMethod.invoke(props); + + String[] exclusionList; + String s = (String) getExclusionListMethod.invoke(props); + exclusionList = s.split(","); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Method getPacFileUrlMethod = cls.getDeclaredMethod(getPacFileUrl); + String pacFileUrl = (String) getPacFileUrlMethod.invoke(props); + if (!TextUtils.isEmpty(pacFileUrl)) { + return new ProxyConfig(host, port, pacFileUrl, exclusionList); + } + } + return new ProxyConfig(host, port, null, exclusionList); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException + | NullPointerException ex) { + Log.e(TAG, "Using no proxy configuration due to exception:" + ex); + return null; + } } } private void proxySettingsChanged(@Nullable ProxyConfig cfg) { - assertOnThread(); + try (TraceEvent e = TraceEvent.scoped("ProxyChangeListener#proxySettingsChanged")) { + assertOnThread(); - if (mDelegate != null) { - // proxySettingsChanged is called even if mNativePtr == 0, for testing purposes. - mDelegate.proxySettingsChanged(); - } - if (mNativePtr == 0) { - return; - } + if (mDelegate != null) { + // proxySettingsChanged is called even if mNativePtr == 0, for testing purposes. + mDelegate.proxySettingsChanged(); + } + if (mNativePtr == 0) { + return; + } - if (cfg != null) { - ProxyChangeListenerJni.get() - .proxySettingsChangedTo( - mNativePtr, - ProxyChangeListener.this, - cfg.mHost, - cfg.mPort, - cfg.mPacUrl, - cfg.mExclusionList); - } else { - ProxyChangeListenerJni.get().proxySettingsChanged(mNativePtr, ProxyChangeListener.this); + if (cfg != null) { + ProxyChangeListenerJni.get() + .proxySettingsChangedTo( + mNativePtr, + ProxyChangeListener.this, + cfg.mHost, + cfg.mPort, + cfg.mPacUrl, + cfg.mExclusionList); + } else { + ProxyChangeListenerJni.get() + .proxySettingsChanged(mNativePtr, ProxyChangeListener.this); + } } } @RequiresApi(Build.VERSION_CODES.M) private @Nullable ProxyConfig getProxyConfig(Intent intent) { - ConnectivityManager connectivityManager = - (ConnectivityManager) - ContextUtils.getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - ProxyConfig configFromConnectivityManager = - ProxyConfig.fromProxyInfo(connectivityManager.getDefaultProxy()); + try (TraceEvent e = TraceEvent.scoped("ProxyChangeListener#getProxyConfig")) { + ConnectivityManager connectivityManager = + (ConnectivityManager) + ContextUtils.getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + ProxyConfig configFromConnectivityManager = + ProxyConfig.fromProxyInfo(connectivityManager.getDefaultProxy()); - if (configFromConnectivityManager == null) { - return ProxyConfig.DIRECT; - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - && configFromConnectivityManager.mHost.equals("localhost") - && configFromConnectivityManager.mPort == -1) { - ProxyConfig configFromIntent = extractNewProxy(intent); - Log.i( - TAG, - "configFromConnectivityManager = %s, configFromIntent = %s", - configFromConnectivityManager, - configFromIntent); + if (configFromConnectivityManager == null) { + return ProxyConfig.DIRECT; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + && configFromConnectivityManager.mHost.equals("localhost") + && configFromConnectivityManager.mPort == -1) { + ProxyConfig configFromIntent = extractNewProxy(intent); + Log.i( + TAG, + "configFromConnectivityManager = %s, configFromIntent = %s", + configFromConnectivityManager, + configFromIntent); - // There's a bug in Android Q+ PAC support. If ConnectivityManager returns localhost:-1 - // then use the intent from the PROXY_CHANGE_ACTION broadcast to extract the - // ProxyConfig's host and port. See http://crbug.com/993538. - // - // -1 is never a reasonable port so just keep this workaround for future versions until - // we're sure it's fixed on the platform side. - if (configFromIntent == null) return null; - String correctHost = configFromIntent.mHost; - int correctPort = configFromIntent.mPort; - return new ProxyConfig( - correctHost, - correctPort, - configFromConnectivityManager.mPacUrl, - configFromConnectivityManager.mExclusionList); + // There's a bug in Android Q+ PAC support. If ConnectivityManager returns + // localhost:-1 + // then use the intent from the PROXY_CHANGE_ACTION broadcast to extract the + // ProxyConfig's host and port. See http://crbug.com/993538. + // + // -1 is never a reasonable port so just keep this workaround for future versions + // until + // we're sure it's fixed on the platform side. + if (configFromIntent == null) return null; + String correctHost = configFromIntent.mHost; + int correctPort = configFromIntent.mPort; + return new ProxyConfig( + correctHost, + correctPort, + configFromConnectivityManager.mPacUrl, + configFromConnectivityManager.mExclusionList); + } + return configFromConnectivityManager; } - return configFromConnectivityManager; } @RequiresApi(Build.VERSION_CODES.M)
diff --git a/net/base/task/OWNERS b/net/base/task/OWNERS new file mode 100644 index 0000000..0121bbf --- /dev/null +++ b/net/base/task/OWNERS
@@ -0,0 +1 @@ +hayato@chromium.org
diff --git a/net/base/task/README.md b/net/base/task/README.md new file mode 100644 index 0000000..1eb0c1a --- /dev/null +++ b/net/base/task/README.md
@@ -0,0 +1,55 @@ +# //net/base/task + +This directory provides a mechanism for obtaining `base::SingleThreadTaskRunner` +instances that are integrated with task scheduling and prioritization system of +the embedder process (e.g network service). + +## Overview + +The primary API offered is: + +```cpp +namespace net { + +const scoped_refptr<base::SingleThreadTaskRunner>& GetTaskRunner( + RequestPriority priority); + +} // namespace net +``` + +This function allows code (typically running on the network thread or +interacting closely with network operations) to post tasks with a specific +`net::RequestPriority`. + +## Integration with Network Service Scheduler + +The `NetworkServiceScheduler` (located in `//services/network/scheduler/`) is +responsible for setting up and managing the actual task queues on the network +service thread, including a high-priority queue. + +During its initialization (specifically in `SetupNetTaskRunners()`), the +`NetworkServiceScheduler` populates +`net::internal::GetTaskRunnerGlobals().high_priority_task_runner` with the task +runner associated with its own high-priority queue. + +This ensures that when `net::GetTaskRunner(net::HIGHEST)` is called, tasks +posted to the returned runner are routed to the network service's designated +high-priority processing queue. + +## Usage + +Components that need to schedule work on the network thread with specific +network-related priorities should use `net::GetTaskRunner()`. This helps ensure +that critical network tasks (like those with `net::HIGHEST` priority) are +processed appropriately by the network service's scheduler. + +If `RequestPriority` is unavailable or you are unsure which `priority` should be +used, continue to use `base::SingleThreadTaskRunner::GetCurrentDefault()` as +usual. This is currently equivalent to calling `net::GetTaskRunner(priority)` +for any `priority` except `net::HIGHEST`. + +Task execution order is not guaranteed if you post tasks to different queues. If +you need posted tasks to be executed in order, use the same task runner. + +The mapping of `RequestPriority` to underlying `TaskQueue` is subject to change. +Please do not write code that depends on this mapping.
diff --git a/net/base/task/task_runner.cc b/net/base/task/task_runner.cc new file mode 100644 index 0000000..692bdf5 --- /dev/null +++ b/net/base/task/task_runner.cc
@@ -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. + +#include "net/base/task/task_runner.h" + +#include "base/no_destructor.h" + +namespace net { + +const scoped_refptr<base::SingleThreadTaskRunner>& GetTaskRunner( + RequestPriority priority) { + if (priority == RequestPriority::HIGHEST && + internal::GetTaskRunnerGlobals().high_priority_task_runner) { + return internal::GetTaskRunnerGlobals().high_priority_task_runner; + } + return base::SingleThreadTaskRunner::GetCurrentDefault(); +} + +namespace internal { + +TaskRunnerGlobals::TaskRunnerGlobals() = default; +TaskRunnerGlobals::~TaskRunnerGlobals() = default; + +TaskRunnerGlobals& GetTaskRunnerGlobals() { + static base::NoDestructor<TaskRunnerGlobals> globals; + return *globals; +} + +} // namespace internal + +} // namespace net
diff --git a/net/base/task/task_runner.h b/net/base/task/task_runner.h new file mode 100644 index 0000000..849efea2 --- /dev/null +++ b/net/base/task/task_runner.h
@@ -0,0 +1,47 @@ +// 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 NET_BASE_TASK_TASK_RUNNER_H_ +#define NET_BASE_TASK_TASK_RUNNER_H_ + +#include "base/memory/scoped_refptr.h" +#include "base/task/single_thread_task_runner.h" +#include "net/base/net_export.h" +#include "net/base/request_priority.h" + +namespace net { + +// Retrieves a task runner suitable for the given `priority`. +// +// This function allows different parts of the //net stack to obtain task +// runners that are integrated with the network service's scheduling mechanism +// (or other embedder's scheduling). For `RequestPriority::HIGHEST`, this may +// return a special high-priority task runner if one has been configured (e.g., +// by the NetworkServiceScheduler). For other priorities, or if no special +// runner is configured, it typically returns the current thread's default task +// runner. +NET_EXPORT const scoped_refptr<base::SingleThreadTaskRunner>& GetTaskRunner( + RequestPriority priority); + +namespace internal { + +// A struct holding global task runner instances that can be set by an +// embedder (like the network service scheduler). This allows `GetTaskRunner` +// to return specialized runners. +struct NET_EXPORT TaskRunnerGlobals { + TaskRunnerGlobals(); + ~TaskRunnerGlobals(); + + // Task runner specifically for `net::RequestPriority::HIGHEST` tasks. + // This is set by the embedder (e.g., NetworkServiceScheduler). + scoped_refptr<base::SingleThreadTaskRunner> high_priority_task_runner; +}; + +NET_EXPORT TaskRunnerGlobals& GetTaskRunnerGlobals(); + +} // namespace internal + +} // namespace net + +#endif // NET_BASE_TASK_TASK_RUNNER_H_
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc index 181b1ec..f1450c1 100644 --- a/net/dns/host_resolver_manager.cc +++ b/net/dns/host_resolver_manager.cc
@@ -826,6 +826,14 @@ *out_stale_info = std::nullopt; CreateTaskSequence(job_key, cache_usage, secure_dns_policy, out_tasks); + source_net_log.AddEvent( + NetLogEventType::HOST_RESOLVER_MANAGER_TASK_SEQUENCE_CREATED, [&] { + base::Value::List tasks_list; + for (TaskType task : *out_tasks) { + tasks_list.Append(static_cast<int>(task)); + } + return base::Value::Dict().Set("tasks", std::move(tasks_list)); + }); if (!ip_address.IsValid()) { // Check that the caller supplied a valid hostname to resolve. For
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h index bedb7aee..df1896c 100644 --- a/net/log/net_log_event_type_list.h +++ b/net/log/net_log_event_type_list.h
@@ -73,6 +73,13 @@ // } EVENT_TYPE(HOST_RESOLVER_MANAGER_IPV6_REACHABILITY_CHECK) +// This event is created when the host resolver creates tasks for a request. +// It contains the following parameter: +// { +// "tasks": <TaskTypes of a request>, +// } +EVENT_TYPE(HOST_RESOLVER_MANAGER_TASK_SEQUENCE_CREATED) + // This event is logged when a request is handled by a cache entry. // It contains the following parameter: // {
diff --git a/net/socket/fuzzed_socket_factory.cc b/net/socket/fuzzed_socket_factory.cc index 6487518..bc671fa7 100644 --- a/net/socket/fuzzed_socket_factory.cc +++ b/net/socket/fuzzed_socket_factory.cc
@@ -93,6 +93,9 @@ // SSLClientSocket implementation: std::vector<uint8_t> GetECHRetryConfigs() override { NOTREACHED(); } + std::vector<std::vector<uint8_t>> GetServerTrustAnchorIDsForRetry() override { + NOTREACHED(); + } private: NetLogWithSource net_log_;
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index 9532fe09..0d3a8a30 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc
@@ -1582,6 +1582,11 @@ return data_->ech_retry_configs; } +std::vector<std::vector<uint8_t>> +MockSSLClientSocket::GetServerTrustAnchorIDsForRetry() { + return data_->server_trust_anchor_ids_for_retry; +} + void MockSSLClientSocket::RunCallbackAsync(CompletionOnceCallback callback, int result) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index 4c63cd2..fa2e045 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h
@@ -598,6 +598,9 @@ // Result for GetECHRetryConfigs(). std::vector<uint8_t> ech_retry_configs; + // Result for GetServerTrustAnchorIDsForRetry(). + std::vector<std::vector<uint8_t>> server_trust_anchor_ids_for_retry; + std::optional<NextProtoVector> next_protos_expected_in_ssl_config; std::optional<SSLConfig::ApplicationSettings> expected_application_settings; @@ -1024,6 +1027,7 @@ // SSLClientSocket implementation. std::vector<uint8_t> GetECHRetryConfigs() override; + std::vector<std::vector<uint8_t>> GetServerTrustAnchorIDsForRetry() override; // This MockSocket does not implement the manual async IO feature. void OnReadComplete(const MockRead& data) override;
diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h index 3672a5f1..5e90d82 100644 --- a/net/socket/ssl_client_socket.h +++ b/net/socket/ssl_client_socket.h
@@ -59,6 +59,15 @@ // connection can be retried with ECH disabled. virtual std::vector<uint8_t> GetECHRetryConfigs() = 0; + // Called in response to a connection error in Connect(), when the client + // advertised the TLS Trust Anchor IDs extension. If this method returns a + // non-empty set, it is the Trust Anchor IDs (in binary representation) that + // the server provided in the handshake. The connection can be retried with + // these new Trust Anchor IDs, overriding the Trust Anchor IDs that the server + // advertised in DNS. + virtual std::vector<std::vector<uint8_t>> + GetServerTrustAnchorIDsForRetry() = 0; + // Log SSL key material to |logger|. Must be called before any // SSLClientSockets are created. //
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc index cc75671..d30d3c8f 100644 --- a/net/socket/ssl_client_socket_impl.cc +++ b/net/socket/ssl_client_socket_impl.cc
@@ -15,6 +15,7 @@ #include <utility> #include "base/containers/span.h" +#include "base/containers/to_vector.h" #include "base/feature_list.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" @@ -312,6 +313,23 @@ std::vector<uint8_t>(retry_configs, retry_configs + retry_configs_len)); } +std::vector<std::vector<uint8_t>> +SSLClientSocketImpl::GetServerTrustAnchorIDsForRetry() { + const uint8_t* available_trust_anchor_ids; + size_t available_trust_anchor_ids_len; + SSL_get0_peer_available_trust_anchors(ssl_.get(), &available_trust_anchor_ids, + &available_trust_anchor_ids_len); + // SAFETY: + // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_get0_peer_available_trust_anchors + // says `available_trust_anchor_ids` and `available_trust_anchor_ids_len` + // define a buffer containing a list of Trust Anchor IDs in wire format + // (length-prefixed non-empty strings); + base::SpanReader<const uint8_t> reader( + UNSAFE_BUFFERS(base::span<const uint8_t>( + available_trust_anchor_ids, available_trust_anchor_ids_len))); + return ParseServerTrustAnchorIDs(&reader); +} + int SSLClientSocketImpl::ExportKeyingMaterial( std::string_view label, std::optional<base::span<const uint8_t>> context, @@ -1470,6 +1488,25 @@ DoWriteCallback(rv_write); } +// static +std::vector<std::vector<uint8_t>> +SSLClientSocketImpl::ParseServerTrustAnchorIDs( + base::SpanReader<const uint8_t>* reader) { + std::vector<std::vector<uint8_t>> trust_anchor_ids; + while (reader->remaining() > 0) { + uint8_t len; + if (!reader->ReadU8BigEndian(len) || len < 1u) { + return {}; + } + std::optional<base::span<const uint8_t>> bytes = reader->Read(len); + if (!bytes) { + return {}; + } + trust_anchor_ids.emplace_back(base::ToVector(*bytes)); + } + return trust_anchor_ids; +} + int SSLClientSocketImpl::ClientCertRequestCallback(SSL* ssl) { DCHECK(ssl == ssl_.get());
diff --git a/net/socket/ssl_client_socket_impl.h b/net/socket/ssl_client_socket_impl.h index 27789ac..e8055bb 100644 --- a/net/socket/ssl_client_socket_impl.h +++ b/net/socket/ssl_client_socket_impl.h
@@ -16,6 +16,7 @@ #include "base/compiler_specific.h" #include "base/containers/lru_cache.h" +#include "base/containers/span_reader.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" @@ -48,8 +49,9 @@ class SSLKeyLogger; class X509Certificate; -class SSLClientSocketImpl : public SSLClientSocket, - public SocketBIOAdapter::Delegate { +class NET_EXPORT_PRIVATE SSLClientSocketImpl + : public SSLClientSocket, + public SocketBIOAdapter::Delegate { public: // Takes ownership of |stream_socket|, which may already be connected. // The given hostname will be compared with the name(s) in the server's @@ -73,6 +75,7 @@ // SSLClientSocket implementation. std::vector<uint8_t> GetECHRetryConfigs() override; + std::vector<std::vector<uint8_t>> GetServerTrustAnchorIDsForRetry() override; // SSLSocket implementation. int ExportKeyingMaterial(std::string_view label, @@ -122,6 +125,7 @@ class SSLContext; friend class SSLClientSocket; friend class SSLContext; + FRIEND_TEST_ALL_PREFIXES(SSLClientSocketTest, ParseServerTrustAnchorIDs); int Init(); void DoReadCallback(int result); @@ -144,6 +148,9 @@ // and, if complete, runs the respective callbacks. void RetryAllOperations(); + static std::vector<std::vector<uint8_t>> ParseServerTrustAnchorIDs( + base::SpanReader<const uint8_t>* reader); + // Callback from the SSL layer when a certificate needs to be verified. This // is called when establishing new (fresh) connections and when evaluating // whether an existing session can be resumed.
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc index cf603e2..10ebd86 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc
@@ -17,6 +17,7 @@ #include <utility> #include "base/containers/span.h" +#include "base/containers/span_reader.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" @@ -67,6 +68,7 @@ #include "net/socket/client_socket_handle.h" #include "net/socket/read_buffering_stream_socket.h" #include "net/socket/socket_test_util.h" +#include "net/socket/ssl_client_socket_impl.h" #include "net/socket/ssl_server_socket.h" #include "net/socket/stream_socket.h" #include "net/socket/tcp_client_socket.h" @@ -2770,6 +2772,11 @@ EXPECT_TRUE(ran_callback); } +// Tests that SSLClientSocket sends Trust Anchor IDs when configured via +// SSLConfig (similar to ConnectWithTrustAnchorIDs, but more end-to-end as it +// tests that sending a Trust Anchor ID influences the actual certificate that +// the server serves), and properly retrieves the server's Trust Anchor IDs from +// the handshake on error. TEST_P(SSLClientSocketVersionTest, ConnectToServerWithTrustAnchorIDs) { SSLServerConfig server_config; SSLConfig client_config; @@ -2778,6 +2785,8 @@ ASSERT_TRUE(StartEmbeddedTestServer( EmbeddedTestServer::CERT_OK_BY_INTERMEDIATE, server_config)); + // If the client doesn't advertise any trust anchor IDs on the connection, + // then the server should provide a full chain (with the intermediate). int rv; ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv)); EXPECT_THAT(rv, IsOk()); @@ -2785,11 +2794,73 @@ ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info)); EXPECT_EQ(1u, ssl_info.unverified_cert->intermediate_buffers().size()); + // If the client advertises trust anchor IDs that don't correspond to the + // server's intermediate, then the server should provide a full chain (with + // the intermediate). + client_config.trust_anchor_ids = {0x03, 0x01, 0x01, 0x01, 0x02, 0x03, 0x03}; + ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv)); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(sock_->GetServerTrustAnchorIDsForRetry().empty()); + ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info)); + EXPECT_EQ(1u, ssl_info.unverified_cert->intermediate_buffers().size()); + + // If the client advertises the trust anchor ID corresponding to the server's + // intermediate, then the server should omit the intermediate from the + // connection. client_config.trust_anchor_ids = {0x03, 0x01, 0x02, 0x03}; ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv)); EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(sock_->GetServerTrustAnchorIDsForRetry().empty()); ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info)); EXPECT_EQ(0u, ssl_info.unverified_cert->intermediate_buffers().size()); + + // If the client advertises multiple trust anchor IDs including the one + // corresponding to the server's intermediate, then the server should omit the + // intermediate from the connection. + client_config.trust_anchor_ids = {0x02, 0x01, 0x01, 0x03, 0x01, 0x02, 0x03}; + ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv)); + EXPECT_THAT(rv, IsOk()); + EXPECT_TRUE(sock_->GetServerTrustAnchorIDsForRetry().empty()); + ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info)); + EXPECT_EQ(0u, ssl_info.unverified_cert->intermediate_buffers().size()); + + // If the client advertises the trust anchor ID corresponding to the server's + // intermediate but gets an error, it should be able to access the trust + // anchor IDs that the server advertised in the handshake. + cert_verifier_->set_default_result(ERR_CERT_INVALID); + client_config.trust_anchor_ids = {0x03, 0x01, 0x02, 0x03}; + ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv)); + EXPECT_THAT(rv, IsError(ERR_CERT_INVALID)); + ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info)); + EXPECT_EQ(0u, ssl_info.unverified_cert->intermediate_buffers().size()); + EXPECT_EQ(sock_->GetServerTrustAnchorIDsForRetry(), + std::vector<std::vector<uint8_t>>({{0x01, 0x02, 0x03}})); +} + +// Tests the method that parses the server's Trust Anchor IDs that it can +// provide in the handshake. +TEST_F(SSLClientSocketTest, ParseServerTrustAnchorIDs) { + struct TestCase { + const std::vector<uint8_t> server_trust_anchor_ids; + const std::vector<std::vector<uint8_t>> expected_parsed_trust_anchor_ids; + }; + TestCase test_cases[] = { + // Two Trust Anchor IDs, correctly formed + {{0x03, 0x01, 0x02, 0x03, 0x02, 0x01, 0x01}, + {{0x01, 0x02, 0x03}, {0x01, 0x01}}}, + // Empty + {{}, {}}, + // Malformed + {{0x02, 0x1}, {}}, + {{0x00, 0x01, 0x02, 0x03}, {}}, + {{0x00}, {}}, + }; + + for (const auto& test : test_cases) { + base::SpanReader<const uint8_t> reader(test.server_trust_anchor_ids); + auto result = SSLClientSocketImpl::ParseServerTrustAnchorIDs(&reader); + EXPECT_EQ(result, test.expected_parsed_trust_anchor_ids); + } } // Tests that OCSP stapling is requested, as per Certificate Transparency (RFC
diff --git a/net/socket/ssl_connect_job.cc b/net/socket/ssl_connect_job.cc index 938e42f..ab9b85f 100644 --- a/net/socket/ssl_connect_job.cc +++ b/net/socket/ssl_connect_job.cc
@@ -249,11 +249,17 @@ DCHECK(!TimerIsRunning()); next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; - // If this is an ECH retry, connect to the same server as before. + // If this is an ECH or Trust Anchor IDs retry, connect to the same server as + // before. std::optional<TransportConnectJob::EndpointResultOverride> endpoint_result_override; - if (ech_retry_configs_) { - DCHECK(ssl_client_context()->config().ech_enabled); + if (ech_retry_configs_ || !trust_anchor_ids_for_retry_.empty()) { + if (ech_retry_configs_) { + DCHECK(ssl_client_context()->config().ech_enabled); + } + if (!trust_anchor_ids_for_retry_.empty()) { + DCHECK(base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs)); + } DCHECK(endpoint_result_); endpoint_result_override.emplace(*endpoint_result_, dns_aliases_); } @@ -380,12 +386,15 @@ } if (base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs) && - endpoint_result_ && - !endpoint_result_->metadata.trust_anchor_ids.empty() && !ssl_client_context()->config().trust_anchor_ids.empty()) { - ssl_config.trust_anchor_ids = SSLConfig::SelectTrustAnchorIDs( - endpoint_result_->metadata.trust_anchor_ids, - ssl_client_context()->config().trust_anchor_ids); + if (!trust_anchor_ids_for_retry_.empty()) { + ssl_config.trust_anchor_ids = trust_anchor_ids_for_retry_; + } else if (endpoint_result_ && + !endpoint_result_->metadata.trust_anchor_ids.empty()) { + ssl_config.trust_anchor_ids = SSLConfig::SelectTrustAnchorIDs( + endpoint_result_->metadata.trust_anchor_ids, + ssl_client_context()->config().trust_anchor_ids); + } } net_log().AddEvent(NetLogEventType::SSL_CONNECT_JOB_SSL_CONNECT, [&] { @@ -458,6 +467,36 @@ return OK; } + // If we got a certificate error and the server advertised some Trust Anchor + // IDs in the handshake that we trust, then retry the connection, using the + // fresh Trust Anchor IDs from the server. We only want to retry once; if we + // already have |server_trust_anchor_ids_for_retry_| set at this point, it + // means we already retried, so we skip all of this and treat the connection + // error as usual. + // + // TODO(https://crbug.com/399937371): clarify and test the interactions of ECH + // retry and TAI retry. + if (IsCertificateError(result) && trust_anchor_ids_for_retry_.empty() && + base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs)) { + std::vector<std::vector<uint8_t>> server_trust_anchor_ids = + ssl_socket_->GetServerTrustAnchorIDsForRetry(); + // https://tlswg.org/tls-trust-anchor-ids/draft-ietf-tls-trust-anchor-ids.html#name-retry-mechanism: + // If the EncryptedExtensions had no trust_anchor extension, or no match was + // found, the client returns the error to the application. + if (!server_trust_anchor_ids.empty()) { + trust_anchor_ids_for_retry_ = SSLConfig::SelectTrustAnchorIDs( + server_trust_anchor_ids, + ssl_client_context()->config().trust_anchor_ids); + if (!trust_anchor_ids_for_retry_.empty()) { + ResetStateForRestart(); + next_state_ = GetInitialState(params_->GetConnectionType()); + return OK; + } + } + } + + // TODO(crbug.com/422931824): pass in Trust Anchor IDs info and record + // TAI-specific metrics. SSLClientSocket::RecordSSLConnectResult(ssl_socket_.get(), result, is_ech_capable, ech_enabled, ech_retry_configs_, connect_timing_);
diff --git a/net/socket/ssl_connect_job.h b/net/socket/ssl_connect_job.h index 9f91944..606b045 100644 --- a/net/socket/ssl_connect_job.h +++ b/net/socket/ssl_connect_job.h
@@ -220,6 +220,13 @@ // If not `std::nullopt`, the ECH retry configs to use in the ECH recovery // flow. `endpoint_result_` will then contain the endpoint to reconnect to. std::optional<std::vector<uint8_t>> ech_retry_configs_; + + // If not empty, the intersection of the client's trusted TLS Trust Anchor IDs + // with those advertised by the server during the handshake, in wire format. + // This is the set of Trust Anchor IDs to advertise in the ClientHello when + // retrying the connection after receiving an error. When this is non-empty, + // `endpoint_result_` will contain the endpoint to reconnect to. + std::vector<uint8_t> trust_anchor_ids_for_retry_; }; } // namespace net
diff --git a/net/socket/ssl_connect_job_unittest.cc b/net/socket/ssl_connect_job_unittest.cc index 342f3c98..95f07f6 100644 --- a/net/socket/ssl_connect_job_unittest.cc +++ b/net/socket/ssl_connect_job_unittest.cc
@@ -1193,6 +1193,307 @@ } } +// Test that when `SSLConnectJob` sends Trust Anchor IDs, it retries on failure, +// using the Trust Anchor IDs that the server provides in the handshake. +TEST_F(SSLConnectJobTest, TrustAnchorIDsRetry) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + HostResolverEndpointResult endpoint; + endpoint.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + host_resolver_.rules()->AddRule( + "host", + MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint})); + + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data1); + // The first connection attempt will fail with a certificate error (simulating + // the server providing a certificate that the client does not trust, because, + // for example, the server's Trust Anchor IDs advertised in DNS were stale and + // it does not actually have a certificate for the trust anchor that the + // client selected). + SSLSocketDataProvider ssl_fail(ASYNC, ERR_CERT_AUTHORITY_INVALID); + ssl_fail.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}); + // The server provides a different set of Trust Anchor IDs in the handshake + // than were present in the DNS record. This simulates the situation in which + // the server can't provide a certificate chaining to a trust anchor that the + // client signalled in the handshake, so it made its best guess, but it has + // another certificate available that the client does actually trust. + ssl_fail.server_trust_anchor_ids_for_retry = + std::vector<std::vector<uint8_t>>({{0x02, 0x02}, {0x05, 0x6}}); + socket_factory_.AddSSLSocketDataProvider(&ssl_fail); + // The second connection attempt and handshake succeed. + StaticSocketDataProvider data2; + data2.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data2.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data2); + SSLSocketDataProvider ssl_success(ASYNC, OK); + ssl_success.expected_trust_anchor_ids = + std::vector<uint8_t>({0x02, 0x02, 0x02}); + socket_factory_.AddSSLSocketDataProvider(&ssl_success); + + TestConnectJobDelegate test_delegate; + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); +} + +// Test that when `SSLConnectJob` sends Trust Anchor IDs and the connection +// fails, the client does not retry if the server does not provide Trust Anchor +// IDs in the handshake. +TEST_F(SSLConnectJobTest, NoRetryIfNoServerTrustAnchorIDs) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + HostResolverEndpointResult endpoint; + endpoint.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + host_resolver_.rules()->AddRule( + "host", + MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint})); + + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data1); + // The first connection attempt will fail with a certificate error (simulating + // the server providing a certificate that the client does not trust, because, + // for example, the server's Trust Anchor IDs advertised in DNS were stale and + // it does not actually have a certificate for the trust anchor that the + // client selected). + SSLSocketDataProvider ssl_fail(ASYNC, ERR_CERT_AUTHORITY_INVALID); + ssl_fail.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}); + // The server does not provide any Trust Anchor IDs in the handshake, so there + // should be no retry. + socket_factory_.AddSSLSocketDataProvider(&ssl_fail); + + TestConnectJobDelegate test_delegate( + TestConnectJobDelegate::SocketExpected::ALWAYS); + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), + test::IsError(ERR_CERT_AUTHORITY_INVALID)); +} + +// Test that when `SSLConnectJob` sends Trust Anchor IDs and the connection +// fails, the client does not retry if it does not trust any of the Trust Anchor +// IDs that the server provides in the handshake. +TEST_F(SSLConnectJobTest, NoRetryIfNoIntersectionWithServerTrustAnchorIDs) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + HostResolverEndpointResult endpoint; + endpoint.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + host_resolver_.rules()->AddRule( + "host", + MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint})); + + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data1); + // The first connection attempt will fail with a certificate error (simulating + // the server providing a certificate that the client does not trust, because, + // for example, the server's Trust Anchor IDs advertised in DNS were stale and + // it does not actually have a certificate for the trust anchor that the + // client selected). + SSLSocketDataProvider ssl_fail(ASYNC, ERR_CERT_AUTHORITY_INVALID); + ssl_fail.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}); + // The server does not provide any Trust Anchor IDs in the handshake that the + // client trusts, so there should be no retry. + ssl_fail.server_trust_anchor_ids_for_retry = + std::vector<std::vector<uint8_t>>({{0x06, 0x06}, {0x07, 0x7}}); + socket_factory_.AddSSLSocketDataProvider(&ssl_fail); + + TestConnectJobDelegate test_delegate( + TestConnectJobDelegate::SocketExpected::ALWAYS); + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), + test::IsError(ERR_CERT_AUTHORITY_INVALID)); +} + +// Test that when `SSLConnectJob` sends Trust Anchor IDs and the connection +// fails, the client does not retry if the error is not certificate-related. +TEST_F(SSLConnectJobTest, NoRetryIfNotCertificateError) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + HostResolverEndpointResult endpoint; + endpoint.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + host_resolver_.rules()->AddRule( + "host", + MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint})); + + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data1); + // The connection attempt will fail with a non-certificate error. + SSLSocketDataProvider ssl_fail(ASYNC, ERR_SSL_KEY_USAGE_INCOMPATIBLE); + ssl_fail.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}); + ssl_fail.server_trust_anchor_ids_for_retry = + std::vector<std::vector<uint8_t>>({{0x02, 0x02}}); + socket_factory_.AddSSLSocketDataProvider(&ssl_fail); + // There should be no retry because the error was not certificate-related. + + TestConnectJobDelegate test_delegate; + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), + test::IsError(ERR_SSL_KEY_USAGE_INCOMPATIBLE)); +} + +// Test that `SSLConnectJob` does not retry more than once even if the server +// provides Trust Anchor IDs in each handshake attempt. +TEST_F(SSLConnectJobTest, TrustAnchorIDsRetryOnlyOnce) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + HostResolverEndpointResult endpoint; + endpoint.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + host_resolver_.rules()->AddRule( + "host", + MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint})); + + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data1); + // The first connection attempt will fail with a certificate error (simulating + // the server providing a certificate that the client does not trust, because, + // for example, the server's Trust Anchor IDs advertised in DNS were stale and + // it does not actually have a certificate for the trust anchor that the + // client selected). + SSLSocketDataProvider ssl_fail(ASYNC, ERR_CERT_INVALID); + ssl_fail.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}); + // The server provides a different set of Trust Anchor IDs in the handshake + // than were present in the DNS record, simulating e.g. stale data in DNS but + // a certificate available on the server that the client might be able to + // actually accept. + ssl_fail.server_trust_anchor_ids_for_retry = + std::vector<std::vector<uint8_t>>({{0x02, 0x02}, {0x05, 0x6}}); + socket_factory_.AddSSLSocketDataProvider(&ssl_fail); + // The second connection attempt again fails with a (different) certificate + // error. + StaticSocketDataProvider data2; + data2.set_expected_addresses(AddressList(endpoint.ip_endpoints)); + data2.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data2); + SSLSocketDataProvider ssl_fail2(ASYNC, ERR_CERT_AUTHORITY_INVALID); + ssl_fail2.expected_trust_anchor_ids = + std::vector<uint8_t>({0x02, 0x02, 0x02}); + ssl_fail2.server_trust_anchor_ids_for_retry = + std::vector<std::vector<uint8_t>>({{0x04, 0x04}, {0x05, 0x6}}); + socket_factory_.AddSSLSocketDataProvider(&ssl_fail2); + // There should be no third attempt. + + TestConnectJobDelegate test_delegate( + TestConnectJobDelegate::SocketExpected::ALWAYS); + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), + test::IsError(ERR_CERT_AUTHORITY_INVALID)); +} + +// Tests that when `SSLConnectJob` retries due to an error after sending Trust +// Anchor IDs, it reuses the same endpoint on the retry. +TEST_F(SSLConnectJobTest, TrustAnchorIDsRetryUsesSameEndpoint) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kTLSTrustAnchorIDs); + + SSLContextConfig config; + config.trust_anchor_ids = {{0x01, 0x02, 0x03}, {0x02, 0x02}, {0x04, 0x04}}; + ssl_config_service_->UpdateSSLConfigAndNotify(config); + + // Configure two HTTPS RR routes, to test the retry uses the correct one. + HostResolverEndpointResult endpoint1, endpoint2; + endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)}; + endpoint1.metadata.trust_anchor_ids = { + {0x01, 0x02, 0x03}, {0x04, 0x04}, {0x05, 0x05, 0x05}}; + endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)}; + endpoint2.metadata.trust_anchor_ids = {{0x04, 0x04}}; + host_resolver_.rules()->AddRule( + "host", MockHostResolverBase::RuleResolver::RuleResult( + std::vector{endpoint1, endpoint2})); + + // The first connection attempt will be to `endpoint1`, which will fail. + StaticSocketDataProvider data1; + data1.set_expected_addresses(AddressList(endpoint1.ip_endpoints)); + data1.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); + socket_factory_.AddSocketDataProvider(&data1); + // The second connection attempt will be to `endpoint2`, which will succeed. + StaticSocketDataProvider data2; + data2.set_expected_addresses(AddressList(endpoint2.ip_endpoints)); + data2.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data2); + // The handshake will then fail, but then provide up-to-date Trust Anchor IDs. + SSLSocketDataProvider ssl2(ASYNC, ERR_CERT_AUTHORITY_INVALID); + ssl2.expected_trust_anchor_ids = std::vector<uint8_t>({0x02, 0x04, 0x04}); + ssl2.server_trust_anchor_ids_for_retry = {{0x01, 0x02, 0x03}}; + socket_factory_.AddSSLSocketDataProvider(&ssl2); + // The third connection attempt should skip `endpoint1` and retry with only + // `endpoint2`. + StaticSocketDataProvider data3; + data3.set_expected_addresses(AddressList(endpoint2.ip_endpoints)); + data3.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + socket_factory_.AddSocketDataProvider(&data3); + // The handshake should use the Trust Anchor IDs that the server provided in + // the handshake. + SSLSocketDataProvider ssl3(ASYNC, OK); + ssl3.expected_trust_anchor_ids = + std::vector<uint8_t>({0x03, 0x01, 0x02, 0x03}); + socket_factory_.AddSSLSocketDataProvider(&ssl3); + + // The connection should ultimately succeed. + TestConnectJobDelegate test_delegate; + std::unique_ptr<ConnectJob> ssl_connect_job = + CreateConnectJob(&test_delegate, ProxyChain::Direct(), MEDIUM); + EXPECT_THAT(ssl_connect_job->Connect(), test::IsError(ERR_IO_PENDING)); + EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); +} + // Test that `SSLConnectJob` passes the ECHConfigList from DNS to // `SSLClientSocket`. TEST_F(SSLConnectJobTest, EncryptedClientHello) {
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn index 2df8fd7..7afc5774 100644 --- a/services/network/BUILD.gn +++ b/services/network/BUILD.gn
@@ -147,6 +147,12 @@ "restricted_cookie_manager.h", "restricted_udp_socket.cc", "restricted_udp_socket.h", + "scheduler/network_service_scheduler.cc", + "scheduler/network_service_scheduler.h", + "scheduler/network_service_task_priority.cc", + "scheduler/network_service_task_priority.h", + "scheduler/network_service_task_queues.cc", + "scheduler/network_service_task_queues.h", "sec_header_helpers.cc", "sec_header_helpers.h", "session_cleanup_cookie_store.cc", @@ -478,6 +484,8 @@ "resource_scheduler/resource_scheduler_params_manager_unittest.cc", "resource_scheduler/resource_scheduler_unittest.cc", "restricted_cookie_manager_unittest.cc", + "scheduler/network_service_scheduler_unittest.cc", + "scheduler/network_service_task_queues_unittest.cc", "sec_header_helpers_unittest.cc", "session_cleanup_cookie_store_unittest.cc", "shared_dictionary/shared_dictionary_data_pipe_writer_unittest.cc",
diff --git a/services/network/network_service.cc b/services/network/network_service.cc index 4cb2af3..515023b 100644 --- a/services/network/network_service.cc +++ b/services/network/network_service.cc
@@ -96,6 +96,7 @@ #include "services/network/public/mojom/network_service_test.mojom.h" #include "services/network/public/mojom/system_dns_resolution.mojom-forward.h" #include "services/network/restricted_cookie_manager.h" +#include "services/network/scheduler/network_service_scheduler.h" #include "services/network/tpcd/metadata/manager.h" #include "services/network/url_loader.h" @@ -380,6 +381,11 @@ DCHECK(!g_network_service); g_network_service = this; + if (base::FeatureList::IsEnabled(features::kNetworkServiceScheduler)) { + scheduler_ = std::make_unique<NetworkServiceScheduler>(); + scheduler_->SetUpNetTaskRunners(); + } + ContentDecodingInterceptor::SetIsNetworkServiceRunningInTheCurrentProcess( true, {});
diff --git a/services/network/network_service.h b/services/network/network_service.h index ad40223..62a5fc6 100644 --- a/services/network/network_service.h +++ b/services/network/network_service.h
@@ -89,6 +89,7 @@ class NetLogProxySink; class NetworkContext; class NetworkService; +class NetworkServiceScheduler; class SCTAuditingCache; class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService @@ -558,6 +559,9 @@ std::unique_ptr<network::tpcd::metadata::Manager> tpcd_metadata_manager_; + // An experimental task scheduler for NetworkService. + std::unique_ptr<NetworkServiceScheduler> scheduler_; + bool exclusive_cookie_database_locking_ = true; base::WeakPtrFactory<NetworkService> weak_factory_{this}; };
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc index 742bb5aa..3bb7707f 100644 --- a/services/network/public/cpp/features.cc +++ b/services/network/public/cpp/features.cc
@@ -617,4 +617,8 @@ /*name=*/"max_size", 1'000'000); +BASE_FEATURE(kNetworkServiceScheduler, + "NetworkServiceScheduler", + base::FEATURE_DISABLED_BY_DEFAULT); + } // namespace network::features
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h index 1a9ec56..a92060e 100644 --- a/services/network/public/cpp/features.h +++ b/services/network/public/cpp/features.h
@@ -340,6 +340,11 @@ COMPONENT_EXPORT(NETWORK_CPP_FLAGS_AND_SWITCHES) BASE_DECLARE_FEATURE_PARAM(size_t, kSharedDictionaryCacheMaxSizeBytes); +// When enabled, Network Service Scheduler is enabled on the Network +// Service's IO Thread. +COMPONENT_EXPORT(NETWORK_CPP_FLAGS_AND_SWITCHES) +BASE_DECLARE_FEATURE(kNetworkServiceScheduler); + } // namespace network::features #endif // SERVICES_NETWORK_PUBLIC_CPP_FEATURES_H_
diff --git a/services/network/scheduler/OWNERS b/services/network/scheduler/OWNERS new file mode 100644 index 0000000..0121bbf --- /dev/null +++ b/services/network/scheduler/OWNERS
@@ -0,0 +1 @@ +hayato@chromium.org
diff --git a/services/network/scheduler/README.md b/services/network/scheduler/README.md new file mode 100644 index 0000000..85dfd16a --- /dev/null +++ b/services/network/scheduler/README.md
@@ -0,0 +1,33 @@ +# Network Service Scheduler + +This directory contains the implementation for an experimental task scheduler on +Chromium's Network Service. + +The scheduler introduces a `base::sequence_manager::SequenceManager` configured +with multiple prioritized task queues. The primary goal is to allow +high-priority network tasks (e.g., those critical for navigation) to execute +with precedence over lower-priority tasks, aiming to improve user-perceived +performance metrics like FCP and LCP. + +## Design + +For a detailed explanation of the design, motivations, and implementation plan, +please refer to the design document: +[go/task-scheduler-in-net](http://go/task-scheduler-in-net) (Google internal) + +## Overview + +The core mechanism involves: + +1. Initializing a `SequenceManager` on the Network Service Thread. +2. Creating at least two `TaskQueue`s: one for high-priority tasks and one for + default-priority tasks. +3. Exposing `TaskRunner`s associated with these queues. +4. Modifying relevant `PostTask` call sites in `//net` and `//services/network` + to route tasks to the appropriate `TaskRunner` based on the task's conceptual + priority (often derived from `net::RequestPriority`). + +## Status + +This feature is currently experimental and under development. Its impact is +being evaluated via diagnostic metrics and Finch experiments.
diff --git a/services/network/scheduler/network_service_scheduler.cc b/services/network/scheduler/network_service_scheduler.cc new file mode 100644 index 0000000..6eabcac --- /dev/null +++ b/services/network/scheduler/network_service_scheduler.cc
@@ -0,0 +1,86 @@ +// 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 "services/network/scheduler/network_service_scheduler.h" + +#include "base/task/sequence_manager/sequence_manager.h" +#include "base/task/sequence_manager/task_queue.h" +#include "net/base/task/task_runner.h" +#include "services/network/scheduler/network_service_task_priority.h" + +namespace network { + +// The NetworkServiceScheduler's lifetime is coupled with the Network Service +// itself. Therefore, explicit cleanup of resources like +// `net::internal::TaskRunnerGlobals` (for non-test scenarios) is not required +// here as they are managed elsewhere or their lifetime is tied to the process. +// +// For testing scenarios using `SetUpNetTaskRunnersForTesting`, the original +// high-priority task runner is restored. +NetworkServiceScheduler::~NetworkServiceScheduler() { + if (original_high_priority_task_runner_for_testing_.has_value()) { + net::internal::GetTaskRunnerGlobals().high_priority_task_runner = + *original_high_priority_task_runner_for_testing_; + } +} + +NetworkServiceScheduler::NetworkServiceScheduler() + : sequence_manager_( + base::sequence_manager::CreateSequenceManagerOnCurrentThread( + base::sequence_manager::SequenceManager::Settings::Builder() + .SetCanRunTasksByBatches(true) + .SetPrioritySettings( + internal::CreateNetworkServiceTaskPrioritySettings()) + .SetShouldSampleCPUTime(true) + .Build())), + task_queues_(sequence_manager_.get()) { + CHECK_EQ(static_cast<size_t>(sequence_manager_->GetPriorityCount()), + static_cast<size_t>( + internal::NetworkServiceTaskPriority::kPriorityCount)); + + // Set a handler to be called upon completion of each task. + task_queues_.SetOnTaskCompletedHandler(base::BindRepeating( + &NetworkServiceScheduler::OnTaskCompleted, base::Unretained(this))); + + // Enable crash keys for the sequence manager to help debug scheduler related + // crashes. + sequence_manager_->EnableCrashKeys("network_service_scheduler_async_stack"); + + // Set the default task runner for the current thread. + sequence_manager_->SetDefaultTaskRunner(task_queues_.GetDefaultTaskRunner()); +} + +void NetworkServiceScheduler::OnTaskCompleted( + const base::sequence_manager::Task& task, + base::sequence_manager::TaskQueue::TaskTiming* task_timing, + base::LazyNow* lazy_now) { + // Records the end time of the task. + task_timing->RecordTaskEnd(lazy_now); + + // Records CPU usage for the completed task. + // + // Note: Thread time is already subsampled in sequence manager by a factor of + // `kTaskSamplingRateForRecordingCPUTime`. + task_timing->RecordUmaOnCpuMetrics("NetworkService.Scheduler.IOThread"); +} + +void NetworkServiceScheduler::SetUpNetTaskRunners() { + net::internal::TaskRunnerGlobals& globals = + net::internal::GetTaskRunnerGlobals(); + globals.high_priority_task_runner = GetTaskRunner(QueueType::kHighPriority); +} + +void NetworkServiceScheduler::SetUpNetTaskRunnersForTesting() { + CHECK(!original_high_priority_task_runner_for_testing_.has_value()); + original_high_priority_task_runner_for_testing_ = + net::internal::GetTaskRunnerGlobals().high_priority_task_runner; + SetUpNetTaskRunners(); +} + +const scoped_refptr<base::SingleThreadTaskRunner>& +NetworkServiceScheduler::GetTaskRunner(QueueType type) const { + return task_queues_.GetTaskRunner(type); +} + +} // namespace network
diff --git a/services/network/scheduler/network_service_scheduler.h b/services/network/scheduler/network_service_scheduler.h new file mode 100644 index 0000000..0cfa73e2 --- /dev/null +++ b/services/network/scheduler/network_service_scheduler.h
@@ -0,0 +1,82 @@ +// 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 SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_SCHEDULER_H_ +#define SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_SCHEDULER_H_ + +#include <memory> +#include <optional> + +#include "base/component_export.h" +#include "base/memory/scoped_refptr.h" +#include "base/task/sequence_manager/task_queue.h" +#include "services/network/scheduler/network_service_task_queues.h" + +namespace base { +class LazyNow; +namespace sequence_manager { +class SequenceManager; +struct Task; +} // namespace sequence_manager +} // namespace base + +namespace network { + +// Manages the task scheduling for the network service. It sets up a +// `base::sequence_manager::SequenceManager` on the current thread with specific +// priorities defined by NetworkServiceTaskPriority` and manages +// `NetworkServiceTaskQueues` (e.g., default, high priority). +// +// This scheduler is responsible for: +// - Creating and configuring the sequence manager for the network thread. +// - Providing task runners for different priority levels. +// - Integrating with `net::GetTaskRunner` by setting up the task runners in +// `net::internal::TaskRunnerGlobals`. +// - Optionally handling task completion notifications for metrics. +class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceScheduler { + public: + NetworkServiceScheduler(); + + NetworkServiceScheduler(const NetworkServiceScheduler&) = delete; + NetworkServiceScheduler& operator=(const NetworkServiceScheduler&) = delete; + + ~NetworkServiceScheduler(); + + using QueueType = NetworkServiceTaskQueues::QueueType; + + // Returns the task runner for the specified `QueueType`. + const scoped_refptr<base::SingleThreadTaskRunner>& GetTaskRunner( + QueueType type) const; + + // Sets up the global task runners in `net::internal::TaskRunnerGlobals` so + // that `net::GetTaskRunner(net::RequestPriority)` returns the appropriate + // task runner managed by this scheduler. + void SetUpNetTaskRunners(); + + // Similar to `SetUpNetTaskRunners`, but specifically for testing. + // It saves the current global high-priority task runner and restores it + // when this `NetworkServiceScheduler` instance is destructed. + void SetUpNetTaskRunnersForTesting(); + + private: + // Callback for when a task completes on one of the managed queues. + // Used for recording metrics. + void OnTaskCompleted( + const base::sequence_manager::Task& task, + base::sequence_manager::TaskQueue::TaskTiming* task_timing, + base::LazyNow* lazy_now); + + std::unique_ptr<base::sequence_manager::SequenceManager> sequence_manager_; + NetworkServiceTaskQueues task_queues_; + + // Stores the original global high-priority task runner when + // `SetUpNetTaskRunnersForTesting()` is called, so it can be restored on + // destruction. + std::optional<scoped_refptr<base::SingleThreadTaskRunner>> + original_high_priority_task_runner_for_testing_; +}; + +} // namespace network + +#endif // SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_SCHEDULER_H_
diff --git a/services/network/scheduler/network_service_scheduler_unittest.cc b/services/network/scheduler/network_service_scheduler_unittest.cc new file mode 100644 index 0000000..43f0a94 --- /dev/null +++ b/services/network/scheduler/network_service_scheduler_unittest.cc
@@ -0,0 +1,165 @@ +// 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 "services/network/scheduler/network_service_scheduler.h" + +#include "base/run_loop.h" +#include "base/task/single_thread_task_runner.h" +#include "base/test/mock_callback.h" +#include "base/test/task_environment.h" +#include "net/base/task/task_runner.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { +namespace { + +using QueueType = NetworkServiceTaskQueues::QueueType; + +using StrictMockTask = + testing::StrictMock<base::MockCallback<base::RepeatingCallback<void()>>>; + +class NetworkServiceSchedulerTest : public testing::Test { + protected: + base::test::TaskEnvironment task_environment; +}; + +// Tests that tasks posted to the default task runner provided by the scheduler +// are executed in order. Also verifies that the scheduler sets the current +// thread's default task runner. +TEST_F(NetworkServiceSchedulerTest, DefaultQueuePostsTasksInOrder) { + NetworkServiceScheduler scheduler; + + scoped_refptr<base::SingleThreadTaskRunner> tq = + scheduler.GetTaskRunner(QueueType::kDefault); + EXPECT_EQ(tq.get(), base::SingleThreadTaskRunner::GetCurrentDefault()); + + StrictMockTask task_1; + StrictMockTask task_2; + + testing::InSequence s; + EXPECT_CALL(task_1, Run); + EXPECT_CALL(task_2, Run); + + base::RunLoop run_loop; + tq->PostTask(FROM_HERE, task_1.Get()); + tq->PostTask(FROM_HERE, task_2.Get()); + tq->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); +} + +// Tests that tasks posted to different priority queues (default and high +// priority) are executed according to their priority, with high priority tasks +// running before default priority tasks. +TEST_F(NetworkServiceSchedulerTest, MultipleQueuesHighPriorityTaskRunsFirst) { + NetworkServiceScheduler scheduler; + + scoped_refptr<base::SingleThreadTaskRunner> tq1 = + scheduler.GetTaskRunner(QueueType::kDefault); + scoped_refptr<base::SingleThreadTaskRunner> tq2 = + scheduler.GetTaskRunner(QueueType::kHighPriority); + + StrictMockTask task_1; + StrictMockTask task_2; + + testing::InSequence s; + // High priority task runs at first. + EXPECT_CALL(task_2, Run); + // Default priority task runs after a high priority task runs. + EXPECT_CALL(task_1, Run); + + base::RunLoop run_loop; + tq1->PostTask(FROM_HERE, task_1.Get()); + tq2->PostTask(FROM_HERE, task_2.Get()); + tq1->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); +} + +// Tests that tasks posted to the current thread's default task runner *before* +// the NetworkServiceScheduler is instantiated are still executed +// correctly once the scheduler is up and running. +TEST_F(NetworkServiceSchedulerTest, + PendingTasksRunAfterSchedulerInitialization) { + StrictMockTask task_1; + StrictMockTask task_2; + + testing::InSequence s; + EXPECT_CALL(task_1, Run); + EXPECT_CALL(task_2, Run); + + base::RunLoop run_loop; + + // Post a task before NetworkServiceScheduler is instantiated. + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, + task_1.Get()); + NetworkServiceScheduler scheduler; + scoped_refptr<base::SingleThreadTaskRunner> tq = + scheduler.GetTaskRunner(QueueType::kDefault); + tq->PostTask(FROM_HERE, task_2.Get()); + tq->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); +} + +// Tests a combination of pending tasks (posted before scheduler instantiation) +// and tasks posted to different priority queues after the scheduler is +// instantiated. Verifies that all tasks run in the correct order based on +// posting time and priority. +TEST_F(NetworkServiceSchedulerTest, PendingAndPostedTasksRunInCorrectOrder) { + StrictMockTask task_1; + StrictMockTask task_2; + StrictMockTask task_3; + StrictMockTask task_4; + + testing::InSequence s; + // Prior tasks run at first. + EXPECT_CALL(task_1, Run); + EXPECT_CALL(task_2, Run); + // High priority task runs. + EXPECT_CALL(task_4, Run); + // Default priority task runs after a high priority task runs. + EXPECT_CALL(task_3, Run); + + base::RunLoop run_loop; + + // Post tasks before NetworkServiceScheduler is instantiated. + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, + task_1.Get()); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, + task_2.Get()); + + NetworkServiceScheduler scheduler; + scoped_refptr<base::SingleThreadTaskRunner> tq_default = + scheduler.GetTaskRunner(QueueType::kDefault); + scoped_refptr<base::SingleThreadTaskRunner> tq_high = + scheduler.GetTaskRunner(QueueType::kHighPriority); + tq_default->PostTask(FROM_HERE, task_3.Get()); + tq_high->PostTask(FROM_HERE, task_4.Get()); + tq_default->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); +} + +// Tests the `SetUpNetTaskRunners`. +// Verifies that after calling `SetupNetTaskRunners`, `net::GetTaskRunner` +// returns the appropriate task runners managed by the scheduler. +TEST_F(NetworkServiceSchedulerTest, + SetUpNetTaskRunnersIntegratesWithNetGetTaskRunner) { + NetworkServiceScheduler scheduler; + scheduler.SetUpNetTaskRunnersForTesting(); + + auto scheduler_high_priority_runner = + scheduler.GetTaskRunner(QueueType::kHighPriority); + auto net_highest_priority_runner = net::GetTaskRunner(net::HIGHEST); + + EXPECT_EQ(net_highest_priority_runner.get(), + scheduler_high_priority_runner.get()); + + auto scheduler_default_runner = scheduler.GetTaskRunner(QueueType::kDefault); + auto net_medium_priority_runner = net::GetTaskRunner(net::MEDIUM); + EXPECT_EQ(net_medium_priority_runner.get(), scheduler_default_runner.get()); + EXPECT_EQ(net_medium_priority_runner.get(), + base::SingleThreadTaskRunner::GetCurrentDefault().get()); +} + +} // namespace +} // namespace network
diff --git a/services/network/scheduler/network_service_task_priority.cc b/services/network/scheduler/network_service_task_priority.cc new file mode 100644 index 0000000..5c513d7 --- /dev/null +++ b/services/network/scheduler/network_service_task_priority.cc
@@ -0,0 +1,47 @@ +// 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 "services/network/scheduler/network_service_task_priority.h" + +#include "base/notreached.h" +#include "base/tracing/protos/chrome_track_event.pbzero.h" + +namespace network::internal { + +namespace { + +using ProtoPriority = perfetto::protos::pbzero::SequenceManagerTask::Priority; + +ProtoPriority ToProtoPriority(NetworkServiceTaskPriority priority) { + switch (priority) { + case NetworkServiceTaskPriority::kHighPriority: + return ProtoPriority::HIGHEST_PRIORITY; + case NetworkServiceTaskPriority::kNormalPriority: + return ProtoPriority::NORMAL_PRIORITY; + case NetworkServiceTaskPriority::kPriorityCount: + NOTREACHED(); + } + NOTREACHED(); +} + +ProtoPriority TaskPriorityToProto( + base::sequence_manager::TaskQueue::QueuePriority priority) { + CHECK_LT(static_cast<size_t>(priority), + static_cast<size_t>(NetworkServiceTaskPriority::kPriorityCount)); + return ToProtoPriority(static_cast<NetworkServiceTaskPriority>(priority)); +} + +} // namespace + +base::sequence_manager::SequenceManager::PrioritySettings +CreateNetworkServiceTaskPrioritySettings() { + using base::sequence_manager::TaskQueue; + base::sequence_manager::SequenceManager::PrioritySettings settings( + NetworkServiceTaskPriority::kPriorityCount, + NetworkServiceTaskPriority::kNormalPriority); + settings.SetProtoPriorityConverter(&TaskPriorityToProto); + return settings; +} + +} // namespace network::internal
diff --git a/services/network/scheduler/network_service_task_priority.h b/services/network/scheduler/network_service_task_priority.h new file mode 100644 index 0000000..9b795161 --- /dev/null +++ b/services/network/scheduler/network_service_task_priority.h
@@ -0,0 +1,33 @@ +// 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 SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_PRIORITY_H_ +#define SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_PRIORITY_H_ + +#include "base/component_export.h" +#include "base/task/sequence_manager/sequence_manager.h" +#include "base/task/sequence_manager/task_queue.h" + +namespace network::internal { + +// Defines the set of task priorities for the Network Service. These priorities +// are used by the `SequenceManager` to schedule tasks. +enum class NetworkServiceTaskPriority : base::sequence_manager::TaskQueue:: + QueuePriority { + // Priorities are in descending order. + kHighPriority = 0, + kNormalPriority = 1, + kDefaultPriority = kNormalPriority, + // Must be the last entry. + kPriorityCount = 2, + }; + +// Creates and returns the priority settings for the Network Service's +// `SequenceManager`. +COMPONENT_EXPORT(NETWORK_SERVICE) +base::sequence_manager::SequenceManager::PrioritySettings +CreateNetworkServiceTaskPrioritySettings(); + +} // namespace network::internal +#endif // SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_PRIORITY_H_
diff --git a/services/network/scheduler/network_service_task_queues.cc b/services/network/scheduler/network_service_task_queues.cc new file mode 100644 index 0000000..aec6388 --- /dev/null +++ b/services/network/scheduler/network_service_task_queues.cc
@@ -0,0 +1,69 @@ +// 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 "services/network/scheduler/network_service_task_queues.h" + +#include "base/notreached.h" +#include "base/task/sequence_manager/sequence_manager.h" +#include "services/network/scheduler/network_service_task_priority.h" + +namespace network { +namespace { + +using NetworkServiceTaskPriority = + ::network::internal::NetworkServiceTaskPriority; +using QueueName = ::perfetto::protos::pbzero::SequenceManagerTask::QueueName; + +QueueName GetTaskQueueName(NetworkServiceTaskQueues::QueueType queue_type) { + switch (queue_type) { + case NetworkServiceTaskQueues::QueueType::kDefault: + return QueueName::NETWORK_SERVICE_THREAD_DEFAULT_TQ; + case NetworkServiceTaskQueues::QueueType::kHighPriority: + return QueueName::NETWORK_SERVICE_THREAD_HIGH_TQ; + default: + NOTREACHED(); + } +} + +} // namespace + +NetworkServiceTaskQueues::NetworkServiceTaskQueues( + base::sequence_manager::SequenceManager* sequence_manager) { + CreateTaskQueues(sequence_manager); + CreateNetworkServiceTaskRunners(); +} + +NetworkServiceTaskQueues::~NetworkServiceTaskQueues() = default; + +void NetworkServiceTaskQueues::CreateTaskQueues( + base::sequence_manager::SequenceManager* sequence_manager) { + for (size_t i = 0; i < task_queues_.size(); ++i) { + task_queues_[i] = sequence_manager->CreateTaskQueue( + base::sequence_manager::TaskQueue::Spec( + GetTaskQueueName(static_cast<QueueType>(i)))); + } + + // Default queue + GetTaskQueue(QueueType::kDefault) + ->SetQueuePriority(NetworkServiceTaskPriority::kDefaultPriority); + + // High Priority queue + GetTaskQueue(QueueType::kHighPriority) + ->SetQueuePriority(NetworkServiceTaskPriority::kHighPriority); +} + +void NetworkServiceTaskQueues::CreateNetworkServiceTaskRunners() { + for (size_t i = 0; i < task_queues_.size(); ++i) { + task_runners_[i] = task_queues_[i]->task_runner(); + } +} + +void NetworkServiceTaskQueues::SetOnTaskCompletedHandler( + base::sequence_manager::TaskQueue::OnTaskCompletedHandler handler) { + for (auto& queue : task_queues_) { + queue->SetOnTaskCompletedHandler(handler); + } +} + +} // namespace network
diff --git a/services/network/scheduler/network_service_task_queues.h b/services/network/scheduler/network_service_task_queues.h new file mode 100644 index 0000000..db349cde --- /dev/null +++ b/services/network/scheduler/network_service_task_queues.h
@@ -0,0 +1,103 @@ +// 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 SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_QUEUES_H_ +#define SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_QUEUES_H_ + +#include <array> + +#include "base/component_export.h" +#include "base/functional/callback_helpers.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "base/task/sequence_manager/task_queue.h" +#include "base/task/single_thread_task_runner.h" + +namespace base::sequence_manager { +class SequenceManager; +} // namespace base::sequence_manager + +namespace network { + +// Task queues for the network service thread. +// +// Instances must be created and destroyed on the same thread as the +// underlying SequenceManager and instances are not allowed to outlive this +// SequenceManager. All methods of this class must be called from the +// associated thread unless noted otherwise. +// +// This class creates and manages a set of `base::sequence_manager::TaskQueue`s +// with different priorities for the network service thread. It provides +// `base::SingleThreadTaskRunner`s for each of these queues. +class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceTaskQueues { + public: + // Defines the types of task queues available. + enum class QueueType { + kDefault, + kHighPriority, + kMaxValue = kHighPriority, + }; + + // Creates task queues and task runners using the provided `sequence_manager`. + // The `sequence_manager` must outlive this `NetworkServiceTaskQueues` + // instance. + explicit NetworkServiceTaskQueues( + base::sequence_manager::SequenceManager* sequence_manager); + + // Destroys all managed task queues. + ~NetworkServiceTaskQueues(); + + // Returns the underlying `TaskQueue` for the default priority. + base::sequence_manager::TaskQueue* GetDefaultTaskQueue() const { + return GetTaskQueue(QueueType::kDefault); + } + + // Returns the task runner that should be returned by + // SingleThreadTaskRunner::GetCurrentDefault(). + // This is typically the task runner for the `QueueType::kDefault`. + const scoped_refptr<base::SingleThreadTaskRunner>& GetDefaultTaskRunner() + const { + return GetTaskRunner(QueueType::kDefault); + } + + // Returns the task runner for the specified `QueueType`. + const scoped_refptr<base::SingleThreadTaskRunner>& GetTaskRunner( + QueueType type) const { + return task_runners_[static_cast<size_t>(type)]; + } + + // Sets a handler to be called when a task is completed on any of the + // managed task queues. + void SetOnTaskCompletedHandler( + base::sequence_manager::TaskQueue::OnTaskCompletedHandler handler); + + private: + static constexpr size_t kNumQueueTypes = + static_cast<size_t>(QueueType::kMaxValue) + 1; + + // Helper to get the underlying `TaskQueue` for a given `QueueType`. + base::sequence_manager::TaskQueue* GetTaskQueue(QueueType type) const { + return task_queues_[static_cast<size_t>(type)].get(); + } + + void CreateTaskQueues( + base::sequence_manager::SequenceManager* sequence_manager); + + void CreateNetworkServiceTaskRunners(); + + // Array of handles to the underlying task queues. + // The index corresponds to the integer value of `QueueType`. + std::array<base::sequence_manager::TaskQueue::Handle, kNumQueueTypes> + task_queues_; + + // Array of task runners, one for each `TaskQueue` in `task_queues_`. There is + // a 1:1 correspondence: `task_runners_[i]` is the runner for + // `task_queues_[i]`. + std::array<scoped_refptr<base::SingleThreadTaskRunner>, kNumQueueTypes> + task_runners_; +}; + +} // namespace network + +#endif // SERVICES_NETWORK_SCHEDULER_NETWORK_SERVICE_TASK_QUEUES_H_
diff --git a/services/network/scheduler/network_service_task_queues_unittest.cc b/services/network/scheduler/network_service_task_queues_unittest.cc new file mode 100644 index 0000000..7f97a42 --- /dev/null +++ b/services/network/scheduler/network_service_task_queues_unittest.cc
@@ -0,0 +1,92 @@ +// 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 "services/network/scheduler/network_service_task_queues.h" + +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_type.h" +#include "base/run_loop.h" +#include "base/task/sequence_manager/sequence_manager.h" +#include "base/test/bind.h" +#include "base/test/mock_callback.h" +#include "services/network/scheduler/network_service_task_priority.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { +namespace { + +using StrictMockTask = + testing::StrictMock<base::MockCallback<base::RepeatingCallback<void()>>>; + +using QueueType = NetworkServiceTaskQueues::QueueType; + +// Test fixture for NetworkServiceTaskQueues. Sets up a SequenceManager and +// NetworkServiceTaskQueues instance for each test. +class NetworkServiceTaskQueuesTest : public testing::Test { + protected: + NetworkServiceTaskQueuesTest() + : sequence_manager_( + base::sequence_manager:: + CreateSequenceManagerOnCurrentThreadWithPump( + base::MessagePump::Create(base::MessagePumpType::DEFAULT), + base::sequence_manager::SequenceManager::Settings::Builder() + .SetPrioritySettings( + internal:: + CreateNetworkServiceTaskPrioritySettings()) + .Build())), + queues_(sequence_manager_.get()) { + sequence_manager_->SetDefaultTaskRunner(queues_.GetDefaultTaskRunner()); + } + + std::unique_ptr<base::sequence_manager::SequenceManager> sequence_manager_; + NetworkServiceTaskQueues queues_; +}; + +// Tests that tasks posted to the default task runner are executed in order. +TEST_F(NetworkServiceTaskQueuesTest, SimplePosting) { + scoped_refptr<base::SingleThreadTaskRunner> tq = + queues_.GetDefaultTaskRunner(); + + StrictMockTask task_1; + StrictMockTask task_2; + + testing::InSequence s; + EXPECT_CALL(task_1, Run); + EXPECT_CALL(task_2, Run); + + base::RunLoop run_loop; + tq->PostTask(FROM_HERE, task_1.Get()); + tq->PostTask(FROM_HERE, task_2.Get()); + tq->PostTask(FROM_HERE, run_loop.QuitClosure()); + run_loop.Run(); +} + +// Tests that tasks posted to different priority queues are executed according +// to their priority (high priority first, then default). +TEST_F(NetworkServiceTaskQueuesTest, PostingToMultipleQueues) { + scoped_refptr<base::SingleThreadTaskRunner> tq1 = + queues_.GetDefaultTaskRunner(); + scoped_refptr<base::SingleThreadTaskRunner> tq2 = + queues_.GetTaskRunner(QueueType::kHighPriority); + + StrictMockTask task_1; + StrictMockTask task_2; + + testing::InSequence s; + EXPECT_CALL(task_2, Run); + EXPECT_CALL(task_1, Run); + + base::RunLoop run_loop; + + tq1->PostTask(FROM_HERE, task_1.Get()); + tq2->PostTask(FROM_HERE, task_2.Get()); + + tq1->PostTask(FROM_HERE, run_loop.QuitClosure()); + + run_loop.Run(); +} + +} // namespace +} // namespace network
diff --git a/services/passage_embeddings/BUILD.gn b/services/passage_embeddings/BUILD.gn index 6d0dd27..478c34770 100644 --- a/services/passage_embeddings/BUILD.gn +++ b/services/passage_embeddings/BUILD.gn
@@ -10,6 +10,8 @@ "passage_embeddings_service.cc", "passage_embeddings_service.h", ] + public_deps = [ "//mojo/public/cpp/bindings" ] + deps = [ "//services/passage_embeddings/public/mojom" ] if (build_with_tflite_lib) { sources += [ @@ -20,12 +22,6 @@ "passage_embeddings_op_resolver.cc", "passage_embeddings_op_resolver.h", ] - } - - public_deps = [ "//mojo/public/cpp/bindings" ] - deps = [ "//services/passage_embeddings/public/mojom" ] - - if (build_with_tflite_lib) { public_deps += [ "//third_party/sentencepiece:sentencepiece", "//third_party/sentencepiece:sentencepiece_proto", @@ -34,14 +30,10 @@ "//third_party/tflite_support", "//third_party/tflite_support:tflite_support_proto", ] - - deps += [ "//services/on_device_model:ml_internal_buildflags" ] - } - - if (build_with_tflite_lib) { deps += [ "//components/optimization_guide:machine_learning_tflite_buildflags", - "//components/optimization_guide/core:machine_learning", + "//components/optimization_guide/core/inference:op_resolver", + "//services/on_device_model:ml_internal_buildflags", ] } @@ -52,7 +44,6 @@ source_set("tests") { testonly = true - sources = [ "passage_embeddings_service_unittest.cc" ] deps = [ ":passage_embeddings",
diff --git a/services/webnn/ort/context_impl_ort.cc b/services/webnn/ort/context_impl_ort.cc index 07f4cba..056131a 100644 --- a/services/webnn/ort/context_impl_ort.cc +++ b/services/webnn/ort/context_impl_ort.cc
@@ -171,13 +171,13 @@ /*scatter_nd_updates=*/{}, /*sigmoid_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank}, /*slice_input=*/{}, - /*softmax_input=*/{}, + /*softmax_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank}, /*softplus_input=*/{}, /*softsign_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank}, /*split_input=*/{}, /*tanh_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank}, /*tile_input=*/{}, - /*transpose_input=*/{}, + /*transpose_input=*/{SupportedDataTypes::All(), kMaxRank}, /*triangular_input=*/{}, /*where_condition=*/{}, /*where_value=*/{}});
diff --git a/services/webnn/ort/graph_builder_ort.cc b/services/webnn/ort/graph_builder_ort.cc index 00daf053..0e64ef4 100644 --- a/services/webnn/ort/graph_builder_ort.cc +++ b/services/webnn/ort/graph_builder_ort.cc
@@ -53,8 +53,10 @@ constexpr base::cstring_view kOpTypeHardSwish = "HardSwish"; constexpr base::cstring_view kOpTypeRelu = "Relu"; constexpr base::cstring_view kOpTypeSigmoid = "Sigmoid"; +constexpr base::cstring_view kOpTypeSoftmax = "Softmax"; constexpr base::cstring_view kOpTypeSoftsign = "Softsign"; constexpr base::cstring_view kOpTypeTanh = "Tanh"; +constexpr base::cstring_view kOpTypeTranspose = "Transpose"; // Pooling operations constexpr base::cstring_view kOpTypeAveragePool2d = "AveragePool"; @@ -588,6 +590,44 @@ model_editor_.AddNode(op_type, node, inputs, outputs, attributes); } +void GraphBuilderOrt::AddSoftmaxOperation(const mojom::Softmax& softmax) { + const std::string node = GenerateOperationName(softmax.label); + const std::string input = GetOperandNameById(softmax.input_operand_id); + const std::string output = GetOperandNameById(softmax.output_operand_id); + + CHECK(context_properties_.data_type_limits.softmax_input.Supports( + GetOperand(softmax.input_operand_id).descriptor)); + + std::array<const char*, 1> inputs = {input.c_str()}; + std::array<const char*, 1> outputs = {output.c_str()}; + + constexpr base::cstring_view kAttrAxis = "axis"; + std::array<ScopedOrtOpAttr, 1> attributes = {model_editor_.CreateAttribute( + kAttrAxis, static_cast<int64_t>(softmax.axis))}; + + model_editor_.AddNode(kOpTypeSoftmax, node, inputs, outputs, attributes); +} + +void GraphBuilderOrt::AddTransposeOperation(const mojom::Transpose& transpose) { + const std::string node = GenerateOperationName(transpose.label); + const std::string input = GetOperandNameById(transpose.input_operand_id); + const std::string output = GetOperandNameById(transpose.output_operand_id); + + CHECK(context_properties_.data_type_limits.transpose_input.Supports( + GetOperand(transpose.input_operand_id).descriptor)); + + std::array<const char*, 1> inputs = {input.c_str()}; + std::array<const char*, 1> outputs = {output.c_str()}; + + constexpr base::cstring_view kAttrPerm = "perm"; + std::vector<int64_t> perm_value(transpose.permutation.begin(), + transpose.permutation.end()); + std::array<ScopedOrtOpAttr, 1> attributes = { + model_editor_.CreateAttribute(kAttrPerm, perm_value)}; + + model_editor_.AddNode(kOpTypeTranspose, node, inputs, outputs, attributes); +} + [[nodiscard]] base::expected<std::unique_ptr<ModelEditor::ModelInfo>, mojom::ErrorPtr> GraphBuilderOrt::BuildModel() { @@ -650,6 +690,10 @@ AddUnaryOperation(*operation->get_sigmoid(), kOpTypeSigmoid); break; } + case mojom::Operation::Tag::kSoftmax: { + AddSoftmaxOperation(*operation->get_softmax()); + break; + } case mojom::Operation::Tag::kSoftsign: { CHECK(data_type_limits.softsign_input.Supports( GetOperand(operation->get_softsign()->input_operand_id) @@ -663,6 +707,10 @@ AddUnaryOperation(*operation->get_tanh(), kOpTypeTanh); break; } + case mojom::Operation::Tag::kTranspose: { + AddTransposeOperation(*operation->get_transpose()); + break; + } case mojom::Operation::Tag::kArgMinMax: case mojom::Operation::Tag::kBatchNormalization: case mojom::Operation::Tag::kConcat: @@ -694,11 +742,9 @@ case mojom::Operation::Tag::kScatterElements: case mojom::Operation::Tag::kScatterNd: case mojom::Operation::Tag::kSlice: - case mojom::Operation::Tag::kSoftmax: case mojom::Operation::Tag::kSoftplus: case mojom::Operation::Tag::kSplit: case mojom::Operation::Tag::kTile: - case mojom::Operation::Tag::kTranspose: case mojom::Operation::Tag::kTriangular: case mojom::Operation::Tag::kWhere: NOTREACHED() << "[WebNN] Unsupported operation.";
diff --git a/services/webnn/ort/graph_builder_ort.h b/services/webnn/ort/graph_builder_ort.h index b06bcd2..bffb46c 100644 --- a/services/webnn/ort/graph_builder_ort.h +++ b/services/webnn/ort/graph_builder_ort.h
@@ -122,6 +122,8 @@ const mojom::ElementWiseUnary& element_wise_unary); void AddGemmOperation(const mojom::Gemm& gemm); void AddPool2dOperation(const mojom::Pool2d& pool2d); + void AddSoftmaxOperation(const mojom::Softmax& softmax); + void AddTransposeOperation(const mojom::Transpose& transpose); [[nodiscard]] base::expected<std::unique_ptr<ModelEditor::ModelInfo>, mojom::ErrorPtr>
diff --git a/services/webnn/webnn_context_impl.cc b/services/webnn/webnn_context_impl.cc index e8935f3..1193451 100644 --- a/services/webnn/webnn_context_impl.cc +++ b/services/webnn/webnn_context_impl.cc
@@ -268,6 +268,12 @@ .IntersectWith(SupportedRanks::Exactly(4)); backend_context_properties.data_type_limits.conv_transpose2d_bias.ranks .IntersectWith(SupportedRanks::Exactly(1)); + backend_context_properties.data_type_limits.logical_and_input.data_types + .RetainAll(DataTypeConstraint::kUint8); + backend_context_properties.data_type_limits.logical_or_input.data_types + .RetainAll(DataTypeConstraint::kUint8); + backend_context_properties.data_type_limits.logical_xor_input.data_types + .RetainAll(DataTypeConstraint::kUint8); backend_context_properties.data_type_limits.logical_not_input.data_types .RetainAll(DataTypeConstraint::kUint8); backend_context_properties.data_type_limits.logical_output.RetainAll(
diff --git a/testing/buildbot/filters/ios.gpu_unittests.filter b/testing/buildbot/filters/ios.gpu_unittests.filter index 734b65ff..0767d978 100644 --- a/testing/buildbot/filters/ios.gpu_unittests.filter +++ b/testing/buildbot/filters/ios.gpu_unittests.filter
@@ -11,7 +11,7 @@ -Service/FeatureInfoTest.InitializeWithANGLE_BGRA8/0 -Service/FeatureInfoTest.InitializeWithANGLE_BGRA8/1 -# TODO(crbug.com/379525871): These tests are failing on the ios-blink-dbg-fyi +# TODO(crbug.com/379525871): These tests are failing on the ios-blink-rel-fyi # bot. -ShaderTranslatorTest.GetAttributes -ShaderTranslatorTest.GetAttributes @@ -28,14 +28,14 @@ -ShaderTranslatorTest.ValidVertexShader -ShaderTranslatorTest.ValidVertexShader -# TODO(crbug.com/379525870): These tests are failing on the ios-blink-dbg-fyi +# TODO(crbug.com/379525870): These tests are failing on the ios-blink-rel-fyi # bot. -OpenGLESContexts/ShaderTranslatorOutputVersionTest.HasCorrectOutputGLSLVersion/0 -OpenGLESContexts/ShaderTranslatorOutputVersionTest.HasCorrectOutputGLSLVersion/1 -OpenGLESContexts/ShaderTranslatorOutputVersionTest.HasCorrectOutputGLSLVersion/2 -OpenGLESContexts/ShaderTranslatorOutputVersionTest.HasCorrectOutputGLSLVersion/3 -# TODO(crbug.com/380308799): These tests are failing on the ios-blink-dbg-fyi +# TODO(crbug.com/380308799): These tests are failing on the ios-blink-rel-fyi # bot. -ES3ShaderTranslatorTest.GetInterfaceBlocks -ES3ShaderTranslatorTest.InvalidInterfaceBlocks
diff --git a/testing/buildbot/filters/ios.ui_base_unittests.filter b/testing/buildbot/filters/ios.ui_base_unittests.filter index 4a67fe7..d3c0695 100644 --- a/testing/buildbot/filters/ios.ui_base_unittests.filter +++ b/testing/buildbot/filters/ios.ui_base_unittests.filter
@@ -4,7 +4,7 @@ -ClipboardTest/PlatformClipboardTest.BitmapWriteAndPngRead_N32_Premul_2x7 # TODO(crbug.com/40276835): A timeout happens on these tests since -# https://ci.chromium.org/ui/p/chromium/builders/ci/ios-blink-dbg-fyi/25367/overview +# https://ci.chromium.org/ui/p/chromium/builders/ci/ios-blink-rel-fyi/25367/overview -ClipboardTest/PlatformClipboardTest.HTMLTest -ClipboardTest/PlatformClipboardTest.MultiFormatTest -ClipboardTest/PlatformClipboardTest.TrickyHTMLTest
diff --git a/testing/buildbot/filters/ios.viz_unittests.filter b/testing/buildbot/filters/ios.viz_unittests.filter index b9853497..330ba20 100644 --- a/testing/buildbot/filters/ios.viz_unittests.filter +++ b/testing/buildbot/filters/ios.viz_unittests.filter
@@ -201,7 +201,7 @@ -SurfaceSynchronizationTestMayAlwaysAckOnActivation.UnAckedSurfaceArrivesBeforeNewActivationDependencies/DoNotAckOnSurfaceActivationWhenInteractive -SurfaceSynchronizationTestMayAlwaysAckOnActivation.UnAckedSurfaceArrivesBeforeNewActivationDependencies/DoNotAckOnSurfaceActivationWhenInteractive- -# TODO(crbug.com/383593269): These tests are failing on the ios-blink-dbg-fyi +# TODO(crbug.com/383593269): These tests are failing on the ios-blink-rel-fyi # bot. -GPURendererPixelTest.TrilinearFiltering/SkiaGraphiteDawn -GPURendererPixelTest.TrilinearFiltering/SkiaGraphiteMetal
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index d1e69074..d521566 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -19677,24 +19677,6 @@ ] } ], - "ReadAnythingDocsIntegrationRollout": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ReadAnythingDocsIntegration" - ] - } - ] - } - ], "ReadAnythingIPHRollout": [ { "platforms": [
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle index a938cb3..53e47b1 100644 --- a/third_party/androidx/build.gradle +++ b/third_party/androidx/build.gradle
@@ -307,7 +307,7 @@ google() maven { // This URL is generated by the fetch_all_androidx.py script. - url 'https://androidx.dev/snapshots/builds/13642928/artifacts/repository' + url 'https://androidx.dev/snapshots/builds/13645243/artifacts/repository' } mavenCentral() }
diff --git a/third_party/angle b/third_party/angle index c53c908..79ec8b3 160000 --- a/third_party/angle +++ b/third_party/angle
@@ -1 +1 @@ -Subproject commit c53c908c41a10167fabdf4f49c98b9d610d43216 +Subproject commit 79ec8b3400ceeafc3e69b9bec29fa39a0e1a9a16
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc index bdc70a8b..8387183 100644 --- a/third_party/blink/renderer/core/layout/length_utils.cc +++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -1086,12 +1086,21 @@ replaced_block = space.AvailableSize().block_size; DCHECK_GE(*replaced_block, 0); } else { + const Length& non_stretch_length = + RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled() + ? Length::FitContent() + : Length::Auto(); const Length& auto_block_length = space.IsBlockAutoBehaviorStretch() ? Length::FillAvailable() - : Length::Auto(); - const LayoutUnit block_size = ResolveMainBlockLength( - space, style, border_padding, block_length, &auto_block_length, - /* intrinsic_size */ kIndefiniteSize); + : non_stretch_length; + const LayoutUnit block_size = + RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled() + ? ResolveMainBlockLength(space, style, border_padding, + block_length, &auto_block_length, + BlockSizeFunc) + : ResolveMainBlockLength(space, style, border_padding, + block_length, &auto_block_length, + /* intrinsic_size */ kIndefiniteSize); if (block_size != kIndefiniteSize) { DCHECK_GE(block_size, LayoutUnit()); replaced_block = block_min_max_sizes.ClampSizeToMinAndMax(block_size); @@ -1138,14 +1147,32 @@ MinMaxSizes inline_min_max_sizes; std::optional<LayoutUnit> replaced_inline; if (mode == ReplacedSizeMode::kIgnoreInlineLengths) { - // Don't resolve any inline lengths or constraints. - inline_min_max_sizes = {LayoutUnit(), LayoutUnit::Max()}; + // Just use the transferred sizes. + inline_min_max_sizes = + RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled() + ? transferred_min_max_sizes + : MinMaxSizes{LayoutUnit(), LayoutUnit::Max()}; } else { inline_min_max_sizes = { ResolveMinInlineLength(space, style, border_padding, MinMaxSizesFunc, style.LogicalMinWidth()), ResolveMaxInlineLength(space, style, border_padding, MinMaxSizesFunc, style.LogicalMaxWidth())}; + + // Transfer the block min/max sizes if applicable. + if (RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled() && + style.LogicalWidth().HasAuto() && + space.InlineAutoBehavior() != AutoSizeBehavior::kStretchExplicit) { + // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers + inline_min_max_sizes.min_size = + std::max(inline_min_max_sizes.min_size, + std::min(transferred_min_max_sizes.min_size, + inline_min_max_sizes.max_size)); + inline_min_max_sizes.max_size = std::min( + inline_min_max_sizes.max_size, transferred_min_max_sizes.max_size); + } + + // Ensure the max-size encompasses the min-size. inline_min_max_sizes.max_size = std::max(inline_min_max_sizes.min_size, inline_min_max_sizes.max_size); @@ -1153,9 +1180,13 @@ replaced_inline = space.AvailableSize().inline_size; DCHECK_GE(*replaced_inline, 0); } else { + const Length& non_stretch_length = + RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled() + ? Length::FitContent() + : Length::Auto(); const Length& auto_length = space.IsInlineAutoBehaviorStretch() ? Length::FillAvailable() - : Length::Auto(); + : non_stretch_length; const LayoutUnit inline_size = ResolveMainInlineLength(space, style, border_padding, MinMaxSizesFunc, inline_length, &auto_length); @@ -1191,15 +1222,16 @@ Length::FillAvailable(), /* auto_length */ nullptr, /* override_available_size */ kIndefiniteSize); } + if (RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled()) { + return size; + } return transferred_min_max_sizes.ClampSizeToMinAndMax(size); }; // We have *only* an aspect-ratio with no sizes (natural or otherwise), we // default to stretching. if (!natural_size && !replaced_inline && !replaced_block) { - replaced_inline = StretchFit(); - replaced_inline = - inline_min_max_sizes.ClampSizeToMinAndMax(*replaced_inline); + replaced_inline = inline_min_max_sizes.ClampSizeToMinAndMax(StretchFit()); } // We only know one size, the other gets computed via the aspect-ratio (if @@ -1227,9 +1259,14 @@ return LogicalSize(*replaced_inline, *replaced_block); } - // Both lengths are unknown, start with the natural-size. - DCHECK(!replaced_inline); - DCHECK(!replaced_block); + // Both sizes are unknown. + if (RuntimeEnabledFeatures::LayoutNewReplacedLogicEnabled()) { + return { + inline_min_max_sizes.ClampSizeToMinAndMax(natural_size->inline_size), + block_min_max_sizes.ClampSizeToMinAndMax(natural_size->block_size)}; + } + + // Start with the natural-size. replaced_inline = natural_size->inline_size; replaced_block = natural_size->block_size;
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc index fcb0236..d976c68c 100644 --- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc +++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc
@@ -3,6 +3,8 @@ // found in the LICENSE file. #include "third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h" +#include <cstddef> + #include "base/feature_list.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -23,6 +25,7 @@ #include "third_party/blink/renderer/core/style/style_fetched_image.h" #include "third_party/blink/renderer/core/timing/dom_window_performance.h" #include "third_party/blink/renderer/core/timing/performance_entry.h" +#include "third_party/blink/renderer/core/timing/soft_navigation_context.h" #include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h" #include "third_party/blink/renderer/platform/heap/thread_state.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" @@ -62,18 +65,6 @@ return visual_size; } -void RecordPotentialSoftNavigationPaint(LocalFrameView* frame_view, - gfx::RectF rect, - Node* node) { - LocalFrame& frame = frame_view->GetFrame(); - if (LocalDOMWindow* window = frame.DomWindow()) { - if (SoftNavigationHeuristics* heuristics = - window->GetSoftNavigationHeuristics()) { - heuristics->RecordPaint(&frame, rect, node); - } - } -} - } // namespace double ImageRecord::EntropyForLCP() const { @@ -90,6 +81,7 @@ void ImageRecord::Trace(Visitor* visitor) const { visitor->Trace(media_timing); + visitor->Trace(soft_navigation_context_); } ImagePaintTimingDetector::ImagePaintTimingDetector(LocalFrameView* frame_view) @@ -210,14 +202,15 @@ added_entry_in_latest_frame_ = false; auto callback = WTF::BindOnce( [](ImagePaintTimingDetector* self, unsigned int frame_index, - const base::TimeTicks& presentation_timestamp, + bool is_recording_lcp, const base::TimeTicks& presentation_timestamp, const DOMPaintTimingInfo& paint_timing_info) { if (self) { self->records_manager_.AssignPaintTimeToRegisteredQueuedRecords( - presentation_timestamp, paint_timing_info, frame_index); + presentation_timestamp, paint_timing_info, frame_index, + is_recording_lcp); } }, - WrapWeakPersistent(this), frame_index_); + WrapWeakPersistent(this), frame_index_, IsRecordingLargestImagePaint()); last_registered_frame_index_ = frame_index_++; // This is for unit-testing purposes only. Some of these tests check for UKMs @@ -252,7 +245,8 @@ void ImageRecordsManager::AssignPaintTimeToRegisteredQueuedRecords( const base::TimeTicks& presentation_timestamp, const DOMPaintTimingInfo& paint_timing_info, - unsigned last_queued_frame_index) { + unsigned last_queued_frame_index, + bool is_recording_lcp) { while (!images_queued_for_paint_time_.empty()) { ImageRecord* record = images_queued_for_paint_time_.front(); // Skip any null records at the start of the queue @@ -301,8 +295,9 @@ record->paint_timing_info = paint_timing_info; } // Update largest if necessary. - if (!largest_painted_image_ || - largest_painted_image_->recorded_size < record->recorded_size) { + if (is_recording_lcp && + (!largest_painted_image_ || + largest_painted_image_->recorded_size < record->recorded_size)) { largest_painted_image_ = std::move(it->value); } // Remove from pending. @@ -349,11 +344,20 @@ if (depth == 1 && IgnorePaintTimingScope::IsDocumentElementInvisible() && media_timing.IsSufficientContentLoadedForPaint()) { records_manager_.MaybeUpdateLargestIgnoredImage( - record_id, rect_size, image_border, mapped_visual_rect); + record_id, rect_size, image_border, mapped_visual_rect, + IsRecordingLargestImagePaint()); } return false; } + SoftNavigationContext* context = nullptr; + if (LocalDOMWindow* window = frame_view_->GetFrame().DomWindow()) { + if (SoftNavigationHeuristics* heuristics = + window->GetSoftNavigationHeuristics()) { + context = heuristics->MaybeGetSoftNavigationContextForTiming(node); + } + } + // RecordImage is called whenever an image is painted, which may happen many // times for the same record. The very first paint for this record, we have // to create and initialize things, and all subsequent paints we just do a @@ -367,13 +371,8 @@ ? media_timing.ContentSizeForEntropy() * 8.0 / rect_size : 0.0; record = records_manager_.RecordFirstPaintAndMaybeCreateImageRecord( - !IsRecordingLargestImagePaint(), record_id, rect_size, image_border, - mapped_visual_rect, bpp); - - // We record soft paint, even if we didn't create a new LCP Record, because - // we only create records for new largest content, but we want to record - // all paints. - RecordPotentialSoftNavigationPaint(frame_view_, mapped_visual_rect, node); + IsRecordingLargestImagePaint(), record_id, rect_size, image_border, + mapped_visual_rect, bpp, context); } // Note: Even if IsRecordedImage() returns `true`, or if we are calling a new @@ -385,6 +384,14 @@ return false; } + // Check if context changed from the last time we painted this media. + if (record->soft_navigation_context_ != context) { + record->soft_navigation_context_ = context; + // TODO(crbug.com/424437484): Find a mechanism to re-report this media, if + // it has already been loaded, because it won't report again otherwise. + // record->loaded = false; + } + // If this frame is the first painted frame for animated content, mark it and // call `QueueToMeasurePaintTime` (eventually) to measure it. // This mechanism works a bit differently for images and video. @@ -410,6 +417,10 @@ object, mapped_visual_rect, media_timing.IsSufficientContentLoadedForPaint(), media_timing.Url()); } + CHECK(context == record->soft_navigation_context_); + if (context) { + context->AddPaintedArea(record); + } return true; } return false; @@ -470,7 +481,8 @@ void ImagePaintTimingDetector::ReportLargestIgnoredImage() { added_entry_in_latest_frame_ = true; - records_manager_.ReportLargestIgnoredImage(frame_index_); + records_manager_.ReportLargestIgnoredImage(frame_index_, + IsRecordingLargestImagePaint()); } ImageRecordsManager::ImageRecordsManager(LocalFrameView* frame_view) @@ -533,7 +545,8 @@ } void ImageRecordsManager::ReportLargestIgnoredImage( - unsigned current_frame_index) { + unsigned current_frame_index, + bool is_recording_lcp) { if (!largest_ignored_image_) return; Node* node = DOMNodeIds::NodeForId(largest_ignored_image_->node_id); @@ -552,7 +565,7 @@ ImageRecord* record = largest_ignored_image_.Get(); CHECK(record); recorded_images_.insert(record->hash); - AddPendingImage(record); + AddPendingImage(record, is_recording_lcp); OnImageLoadedInternal(record, current_frame_index); } @@ -566,23 +579,27 @@ const MediaRecordId& record_id, const uint64_t& visual_size, const gfx::Rect& frame_visual_rect, - const gfx::RectF& root_visual_rect) { - if (visual_size && (!largest_ignored_image_ || - visual_size > largest_ignored_image_->recorded_size)) { + const gfx::RectF& root_visual_rect, + bool is_recording_lcp) { + if (visual_size && is_recording_lcp && + (!largest_ignored_image_ || + visual_size > largest_ignored_image_->recorded_size)) { largest_ignored_image_ = CreateImageRecord( *record_id.GetLayoutObject(), record_id.GetMediaTiming(), visual_size, - frame_visual_rect, root_visual_rect, record_id.GetHash()); + frame_visual_rect, root_visual_rect, record_id.GetHash(), + /*soft_navigation_context=*/nullptr); largest_ignored_image_->load_time = base::TimeTicks::Now(); } } ImageRecord* ImageRecordsManager::RecordFirstPaintAndMaybeCreateImageRecord( - bool record_media_only_and_skip_measuring_all_image_records, + bool is_recording_lcp, const MediaRecordId& record_id, const uint64_t& visual_size, const gfx::Rect& frame_visual_rect, const gfx::RectF& root_visual_rect, - double bpp) { + double bpp, + SoftNavigationContext* soft_navigation_context) { // Don't process the image yet if it is invisible, as it may later become // visible, and potentially eligible to be an LCP candidate. if (visual_size == 0u) { @@ -590,28 +607,38 @@ } recorded_images_.insert(record_id.GetHash()); - if (record_media_only_and_skip_measuring_all_image_records) { + // If we are recording LCP, take the timing unless the correct LCP is already + // larger. + bool timing_needed_for_lcp = + is_recording_lcp && + !(largest_painted_image_ && + largest_painted_image_->recorded_size > visual_size); + // If we have a context involved in this node creation, we need to do record + // keeping. + // Node: Once the soft nav entry is emitted, we might be able to switch to + // largest-area-only recording. + bool timing_needed_for_soft_nav = soft_navigation_context != nullptr; + + if (!timing_needed_for_lcp && !timing_needed_for_soft_nav) { return nullptr; } - // If this cannot become an LCP candidate, no need to do anything else. - if (largest_painted_image_ && - largest_painted_image_->recorded_size > visual_size) { - return nullptr; - } if (bpp < kMinimumEntropyForLCP) { return nullptr; } ImageRecord* record = CreateImageRecord( *record_id.GetLayoutObject(), record_id.GetMediaTiming(), visual_size, - frame_visual_rect, root_visual_rect, record_id.GetHash()); - AddPendingImage(record); + frame_visual_rect, root_visual_rect, record_id.GetHash(), + soft_navigation_context); + AddPendingImage(record, is_recording_lcp); return record; } -void ImageRecordsManager::AddPendingImage(ImageRecord* record) { - if (!largest_pending_image_ || - (largest_pending_image_->recorded_size < record->recorded_size)) { +void ImageRecordsManager::AddPendingImage(ImageRecord* record, + bool is_recording_lcp) { + if (is_recording_lcp && + (!largest_pending_image_ || + (largest_pending_image_->recorded_size < record->recorded_size))) { largest_pending_image_ = record; } pending_images_.insert(record->hash, record); @@ -623,29 +650,20 @@ const uint64_t& visual_size, const gfx::Rect& frame_visual_rect, const gfx::RectF& root_visual_rect, - MediaRecordIdHash hash) { + MediaRecordIdHash hash, + SoftNavigationContext* soft_navigaton_context) { DCHECK_GT(visual_size, 0u); Node* node = object.GetNode(); DOMNodeId node_id = node->GetDomNodeId(); return MakeGarbageCollected<ImageRecord>(node_id, media_timing, visual_size, frame_visual_rect, root_visual_rect, - hash); + hash, soft_navigaton_context); } void ImageRecordsManager::ClearImagesQueuedForPaintTime() { images_queued_for_paint_time_.clear(); } -void ImageRecordsManager::Clear() { - largest_painted_image_ = nullptr; - largest_pending_image_ = nullptr; - images_queued_for_paint_time_.clear(); - recorded_images_.clear(); - pending_images_.clear(); - image_finished_times_.clear(); - largest_ignored_image_ = nullptr; -} - void ImageRecordsManager::Trace(Visitor* visitor) const { visitor->Trace(frame_view_); visitor->Trace(largest_painted_image_);
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h index 8c398a1..a1c473f8 100644 --- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h +++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h
@@ -27,6 +27,7 @@ #include "third_party/blink/renderer/platform/heap/collection_support/heap_deque.h" #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/heap/member.h" #include "third_party/blink/renderer/platform/loader/fetch/media_timing.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" #include "third_party/blink/renderer/platform/wtf/hash_set.h" @@ -42,23 +43,27 @@ class PaintTimingCallbackManager; class StyleImage; struct DOMPaintTimingInfo; +class SoftNavigationContext; static constexpr double kMinimumEntropyForLCP = 0.05; // TODO(crbug/960502): we should limit the access of these properties. // TODO(yoav): Rename all mentions of "image" to "media" -class ImageRecord : public GarbageCollected<ImageRecord> { +class CORE_EXPORT ImageRecord : public GarbageCollected<ImageRecord> { public: ImageRecord(DOMNodeId new_node_id, const MediaTiming* new_media_timing, uint64_t new_recorded_size, const gfx::Rect& frame_visual_rect, const gfx::RectF& root_visual_rect, - MediaRecordIdHash hash) + MediaRecordIdHash hash, + SoftNavigationContext* soft_navigation_context) : node_id(new_node_id), media_timing(new_media_timing), hash(hash), - recorded_size(new_recorded_size) { + root_visual_rect(root_visual_rect), + recorded_size(new_recorded_size), + soft_navigation_context_(soft_navigation_context) { if (PaintTimingVisualizer::IsTracingEnabled()) { lcp_rect_info_ = std::make_unique<LCPRectInfo>( frame_visual_rect, gfx::ToRoundedRect(root_visual_rect)); @@ -83,6 +88,7 @@ MediaRecordIdHash hash; // Mind that |recorded_size| has to be assigned before any size comparisons // (to determine largest image) are performed. + gfx::RectF root_visual_rect; uint64_t recorded_size = 0; unsigned frame_index = 0; // The time of the first paint after fully loaded. 0 means not painted yet. @@ -100,6 +106,7 @@ // Images that come from origin-dirty styles should have some limitations on // what they report. bool origin_clean = true; + WeakMember<SoftNavigationContext> soft_navigation_context_; }; // |ImageRecordsManager| is the manager of all of the images that Largest @@ -140,12 +147,13 @@ // Always adds media record to `recorded_images_`, and might create a new // ImageRecord to add to `pending_images_`. ImageRecord* RecordFirstPaintAndMaybeCreateImageRecord( - bool record_media_only_and_skip_measuring_all_image_records, + bool is_recording_lcp, const MediaRecordId& record_id, const uint64_t& visual_size, const gfx::Rect& frame_visual_rect, const gfx::RectF& root_visual_rect, - double bpp); + double bpp, + SoftNavigationContext* soft_navigation_context); bool IsRecordedImage(MediaRecordIdHash record_id_hash) const { return recorded_images_.Contains(record_id_hash); } @@ -177,24 +185,28 @@ void MaybeUpdateLargestIgnoredImage(const MediaRecordId&, const uint64_t& visual_size, const gfx::Rect& frame_visual_rect, - const gfx::RectF& root_visual_rect); - void ReportLargestIgnoredImage(unsigned current_frame_index); + const gfx::RectF& root_visual_rect, + bool is_recording_lcp); + void ReportLargestIgnoredImage(unsigned current_frame_index, + bool is_recording_lcp); void AssignPaintTimeToRegisteredQueuedRecords( const base::TimeTicks&, const DOMPaintTimingInfo&, - unsigned last_queued_frame_index); + unsigned last_queued_frame_index, + bool is_recording_lcp); - void AddPendingImage(ImageRecord* record); + void AddPendingImage(ImageRecord* record, bool is_recording_lcp); void ClearImagesQueuedForPaintTime(); - void Clear(); - ImageRecord* CreateImageRecord(const LayoutObject& object, - const MediaTiming* media_timing, - const uint64_t& visual_size, - const gfx::Rect& frame_visual_rect, - const gfx::RectF& root_visual_rect, - MediaRecordIdHash hash); + ImageRecord* CreateImageRecord( + const LayoutObject& object, + const MediaTiming* media_timing, + const uint64_t& visual_size, + const gfx::Rect& frame_visual_rect, + const gfx::RectF& root_visual_rect, + MediaRecordIdHash hash, + SoftNavigationContext* soft_navigation_context); inline void QueueToMeasurePaintTime(ImageRecord* record, unsigned current_frame_index) { CHECK(record); @@ -313,11 +325,6 @@ void StopRecordingLargestImagePaint() { recording_largest_image_paint_ = false; } - void RestartRecordingLargestImagePaint() { - recording_largest_image_paint_ = true; - records_manager_.Clear(); - } - void Trace(Visitor*) const; private:
diff --git a/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.cc b/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.cc index 24d4ff67..f001b31 100644 --- a/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.cc +++ b/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.cc
@@ -122,13 +122,13 @@ const AtomicString& image_id = image_element ? image_element->GetIdAttribute() : AtomicString(); + // TODO(crbug.com/424433918): Create a new entry type, specific to soft-navs. window_performance_->OnLargestContentfulPaintUpdated( expose_paint_time_to_api ? std::make_optional(largest_image->paint_timing_info) : std::nullopt, /*paint_size=*/largest_image->recorded_size, /*load_time=*/largest_image->load_time, - /*id=*/image_id, /*url=*/image_url, /*element=*/image_element, is_triggered_by_soft_navigation); @@ -164,6 +164,8 @@ text_node->IsInShadowTree() ? nullptr : DynamicTo<Element>(text_node); const AtomicString& text_id = text_element ? text_element->GetIdAttribute() : AtomicString(); + + // TODO(crbug.com/424433918): Create a new entry type, specific to soft-navs. // Always use paint time as start time for text LCP candidate. window_performance_->OnLargestContentfulPaintUpdated( largest_text.paint_timing_info,
diff --git a/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.h b/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.h index 5cf5bea..74588668 100644 --- a/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.h +++ b/third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.h
@@ -12,6 +12,9 @@ namespace blink { +class TextRecord; +class ImageRecord; + // LargestContentfulPaintCalculator is responsible for tracking the largest // image paint and the largest text paint and notifying WindowPerformance // whenever a new LatestLargestContentfulPaint entry should be dispatched.
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing.cc b/third_party/blink/renderer/core/paint/timing/paint_timing.cc index 5c3e7f6..50cfaecc 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing.cc +++ b/third_party/blink/renderer/core/paint/timing/paint_timing.cc
@@ -33,6 +33,7 @@ #include "third_party/blink/renderer/core/timing/dom_window_performance.h" #include "third_party/blink/renderer/core/timing/performance_entry.h" #include "third_party/blink/renderer/core/timing/performance_timing_for_reporting.h" +#include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h" #include "third_party/blink/renderer/core/timing/window_performance.h" #include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h" #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h" @@ -61,7 +62,6 @@ struct PendingPaintTimingRecord { HashSet<PaintEvent> paint_events; - bool is_soft_navigation = false; base::TimeTicks rendering_update_end_time; }; @@ -247,6 +247,8 @@ void PaintTiming::MarkPaintTimingInternal() { PaintTimingDetector* detector = &GetFrame()->View()->GetPaintTimingDetector(); + SoftNavigationHeuristics* soft_navigation_heuristics = + GetFrame()->DomWindow()->GetSoftNavigationHeuristics(); // 3. Let paintedImages be a new ordered set... auto add_painted_images_element_timing_entries = @@ -264,7 +266,6 @@ // 7. Let reportedPaints be the document’s set of previously reported paints. PendingPaintTimingRecord paint_timing_record{ .paint_events = pending_paint_events_, - .is_soft_navigation = first_paints_reset_, .rendering_update_end_time = last_rendering_update_end_time_}; pending_paint_events_.clear(); @@ -298,6 +299,7 @@ OptionalPaintTimingCallback painted_images_callback, OptionalPaintTimingCallback painted_text_callback, PaintTimingDetector* paint_timing_detector, + SoftNavigationHeuristics* soft_navigation_heuristics, const base::TimeTicks& raw_presentation_timestamp, const DOMPaintTimingInfo& paint_timing_info) { if (!performance) { @@ -308,8 +310,7 @@ // then: Report paint timing given document, // "first-paint", and paintTimingInfo. if (record.paint_events.Contains(PaintEvent::kFirstPaint)) { - performance->AddFirstPaintTiming(paint_timing_info, - record.is_soft_navigation); + performance->AddFirstPaintTiming(paint_timing_info); } // 10.2. If document should report first contentful paint, @@ -317,8 +318,7 @@ // "first-contentful-paint", and paintTimingInfo. if (record.paint_events.Contains( PaintEvent::kFirstContentfulPaint)) { - performance->AddFirstContentfulPaintTiming( - paint_timing_info, record.is_soft_navigation); + performance->AddFirstContentfulPaintTiming(paint_timing_info); } // 10.3. Report largest contentful paint given document, @@ -346,6 +346,10 @@ paint_timing_detector->UpdateLcpCandidate(); } + if (soft_navigation_heuristics && may_have_lcp) { + soft_navigation_heuristics->UpdateSoftLcpCandidate(); + } + // 10.5 If frameTimingInfo is not null, then queue a long // animation frame entry given document, frameTimingInfo, and // paintTimingInfo. @@ -358,7 +362,8 @@ paint_timing_record, WrapPersistent(frame_timing_info), std::move(add_image_lcp_entries), std::move(add_painted_images_element_timing_entries), - std::move(add_painted_text_entries), WrapWeakPersistent(detector)); + std::move(add_painted_text_entries), WrapWeakPersistent(detector), + WrapWeakPersistent(soft_navigation_heuristics)); // 11. If the user-agent does not support implementation-defined presentation // times, call flushPaintTimings and return. @@ -472,12 +477,10 @@ relevant_paint_details.first_paint_ = stamp; - if (!first_paints_reset_) { LocalFrame* frame = GetFrame(); if (frame && frame->GetDocument()) { frame->GetDocument()->MarkFirstPaint(); } - } pending_paint_events_.insert(PaintEvent::kFirstPaint); } @@ -492,23 +495,18 @@ relevant_paint_details.first_contentful_paint_ = stamp; // This only happens in hard navigations. - if (!first_paints_reset_) { - LocalFrame* frame = GetFrame(); - if (!frame) { - return; - } - frame->View()->OnFirstContentfulPaint(); + LocalFrame* frame = GetFrame(); + if (!frame) { + return; + } + frame->View()->OnFirstContentfulPaint(); - if (frame->IsMainFrame() && frame->GetFrameScheduler()) { - frame->GetFrameScheduler()->OnFirstContentfulPaintInMainFrame(); - } + if (frame->IsMainFrame() && frame->GetFrameScheduler()) { + frame->GetFrameScheduler()->OnFirstContentfulPaintInMainFrame(); } SetFirstPaint(stamp); Mark(PaintEvent::kFirstContentfulPaint); - - if (!first_paints_reset_ || soft_navigation_detected_) { - NotifyPaintTimingChanged(); - } + NotifyPaintTimingChanged(); } void PaintTiming::Mark(PaintEvent event) { @@ -598,18 +596,7 @@ void PaintTiming::SetFirstPaintPresentation( const PaintTimingInfo& paint_timing_info) { - if (soft_navigation_fp_reported_) { - return; - } - if (first_paints_reset_ && !soft_navigation_detected_) { - // We're expecting a soft navigation paint, but soft navigation wasn't yet - // detected. Avoid reporting it for now, and it'll be reported once soft - // navigation is detected. - soft_navigation_pending_first_paint_timing_info_ = paint_timing_info; - return; - } PaintDetails& relevant_paint_details = GetRelevantPaintDetails(); - soft_navigation_pending_first_paint_timing_info_ = std::nullopt; DCHECK(relevant_paint_details.first_paint_presentation_.is_null()); relevant_paint_details.first_paint_presentation_ = paint_timing_info.presentation_time; @@ -621,51 +608,24 @@ relevant_paint_details.first_paint_presentation_.since_origin() .InSecondsF()); NotifyPaintTimingChanged(); - if (first_paints_reset_) { - soft_navigation_fp_reported_ = true; - } } void PaintTiming::SetFirstContentfulPaintPresentation( const PaintTimingInfo& paint_timing_info) { - if (soft_navigation_fcp_reported_) { - return; - } - if (first_paints_reset_ && !soft_navigation_detected_) { - // We're expecting a soft navigation paint, but soft navigation wasn't yet - // detected. Avoid reporting it for now, and it'll be reported once soft - // navigation is detected. - soft_navigation_pending_first_contentful_paint_timing_info_ = - paint_timing_info; - return; - } PaintDetails& relevant_paint_details = GetRelevantPaintDetails(); - soft_navigation_pending_first_contentful_paint_timing_info_ = std::nullopt; DCHECK(relevant_paint_details.first_contentful_paint_presentation_.is_null()); TRACE_EVENT_INSTANT_WITH_TIMESTAMP0( "benchmark,loading", "GlobalFirstContentfulPaint", TRACE_EVENT_SCOPE_GLOBAL, paint_timing_info.presentation_time); relevant_paint_details.first_contentful_paint_presentation_ = paint_timing_info.presentation_time; - bool is_soft_navigation_fcp = false; - if (first_contentful_paint_presentation_ignoring_soft_navigations_ - .is_null()) { - first_contentful_paint_presentation_ignoring_soft_navigations_ = - paint_timing_info.presentation_time; - } else { - is_soft_navigation_fcp = true; - } + CHECK(first_contentful_paint_presentation_.is_null()); + first_contentful_paint_presentation_ = paint_timing_info.presentation_time; probe::PaintTiming( GetSupplementable(), "firstContentfulPaint", relevant_paint_details.first_contentful_paint_presentation_.since_origin() .InSecondsF()); WindowPerformance* performance = GetPerformanceInstance(GetFrame()); - // For soft navigations, we just want to report a performance entry, but not - // trigger any of the other FCP observers. - if (is_soft_navigation_fcp) { - soft_navigation_fcp_reported_ = true; - return; - } if (GetFrame()) { GetFrame()->OnFirstContentfulPaint(); GetFrame()->Loader().Progress().DidFirstContentfulPaint(); @@ -765,16 +725,4 @@ index)); } -void PaintTiming::SoftNavigationDetected() { - soft_navigation_detected_ = true; - if (soft_navigation_pending_first_paint_timing_info_.has_value()) { - SetFirstPaintPresentation( - *soft_navigation_pending_first_paint_timing_info_); - } - if (soft_navigation_pending_first_contentful_paint_timing_info_.has_value()) { - SetFirstContentfulPaintPresentation( - *soft_navigation_pending_first_contentful_paint_timing_info_); - } -} - } // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing.h b/third_party/blink/renderer/core/paint/timing/paint_timing.h index c9428c1..46127e7 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing.h +++ b/third_party/blink/renderer/core/paint/timing/paint_timing.h
@@ -123,7 +123,7 @@ // instance, the first time that text or image content was painted after the // user landed on the page. base::TimeTicks FirstContentfulPaintIgnoringSoftNavigations() const { - return first_contentful_paint_presentation_ignoring_soft_navigations_; + return first_contentful_paint_presentation_; } base::TimeTicks FirstContentfulPaintRenderedButNotPresentedAsMonotonicTime() @@ -131,14 +131,6 @@ return paint_details_.first_contentful_paint_; } - void ResetFirstPaintAndFCP() { - soft_navigation_pending_paint_details_ = PaintDetails(); - first_paints_reset_ = true; - soft_navigation_detected_ = false; - soft_navigation_fp_reported_ = false; - soft_navigation_fcp_reported_ = false; - } - // FirstImagePaint returns the first time that image content was painted. base::TimeTicks FirstImagePaint() const { return paint_details_.first_image_paint_presentation_; @@ -186,8 +178,6 @@ void OnRestoredFromBackForwardCache(); - void SoftNavigationDetected(); - void MarkPaintTiming(); void Trace(Visitor*) const override; @@ -254,33 +244,20 @@ base::TimeTicks first_contentful_paint_presentation_; }; - PaintDetails& GetRelevantPaintDetails() { - return first_paints_reset_ ? soft_navigation_pending_paint_details_ - : paint_details_; - } + PaintDetails& GetRelevantPaintDetails() { return paint_details_; } DOMPaintTimingInfo ToDOMPaintTimingInfo(const PaintTimingInfo&) const; PaintDetails paint_details_; - PaintDetails soft_navigation_pending_paint_details_; - std::optional<PaintTimingInfo> - soft_navigation_pending_first_paint_timing_info_; - std::optional<PaintTimingInfo> - soft_navigation_pending_first_contentful_paint_timing_info_; // First paint timestamp that doesn't update after soft navigations, and only // used for UKM reporting. base::TimeTicks first_paint_presentation_for_ukm_; // FCP timestamp that does not update after soft navigations. - base::TimeTicks - first_contentful_paint_presentation_ignoring_soft_navigations_; + base::TimeTicks first_contentful_paint_presentation_; base::TimeTicks first_meaningful_paint_presentation_; base::TimeTicks first_meaningful_paint_candidate_; base::TimeTicks first_eligible_to_paint_; base::TimeTicks last_rendering_update_end_time_; - bool first_paints_reset_ = false; - bool soft_navigation_detected_ = false; - bool soft_navigation_fp_reported_ = false; - bool soft_navigation_fcp_reported_ = false; base::TimeTicks lcp_mouse_over_dispatch_time_;
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc index b2124090..3c00718 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc +++ b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.cc
@@ -245,7 +245,7 @@ void PaintTimingDetector::OnInputOrScroll() { // If we have already stopped and we're no longer recording the largest image // paint, then abort. - if (!image_paint_timing_detector_->IsRecordingLargestImagePaint()) { + if (!record_lcp_to_metrics_) { return; } @@ -295,50 +295,6 @@ image_paint_timing_detector_; } -void PaintTimingDetector::RestartRecordingLCP() { - text_paint_timing_detector_->RestartRecordingLargestTextPaint(); - image_paint_timing_detector_->RestartRecordingLargestImagePaint(); - lcp_was_restarted_ = true; - soft_navigation_was_detected_ = false; - GetLargestContentfulPaintCalculator()->ResetMetricsLcp(); -} - -void PaintTimingDetector::SoftNavigationDetected(LocalDOMWindow* window) { - soft_navigation_was_detected_ = true; - auto* lcp_calculator = GetLargestContentfulPaintCalculator(); - // If the window is detached (no calculator) or we haven't yet got any - // presentation times for neither a text record nor an image one, bail. The - // web exposed entry will get updated when the presentation times callback - // will be called. - if (!lcp_calculator || (!potential_soft_navigation_text_record_ && - !potential_soft_navigation_image_record_)) { - return; - } - if (!lcp_was_restarted_ || - RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled(window)) { - lcp_calculator->UpdateWebExposedLargestContentfulPaintIfNeeded( - potential_soft_navigation_text_record_, - potential_soft_navigation_image_record_, - /*is_triggered_by_soft_navigation=*/lcp_was_restarted_); - } - - // Report the soft navigation LCP to metrics. - CHECK(record_soft_navigation_lcp_for_metrics_); - soft_navigation_lcp_details_for_metrics_ = - largest_contentful_paint_calculator_->LatestLcpDetails(); - DidChangePerformanceTiming(); -} - -void PaintTimingDetector::RestartRecordingLCPToUkm() { - text_paint_timing_detector_->RestartRecordingLargestTextPaint(); - image_paint_timing_detector_->RestartRecordingLargestImagePaint(); - record_soft_navigation_lcp_for_metrics_ = true; - // Reset the lcp candidate and the soft navigation LCP for reporting to UKM - // when a new soft navigation happens. When this resetting happens, the - // previous lcp details should already be updated. - soft_navigation_lcp_details_for_metrics_ = LargestContentfulPaintDetails(); -} - LargestContentfulPaintCalculator* PaintTimingDetector::GetLargestContentfulPaintCalculator() { if (largest_contentful_paint_calculator_) { @@ -359,24 +315,13 @@ void PaintTimingDetector::UpdateMetricsLcp() { // The DidChangePerformanceTiming method which triggers the reporting of // metrics LCP would not be called when we are not recording metrics LCP. - if (!record_lcp_to_metrics_ && !record_soft_navigation_lcp_for_metrics_) { + if (!record_lcp_to_metrics_) { return; } - if (record_lcp_to_metrics_) { - auto latest_lcp_details = - GetLargestContentfulPaintCalculator()->LatestLcpDetails(); - lcp_details_for_metrics_ = latest_lcp_details; - } - - // If we're waiting on a softnav and it wasn't detected yet, keep on waiting - // and don't update. - if (record_soft_navigation_lcp_for_metrics_ && - soft_navigation_was_detected_) { - auto latest_lcp_details = - GetLargestContentfulPaintCalculator()->LatestLcpDetails(); - soft_navigation_lcp_details_for_metrics_ = latest_lcp_details; - } + auto latest_lcp_details = + GetLargestContentfulPaintCalculator()->LatestLcpDetails(); + lcp_details_for_metrics_ = latest_lcp_details; DidChangePerformanceTiming(); } @@ -467,25 +412,11 @@ if (image_update_result.second || text_update_result.second) { UpdateMetricsLcp(); } - // If we stopped and then restarted LCP measurement (to support soft - // navigations), and didn't yet detect a soft navigation, put aside the - // records as potential soft navigation LCP ones, and don't update the web - // exposed entries just yet. We'll do that once we actually detect the soft - // navigation. - if (lcp_was_restarted_ && !soft_navigation_was_detected_) { - potential_soft_navigation_text_record_ = text_update_result.first; - potential_soft_navigation_image_record_ = image_update_result.first; - return; - } - potential_soft_navigation_text_record_ = nullptr; - potential_soft_navigation_image_record_ = nullptr; - // If we're still recording the initial LCP, or if LCP was explicitly - // restarted for soft navigations, fire the web exposed entry. - if (record_lcp_to_metrics_ || lcp_was_restarted_) { + if (record_lcp_to_metrics_) { lcp_calculator->UpdateWebExposedLargestContentfulPaintIfNeeded( text_update_result.first, image_update_result.first, - /*is_triggered_by_soft_navigation=*/lcp_was_restarted_); + /*is_triggered_by_soft_navigation=*/false); } } @@ -558,8 +489,6 @@ visitor->Trace(image_paint_timing_detector_); visitor->Trace(frame_view_); visitor->Trace(largest_contentful_paint_calculator_); - visitor->Trace(potential_soft_navigation_image_record_); - visitor->Trace(potential_soft_navigation_text_record_); } } // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h index 34493b3a..56f52b0 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h +++ b/third_party/blink/renderer/core/paint/timing/paint_timing_detector.h
@@ -23,7 +23,6 @@ class Image; class ImagePaintTimingDetector; -class ImageRecord; class ImageResourceContent; class LargestContentfulPaintCalculator; class LayoutObject; @@ -31,7 +30,6 @@ class PropertyTreeStateOrAlias; class MediaTiming; class TextPaintTimingDetector; -class TextRecord; class StyleImage; // PaintTimingDetector receives signals regarding text and image paints and @@ -100,22 +98,12 @@ DCHECK(image_paint_timing_detector_); return *image_paint_timing_detector_; } - void RestartRecordingLCP(); - void SoftNavigationDetected(LocalDOMWindow*); - bool IsSoftNavigationDetected() const { - return soft_navigation_was_detected_; - } - bool WasLCPRestarted() const { return lcp_was_restarted_; } - - void RestartRecordingLCPToUkm(); - LargestContentfulPaintCalculator* GetLargestContentfulPaintCalculator(); const LargestContentfulPaintDetails& LargestContentfulPaintDetailsForMetrics() const { return lcp_details_for_metrics_; } - const LargestContentfulPaintDetails& SoftNavigationLargestContentfulPaintDetailsForMetrics() const { return soft_navigation_lcp_details_for_metrics_; @@ -171,17 +159,6 @@ // Ensures LCP stops being reported as a hard navigation metric once we start // reporting soft navigation ones. bool record_lcp_to_metrics_ = true; - // LCP was restarted, due to a potential soft navigation. - bool lcp_was_restarted_ = false; - // The soft navigation was detected, so the LCP entries can be updated. - bool soft_navigation_was_detected_ = false; - // Records of entries discovered after LCP was restarted but before a soft - // navigation was detected. - Member<TextRecord> potential_soft_navigation_text_record_; - Member<ImageRecord> potential_soft_navigation_image_record_; - - // This flag indicates if LCP is being reported to UKM. - bool record_soft_navigation_lcp_for_metrics_ = false; }; // Largest Text Paint and Text Element Timing aggregate text nodes by these
diff --git a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc index 066b0fe..c27f2b7 100644 --- a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc +++ b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc
@@ -14,6 +14,7 @@ #include "third_party/blink/renderer/core/paint/timing/paint_timing.h" #include "third_party/blink/renderer/core/paint/timing/paint_timing_callback_manager.h" #include "third_party/blink/renderer/core/paint/timing/paint_timing_detector.h" +#include "third_party/blink/renderer/core/timing/soft_navigation_context.h" #include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" @@ -23,6 +24,7 @@ void TextRecord::Trace(Visitor* visitor) const { visitor->Trace(node_); + visitor->Trace(soft_navigation_context_); } TextPaintTimingDetector::TextPaintTimingDetector( @@ -112,26 +114,52 @@ } bool TextPaintTimingDetector::ShouldWalkObject( - const LayoutBoxModelObject& object) const { - // TODO(crbug.com/933479): Use LayoutObject::GeneratingNode() to include - // anonymous objects' rect. - Node* node = object.GetNode(); + const LayoutBoxModelObject& aggregator) { + Node* node = aggregator.GetNode(); if (!node) return false; - // If we have finished recording Largest Text Paint and the element is a - // shadow element or has no elementtiming attribute, then we should not record - // its text. - if (!IsRecordingLargestTextPaint() && - !TextElementTiming::NeededForTiming(*node)) { - return false; + + // Do not walk the object if it has already been recorded, unless it has + // specifically been marked for "re-walking". + if (recorded_set_.Contains(&aggregator)) { + // TODO(crbug.com/40220033): rewalkable_set_ should be empty most of the + // time, until we ship the feature for custom fonts. + // HashSet::Contains() appears to hash key even when container is empty. + return !rewalkable_set_.empty() && rewalkable_set_.Contains(&aggregator); } - if (rewalkable_set_.Contains(&object)) + // Check if we know for certain that we need to measure this node, first. + if (IsRecordingLargestTextPaint() || + TextElementTiming::NeededForTiming(*node)) { return true; + } - // This metric defines the size of a text block by its first size, so we - // should not walk the object if it has been recorded. - return !recorded_set_.Contains(&object); + // If we haven't seen this node before, an we aren't recording LCP nor is this + // node needed for element timing, the only remaining reason to measure text + // timing is for soft navs paints. We leave this check for last, just because + // it might be more expensive. + // TODO(crbug.com/423670827): If we cache this value during pre-paint, then we + // might not need to worry about it. + if (LocalDOMWindow* window = frame_view_->GetFrame().DomWindow()) { + if (SoftNavigationHeuristics* heuristics = + window->GetSoftNavigationHeuristics(); + heuristics && heuristics->MaybeGetSoftNavigationContextForTiming( + aggregator.GetNode())) { + return true; + } + } + + // If we've decided not to visit this node for any reason, then let's add it + // to the set of recorded nodes, even without measuring its paint, so we never + // bother to check it again. + // TODO(crbug.com/423670827): Part of the motivation for doing this is so we + // don't try to look up context more than once per node. But then this + // content becomes un-recorded for any future observers, and that isn't always + // correct (i.e. late application of elementtiming or an Interaction which + // toggles content within the node, i.e. adding textContent for the first time + // to a previously empty node.) + recorded_set_.insert(&aggregator); + return false; } void TextPaintTimingDetector::RecordAggregatedText( @@ -179,16 +207,22 @@ } } - LocalFrame& frame = frame_view_->GetFrame(); - if (LocalDOMWindow* window = frame.DomWindow()) { + SoftNavigationContext* context = nullptr; + if (LocalDOMWindow* window = frame_view_->GetFrame().DomWindow()) { if (SoftNavigationHeuristics* heuristics = window->GetSoftNavigationHeuristics()) { - heuristics->RecordPaint(&frame, mapped_visual_rect, aggregator.GetNode()); + context = heuristics->MaybeGetSoftNavigationContextForTiming( + aggregator.GetNode()); } } + recorded_set_.insert(&aggregator); - MaybeRecordTextRecord(aggregator, aggregated_size, property_tree_state, - aggregated_visual_rect, mapped_visual_rect); + TextRecord* record = MaybeRecordTextRecord( + aggregator, aggregated_size, property_tree_state, aggregated_visual_rect, + mapped_visual_rect, context); + if (context && record) { + context->AddPaintedArea(record); + } if (std::optional<PaintTimingVisualizer>& visualizer = frame_view_->GetPaintTimingDetector().Visualizer()) { visualizer->DumpTextDebuggingRect(aggregator, mapped_visual_rect); @@ -199,12 +233,6 @@ recording_largest_text_paint_ = false; } -void TextPaintTimingDetector::RestartRecordingLargestTextPaint() { - recording_largest_text_paint_ = true; - texts_queued_for_paint_time_.clear(); - ltp_manager_->Clear(); -} - void TextPaintTimingDetector::ReportLargestIgnoredText() { if (!ltp_manager_) return; @@ -254,7 +282,8 @@ // queued for paint, we'll set the appropriate |frame_index_|. largest_ignored_text_ = MakeGarbageCollected<TextRecord>( *object.GetNode(), size, gfx::RectF(), frame_visual_rect, - root_visual_rect, 0u, false /* is_needed_for_timing */); + root_visual_rect, 0u, /*is_needed_for_timing=*/false, + /*soft_navigation_context=*/nullptr); } } @@ -277,6 +306,7 @@ } } + bool is_needed_for_lcp = IsRecordingLargestTextPaint(); bool can_report_timing = text_element_timing_ ? text_element_timing_->CanReportElements() : false; HeapVector<Member<const LayoutObject>> keys_to_be_removed; @@ -290,7 +320,7 @@ text_element_timing_->OnTextObjectPainted(*record, paint_timing_info); } - if (ltp_manager_ && (record->recorded_size > 0u)) { + if (is_needed_for_lcp && ltp_manager_ && (record->recorded_size > 0u)) { ltp_manager_->MaybeUpdateLargestText(record); } keys_to_be_removed.push_back(key); @@ -303,16 +333,19 @@ const uint64_t& visual_size, const PropertyTreeStateOrAlias& property_tree_state, const gfx::Rect& frame_visual_rect, - const gfx::RectF& root_visual_rect) { + const gfx::RectF& root_visual_rect, + SoftNavigationContext* context) { Node* node = object.GetNode(); DCHECK(node); bool is_needed_for_lcp = IsRecordingLargestTextPaint() && visual_size > 0u; bool is_needed_for_element_timing = TextElementTiming::NeededForTiming(*node); + bool is_needed_for_soft_navs = context != nullptr; - // If the node is not required by LCP and not required by ElementTiming, we - // can bail out early. - if (!is_needed_for_lcp && !is_needed_for_element_timing) { + // If the node is not required by LCP and not required by ElementTiming, + // we can bail out early. + if (!is_needed_for_lcp && !is_needed_for_element_timing && + !is_needed_for_soft_navs) { return nullptr; } @@ -320,14 +353,14 @@ if (visual_size == 0u) { record = MakeGarbageCollected<TextRecord>( *node, visual_size, gfx::RectF(), gfx::Rect(), gfx::RectF(), - frame_index_, is_needed_for_element_timing); + frame_index_, is_needed_for_element_timing, context); } else { record = MakeGarbageCollected<TextRecord>( *node, visual_size, TextElementTiming::ComputeIntersectionRect( object, frame_visual_rect, property_tree_state, frame_view_), frame_visual_rect, root_visual_rect, frame_index_, - is_needed_for_element_timing); + is_needed_for_element_timing, context); } QueueToMeasurePaintTime(object, record); return record;
diff --git a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h index 4b86df1..58e90cad 100644 --- a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h +++ b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h
@@ -23,8 +23,9 @@ class TextElementTiming; class TracedValue; struct DOMPaintTimingInfo; +class SoftNavigationContext; -class TextRecord final : public GarbageCollected<TextRecord> { +class CORE_EXPORT TextRecord final : public GarbageCollected<TextRecord> { public: TextRecord(Node& node, uint64_t new_recorded_size, @@ -32,12 +33,15 @@ const gfx::Rect& frame_visual_rect, const gfx::RectF& root_visual_rect, uint32_t frame_index, - bool is_needed_for_timing) + bool is_needed_for_timing, + SoftNavigationContext* soft_navigation_context) : node_(&node), recorded_size(new_recorded_size), frame_index_(frame_index), element_timing_rect_(element_timing_rect), - is_needed_for_timing_(is_needed_for_timing) { + root_visual_rect_(root_visual_rect), + is_needed_for_timing_(is_needed_for_timing), + soft_navigation_context_(soft_navigation_context) { if (PaintTimingVisualizer::IsTracingEnabled()) { lcp_rect_info_ = std::make_unique<LCPRectInfo>( frame_visual_rect, gfx::ToRoundedRect(root_visual_rect)); @@ -52,11 +56,13 @@ uint64_t recorded_size = 0; uint32_t frame_index_ = 0; gfx::RectF element_timing_rect_; + gfx::RectF root_visual_rect_; std::unique_ptr<LCPRectInfo> lcp_rect_info_; // The time of the first paint after fully loaded. base::TimeTicks paint_time = base::TimeTicks(); DOMPaintTimingInfo paint_timing_info; bool is_needed_for_timing_ = false; + WeakMember<SoftNavigationContext> soft_navigation_context_; }; class CORE_EXPORT LargestTextPaintManager final @@ -86,12 +92,6 @@ return std::move(largest_ignored_text_); } - void Clear() { - count_candidates_ = 0; - largest_text_.Clear(); - largest_ignored_text_.Clear(); - } - void Trace(Visitor*) const; private: @@ -135,7 +135,7 @@ TextPaintTimingDetector(const TextPaintTimingDetector&) = delete; TextPaintTimingDetector& operator=(const TextPaintTimingDetector&) = delete; - bool ShouldWalkObject(const LayoutBoxModelObject&) const; + bool ShouldWalkObject(const LayoutBoxModelObject&); void RecordAggregatedText(const LayoutBoxModelObject& aggregator, const gfx::Rect& aggregated_visual_rect, const PropertyTreeStateOrAlias&); @@ -144,7 +144,6 @@ TakePaintTimingCallback(); void LayoutObjectWillBeDestroyed(const LayoutObject&); void StopRecordingLargestTextPaint(); - void RestartRecordingLargestTextPaint(); void ResetCallbackManager(PaintTimingCallbackManager* manager) { callback_manager_ = manager; } @@ -169,7 +168,8 @@ const uint64_t& visual_size, const PropertyTreeStateOrAlias& property_tree_state, const gfx::Rect& frame_visual_rect, - const gfx::RectF& root_visual_rect); + const gfx::RectF& root_visual_rect, + SoftNavigationContext* context); inline void QueueToMeasurePaintTime(const LayoutObject& object, TextRecord* record) { texts_queued_for_paint_time_.insert(&object, record);
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc index e767e30..79e9b66 100644 --- a/third_party/blink/renderer/core/style/computed_style.cc +++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -1942,7 +1942,7 @@ return text; } UChar character = text[0]; - UChar32 transformed_char = ItalicMathVariant(text[0]); + UChar32 transformed_char = unicode::ItalicMathVariant(text[0]); if (transformed_char == static_cast<UChar32>(character)) { return text; }
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc index ee864f3..bfbc508 100644 --- a/third_party/blink/renderer/core/timing/performance.cc +++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -1283,11 +1283,10 @@ } void Performance::AddPaintTiming(PerformancePaintTiming::PaintType type, - const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation) { + const DOMPaintTimingInfo& paint_timing_info) { PerformancePaintTiming* entry = MakeGarbageCollected<PerformancePaintTiming>( - type, paint_timing_info, DynamicTo<LocalDOMWindow>(GetExecutionContext()), - is_triggered_by_soft_navigation); + type, paint_timing_info, + DynamicTo<LocalDOMWindow>(GetExecutionContext())); DCHECK((type == PerformancePaintTiming::PaintType::kFirstPaint) || (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint));
diff --git a/third_party/blink/renderer/core/timing/performance.h b/third_party/blink/renderer/core/timing/performance.h index 178f977..f5a3a526 100644 --- a/third_party/blink/renderer/core/timing/performance.h +++ b/third_party/blink/renderer/core/timing/performance.h
@@ -386,8 +386,7 @@ virtual void BuildJSONValue(V8ObjectBuilder&) const; void AddPaintTiming(PerformancePaintTiming::PaintType, - const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation); + const DOMPaintTimingInfo& paint_timing_info); PerformanceEntryVector resource_timing_buffer_; // The secondary RT buffer, used to store incoming entries after the main
diff --git a/third_party/blink/renderer/core/timing/performance_paint_timing.cc b/third_party/blink/renderer/core/timing/performance_paint_timing.cc index 73364a7..ecb9a3c3 100644 --- a/third_party/blink/renderer/core/timing/performance_paint_timing.cc +++ b/third_party/blink/renderer/core/timing/performance_paint_timing.cc
@@ -36,8 +36,7 @@ PerformancePaintTiming::PerformancePaintTiming( PaintType type, const DOMPaintTimingInfo& paint_timing_info, - DOMWindow* source, - bool is_triggered_by_soft_navigation) + DOMWindow* source) : PerformanceEntry( FromPaintTypeToString(type), // https://w3c.github.io/paint-timing/#report-paint-timing @@ -45,8 +44,7 @@ // given paintTimingInfo. paint_timing_info.presentation_time, paint_timing_info.presentation_time, - source, - is_triggered_by_soft_navigation) { + source) { SetPaintTimingInfo(paint_timing_info); }
diff --git a/third_party/blink/renderer/core/timing/performance_paint_timing.h b/third_party/blink/renderer/core/timing/performance_paint_timing.h index 8b64aff..a5d0ae0 100644 --- a/third_party/blink/renderer/core/timing/performance_paint_timing.h +++ b/third_party/blink/renderer/core/timing/performance_paint_timing.h
@@ -19,8 +19,7 @@ PerformancePaintTiming(PaintType, const DOMPaintTimingInfo& paint_timing_info, - DOMWindow* source, - bool is_triggered_by_soft_navigation); + DOMWindow* source); ~PerformancePaintTiming() override; const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_context.cc b/third_party/blink/renderer/core/timing/soft_navigation_context.cc index a234388c..bb9023e5 100644 --- a/third_party/blink/renderer/core/timing/soft_navigation_context.cc +++ b/third_party/blink/renderer/core/timing/soft_navigation_context.cc
@@ -8,14 +8,21 @@ #include "third_party/blink/renderer/core/dom/container_node.h" #include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.h" +#include "third_party/blink/renderer/core/paint/timing/largest_contentful_paint_calculator.h" +#include "third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h" +#include "third_party/blink/renderer/core/timing/dom_window_performance.h" namespace blink { uint64_t SoftNavigationContext::last_context_id_ = 0; SoftNavigationContext::SoftNavigationContext( + LocalDOMWindow& window, features::SoftNavigationHeuristicsMode mode) - : paint_attribution_mode_(mode) {} + : paint_attribution_mode_(mode), + lcp_calculator_(MakeGarbageCollected<LargestContentfulPaintCalculator>( + DOMWindowPerformance::performance(window))) {} void SoftNavigationContext::AddModifiedNode(Node* node) { node->SetIsModifiedBySoftNavigation(); @@ -38,9 +45,64 @@ } } -bool SoftNavigationContext::AddPaintedArea(Node* node, - const gfx::RectF& rect, - bool is_newest_context) { +bool SoftNavigationContext::IsNeededForTiming(Node* node) { + if (!node) { + return false; + } + for (Node* current_node = node; current_node; + current_node = current_node->parentNode()) { + if (current_node == known_not_related_parent_) { + return false; + } + // If the current_node is known modified, it is a container root. + if (modified_nodes_.Contains(current_node)) { + return true; + } + // For now, do not "tree walk" when in basic mode. + if (paint_attribution_mode_ == + features::SoftNavigationHeuristicsMode::kBasic) { + break; + } + } + // This node was not part of a container root for this context. + // Let's cache this node's parent node, so if any of this node's siblings + // paint next, we can finish this check quicker for them. + if (Node* parent = node->parentNode()) { + known_not_related_parent_ = parent; + } + return false; +} + +bool SoftNavigationContext::AddPaintedArea(TextRecord* text_record) { + Node* node = text_record->node_; + const gfx::RectF& rect = text_record->root_visual_rect_; + bool is_attributable = AddPaintedAreaInternal(node, rect); + if (is_attributable) { + if (!largest_text_ || + largest_text_->recorded_size < text_record->recorded_size) { + largest_text_ = text_record; + } + } + return is_attributable; +} + +bool SoftNavigationContext::AddPaintedArea(ImageRecord* image_record) { + Node* node = Node::FromDomNodeId(image_record->node_id); + const gfx::RectF& rect = image_record->root_visual_rect; + bool is_attributable = AddPaintedAreaInternal(node, rect); + if (is_attributable) { + if (!largest_image_ || + largest_image_->recorded_size < image_record->recorded_size) { + largest_image_ = image_record; + } + } + return is_attributable; +} + +bool SoftNavigationContext::AddPaintedAreaInternal(Node* node, + const gfx::RectF& rect) { + DCHECK(IsNeededForTiming(node)); + uint64_t painted_area = rect.size().GetArea(); if (already_painted_modified_nodes_.Contains(node)) { @@ -50,53 +112,18 @@ return false; } - // Iterate up the dom tree: - for (Node* current_node = node; current_node; - current_node = current_node->parentNode()) { - if (current_node == known_not_related_parent_) { - TRACE_EVENT_INSTANT( - TRACE_DISABLED_BY_DEFAULT("loading"), - "SoftNavigationContext::AddPaintedAreaWithEarlyExitTreeWalk", - "context", this); - break; - } - // If the current_node is known modified, it is a container root, and this - // paint counts. - if (modified_nodes_.Contains(current_node)) { - already_painted_modified_nodes_.insert(node); - // If this is the first paint this animation frame, trace it. - if (painted_area_ == painted_area_last_animation_frame_) { - // TODO(crbug.com/353218760): Add support for reporting every single - // paint. - TRACE_EVENT_INSTANT( - TRACE_DISABLED_BY_DEFAULT("loading"), - "SoftNavigationContext::FirstAttributablePaintInAnimationFrame", - "context", this); - } - painted_area_ += painted_area; - return true; - } - - // For now, do not "tree walk" when in basic mode. - if (paint_attribution_mode_ == - features::SoftNavigationHeuristicsMode::kBasic) { - break; - } + already_painted_modified_nodes_.insert(node); + // If this is the first paint this animation frame, trace it. + if (painted_area_ == painted_area_last_animation_frame_) { + // TODO(crbug.com/353218760): Add support for reporting every single + // paint. + TRACE_EVENT_INSTANT( + TRACE_DISABLED_BY_DEFAULT("loading"), + "SoftNavigationContext::FirstAttributablePaintInAnimationFrame", + "context", this); } - - // This node was not part of a container root for this context. - // Let's cache this node's parent node, so if any of this node's siblings - // paint next, we can finish this check quicker for them. - known_not_related_parent_ = node->parentNode(); - - if (is_newest_context) { - // We want to know how much paint the page is doing that isn't attributed. - // We only want to do this for a single (most recent) context, in order to - // never double count painted areas. - unattributed_area_ += painted_area; - } - - return false; + painted_area_ += painted_area; + return true; } bool SoftNavigationContext::SatisfiesSoftNavNonPaintCriteria() const { @@ -140,6 +167,39 @@ return new_painted_area > 0; } +// TODO(crbug.com/419386429): This gets called after each new presentation time +// update, but this might have a range of deficiencies: +// +// 1. Candidate records might get replaced between paint and presentation. +// +// `largest_text_` and `largest_image_` are updated in `AddPaintedArea` from +// Paint stage of rendering. But `UpdateSoftLcpCandidate` is called after we +// receive frame presentation time feedback (via `PaintTimingMixin`). It is +// possible that we replace the current largest* paint record with a "pending" +// candidate, but unrelated to the presentation feedback of this +// `UpdateSoftLcpCandidate`. We should only report fully recorded paint records. +// One option is to manage a largest pending/painted recortd (like LCP +// calculator), or, just skip this next step if the candidates aren't done. +// +// 2. We might not be ready to Emit LCP candidates yet, and we might not get +// another chance later. +// +// Right now we will skip emitting LCP candidates until after soft-navigation +// entry and NavigationID are incremented. But, this might happen after a few +// frames/paints. Potentially unlikely given the low paint area requirement +// right now, but increasingly likely as we bump that up. +// We might want to also call `UpdateSoftLcpCandidate()` as soon as we emit +// Soft-nav entry if we already have candidates to report. Similar to above, +// there are concerns with reporting Candidates after Paint but before +// Presentation. +void SoftNavigationContext::UpdateSoftLcpCandidate() { + if (!WasEmitted()) { + return; + } + lcp_calculator_->UpdateWebExposedLargestContentfulPaintIfNeeded( + largest_text_, largest_image_, true); +} + void SoftNavigationContext::WriteIntoTrace( perfetto::TracedValue context) const { perfetto::TracedDictionary dict = std::move(context).WriteDictionary(); @@ -153,13 +213,15 @@ dict.Add("domModifications", num_modified_dom_nodes_); dict.Add("paintedArea", painted_area_); dict.Add("repaintedArea", repainted_area_); - dict.Add("unattributedPaintedArea", unattributed_area_); } void SoftNavigationContext::Trace(Visitor* visitor) const { visitor->Trace(modified_nodes_); visitor->Trace(already_painted_modified_nodes_); visitor->Trace(known_not_related_parent_); + visitor->Trace(lcp_calculator_); + visitor->Trace(largest_text_); + visitor->Trace(largest_image_); } } // namespace blink
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_context.h b/third_party/blink/renderer/core/timing/soft_navigation_context.h index 0cd3757..9236818 100644 --- a/third_party/blink/renderer/core/timing/soft_navigation_context.h +++ b/third_party/blink/renderer/core/timing/soft_navigation_context.h
@@ -19,12 +19,17 @@ namespace blink { class Node; +class TextRecord; +class ImageRecord; +class LargestContentfulPaintCalculator; + class CORE_EXPORT SoftNavigationContext : public GarbageCollected<SoftNavigationContext> { static uint64_t last_context_id_; public: - explicit SoftNavigationContext(features::SoftNavigationHeuristicsMode); + explicit SoftNavigationContext(LocalDOMWindow& window, + features::SoftNavigationHeuristicsMode); bool IsMostRecentlyCreatedContext() const { return context_id_ == last_context_id_; @@ -57,15 +62,16 @@ uint64_t PaintedArea() const { return painted_area_; } + // Returns true if this Context is involved in modifying the container root + // for this Node*. + bool IsNeededForTiming(Node* node); // Reports a new contentful paint area to this context, and the Node painted. - // Returns true if we update the total attributed area (meaning this context - // was involved in modifying this dom node, and we grew the painted region). - // Return value is used to check if it is worthwhile to check for "sufficient - // paints" (to emit a new soft-nav entry). - bool AddPaintedArea(Node* node, - const gfx::RectF& rect, - bool is_newest_context); + bool AddPaintedArea(TextRecord*); + bool AddPaintedArea(ImageRecord*); + // Returns true if we update the total attributed area this animation frame. + // Used to check if it is worthwhile to call `SatisfiesSoftNavPaintCriteria`. bool OnPaintFinished(); + void UpdateSoftLcpCandidate(); bool SatisfiesSoftNavNonPaintCriteria() const; bool SatisfiesSoftNavPaintCriteria(uint64_t required_paint_area) const; @@ -75,6 +81,8 @@ void Trace(Visitor* visitor) const; private: + bool AddPaintedAreaInternal(Node* node, const gfx::RectF& rect); + // Pre-Increment `last_context_id_` such that the newest context uses the // largest value and can be used to identify the most recent context. const uint64_t context_id_ = ++last_context_id_; @@ -88,12 +96,15 @@ blink::HeapHashSet<WeakMember<Node>> modified_nodes_; blink::HeapHashSet<WeakMember<Node>> already_painted_modified_nodes_; + Member<LargestContentfulPaintCalculator> lcp_calculator_; + Member<TextRecord> largest_text_; + Member<ImageRecord> largest_image_; + // Elements of `modified_nodes_` can get GC-ed, so we need to keep a count of // the total nodes modified. size_t num_modified_dom_nodes_ = 0; uint64_t painted_area_ = 0; uint64_t repainted_area_ = 0; - uint64_t unattributed_area_ = 0; size_t num_modified_dom_nodes_last_animation_frame_ = 0; size_t num_live_nodes_last_animation_frame_ = 0;
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc index c37235e..21e979a 100644 --- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc +++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
@@ -399,8 +399,6 @@ auto* performance = DOMWindowPerformance::performance(*window_.Get()); performance->AddSoftNavigationEntry(AtomicString(context->InitialUrl()), context->UserInteractionTimestamp()); - - CommitPreviousPaintTimings(frame); ReportSoftNavigationToMetrics(frame, context); TRACE_EVENT_INSTANT("scheduler,devtools.timeline,loading", @@ -411,12 +409,13 @@ return true; } -void SoftNavigationHeuristics::RecordPaint(LocalFrame* frame, - const gfx::RectF& rect, - Node* node) { - if (context_for_current_url_) { - context_for_current_url_->AddPaintedArea(node, rect, true); +SoftNavigationContext* +SoftNavigationHeuristics::MaybeGetSoftNavigationContextForTiming(Node* node) { + if (context_for_current_url_ && + context_for_current_url_->IsNeededForTiming(node)) { + return context_for_current_url_; } + return nullptr; } void SoftNavigationHeuristics::OnPaintFinished() { @@ -427,6 +426,17 @@ } } +void SoftNavigationHeuristics::UpdateSoftLcpCandidate() { + if (!context_for_current_url_) { + return; + } + // Performance timeline won't allow emitting LCP entries without this flag, + // but we can save a lot of needless work by also just not even trying. + if (RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled(window_)) { + context_for_current_url_->UpdateSoftLcpCandidate(); + } +} + void SoftNavigationHeuristics::ReportSoftNavigationToMetrics( LocalFrame* frame, SoftNavigationContext* context) const { @@ -456,34 +466,6 @@ SoftNavigationOutcome::kSoftNavigationDetected); } -void SoftNavigationHeuristics::ResetPaintTimingsIfNeeded() { - LocalFrame* frame = GetLocalFrameIfOutermostAndNotDetached(); - if (!frame) { - return; - } - LocalFrameView* local_frame_view = frame->View(); - CHECK(local_frame_view); - if (RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled(window_)) { - local_frame_view->GetPaintTimingDetector().RestartRecordingLCP(); - } - - local_frame_view->GetPaintTimingDetector().RestartRecordingLCPToUkm(); -} - -// Once all the soft navigation conditions are met (verified in -// `EmitSoftNavigationEntryIfAllConditionsMet()`), the previous paints are -// committed, to make sure accumulated FP, FCP and LCP entries are properly -// fired. -void SoftNavigationHeuristics::CommitPreviousPaintTimings(LocalFrame* frame) { - CHECK(frame && frame->IsOutermostMainFrame()); - LocalFrameView* local_frame_view = frame->View(); - - CHECK(local_frame_view); - - local_frame_view->GetPaintTimingDetector().SoftNavigationDetected( - window_.Get()); -} - void SoftNavigationHeuristics::Trace(Visitor* visitor) const { visitor->Trace(active_interaction_context_); visitor->Trace(context_for_current_url_); @@ -590,17 +572,13 @@ // "new interaction" (i.e. keydown), but will create a new one if that has // been cleared, which can happen in tests. if (IsInteractionStart(type) || !active_interaction_context_) { - active_interaction_context_ = - MakeGarbageCollected<SoftNavigationContext>(paint_attribution_mode_); + active_interaction_context_ = MakeGarbageCollected<SoftNavigationContext>( + *window_, paint_attribution_mode_); potential_soft_navigations_.push_back(active_interaction_context_); TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("loading"), "SoftNavigationHeuristics::CreateNewContext", "context", *active_interaction_context_); } - - // Ensure that paints would be reset, so that paint recording would continue - // despite the user interaction. - ResetPaintTimingsIfNeeded(); } CHECK(active_interaction_context_.Get());
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h index 8d143c3d..45821f05 100644 --- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h +++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
@@ -100,9 +100,9 @@ // TaskAttributionTracker::Observer's implementation. void OnCreateTaskScope(scheduler::TaskAttributionInfo&) override; - void RecordPaint(LocalFrame*, const gfx::RectF& rect, Node* node); - + SoftNavigationContext* MaybeGetSoftNavigationContextForTiming(Node* node); void OnPaintFinished(); + void UpdateSoftLcpCandidate(); // Returns an `EventScope` suitable for navigation. Used for navigations not // yet associated with an event. @@ -130,8 +130,6 @@ SoftNavigationContext*) const; SoftNavigationContext* GetSoftNavigationContextForCurrentTask() const; - void ResetPaintTimingsIfNeeded(); - void CommitPreviousPaintTimings(LocalFrame*); bool EmitSoftNavigationEntryIfAllConditionsMet(SoftNavigationContext*); LocalFrame* GetLocalFrameIfOutermostAndNotDetached() const; void OnSoftNavigationEventScopeDestroyed(const EventScope&);
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc index e2dd01a..593d2d0 100644 --- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc +++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
@@ -19,6 +19,7 @@ #include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/html/html_body_element.h" #include "third_party/blink/renderer/core/html/html_div_element.h" +#include "third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/core/timing/soft_navigation_context.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" @@ -26,7 +27,6 @@ #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h" #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h" #include "third_party/blink/renderer/platform/testing/task_environment.h" - namespace blink { using TaskScope = scheduler::TaskAttributionTracker::TaskScope; @@ -56,16 +56,6 @@ return document->CreateRawElement(html_names::kDivTag); } - void ReportPaintRectForTest(SoftNavigationHeuristics* heuristics, - Node* node) { - ScriptState* script_state = GetScriptStateForTest(); - LocalDOMWindow* window = LocalDOMWindow::From(script_state); - LocalFrame* frame = window->GetFrame(); - gfx::RectF rect{1000, 1000}; - heuristics->RecordPaint(frame, rect, node); - return heuristics->OnPaintFinished(); - } - ScriptState* GetScriptStateForTest() { return ToScriptStateForMainWorld(page_holder_->GetDocument().GetFrame()); } @@ -353,7 +343,13 @@ // Simulate a paint in a separate task. { - ReportPaintRectForTest(heuristics, node1); + TextRecord* record = MakeGarbageCollected<TextRecord>( + *node1, 0, gfx::RectF(1000, 1000), gfx::Rect(1000, 1000), + gfx::RectF(1000, 1000), + /* frame_index= */ 0, + /* is_needed_for_timing= */ false, context); + context->AddPaintedArea(record); + heuristics->OnPaintFinished(); EXPECT_TRUE(context->SatisfiesSoftNavPaintCriteria(1)); EXPECT_EQ(heuristics->SoftNavigationCount(), 1u); } @@ -369,13 +365,19 @@ // And another paint { - ReportPaintRectForTest(heuristics, node2); + TextRecord* record = MakeGarbageCollected<TextRecord>( + *node2, 0, gfx::RectF(1000, 1000), gfx::Rect(1000, 1000), + gfx::RectF(1000, 1000), + /* frame_index= */ 0, + /* is_needed_for_timing= */ false, context); + context->AddPaintedArea(record); + heuristics->OnPaintFinished(); + EXPECT_TRUE(context->SatisfiesSoftNavPaintCriteria(1)); + // Should still just have one single soft-nav because a single context + // with a single Interaction should only emit once, even if it e.g. + // navigates twice (i.e. client-side redirects). + EXPECT_EQ(heuristics->SoftNavigationCount(), 1u); } - - // Should still just have one single soft-nav because a single context - // with a single Interaction should only emit once, even if it e.g. navigates - // twice (i.e. client-side redirects). - EXPECT_EQ(heuristics->SoftNavigationCount(), 1u); } TEST_F(SoftNavigationHeuristicsTest, AsyncSameDocumentNavigation) {
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc index 81c4fd9..3e46f9ff 100644 --- a/third_party/blink/renderer/core/timing/window_performance.cc +++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -1304,17 +1304,15 @@ } void WindowPerformance::AddFirstPaintTiming( - const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation) { + const DOMPaintTimingInfo& paint_timing_info) { AddPaintTiming(PerformancePaintTiming::PaintType::kFirstPaint, - paint_timing_info, is_triggered_by_soft_navigation); + paint_timing_info); } void WindowPerformance::AddFirstContentfulPaintTiming( - const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation) { + const DOMPaintTimingInfo& paint_timing_info) { AddPaintTiming(PerformancePaintTiming::PaintType::kFirstContentfulPaint, - paint_timing_info, is_triggered_by_soft_navigation); + paint_timing_info); } void WindowPerformance::AddLongAnimationFrameEntry(PerformanceEntry* entry) { @@ -1487,6 +1485,11 @@ const String& url, Element* element, bool is_triggered_by_soft_navigation) { + if (is_triggered_by_soft_navigation && + !RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled( + GetExecutionContext())) { + return; + } DOMHighResTimeStamp load_timestamp = MonotonicTimeToDOMHighResTimeStamp(load_time);
diff --git a/third_party/blink/renderer/core/timing/window_performance.h b/third_party/blink/renderer/core/timing/window_performance.h index 00040ed..803c010 100644 --- a/third_party/blink/renderer/core/timing/window_performance.h +++ b/third_party/blink/renderer/core/timing/window_performance.h
@@ -141,12 +141,10 @@ void QueueLongAnimationFrameTiming( AnimationFrameTimingInfo*, std::optional<DOMPaintTimingInfo> paint_timing_info = std::nullopt); - void AddFirstPaintTiming(const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation); + void AddFirstPaintTiming(const DOMPaintTimingInfo& paint_timing_info); void AddFirstContentfulPaintTiming( - const DOMPaintTimingInfo& paint_timing_info, - bool is_triggered_by_soft_navigation); + const DOMPaintTimingInfo& paint_timing_info); // PerformanceMonitor::Client implementation. void ReportLongTask(base::TimeTicks start_time,
diff --git a/third_party/blink/renderer/platform/math_transform_fuzzer.cc b/third_party/blink/renderer/platform/math_transform_fuzzer.cc index ad46f3a..5bdd7f0 100644 --- a/third_party/blink/renderer/platform/math_transform_fuzzer.cc +++ b/third_party/blink/renderer/platform/math_transform_fuzzer.cc
@@ -22,7 +22,7 @@ blink::UTF16TextIterator text_iterator(content.Span16()); UChar32 code_point; while (text_iterator.Consume(code_point)) { - WTF::unicode::ItalicMathVariant(code_point); + blink::unicode::ItalicMathVariant(code_point); text_iterator.Advance(); } return 0;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 3ced760..7ce65e0 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2898,6 +2898,10 @@ status: "stable", }, { + name: "LayoutNewReplacedLogic", + status: "stable", + }, + { name: "LayoutNGShapeCache", status: "stable", base_feature: "LayoutNGShapeCache",
diff --git a/third_party/blink/renderer/platform/wtf/text/math_transform.cc b/third_party/blink/renderer/platform/wtf/text/math_transform.cc index fdd96c2..975432b9 100644 --- a/third_party/blink/renderer/platform/wtf/text/math_transform.cc +++ b/third_party/blink/renderer/platform/wtf/text/math_transform.cc
@@ -8,7 +8,7 @@ #include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h" #include "third_party/blink/renderer/platform/wtf/text/character_names.h" -namespace WTF { +namespace blink { namespace unicode { static UChar32 mathVariantGreek(UChar32 code_point, UChar32 base_char) { @@ -124,4 +124,4 @@ } } // namespace unicode -} // namespace WTF +} // namespace blink
diff --git a/third_party/blink/renderer/platform/wtf/text/math_transform.h b/third_party/blink/renderer/platform/wtf/text/math_transform.h index 6a994c0..d12f782 100644 --- a/third_party/blink/renderer/platform/wtf/text/math_transform.h +++ b/third_party/blink/renderer/platform/wtf/text/math_transform.h
@@ -9,7 +9,7 @@ #include "third_party/blink/renderer/platform/wtf/wtf_export.h" #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h" -namespace WTF { +namespace blink { namespace unicode { // Performs the character mapping needed to implement MathML's mathvariant @@ -28,8 +28,6 @@ WTF_EXPORT UChar32 ItalicMathVariant(UChar32 code_point); } // namespace unicode -} // namespace WTF - -using WTF::unicode::ItalicMathVariant; +} // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_TEXT_MATH_TRANSFORM_H_
diff --git a/third_party/blink/renderer/platform/wtf/text/math_transform_test.cc b/third_party/blink/renderer/platform/wtf/text/math_transform_test.cc index abe7513..b5ed658 100644 --- a/third_party/blink/renderer/platform/wtf/text/math_transform_test.cc +++ b/third_party/blink/renderer/platform/wtf/text/math_transform_test.cc
@@ -6,10 +6,9 @@ #include "testing/gtest/include/gtest/gtest.h" -namespace WTF { +namespace blink { namespace { - // https://w3c.github.io/mathml-core/#italic-mappings TEST(MathTransform, Italics) { static struct ItalicsTestData { @@ -55,9 +54,11 @@ {0x03F0, 0x1D718}, {0x03F1, 0x1D71A}, {0x03F4, 0x1D6F3}, {0x03F5, 0x1D716}}; - for (auto& test_data : italics_test_data) - EXPECT_EQ(ItalicMathVariant(test_data.code_point), test_data.expected); + for (auto& test_data : italics_test_data) { + EXPECT_EQ(unicode::ItalicMathVariant(test_data.code_point), + test_data.expected); + } } } // anonymous namespace -} // namespace WTF +} // namespace blink
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index c16bb01..e8fe969 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -5275,6 +5275,7 @@ "external/wpt/webnn/validation_tests/lstm.https.any.html?cpu", "external/wpt/webnn/validation_tests/lstmCell.https.any.html?cpu", "external/wpt/webnn/validation_tests/matmul.https.any.html?cpu", + "external/wpt/webnn/validation_tests/opSupportLimits.https.any.html?cpu", "external/wpt/webnn/validation_tests/pad.https.any.html?cpu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.html?cpu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.serviceworker.html?cpu", @@ -5536,6 +5537,7 @@ "external/wpt/webnn/validation_tests/lstm.https.any.html?npu", "external/wpt/webnn/validation_tests/lstmCell.https.any.html?npu", "external/wpt/webnn/validation_tests/matmul.https.any.html?npu", + "external/wpt/webnn/validation_tests/opSupportLimits.https.any.html?npu", "external/wpt/webnn/validation_tests/pad.https.any.html?npu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.html?npu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.serviceworker.html?npu", @@ -5797,6 +5799,7 @@ "external/wpt/webnn/validation_tests/lstm.https.any.html?gpu", "external/wpt/webnn/validation_tests/lstmCell.https.any.html?gpu", "external/wpt/webnn/validation_tests/matmul.https.any.html?gpu", + "external/wpt/webnn/validation_tests/opSupportLimits.https.any.html?gpu", "external/wpt/webnn/validation_tests/pad.https.any.html?gpu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.html?gpu", "external/wpt/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.serviceworker.html?gpu",
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json index 9bea152..6947c2e 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -26607,7 +26607,7 @@ }, "apng": { "animated-png-timeout.html": [ - "6975bd9c4e7339ceceb9f078bbf0c356f9949f3e", + "2a10f8df06ccce550ebd642f7602c3b55dad9a8b", [ null, [ @@ -361973,7 +361973,7 @@ [] ], "position-sticky-fractional-offset-ref.html": [ - "8b7a1f8a195843b2b943e39813b5e24781955b8d", + "37a087b7be7182c2bdefc3a39124132360c1bab0", [] ], "position-sticky-grid-ref.html": [ @@ -365525,6 +365525,10 @@ "29a0fe8013dcbda6c44369794dfe6e7cde21c1a8", [] ], + "intrinsic-size-fallback-video-expected.txt": [ + "bd9d06061c0cb27839d535ca89335f8eabc527a4", + [] + ], "max-content-input-001-ref.html": [ "c01e46550a7f0b5b92321d5893a8fe2a1ea0c9ac", [] @@ -382757,10 +382761,6 @@ "de22e864d7d14a9e0280ec2f41678dbc90ee2e58", [] ], - "define-expected.txt": [ - "5fa6248617c90049e42ebbd8c7d7dc77edb056af", - [] - ], "initial-about-blank.window-expected.txt": [ "a715c1e2339881b9cc061246cf1c0b71cdf4d371", [] @@ -404704,11 +404704,11 @@ ] }, "video-aspect-ratio-expected.txt": [ - "76fe81a9a006565441d57d28db0bd60b76914cfc", + "f3d1ccdb2c613268fbcc76e618d8dea71aa6b534", [] ], "video-intrinsic-width-height-expected.txt": [ - "c67465b3c1e392467370be8dc84d7b03fd21fd15", + "2ecf1dc355df97e43cb14ad17bcab3aee06ac732", [] ] }, @@ -406775,6 +406775,10 @@ "399cf2aa213438f79a37707a56cb751b07da869e", [] ], + "intrinsic_sizes-expected.txt": [ + "028e8945ef2dcbb9320c19404557e8bd087cb604", + [] + ], "video-poster-shown-preload-auto-ref.html": [ "66b42e40253f1a47c90ac781e9a13cc94996f52b", [] @@ -484192,7 +484196,7 @@ ] ], "compute_pressure_duplicate_updates.https.window.js": [ - "abf53854b40637d17102132d39e8a5e0aeaa438d", + "b2968375db06ad348bd92b403f56828b84ff5fb9", [ "compute-pressure/compute_pressure_duplicate_updates.https.window.html?globalScope=dedicated_worker", { @@ -542000,7 +542004,7 @@ ] ], "define.html": [ - "d3d923bd916fd04576d0de1d809e43a5395b9edb", + "52193ebd17e4ea01e2dccf0656ad28915894d65b", [ null, {} @@ -542062,7 +542066,7 @@ {} ] ], - "valid-custom-element-names.tentative.html": [ + "valid-custom-element-names.html": [ "72a5999e3a9a76bd35b3f17402cf13f6ef43dc4d", [ null, @@ -645486,7 +645490,7 @@ ] ], "video-intrinsic-width-height.html": [ - "047c832da35246278ce58a255037c64bb1d8b723", + "836fadb24fda2f90b5730babe801b212d3125fea", [ null, {}
diff --git a/third_party/blink/web_tests/external/wpt/apng/animated-png-timeout.html b/third_party/blink/web_tests/external/wpt/apng/animated-png-timeout.html index 6975bd9..2a10f8d 100644 --- a/third_party/blink/web_tests/external/wpt/apng/animated-png-timeout.html +++ b/third_party/blink/web_tests/external/wpt/apng/animated-png-timeout.html
@@ -1,7 +1,6 @@ <html class="reftest-wait"> <title>APNG: Second frame displays quickly, replacing red with green.</title> <link rel="match" href="animated-png-timeout-ref.html"/> -<img src=../images/apng.png onload="loaded()"/> <script> function loaded() { setTimeout(function() { @@ -9,3 +8,4 @@ }, 1000); } </script> +<img src=../images/apng.png onload="loaded()"/>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-001.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-001.html new file mode 100644 index 0000000..d5356ea --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-001.html
@@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gap decorations are painted with the grid subgridded on both axes. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(3, 1fr); + gap: 10px; + width: 100px; + height: 100px; + } + .subgrid { + display: grid; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + + background: red; + grid-column: 1 / -1; + grid-row: 1 / -1; + column-rule: green solid 10px; + row-rule: green solid 10px; + } + .subgrid-item { + background: green; + } + </style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="grid-container"> + <div class="subgrid"> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-002.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-002.html new file mode 100644 index 0000000..82df19b2 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-002.html
@@ -0,0 +1,47 @@ + +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gap decorations are painted with the grid subgridded on column axis. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(3, 1fr); + gap: 10px; + width: 100px; + height: 100px; + } + .subgrid { + display: grid; + grid-template-columns: subgrid; + grid-template-rows: repeat(2, 1fr); + + grid-column: 1 / -1; + grid-row: 1 / -1; + column-rule: green solid 10px; + row-gap: 20px; + row-rule: green solid 20px; + + background: red; + } + .subgrid-item { + background: green; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="grid-container"> + <div class="subgrid"> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-003.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-003.html new file mode 100644 index 0000000..2119926 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-003.html
@@ -0,0 +1,47 @@ + +<!DOCTYPE html> +<title> + CSS Gap Decorations: Gap decorations are painted with the grid subgridded on row axis. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(3, 1fr); + gap: 10px; + width: 100px; + height: 100px; + } + .subgrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: subgrid; + + grid-column: 1 / -1; + grid-row: 1 / -1; + column-gap: 20px; + column-rule: green solid 20px; + row-rule: green solid 10px; + + background: red; + } + .subgrid-item { + background: green; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="grid-container"> + <div class="subgrid"> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + <div class="subgrid-item"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-004.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-004.html new file mode 100644 index 0000000..20fb140 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-004.html
@@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: *rule-break defaults to spanning-item and avoids painting behind spanners in subgrid. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#break"> +<link rel="match" href="../grid-gap-decorations-006-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0; + } + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(4, 1fr); + gap: 10px; + width: 430px; + height: 430px; + } + .subgrid { + display: grid; + grid-column: 1 / -1; + grid-row: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + column-rule: blue solid 5px; + row-rule: red solid 5px; + } + .grid-item { + background-color: gray; + opacity: 0.5; + border: 1px solid #000; + } +</style> +<div class="grid-container"> + <div class="subgrid"> + <div class="grid-item" style="grid-column: 1 / 3; grid-row: 1 / 2;"></div> + <div class="grid-item" style="grid-column: 3 / 4; grid-row: 1 / 3;"></div> + <div class="grid-item" style="grid-row: 2 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 4; grid-row: 3 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 3; grid-row: 2 / 3;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 1 / 4;"></div> + <div class="grid-item" style="grid-column: 1 / 4; grid-row: 4 / 5;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 4 / 5;"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-005.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-005.html new file mode 100644 index 0000000..7c92642 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-005.html
@@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: setting *rule-break to intersection stops painting decorations at each intersection in subgrid. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#break"> +<link rel="match" href="../grid-gap-decorations-007-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0; + } + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(4, 1fr); + gap: 10px; + width: 430px; + height: 430px; + } + .subgrid { + display: grid; + grid-column: 1 / -1; + grid-row: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + column-rule: blue solid 5px; + row-rule: red solid 5px; + + column-rule-break: intersection; + column-rule-outset: 0px; + + row-rule-break: intersection; + row-rule-outset: 0px; + } + .grid-item { + background-color: gray; + opacity: 0.5; + border: 1px solid #000; + } +</style> +<div class="grid-container"> + <div class="subgrid"> + <div class="grid-item" style="grid-column: 1 / 3; grid-row: 1 / 2;"></div> + <div class="grid-item" style="grid-column: 3 / 4; grid-row: 1 / 3;"></div> + <div class="grid-item" style="grid-row: 2 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 4; grid-row: 3 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 3; grid-row: 2 / 3;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 1 / 4;"></div> + <div class="grid-item" style="grid-column: 1 / 4; grid-row: 4 / 5;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 4 / 5;"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-006.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-006.html new file mode 100644 index 0000000..8ac8b7a9 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-006.html
@@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: setting *rule-break to none paints decorations from first grid line to last grid line in subgrid. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/#break"> +<link rel="match" href="../grid-gap-decorations-008-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0; + } + .grid-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(4, 1fr); + gap: 10px; + width: 430px; + height: 430px; + } + .subgrid { + display: grid; + grid-column: 1 / -1; + grid-row: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + column-rule: blue solid 5px; + row-rule: red solid 5px; + + column-rule-break: none; + row-rule-break: none; + } + .grid-item { + background-color: gray; + opacity: 0.5; + border: 1px solid #000; + } +</style> +<div class="grid-container"> + <div class="subgrid"> + <div class="grid-item" style="grid-column: 1 / 3; grid-row: 1 / 2;"></div> + <div class="grid-item" style="grid-column: 3 / 4; grid-row: 1 / 3;"></div> + <div class="grid-item" style="grid-row: 2 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 4; grid-row: 3 / 4;"></div> + <div class="grid-item" style="grid-column: 2 / 3; grid-row: 2 / 3;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 1 / 4;"></div> + <div class="grid-item" style="grid-column: 1 / 4; grid-row: 4 / 5;"></div> + <div class="grid-item" style="grid-column: 4 / 5; grid-row: 4 / 5;"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-007.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-007.html new file mode 100644 index 0000000..64a87d62 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-007.html
@@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: An outset of 0px aligns the ends of gap decorations with adjacent item in subgrid. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../grid-gap-decorations-010-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + body { + margin: 0; + } + .grid-container { + display: grid; + grid-template-columns: repeat(3, 100px); + grid-template-rows: repeat(3, 100px); + gap: 10px; + } + .subgrid { + display: grid; + grid-column: 1 / -1; + grid-row: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + + column-rule: blue solid 5px; + row-rule: red solid 5px; + + column-rule-outset: 0px; + column-rule-break: intersection; + } + .item { + background: gray; + opacity: 0.5; + } +</style> +<div class="grid-container"> + <div class="subgrid"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-008.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-008.html new file mode 100644 index 0000000..b43dea71 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/grid/subgrid/subgrid-gap-decorations-008.html
@@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: An outset of 5px extends decorations slightly beyond the edges of adjacent items in subgrid. +</title> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="match" href="../grid-gap-decorations-012-ref.html"> +<link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com"> +<style> + .grid-container { + display: grid; + grid-template-columns: repeat(3, 100px); + grid-template-rows: repeat(3, 100px); + gap: 10px; + } + .subgrid { + display: grid; + grid-column: 1 / -1; + grid-row: 1 / -1; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + + column-rule: blue solid 5px; + row-rule: red solid 5px; + + column-rule-outset: 5px; + column-rule-break: intersection; + } + .item { + background: gray; + opacity: 0.5; + } +</style> +<body> + <div class="grid-container"> + <div class="subgrid"> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + <div class="item"></div> + </div> + </div> +</body>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/background-image-set-image.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/background-image-set-image.html index 4dbb97c..27a70c7 100644 --- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/background-image-set-image.html +++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/background-image-set-image.html
@@ -1,41 +1,42 @@ -<!DOCTYPE html> -<html> - -<head> - <title>Background image-set images should be LCP candidates</title> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <style> - .background { - width: calc(100vw - 40px); - height: calc(100vw - 40px); - max-width: 100px; - max-height: 100px; - background: #eee image-set('/images/lcp-100x50.png' type('image/png')) center center no-repeat; - background-size: cover; - } - </style> -</head> - +<!doctype html> +<title>Background image-set images should be LCP candidates</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + .background { + width: calc(100vw - 40px); + height: calc(100vw - 40px); + max-width: 100px; + max-height: 100px; + background: #eee image-set("/images/lcp-100x50.png" type("image/png")) center center no-repeat; + background-size: cover; + } +</style> <body> <div class="background"></div> <p>fallback</p> - - <script> - promise_test(async (t) => { - t.step_timeout(async () => { - let lcpEntryNames = - await new Promise(resolve => { - new PerformanceObserver((list) => { - resolve(list.getEntries().map(e => e.url)); - }).observe({ type: "largest-contentful-paint", buffered: true }); - }); - - assert_true(lcpEntryNames.find(e => e.includes('lcp-100x50')), - 'Background image-set image should be an LCP candidate.'); - }, 100); - }, "Background image-set images should be eligible for LCP candidates"); - </script> </body> -</html> \ No newline at end of file +<script> + promise_test(async (t) => { + await Promise.race([ + new Promise((resolve) => { + const entries = []; + new PerformanceObserver((list) => { + entries.push(...list.getEntries()); + for (const entry of entries) { + if (entry.url.includes("lcp-100x50")) { + resolve(entry.url); + return; + } + } + }).observe({ type: "largest-contentful-paint", buffered: true }); + }), + new Promise((resolve, reject) => { + t.step_timeout(() => { + reject(new Error("Timed out waiting for LCP entry for background image-set image.")); + }, 3000); + }), + ]); + }, "Background image-set images should be eligible for LCP candidates"); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/broken-image-icon.html b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/broken-image-icon.html new file mode 100644 index 0000000..fbfb228 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/broken-image-icon.html
@@ -0,0 +1,78 @@ +<!doctype html> +<!-- +The soft navigation version of the identically named test in +/largest-contentful-paint/broken-image-icon.html. +Notes: + - Awaits trivial soft navigation with same page contents as original test. + - Viewport is very small so that the small icon below (16x8) is + sufficiently large to trigger a soft navigation. + - Original test was awaiting FCP, but we don't support that yet + for soft navs; so now we await LCP for the hard navigation, and then + LCP and soft nav for the soft navigation, with the promise set up prior + to the click. +--> +<meta name="viewport" content="width=50, height=50, initial-scale=1" /> +<title>Broken Image Icon Should Not Be LCP after soft navigation</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 src="/soft-navigation-heuristics/resources/soft-navigation-test-helper.js"></script> +<script> + function clickHandler() { + document.body.innerHTML = ` + <img src="../non-existent-image.jpg"> + <img src="/css/css-images/support/colors-16x8.png"> + `; + history.pushState({}, "", "/test"); + } +</script> +<body> + <div id="click-target" onclick="clickHandler()">Click!</div> +</body> +<script> + promise_test(async (t) => { + assert_implements(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented"); + const helper = new SoftNavigationTestHelper(t); + const lcpEntries = await helper.getBufferedPerformanceEntriesWithTimeout( + /*type=*/ "largest-contentful-paint", + /*includeSoftNavigationObservations=*/ false, + /*minNumEntries=*/ 1, + ); + assert_equals(lcpEntries.length, 1); + assert_equals(lcpEntries[0].id, "click-target", "The first entry should be the button"); + + const promise = Promise.all([ + SoftNavigationTestHelper.getPerformanceEntries( + /*type=*/ "largest-contentful-paint", + /*includeSoftNavigationObservations=*/ true, + /*minNumEntries=*/ 1, + ), + SoftNavigationTestHelper.getPerformanceEntries( + /*type=*/ "soft-navigation", + /*includeSoftNavigationObservations=*/ true, + /*minNumEntries=*/ 1, + ), + ]); + + if (test_driver) { + test_driver.click(document.getElementById("click-target")); + } + + const [softLcpEntries, softNavigationEntries] = await promise; + assert_equals(softNavigationEntries.length, 1, "One soft navigation entry."); + assert_true( + softNavigationEntries[0].name.endsWith("test"), + "Soft navigation should be to test page.", + ); + + // There should be only 1 LCP entry and it should be the colors-16x8.png though + // being smaller than the broken image icon. The broken image icon should not + // emit an LCP entry. + assert_equals(softLcpEntries.length, 1, "There should be one and only one LCP entry."); + assert_true( + softLcpEntries[0].url.includes("colors-16x8.png"), + "The LCP entry should be the colors-16x8.png", + ); + }, "The broken image icon should not emit an LCP entry after soft navigation."); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/contracted-image.html b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/contracted-image.html new file mode 100644 index 0000000..46d359a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/lcp/tentative/contracted-image.html
@@ -0,0 +1,63 @@ +<!doctype html> +<!-- + The soft navigation version of the identically named + test in /largest-contentful-paint/contracted-image.html. + Notes: + - Awaits trivial soft navigation with same page contents as original test. + - Uses promise_test and slightly revised HTML tags, to make it easy to + observe the initial LCP before the soft navigation (the click target) + and distinguish it from the interesting LCP after the soft navigation. +--> +<meta charset="utf-8" /> +<title> + Largest Contentful Paint: contracted image bounded by display size after soft navigation. +</title> +<style type="text/css"> + #image_id { + width: 50px; + height: 50px; + } +</style> +<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 src="/largest-contentful-paint/resources/largest-contentful-paint-helpers.js"></script> +<script src="/soft-navigation-heuristics/resources/soft-navigation-test-helper.js"></script> +<script> + function clickHandler() { + document.body.innerHTML = `<img src="/images/black-rectangle.png" id="image_id" />`; + history.pushState({}, "", "/test"); + } +</script> +<body> + <div id="click-target" onclick="clickHandler()">Click!</div> +</body> +<script> + promise_test(async (t) => { + assert_implements(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented"); + if (test_driver) { + test_driver.click(document.getElementById("click-target")); + } + const beforeLoad = performance.now(); + const helper = new SoftNavigationTestHelper(t); + await helper.getBufferedPerformanceEntriesWithTimeout( + /*type=*/ "soft-navigation", + /*includeSoftNavigationObservations=*/ false, + /*minNumEntries=*/ 1, + ); + const entries = await helper.getBufferedPerformanceEntriesWithTimeout( + /*type=*/ "largest-contentful-paint", + /*includeSoftNavigationObservations=*/ true, + /*minNumEntries=*/ 2, + ); + assert_equals(entries.length, 2); + assert_equals(entries[0].id, "click-target", "The first entry should be the button"); + + const entry = entries[1]; + const url = window.location.origin + "/images/black-rectangle.png"; + // black-rectangle.png is 100 x 50. It occupies 50 x 50 so size will be bounded by the displayed size. + const size = 50 * 50; + checkImage(entry, url, "image_id", size, beforeLoad); + }, "Largest Contentful Paint: |size| attribute is bounded by display size after soft navigation."); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/softnav-before-lcp-paint.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/softnav-before-lcp-paint.tentative-expected.txt new file mode 100644 index 0000000..76a2182 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/softnav-before-lcp-paint.tentative-expected.txt
@@ -0,0 +1,5 @@ +This is a testharness.js-based test. +[FAIL] Detect simple soft navigation. + assert_greater_than_equal: Got at least 3 LCP entries expected a number greater than or equal to 3 but got 2 +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/opSupportLimits.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/opSupportLimits.https.any.js new file mode 100644 index 0000000..cd28df9 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/opSupportLimits.https.any.js
@@ -0,0 +1,54 @@ +// META: title=validation context.opSupportLimits() interface +// META: global=window +// META: variant=?cpu +// META: variant=?gpu +// META: variant=?npu +// META: script=../resources/utils_validation.js + +'use strict'; + + +const tests = [ + { + operator: 'logicalAnd', + limits: { + a: {dataTypes: ['uint8']}, + b: {dataTypes: ['uint8']}, + output: {dataTypes: ['uint8']}, + } + }, + { + operator: 'logicalOr', + limits: { + a: {dataTypes: ['uint8']}, + b: {dataTypes: ['uint8']}, + output: {dataTypes: ['uint8']}, + } + }, + { + operator: 'logicalXor', + limits: { + a: {dataTypes: ['uint8']}, + b: {dataTypes: ['uint8']}, + output: {dataTypes: ['uint8']}, + } + }, + { + operator: 'logicalNot', + limits: { + a: {dataTypes: ['uint8']}, + output: {dataTypes: ['uint8']}, + } + } +]; + +tests.forEach(test => promise_test(async t => { + const limits = context.opSupportLimits()[test.operator]; + for (let [name, expected] of Object.entries(test.limits)) { + for (let actualDataType of limits[name].dataTypes) { + assert_in_array( + actualDataType, expected.dataTypes, + `${test.operator}.${name}.dataTypes`); + } + } + }, `check opSupportLimits data types of ${test.operator}`));
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/soft-navigations-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/soft-navigations-expected.txt index 8a128648..42d43ea2 100644 --- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/soft-navigations-expected.txt +++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/soft-navigations-expected.txt
@@ -37,7 +37,6 @@ paintedArea: number repaintedArea: number softNavContextId: number - unattributedPaintedArea: number wasEmitted: boolean } frame: string
diff --git a/third_party/blink/web_tests/images/zoomed-img-size-expected.txt b/third_party/blink/web_tests/images/zoomed-img-size-expected.txt deleted file mode 100644 index fba986e..0000000 --- a/third_party/blink/web_tests/images/zoomed-img-size-expected.txt +++ /dev/null
@@ -1,7 +0,0 @@ -This is a testharness.js-based test. -[FAIL] Zoom 1% should not affect width/height of images. - assert_equals: zoom: 1% expected 37 but got 100 -[FAIL] Zoom 2% should not affect width/height of images. - assert_equals: zoom: 2% expected 37 but got 50 -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/images/zoomed-img-size.html b/third_party/blink/web_tests/images/zoomed-img-size.html index 4ccb480f..01b71399 100644 --- a/third_party/blink/web_tests/images/zoomed-img-size.html +++ b/third_party/blink/web_tests/images/zoomed-img-size.html
@@ -5,25 +5,22 @@ <script src="../resources/testharnessreport.js"></script> </head> <body> - <img id="oval0" src="resources/oval.png" style="zoom: 1%"> - <img id="oval1" src="resources/oval.png" style="zoom: 2%"> - <img id="oval2" src="resources/oval.png" style="zoom: 3%"> - <img id="oval3" src="resources/oval.png" style="zoom: 4%"> - <img id="oval4" src="resources/oval.png" style="zoom: 5%"> - <img id="oval5" src="resources/oval.png" style="zoom: 30%"> - <img id="oval6" src="resources/oval.png" style="zoom: 33%"> - <img id="oval7" src="resources/oval.png" style="zoom: 50%"> - <img id="oval8" src="resources/oval.png" style="zoom: 70%"> - <img id="oval9" src="resources/oval.png" style="zoom: 100%"> - <img id="oval10" src="resources/oval.png" style="zoom: 111%"> - <img id="oval11" src="resources/oval.png" style="zoom: 150%"> - <img id="oval12" src="resources/oval.png" style="zoom: 333%"> - <img id="oval13" src="resources/oval.png" style="zoom: 400%"> - <img id="oval14" src="resources/oval.png" style="zoom: 1234%"> + <img id="oval0" src="resources/oval.png" style="zoom: 4%"> + <img id="oval1" src="resources/oval.png" style="zoom: 5%"> + <img id="oval2" src="resources/oval.png" style="zoom: 30%"> + <img id="oval3" src="resources/oval.png" style="zoom: 33%"> + <img id="oval4" src="resources/oval.png" style="zoom: 50%"> + <img id="oval5" src="resources/oval.png" style="zoom: 70%"> + <img id="oval6" src="resources/oval.png" style="zoom: 100%"> + <img id="oval7" src="resources/oval.png" style="zoom: 111%"> + <img id="oval8" src="resources/oval.png" style="zoom: 150%"> + <img id="oval9" src="resources/oval.png" style="zoom: 333%"> + <img id="oval10" src="resources/oval.png" style="zoom: 400%"> + <img id="oval11" src="resources/oval.png" style="zoom: 1234%"> <script> const EXPECTED_WIDTH = 37; const EXPECTED_HEIGHT = 33; -for (i = 0; i < 15; ++i) { +for (i = 0; i < 12; ++i) { const oval = document.getElementById('oval' + i); const testDesc = `Zoom ${oval.style.zoom} should not affect width/height of images.`; const testBody = () => {
diff --git a/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-svg-through-object-with-percentage-size-expected.png b/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-svg-through-object-with-percentage-size-expected.png index d3cea2d..1ee7b99 100644 --- a/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-svg-through-object-with-percentage-size-expected.png +++ b/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-svg-through-object-with-percentage-size-expected.png Binary files differ
diff --git a/third_party/dawn b/third_party/dawn index 9635124..223318d 160000 --- a/third_party/dawn +++ b/third_party/dawn
@@ -1 +1 @@ -Subproject commit 9635124871e8ab6d38d416e78759a447e68c35e5 +Subproject commit 223318d090e36d901f7c7a83b764e0a336b19385
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index d7cb23e..fd0c28c 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit d7cb23eac0fda3c06441034ccea0c4bb9c947add +Subproject commit fd0c28cbf06b695f677c525b6630f9f9e4a90eee
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src index 1f64d05..ff75100 160000 --- a/third_party/llvm-libc/src +++ b/third_party/llvm-libc/src
@@ -1 +1 @@ -Subproject commit 1f64d055dbb79dfcef1a63588341d171f327cd73 +Subproject commit ff75100b45027c70e17770cb80e720193b742c76
diff --git a/third_party/perfetto b/third_party/perfetto index 3231268..d7984eb 160000 --- a/third_party/perfetto +++ b/third_party/perfetto
@@ -1 +1 @@ -Subproject commit 3231268f2960b13cb52788389e88d7932117f57e +Subproject commit d7984ebb216b56e0eb99ba29adfae91f37819602
diff --git a/third_party/skia b/third_party/skia index dc1958e..f8cd9fe 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit dc1958e7118c351cef5adae548337f7f11746b95 +Subproject commit f8cd9fe75f21d3be759cbf9491ddc582efcf1e2a
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps index 222889e..5f9183d 160000 --- a/third_party/vulkan-deps +++ b/third_party/vulkan-deps
@@ -1 +1 @@ -Subproject commit 222889ea039509b86fa6dd90fcb6acf47e79cdb9 +Subproject commit 5f9183df4e99d153dcf2de173ddfd47034f72601
diff --git a/third_party/vulkan-headers/src b/third_party/vulkan-headers/src index 1d6c53f..b39ab38 160000 --- a/third_party/vulkan-headers/src +++ b/third_party/vulkan-headers/src
@@ -1 +1 @@ -Subproject commit 1d6c53f65443ceeb97d3bdc695aaecc7ea6cc441 +Subproject commit b39ab380a44b6c8df462c34e976ea9ce2d2c336b
diff --git a/third_party/vulkan-loader/src b/third_party/vulkan-loader/src index 63b05d2..22c0f13 160000 --- a/third_party/vulkan-loader/src +++ b/third_party/vulkan-loader/src
@@ -1 +1 @@ -Subproject commit 63b05d2b087952662623de7eddd44f2e57d71a1e +Subproject commit 22c0f133e6675f9313c12fb5e58337f8fa9b9195
diff --git a/third_party/vulkan-tools/src b/third_party/vulkan-tools/src index fbe7226..d6719230 160000 --- a/third_party/vulkan-tools/src +++ b/third_party/vulkan-tools/src
@@ -1 +1 @@ -Subproject commit fbe722654b7173da961398cf78bd4a62d1839b65 +Subproject commit d671923090e4dc74c0ebdb10c6e09fa0826e1fe9
diff --git a/third_party/vulkan-utility-libraries/src b/third_party/vulkan-utility-libraries/src index e48ae20..54c9baf 160000 --- a/third_party/vulkan-utility-libraries/src +++ b/third_party/vulkan-utility-libraries/src
@@ -1 +1 @@ -Subproject commit e48ae20a7938b01aee62806bfcdafe8a0883b1e4 +Subproject commit 54c9baf20802a13279e23fa4cb0528dd5cf16064
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src index 6d6e6ec..4b8f3ca 160000 --- a/third_party/vulkan-validation-layers/src +++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@ -Subproject commit 6d6e6ec8e51cd219498359cbc48e4762d1a80616 +Subproject commit 4b8f3caab2cf0ace618ba857364346aed6297d29
diff --git a/third_party/webrtc b/third_party/webrtc index cf71427..70347d9 160000 --- a/third_party/webrtc +++ b/third_party/webrtc
@@ -1 +1 @@ -Subproject commit cf714279e2a0d54e03b4065f14748dea1dc24606 +Subproject commit 70347d915e9b3d74cfbcc1323f35edc834f097e0
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index bc7512c6..4c2c206 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -13822,6 +13822,7 @@ <int value="-42175674" label="ShareUsageRankingFixedMore:disabled"/> <int value="-41548966" label="AutocorrectUseReplaceSurroundingText:disabled"/> <int value="-41460305" label="InfobarScrollOptimization:disabled"/> + <int value="-41263570" label="OmniboxToolbelt:enabled"/> <int value="-41254374" label="WebRtcMetronomeTaskQueue:enabled"/> <int value="-40935502" label="ContextualSuggestionsSlimPeekUI:enabled"/> <int value="-38525285" label="AccessibilityManifestV3SelectToSpeak:enabled"/> @@ -14535,6 +14536,7 @@ <int value="228024890" label="AutofillEnableSaveAndFill:enabled"/> <int value="228107154" label="OmniboxUpdatedConnectionSecurityIndicators:enabled"/> + <int value="228814932" label="OmniboxToolbelt:disabled"/> <int value="228845546" label="ShimlessRMAOsUpdate:enabled"/> <int value="229312162" label="PriceInsights:disabled"/> <int value="229476202" label="CryptAuthV2Enrollment:enabled"/>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml index 61806bf..64cec22a 100644 --- a/tools/metrics/histograms/metadata/autofill/enums.xml +++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -3644,6 +3644,8 @@ <int value="5" label="Missing href"/> <int value="6" label="Missing form data"/> <int value="7" label="Form extraction failure"/> + <int value="8" label="Renderer error"/> + <int value="9" label="Unparsed renderer error"/> </enum> <!-- LINT.ThenChange(//components/autofill/ios/form_util/form_activity_tab_helper.mm:FormSubmissionOutcomeIOS) -->
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml index 7284442..549f500e 100644 --- a/tools/metrics/histograms/metadata/autofill/histograms.xml +++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -3770,7 +3770,19 @@ </summary> </histogram> -<histogram name="Autofill.iOS.FormSubmission.Outcome" +<histogram name="Autofill.iOS.FormSubmission.Outcome.InvalidFormReason" + enum="InvalidSubmittedFormReasonIOS" expires_after="2025-11-16"> + <owner>eic@google.com</owner> + <owner>vincb@google.com</owner> + <owner>bling-transactions@google.com</owner> + <summary> + Records the reason why a submitted form data was found invalid by the + browser process. Recorded when the browser process receives a form + submission message from the renderer. iOS only. + </summary> +</histogram> + +<histogram name="Autofill.iOS.FormSubmission.OutcomeV2" enum="FormSubmissionOutcomeIOS" expires_after="2025-11-16"> <owner>eic@google.com</owner> <owner>vincb@google.com</owner> @@ -3784,18 +3796,6 @@ </summary> </histogram> -<histogram name="Autofill.iOS.FormSubmission.Outcome.InvalidFormReason" - enum="InvalidSubmittedFormReasonIOS" expires_after="2025-11-16"> - <owner>eic@google.com</owner> - <owner>vincb@google.com</owner> - <owner>bling-transactions@google.com</owner> - <summary> - Records the reason why a submitted form data was found invalid by the - browser process. Recorded when the browser process receives a form - submission message from the renderer. iOS only. - </summary> -</histogram> - <histogram name="Autofill.iOS.TriggeredFormExtractionFromDriver.{Range}" units="count" expires_after="2025-10-12"> <owner>vincb@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/lens/enums.xml b/tools/metrics/histograms/metadata/lens/enums.xml index 101f00b..543ddf58 100644 --- a/tools/metrics/histograms/metadata/lens/enums.xml +++ b/tools/metrics/histograms/metadata/lens/enums.xml
@@ -143,6 +143,19 @@ <!-- LINT.ThenChange(//components/lens/lens_overlay_side_panel_menu_option.h:LensOverlaySidePanelMenuOption) --> +<!-- LINT.IfChange(LensOverlayTextDirectiveResult) --> + +<enum name="LensOverlayTextDirectiveResult"> + <summary> + The result of handling a text directive in the Lens Overlay. + </summary> + <int value="0" label="Found on page"/> + <int value="1" label="Opened in new tab"/> + <int value="2" label="Not found on page"/> +</enum> + +<!-- LINT.ThenChange(//components/lens/lens_overlay_metrics.h:LensOverlayTextDirectiveResult) --> + <!-- LINT.IfChange(LensOverlayUserAction) --> <enum name="LensOverlayUserAction">
diff --git a/tools/metrics/histograms/metadata/lens/histograms.xml b/tools/metrics/histograms/metadata/lens/histograms.xml index b0fc0b6..b59e5ebf 100644 --- a/tools/metrics/histograms/metadata/lens/histograms.xml +++ b/tools/metrics/histograms/metadata/lens/histograms.xml
@@ -732,6 +732,17 @@ </summary> </histogram> +<histogram name="Lens.Overlay.TextDirectiveResult" + enum="LensOverlayTextDirectiveResult" expires_after="2026-01-31"> + <owner>juanmojica@google.com</owner> + <owner>lens-chrome@google.com</owner> + <summary> + Recorded once whenever the text finder is invoked on the Lens overlay. + Indicates whether the text finder was successful in finding text in the + page. + </summary> +</histogram> + <histogram name="Lens.Overlay.TimeToFirstInteraction" units="ms" expires_after="2025-11-09"> <owner>jdonnelly@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml index f9c9cb0..2562d88 100644 --- a/tools/metrics/histograms/metadata/network/histograms.xml +++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -5785,6 +5785,39 @@ <token key="Result" variants="HTTPRequestResult"/> </histogram> +<histogram name="NetworkService.Scheduler.IOThread.TaskOffCpuDuration" + units="ms" expires_after="2025-12-25"> + <owner>hayato@chromium.org</owner> + <owner>net-dev@chromium.org</owner> + <summary> + Records the duration spent off-CPU by tasks running on the Network Service + IO thread. Recorded with 0.001 probability when a task scheduled on the IO + thread finishes running. + </summary> +</histogram> + +<histogram name="NetworkService.Scheduler.IOThread.TaskOnCpuDuration" + units="ms" expires_after="2025-12-25"> + <owner>hayato@chromium.org</owner> + <owner>net-dev@chromium.org</owner> + <summary> + Records the duration spent on-CPU by tasks running on the Network Service IO + Thread. Recorded with 0.001 probability when a task scheduled on the IO + thread finishes running. + </summary> +</histogram> + +<histogram name="NetworkService.Scheduler.IOThread.TaskOnCpuPercentage" + units="%" expires_after="2025-12-25"> + <owner>hayato@chromium.org</owner> + <owner>net-dev@chromium.org</owner> + <summary> + Records the percentage of wall time spent on-CPU by tasks running on the + Network Service IO Thread. Recorded with 0.001 probability when a task + scheduled on the IO thread finishes running. + </summary> +</histogram> + <histogram name="NetworkService.SlopBucket.DisabledReason" enum="SlopBucketDisabledReason" expires_after="2024-10-23"> <owner>ricea@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml index 88ca3f4..2c0e5ce 100644 --- a/tools/metrics/histograms/metadata/omnibox/histograms.xml +++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -1287,7 +1287,7 @@ <histogram name="Omnibox.FocusToOpenTimeAnyPopupState3.{OmniboxZeroPrefixState}" - units="ms" expires_after="2025-06-23"> + units="ms" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -1326,7 +1326,7 @@ <histogram name="Omnibox.FocusToOpenTimeAnyPopupState3.{OmniboxZeroPrefixState}.BySummarizedResultType.{OmniboxSummarizedResultType}" - units="ms" expires_after="2025-06-23"> + units="ms" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -1347,7 +1347,7 @@ <histogram name="Omnibox.FocusToOpenTimeAnyPopupState3.{OmniboxZeroPrefixState}.BySummarizedResultType.{OmniboxSummarizedResultType}.ByPageContext.{OmniboxPageContext}" - units="ms" expires_after="2025-06-23"> + units="ms" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -1666,7 +1666,7 @@ </histogram> <histogram name="Omnibox.LocalHistoryPrefixSuggest.SearchTermsExtractionTimeV2" - units="ms" expires_after="2025-07-13"> + units="ms" expires_after="2025-12-12"> <owner>mahmadi@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -2253,7 +2253,7 @@ </histogram> <histogram name="Omnibox.Search.OffTheRecord" enum="BooleanOffTheRecord" - expires_after="2025-09-01"> + expires_after="2025-12-12"> <owner>jdonnelly@chromium.org</owner> <owner>roagarwal@chromium.org</owner> <owner>chrome-incognito@google.com</owner> @@ -2265,7 +2265,7 @@ </histogram> <histogram name="Omnibox.SearchboxStats.Length" units="chars" - expires_after="2025-08-24"> + expires_after="2025-12-12"> <owner>mahmadi@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -2501,7 +2501,7 @@ <histogram name="Omnibox.SuggestionShown.ClientSummarizedResultType.ByPageContext.{OmniboxPageContext}" - enum="ClientSummarizedResultType" expires_after="2025-06-24"> + enum="ClientSummarizedResultType" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -2551,7 +2551,7 @@ <histogram name="Omnibox.SuggestionShown.{OmniboxZeroPrefixState}.ClientSummarizedResultType.ByPageContext.{OmniboxPageContext}" - enum="ClientSummarizedResultType" expires_after="2025-06-24"> + enum="ClientSummarizedResultType" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -2866,7 +2866,7 @@ <histogram name="Omnibox.SuggestionUsed.URL.Experimental.NavigationToFirstMeaningfulPaint" - units="ms" expires_after="2025-08-10"> + units="ms" expires_after="2025-12-12"> <owner>jdonnelly@chromium.org</owner> <owner>mpearson@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> @@ -3085,7 +3085,7 @@ </histogram> <histogram name="Omnibox.SuggestTiles.DeletedTileIndex" units="position" - expires_after="2025-09-01"> + expires_after="2025-12-12"> <owner>ender@google.com</owner> <owner>mahmadi@chromium.org</owner> <summary> @@ -3359,7 +3359,7 @@ </histogram> <histogram name="Omnibox.URLScoringModelExecuted.MLScoreCoverage" - units="% of matches" expires_after="2025-06-29"> + units="% of matches" expires_after="2025-12-12"> <owner>khalidpeer@chromium.org</owner> <owner>manukh@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> @@ -3499,7 +3499,7 @@ </histogram> <histogram name="Omnibox.Views.PopupFirstPaint" units="ms" - expires_after="2025-07-13"> + expires_after="2025-12-12"> <owner>tluk@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -3720,7 +3720,7 @@ </histogram> <histogram name="Omnibox.ZeroSuggestProvider.CacheMemoryUsage" units="Bytes" - expires_after="2025-05-11"> + expires_after="2025-12-12"> <owner>mahmadi@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary> @@ -4089,7 +4089,7 @@ </histogram> <histogram name="ShortcutsProvider.OldEntryDeletions.OnInit" units="units" - expires_after="2025-06-22"> + expires_after="2025-12-12"> <owner>jdonnelly@chromium.org</owner> <owner>chrome-desktop-search@google.com</owner> <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 6507934..091fd4b7 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": "99424f9df1446b999716c6628e4e3de043dfe39d", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/9da8e356eae50f441bdffb0523c5b2c9a7eba8bc/trace_processor_shell.exe" + "hash": "ca11d3bec4379010be7a4e500bc053f203e75797", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d7984ebb216b56e0eb99ba29adfae91f37819602/trace_processor_shell.exe" }, "linux_arm": { "hash": "99f971ca131f6d11c73f4b918099d434bdd8093c", @@ -21,8 +21,8 @@ "full_remote_path": "perfetto-luci-artifacts/v50.1/mac-arm64/trace_processor_shell" }, "linux": { - "hash": "daab1ca0f1bbb0fb368fdd6fcbe8478f1791b151", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/3231268f2960b13cb52788389e88d7932117f57e/trace_processor_shell" + "hash": "eb1ebc49690ee1a5f6be8d0d2b726691af37dea0", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/d7984ebb216b56e0eb99ba29adfae91f37819602/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/ui/android/java/src/org/chromium/ui/widget/Toast.java b/ui/android/java/src/org/chromium/ui/widget/Toast.java index 1f69689..bd7cd83 100644 --- a/ui/android/java/src/org/chromium/ui/widget/Toast.java +++ b/ui/android/java/src/org/chromium/ui/widget/Toast.java
@@ -21,6 +21,7 @@ import androidx.annotation.IntDef; import androidx.annotation.StringRes; import androidx.annotation.StyleRes; +import androidx.annotation.VisibleForTesting; import org.chromium.base.SysUtils; import org.chromium.build.annotations.NullMarked; @@ -145,8 +146,9 @@ mText = text; } + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @Nullable - CharSequence getText() { + public CharSequence getText() { return mText; }
diff --git a/ui/android/java/src/org/chromium/ui/widget/ToastManager.java b/ui/android/java/src/org/chromium/ui/widget/ToastManager.java index 03fe9813..1c913358 100644 --- a/ui/android/java/src/org/chromium/ui/widget/ToastManager.java +++ b/ui/android/java/src/org/chromium/ui/widget/ToastManager.java
@@ -14,6 +14,7 @@ import org.jni_zero.JNINamespace; +import org.chromium.base.ResettersForTesting; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; @@ -61,6 +62,13 @@ return sInstance; } + /** Override the toast manager for use in testing. */ + public static void setInstanceForTesting(ToastManager manager) { + ToastManager previousManager = sInstance; + sInstance = manager; + ResettersForTesting.register(() -> sInstance = previousManager); + } + private ToastManager() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { mToastEvent = new ToastEventPreR(this::toastHidden);
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox_compose_button.css b/ui/webui/resources/cr_components/searchbox/searchbox_compose_button.css index 74b80d65..5d43593 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox_compose_button.css +++ b/ui/webui/resources/cr_components/searchbox/searchbox_compose_button.css
@@ -20,7 +20,7 @@ font-size: inherit; border: none; position: relative; - gap: 0px; + gap: 2px; } .compose-container {