Version in base suite: 145.0.7632.159-1~deb13u1 Version in overlay suite: 147.0.7727.116-1~deb13u1 Base version: chromium_147.0.7727.116-1~deb13u1 Target version: chromium_147.0.7727.137-1~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/chromium/chromium_147.0.7727.116-1~deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/chromium/chromium_147.0.7727.137-1~deb13u1.dsc DEPS | 14 ash/BUILD.gn | 2 ash/strings/ash_strings_kn.xtb | 2 ash/strings/ash_strings_my.xtb | 6 ash/user_education/user_education_help_bubble_controller.cc | 24 ash/user_education/user_education_help_bubble_controller.h | 14 ash/user_education/user_education_help_bubble_controller_unittest.cc | 11 ash/user_education/views/help_bubble_factory_views_ash.cc | 195 ash/user_education/views/help_bubble_factory_views_ash.h | 89 ash/user_education/views/help_bubble_view_ash.cc | 46 ash/user_education/views/help_bubble_view_ash.h | 16 ash/user_education/views/help_bubble_view_ash_pixeltest.cc | 12 ash/user_education/views/help_bubble_view_ash_test_base.cc | 24 ash/user_education/views/help_bubble_view_ash_test_base.h | 16 ash/user_education/views/help_bubble_view_ash_unittest.cc | 33 build/util/LASTCHANGE | 2 build/util/LASTCHANGE.committime | 2 chrome/VERSION | 2 chrome/app/resources/chromium_strings_az.xtb | 2 chrome/app/resources/chromium_strings_fa.xtb | 2 chrome/app/resources/chromium_strings_fi.xtb | 2 chrome/app/resources/chromium_strings_it.xtb | 2 chrome/app/resources/generated_resources_cs.xtb | 4 chrome/app/resources/generated_resources_da.xtb | 18 chrome/app/resources/generated_resources_fa.xtb | 14 chrome/app/resources/generated_resources_it.xtb | 18 chrome/app/resources/generated_resources_kn.xtb | 32 chrome/app/resources/generated_resources_lv.xtb | 2 chrome/app/resources/generated_resources_my.xtb | 14 chrome/app/resources/generated_resources_sw.xtb | 2 chrome/app/resources/generated_resources_uk.xtb | 2 chrome/app/resources/generated_resources_uz.xtb | 2 chrome/app/resources/generated_resources_vi.xtb | 2 chrome/app/resources/generated_resources_zh-HK.xtb | 2 chrome/app/resources/google_chrome_strings_cs.xtb | 2 chrome/app/resources/google_chrome_strings_fa.xtb | 2 chrome/app/resources/google_chrome_strings_fi.xtb | 2 chrome/app/resources/google_chrome_strings_it.xtb | 2 chrome/browser/ash/file_manager/restore_io_task_unittest.cc | 91 chrome/browser/ash/file_manager/trash_info_validator.cc | 6 chrome/browser/ash/file_system_provider/operations/get_metadata.cc | 10 chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc | 17 chrome/browser/chromeos/extensions/vpn_provider/vpn_service.h | 2 chrome/browser/contextual_cueing/contextual_cueing_enums.h | 11 chrome/browser/contextual_cueing/contextual_cueing_helper.cc | 54 chrome/browser/flags/android/chrome_feature_list.cc | 2 chrome/browser/glic/glic_context_menu_browsertest.cc | 44 chrome/browser/glic/glic_metrics_browsertest.cc | 24 chrome/browser/glic/service/glic_instance_impl.cc | 8 chrome/browser/media/router/providers/cast/cast_activity_manager.cc | 7 chrome/browser/media/router/providers/cast/cast_activity_manager.h | 18 chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc | 57 chrome/browser/renderer_context_menu/render_view_context_menu.cc | 10 chrome/browser/ui/android/strings/translations/android_chrome_strings_ar.xtb | 2 chrome/browser/ui/android/strings/translations/android_chrome_strings_de.xtb | 4 chrome/browser/ui/android/strings/translations/android_chrome_strings_es.xtb | 2 chrome/browser/ui/android/strings/translations/android_chrome_strings_th.xtb | 2 chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb | 2 chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc | 5 chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc | 20 chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h | 10 chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc | 72 chrome/browser/ui/user_education/show_promo_in_page.cc | 7 chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc | 15 chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc | 40 chrome/browser/ui/views/bookmarks/bookmark_bar_view.h | 6 chrome/browser/ui/views/bookmarks/bookmark_bar_view_browsertest.cc | 11 chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc | 33 chrome/browser/ui/views/bookmarks/bookmark_context_menu.h | 7 chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc | 57 chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc | 47 chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h | 7 chrome/browser/ui/views/toolbar/toolbar_controller.cc | 17 chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc | 38 chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc | 3 chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc | 5 chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc | 44 chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc | 113 chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h | 19 chrome/chrome_branch_deps.json | 39 chromeos/CHROMEOS_LKGM | 2 chromeos/components/onc/onc_utils.cc | 3 chromeos/profiles/arm.afdo.newest.txt | 2 chromeos/profiles/atom.afdo.newest.txt | 2 chromeos/profiles/bigcore.afdo.newest.txt | 2 chromeos/strings/chromeos_strings_my.xtb | 12 chromeos/strings/chromeos_strings_uz.xtb | 2 components/browser_ui/strings/android/translations/browser_ui_strings_ar.xtb | 2 components/certificate_transparency/data/log_list.json | 4 components/contextual_tasks/public/features.cc | 2 components/exo/data_device.cc | 8 components/exo/data_device.h | 5 components/exo/data_device_unittest.cc | 10 components/exo/wayland/wayland_display_observer.cc | 7 components/feedback/feedback_util.cc | 9 components/feedback/feedback_util_unittest.cc | 39 components/js_injection/renderer/js_binding.cc | 7 components/js_injection/renderer/js_binding.h | 5 components/omnibox/resources/translations/omnibox_pedal_synonyms_it.xtb | 2 components/policy/resources/policy_templates_it.xtb | 10 components/strings/components_strings_ar.xtb | 2 components/strings/components_strings_cs.xtb | 6 components/strings/components_strings_da.xtb | 2 components/strings/components_strings_es.xtb | 4 components/strings/components_strings_fi.xtb | 2 components/strings/components_strings_it.xtb | 4 components/strings/components_strings_uz.xtb | 2 components/user_education/common/feature_promo/feature_promo_lifecycle_unittest.cc | 4 components/user_education/common/feature_promo/impl/feature_promo_controller_impl.cc | 8 components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h | 3 components/user_education/common/feature_promo/impl/feature_promo_controller_impl_unittest.cc | 34 components/user_education/common/help_bubble/help_bubble.cc | 60 components/user_education/common/help_bubble/help_bubble.h | 73 components/user_education/common/help_bubble/help_bubble_factory_registry.cc | 12 components/user_education/common/help_bubble/help_bubble_factory_registry.h | 5 components/user_education/common/help_bubble/help_bubble_factory_registry_unittest.cc | 12 components/user_education/common/tutorial/tutorial_service.cc | 13 components/user_education/common/tutorial/tutorial_service.h | 2 components/user_education/common/tutorial/tutorial_unittest.cc | 3 components/user_education/test/test_help_bubble.cc | 6 components/user_education/test/test_help_bubble.h | 3 components/user_education/views/BUILD.gn | 2 components/user_education/views/help_bubble_factory_mac.mm | 2 components/user_education/views/help_bubble_factory_views.cc | 8 components/user_education/views/help_bubble_factory_views_unittest.cc | 58 components/user_education/views/help_bubble_view.cc | 44 components/user_education/views/help_bubble_view.h | 25 components/user_education/views/help_bubble_view_info.cc | 20 components/user_education/views/help_bubble_view_info.h | 28 components/user_education/views/help_bubble_view_unittest.cc | 55 components/user_education/views/help_bubble_views.cc | 236 components/user_education/views/help_bubble_views.h | 36 components/user_education/views/help_bubble_views_unittest.cc | 112 components/user_education/webui/help_bubble_handler.cc | 21 components/user_education/webui/help_bubble_handler.h | 2 components/user_education/webui/help_bubble_handler_unittest.cc | 35 components/user_education/webui/help_bubble_webui.cc | 8 components/user_education/webui/help_bubble_webui.h | 2 components/viz/service/frame_sinks/compositor_frame_sink_support.cc | 7 components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc | 73 components/viz/service/frame_sinks/frame_sink_manager_impl.cc | 9 components/viz/service/frame_sinks/frame_sink_manager_impl.h | 4 components/viz/service/hit_test/hit_test_aggregator.cc | 49 components/viz/service/hit_test/hit_test_aggregator.h | 14 components/viz/service/hit_test/hit_test_aggregator_delegate.h | 6 components/viz/service/hit_test/hit_test_aggregator_unittest.cc | 104 components/viz/service/surfaces/surface_manager.cc | 4 components/viz/service/surfaces/surface_unittest.cc | 65 content/browser/devtools/devtools_renderer_channel.cc | 2 content/browser/download/mhtml_generation_manager.cc | 34 content/browser/renderer_host/navigator.cc | 7 content/browser/web_contents/web_contents_impl.cc | 6 debian/changelog | 54 debian/patches/fixes/enable-widevine-on-arm64-linux-platform.patch | 25 debian/patches/series | 2 debian/patches/upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch | 80 extensions/common/BUILD.gn | 5 extensions/common/manifest_handlers/input_components_handler.cc | 10 extensions/common/manifest_handlers/input_components_handler_unittest.cc | 99 gpu/command_buffer/service/shared_image/shared_image_factory.cc | 24 gpu/command_buffer/service/shared_image/shared_image_manager.cc | 44 gpu/command_buffer/service/shared_image/shared_image_manager.h | 11 gpu/config/gpu_lists_version.h | 2 gpu/webgpu/DAWN_VERSION | 2 gpu/webgpu/dawn_commit_hash.h | 2 media/filters/source_buffer_stream.cc | 11 media/filters/source_buffer_stream_unittest.cc | 34 media/midi/midi_manager.cc | 2 media/midi/midi_manager_unittest.cc | 27 media/midi/midi_manager_win.cc | 15 media/remoting/stream_provider.cc | 5 media/remoting/stream_provider_unittest.cc | 23 media/webrtc/audio_processor.cc | 2 net/http/transport_security_state_static.pins | 4 net/http/transport_security_state_static_pins.json | 2 remoting/protocol/BUILD.gn | 1 remoting/protocol/webrtc_video_renderer_adapter.cc | 19 remoting/protocol/webrtc_video_renderer_adapter.h | 2 remoting/protocol/webrtc_video_renderer_adapter_unittest.cc | 109 remoting/resources/remoting_strings_fa.xtb | 6 skia/ext/skia_commit_hash.h | 2 third_party/angle/src/libANGLE/Buffer.cpp | 12 third_party/angle/src/libANGLE/Context.cpp | 69 third_party/angle/src/libANGLE/Context.h | 11 third_party/angle/src/libANGLE/Fence.cpp | 6 third_party/angle/src/libANGLE/Framebuffer.cpp | 7 third_party/angle/src/libANGLE/ProgramPipeline.cpp | 5 third_party/angle/src/libANGLE/Renderbuffer.cpp | 5 third_party/angle/src/libANGLE/ResourceManager.cpp | 9 third_party/angle/src/libANGLE/ResourceManager.h | 11 third_party/angle/src/libANGLE/Sampler.cpp | 6 third_party/angle/src/libANGLE/Texture.cpp | 15 third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp | 20 third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp | 3 third_party/angle/src/tests/angle_end2end_tests.gni | 1 third_party/angle/src/tests/gl_tests/BPTCCompressedTextureTest.cpp | 52 third_party/angle/src/tests/gl_tests/BindRecyclesResourceTest.cpp | 48 third_party/angle/src/tests/gl_tests/VertexAttributeTest.cpp | 106 third_party/blink/renderer/core/animation/animation_trigger.cc | 6 third_party/blink/renderer/core/dom/pseudo_element.cc | 4 third_party/blink/renderer/core/events/pointer_event_factory.cc | 26 third_party/blink/renderer/core/events/pointer_event_factory.h | 1 third_party/blink/renderer/core/input/event_handler.h | 4 third_party/blink/renderer/core/input/mouse_event_manager.cc | 27 third_party/blink/renderer/core/input/mouse_event_manager.h | 4 third_party/blink/renderer/core/input/pointer_event_manager.cc | 41 third_party/blink/renderer/core/input/pointer_event_manager.h | 4 third_party/blink/renderer/core/input/touch_event_manager.cc | 12 third_party/blink/renderer/core/input/touch_event_manager.h | 1 third_party/blink/renderer/core/style/computed_style.h | 7 third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc | 33 third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h | 1 third_party/blink/renderer/platform/graphics/image_decoding_store.cc | 31 third_party/blink/renderer/platform/graphics/image_decoding_store_test.cc | 64 third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc | 11 third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc | 17 third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.cc | 169 third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer_test.cc | 37 third_party/dawn/src/dawn/native/CommandEncoder.cpp | 10 third_party/dawn/src/dawn/native/Toggles.cpp | 5 third_party/dawn/src/dawn/native/Toggles.h | 1 third_party/dawn/src/dawn/tests/end2end/BufferZeroInitTests.cpp | 58 third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis.cc | 65 third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis_test.cc | 1864 -- third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout.cc | 7 third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc | 53 third_party/skia/include/core/SkRegion.h | 15 third_party/skia/src/core/SkRegion.cpp | 62 third_party/skia/src/core/SkRegionPriv.h | 3 third_party/skia/tests/RegionTest.cpp | 112 third_party/webrtc/call/BUILD.gn | 1 third_party/webrtc/call/rtp_video_sender.cc | 5 third_party/webrtc/call/rtp_video_sender_unittest.cc | 70 third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc | 21 tools/metrics/histograms/metadata/contextual_cueing/enums.xml | 12 tools/metrics/histograms/metadata/contextual_cueing/histograms.xml | 10 tools/metrics/histograms/metadata/glic/histograms.xml | 1 ui/accessibility/platform/ax_platform_node_win.cc | 14 ui/accessibility/platform/ax_platform_node_win.h | 6 ui/accessibility/platform/ax_platform_node_win_unittest.cc | 35 ui/base/interaction/interaction_sequence_test_util.h | 2 ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc | 26 ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc | 8 ui/strings/translations/ax_strings_it.xtb | 2 ui/strings/translations/ui_strings_ta.xtb | 2 ui/views/controls/button/button.h | 4 ui/views/win/hwnd_message_handler.cc | 20 v8/include/v8-version.h | 2 v8/src/builtins/loong64/builtins-loong64.cc | 2 v8/src/codegen/loong64/macro-assembler-loong64.cc | 14 v8/src/codegen/loong64/macro-assembler-loong64.h | 5 v8/src/compiler/backend/loong64/code-generator-loong64.cc | 84 v8/src/maglev/maglev-graph-builder.cc | 13 v8/src/wasm/baseline/loong64/liftoff-assembler-loong64-inl.h | 68 v8/tools/builtins-pgo/profiles/meta.json | 2 v8/tools/builtins-pgo/profiles/x64-rl.profile | 6968 +++++----- v8/tools/builtins-pgo/profiles/x64.profile | 5751 ++++---- v8/tools/builtins-pgo/profiles/x86-rl.profile | 2360 +-- v8/tools/builtins-pgo/profiles/x86.profile | 2977 +--- 259 files changed, 13143 insertions(+), 12206 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpk62jdegu/chromium_147.0.7727.116-1~deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpk62jdegu/chromium_147.0.7727.137-1~deb13u1.dsc: no acceptable signature found diff -Nru chromium-147.0.7727.116/DEPS chromium-147.0.7727.137/DEPS --- chromium-147.0.7727.116/DEPS 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/DEPS 2026-04-27 20:03:22.000000000 +0000 @@ -312,15 +312,15 @@ # 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': 'f8cd2da0256752afb0059bf17e4bf2bec422967e', + 'skia_revision': '6e0fbe154ccaf018b2dd1f0e42eec285e7d79d00', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '9b21082faf16a5f029a4316272c9a627d8b33eb4', + 'v8_revision': 'c152c31c55cd54fd239772532a86c802d95b4617', # 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': '82ab43bfda5a3f59e1876fd3c828f047c689bc12', + 'angle_revision': '534e0d1c1d0fcb4b57fd6a3fb9284cd14eaa28cd', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -424,7 +424,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': 'ff7b4f6c5d964879b5f4356ef6e732adeed2f627', + 'dawn_revision': '049880d58d6636a819168c00f44f8a4ed1e33e51', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -1220,7 +1220,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm', - 'version': 'EblfsCRTnPc3sPVlsMOy81HIfmVT9qh7B2fDyVy1YWcC', + 'version_file': 'android_webview/tools/android-webview-arm.orderfile.txt', }, ], 'condition': 'checkout_android and non_git_source', @@ -1231,7 +1231,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm64', - 'version': 'udP6KDP97jjBYtizhmgFvSaAaNYT7aMyEWo_IIejfh8C', + 'version_file': 'android_webview/tools/android-webview-arm64.orderfile.txt', }, ], 'condition': 'checkout_android and non_git_source', @@ -3010,7 +3010,7 @@ Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'), 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '997079137283f693a0fac6a5350ae7f6f2cf3b59', + Var('webrtc_git') + '/src.git' + '@' + '28452dff1bf86fec881a47949d4dedd4a2fe1f09', # 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 -Nru chromium-147.0.7727.116/ash/BUILD.gn chromium-147.0.7727.137/ash/BUILD.gn --- chromium-147.0.7727.116/ash/BUILD.gn 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/BUILD.gn 2026-04-27 20:03:22.000000000 +0000 @@ -4726,6 +4726,7 @@ "//components/ukm:test_support", "//components/user_education/common", "//components/user_education/common:events", + "//components/user_education/views", "//components/user_education/views:test_support", "//components/user_manager", "//components/user_manager:test_support", @@ -5458,6 +5459,7 @@ "//components/session_manager/core", "//components/session_manager/core:test_support", "//components/user_education/common", + "//components/user_education/views", "//components/user_manager", "//components/viz/test:test_support", "//device/bluetooth", diff -Nru chromium-147.0.7727.116/ash/strings/ash_strings_kn.xtb chromium-147.0.7727.137/ash/strings/ash_strings_kn.xtb --- chromium-147.0.7727.116/ash/strings/ash_strings_kn.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/strings/ash_strings_kn.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -2444,7 +2444,7 @@ ಸಲಹೆ ಮಾಡಿರುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಇದನ್ನು ನಂತರ ಮಾಡಿ ಕುರಿತಾದ 1 ಫಲಿತಾಂಶವನ್ನು ಪ್ರದರ್ಶಿಸಲಾಗುತ್ತಿದೆ -ಸೈನ್-ಇನ್ ಮಾಡಿರುವ ಯಾವುದೇ ಬಳಕೆದಾರರನ್ನು, ಪಾಸ್‌ವರ್ಡ್ ಇಲ್ಲದೆಯೇ ಪ್ರವೇಶಿಸಲು ಈ ವೈಶಿಷ್ಟ್ಯವು ಅವಕಾಶ ನೀಡುತ್ತದೆ. ನಿಮಗೆ ವಿಶ್ವಾಸವಿರುವ ಖಾತೆಗಳೊಂದಿಗೆ ಮಾತ್ರ ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಿ. +ಸೈನ್-ಇನ್ ಮಾಡಿರುವ ಯಾವುದೇ ಬಳಕೆದಾರರನ್ನು, ಪಾಸ್‌ವರ್ಡ್ ಇಲ್ಲದೆಯೇ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಫೀಚರ್ ನಿಮಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ನಿಮಗೆ ವಿಶ್ವಾಸವಿರುವ ಖಾತೆಗಳೊಂದಿಗೆ ಮಾತ್ರ ಈ ಫೀಚರ್ ಅನ್ನು ಬಳಸಿ. Google Docs ಫುಲ್‌ಸ್ಕ್ರೀನ್ ವರ್ಧಕ "" ಅನ್ನು ಬಳಸಿ diff -Nru chromium-147.0.7727.116/ash/strings/ash_strings_my.xtb chromium-147.0.7727.137/ash/strings/ash_strings_my.xtb --- chromium-147.0.7727.116/ash/strings/ash_strings_my.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/strings/ash_strings_my.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -40,7 +40,7 @@ တွဲချိတ်ထားသည့် စက်ပစ္စည်းများ ‘အာရုံစိုက်ခြင်း အသံ’ နှင့် ချိတ်ဆက်၍မရလိုက်ပါ လက်ရှိဘက်ထရီအဆင့် % -တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် စမ်းသပ်ဆဲဖြစ်ပြီး အကြောင်းအရာသည် တိကျမှန်ကန်မှုမရှိခြင်း၊ အထင်မှားစေခြင်း (သို့) စိတ်အနှောင့်အယှက်ဖြစ်စေခြင်း ရှိနိုင်သည်။ ပြဿနာကို တိုင်ကြားရန် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။ +Generative AI သည် စမ်းသပ်ဆဲဖြစ်ပြီး အကြောင်းအရာသည် တိကျမှန်ကန်မှုမရှိခြင်း၊ အထင်မှားစေခြင်း (သို့) စိတ်အနှောင့်အယှက်ဖြစ်စေခြင်း ရှိနိုင်သည်။ ပြဿနာကို တိုင်ကြားရန် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။ ဖွင့်ထားပြီး သုံးနေသည် ‘ဖုန်းစင်တာ’ ကို စင်မှဖယ်ရှားပါ @@ -2163,7 +2163,7 @@ ကိုယ်ပိုင်ကွန်ရက် အခြားစက်များနှင့် အမြန်တွဲချိတ်ရန် သင်၏ ကို တွင် သိမ်းနိုင်သည် မှတ်တမ်းတင်သည့် ဖော်မက်ရွေးရန် -တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ။ +Generative AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ။ ဘလူးတုဆက်တင်များ ပြပါ။ ပိတ်ရန် Ctrl + W နှိပ်ပါ။ ၊ အလိုအလျောက်ဖြည့်သည် @@ -2269,7 +2269,7 @@ ရက်စွဲနှင့် အချိန်ဆက်တင်များ မျက်နှာပြင်နေရာအားလုံးတွင် သတ်မှတ်ပြီးပါပြီ။ လက်ရှိမျက်နှာပြင် ဖယ်ရှားရန် -တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာစိစစ်ပါ။ +Generative AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာစိစစ်ပါ။ က တည်းဖြတ်ထားသည် °F မီနူးကို ဖန်သားပြင်၏ ညာဘက်အောက်ခြေထောင့်သို့ ရွှေ့လိုက်သည်။ diff -Nru chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller.cc chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller.cc --- chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller.cc 2026-04-27 20:03:22.000000000 +0000 @@ -67,30 +67,6 @@ return g_instance; } -std::optional UserEducationHelpBubbleController::GetHelpBubbleId( - ui::ElementIdentifier element_id, - ui::ElementContext element_context) const { - if (help_bubble_ && help_bubble_->IsA()) { - // Cache the `bubble_view` with its associated anchor. - auto* bubble_view = help_bubble_->AsA()->bubble_view(); - auto* anchor_view = bubble_view->GetAnchorView(); - - // Find all `tracked_views` matching `element_id` and `element_context`. - const views::ElementTrackerViews::ViewList tracked_views = - views::ElementTrackerViews::GetInstance()->GetAllMatchingViews( - element_id, element_context); - - // A help bubble exists for a `tracked_view` if the `tracked_view` is the - // `anchor_view` for the help bubble. - for (const auto* tracked_view : tracked_views) { - if (tracked_view == anchor_view) { - return bubble_view->id(); - } - } - } - return std::nullopt; -} - base::CallbackListSubscription UserEducationHelpBubbleController::AddHelpBubbleAnchorBoundsChangedCallback( base::RepeatingClosure callback) { diff -Nru chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller.h chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller.h --- chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller.h 2026-04-27 20:03:22.000000000 +0000 @@ -16,11 +16,6 @@ #include "base/memory/raw_ptr.h" #include "base/types/pass_key.h" -namespace ui { -class ElementContext; -class ElementIdentifier; -} // namespace ui - namespace user_education { class HelpBubble; } // namespace user_education @@ -46,15 +41,6 @@ // NOTE: Exists if and only if user education features are enabled. static UserEducationHelpBubbleController* Get(); - // Returns the unique identifier for the help bubble currently being shown for - // the tracked element associated with the specified `element_id` in the - // specified `element_context`. If no help bubble is currently being shown for - // the tracked element or if the tracked element does not exist, an absent - // value is returned. - std::optional GetHelpBubbleId( - ui::ElementIdentifier element_id, - ui::ElementContext element_context) const; - // Adds a `callback` to be invoked whenever a help bubble's anchor bounds // change until the returned subscription is destroyed. [[nodiscard]] base::CallbackListSubscription diff -Nru chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller_unittest.cc chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller_unittest.cc --- chromium-147.0.7727.116/ash/user_education/user_education_help_bubble_controller_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/user_education_help_bubble_controller_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -20,6 +20,7 @@ #include "base/test/scoped_feature_list.h" #include "components/user_education/common/help_bubble/help_bubble.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_views.h" #include "components/user_education/views/help_bubble_views_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/window.h" @@ -63,7 +64,9 @@ HelpBubbleViewAsh* GetHelpBubbleView(HelpBubble* help_bubble) { return help_bubble->IsA() - ? help_bubble->AsA()->bubble_view() + ? views::AsViewClass( + help_bubble->AsA() + ->bubble_view_for_testing()) : nullptr; } @@ -234,7 +237,8 @@ // Destroy `help_bubble`. views::test::WidgetDestroyedWaiter waiter(help_bubble_view->GetWidget()); - help_bubble->Close(); + help_bubble->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); waiter.Wait(); help_bubble = nullptr; help_bubble_view = nullptr; @@ -284,7 +288,8 @@ // Close the `help_bubble`. ASSERT_TRUE(help_bubble); - help_bubble->Close(); + help_bubble->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); help_bubble = nullptr; // Verify expectations. diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_factory_views_ash.cc chromium-147.0.7727.137/ash/user_education/views/help_bubble_factory_views_ash.cc --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_factory_views_ash.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_factory_views_ash.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4,197 +4,20 @@ #include "ash/user_education/views/help_bubble_factory_views_ash.h" -#include -#include - #include "ash/user_education/user_education_class_properties.h" #include "ash/user_education/user_education_util.h" #include "ash/user_education/views/help_bubble_view_ash.h" -#include "base/callback_list.h" -#include "base/functional/bind.h" -#include "base/logging.h" -#include "base/memory/ptr_util.h" -#include "components/user_education/common/help_bubble/help_bubble.h" -#include "components/user_education/common/help_bubble/help_bubble_params.h" #include "components/user_education/common/user_education_class_properties.h" -#include "components/user_education/common/user_education_events.h" #include "components/user_education/views/help_bubble_delegate.h" -#include "ui/base/interaction/element_identifier.h" -#include "ui/base/interaction/element_tracker.h" -#include "ui/views/accessibility/view_accessibility.h" -#include "ui/views/accessible_pane_view.h" -#include "ui/views/bubble/bubble_dialog_delegate_view.h" +#include "components/user_education/views/help_bubble_view.h" +#include "components/user_education/views/help_bubble_views.h" +#include "ui/base/interaction/framework_specific_implementation.h" #include "ui/views/interaction/element_tracker_views.h" -#include "ui/views/view_utils.h" namespace ash { -DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleViewsAsh) DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleFactoryViewsAsh) -HelpBubbleViewsAsh::HelpBubbleViewsAsh(HelpBubbleViewAsh* help_bubble_view, - ui::TrackedElement* anchor_element) - : help_bubble_view_(help_bubble_view), anchor_element_(anchor_element) { - DCHECK(help_bubble_view); - DCHECK(help_bubble_view->GetWidget()); - scoped_observation_.Observe(help_bubble_view->GetWidget()); - - anchor_hidden_subscription_ = - ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback( - anchor_element->identifier(), anchor_element->context(), - base::BindRepeating(&HelpBubbleViewsAsh::OnElementHidden, - base::Unretained(this))); - anchor_bounds_changed_subscription_ = - ui::ElementTracker::GetElementTracker()->AddCustomEventCallback( - user_education::kHelpBubbleAnchorBoundsChangedEvent, - anchor_element->context(), - base::BindRepeating(&HelpBubbleViewsAsh::OnElementBoundsChanged, - base::Unretained(this))); -} - -HelpBubbleViewsAsh::~HelpBubbleViewsAsh() { - // Needs to be called here while we still have access to HelpBubbleViews- - // specific logic. - Close(); -} - -bool HelpBubbleViewsAsh::ToggleFocusForAccessibility() { - // // If the bubble isn't present or can't be meaningfully focused, stop. - if (!help_bubble_view_) { - return false; - } - - // If the focus isn't in the help bubble, focus the help bubble. - // Note that if is_focus_in_ancestor_widget is true, then anchor both exists - // and has a widget, so anchor->GetWidget() will always be valid. - if (!help_bubble_view_->IsFocusInHelpBubble()) { - help_bubble_view_->GetWidget()->Activate(); - help_bubble_view_->RequestFocus(); - return true; - } - - auto* const anchor = help_bubble_view_->GetAnchorView(); - if (!anchor) { - return false; - } - - bool set_focus = false; - if (anchor->GetViewAccessibility().IsAccessibilityFocusable()) { -#if BUILDFLAG(IS_MAC) - // Mac does not automatically pass activation on focus, so we have to do it - // manually. - anchor->GetWidget()->Activate(); -#else - // Focus the anchor. We can't request focus for an accessibility-only view - // until we turn on keyboard accessibility for its focus manager. - anchor->GetFocusManager()->SetKeyboardAccessible(true); -#endif - anchor->RequestFocus(); - set_focus = true; - } else if (views::IsViewClass(anchor)) { - // An AccessiblePaneView can receive focus, but is not necessarily itself - // accessibility focusable. Use the built-in functionality for focusing - // elements of AccessiblePaneView instead. -#if BUILDFLAG(IS_MAC) - // Mac does not automatically pass activation on focus, so we have to do it - // manually. - anchor->GetWidget()->Activate(); -#else - // You can't focus an accessible pane if it's already in accessibility - // mode, so avoid doing that; the SetPaneFocus() call will go back into - // accessibility navigation mode. - anchor->GetFocusManager()->SetKeyboardAccessible(false); -#endif - set_focus = - static_cast(anchor)->SetPaneFocus(nullptr); - } - - return set_focus; -} - -void HelpBubbleViewsAsh::OnAnchorBoundsChanged() { - if (help_bubble_view_) { - static_cast(help_bubble_view_) - ->OnAnchorBoundsChanged(); - } -} - -gfx::Rect HelpBubbleViewsAsh::GetBoundsInScreen() const { - return help_bubble_view_ - ? help_bubble_view_->GetWidget()->GetWindowBoundsInScreen() - : gfx::Rect(); -} - -ui::ElementContext HelpBubbleViewsAsh::GetContext() const { - return help_bubble_view_ - ? views::ElementTrackerViews::GetContextForView(help_bubble_view_) - : ui::ElementContext(); -} - -bool HelpBubbleViewsAsh::AcceleratorPressed( - const ui::Accelerator& accelerator) { - if (CanHandleAccelerators()) { - ToggleFocusForAccessibility(); - return true; - } - - return false; -} - -bool HelpBubbleViewsAsh::CanHandleAccelerators() const { - return help_bubble_view_ && help_bubble_view_->GetWidget() && - help_bubble_view_->GetWidget()->IsActive(); -} - -void HelpBubbleViewsAsh::MaybeResetAnchorView() { - if (!help_bubble_view_) { - return; - } - auto* const anchor_view = help_bubble_view_->GetAnchorView(); - if (!anchor_view) { - return; - } - anchor_view->SetProperty(user_education::kHasInProductHelpPromoKey, false); -} - -void HelpBubbleViewsAsh::CloseBubbleImpl() { - anchor_hidden_subscription_ = base::CallbackListSubscription(); - anchor_bounds_changed_subscription_ = base::CallbackListSubscription(); - scoped_observation_.Reset(); - MaybeResetAnchorView(); - - // Reset the anchor view. Closing the widget could cause callbacks which could - // theoretically destroy `this`, so - auto* const help_bubble_view = help_bubble_view_.get(); - help_bubble_view_ = nullptr; - if (help_bubble_view && help_bubble_view->GetWidget()) { - help_bubble_view->GetWidget()->Close(); - } -} - -void HelpBubbleViewsAsh::OnWidgetDestroying(views::Widget* widget) { - Close(); -} - -void HelpBubbleViewsAsh::OnElementHidden(ui::TrackedElement* element) { - // There could be other elements with the same identifier as the anchor - // element, so don't close the bubble unless it is actually the anchor. - if (element != anchor_element_) { - return; - } - - anchor_hidden_subscription_ = base::CallbackListSubscription(); - anchor_bounds_changed_subscription_ = base::CallbackListSubscription(); - anchor_element_ = nullptr; - Close(); -} - -void HelpBubbleViewsAsh::OnElementBoundsChanged(ui::TrackedElement* element) { - if (help_bubble_view_ && element == anchor_element_) { - OnAnchorBoundsChanged(); - } -} - HelpBubbleFactoryViewsAsh::HelpBubbleFactoryViewsAsh( const user_education::HelpBubbleDelegate* delegate) : delegate_(delegate) { @@ -230,12 +53,12 @@ const HelpBubbleId help_bubble_id = user_education_util::GetHelpBubbleId(params.extended_properties); auto result = base::WrapUnique(new HelpBubbleViewsAsh( - new HelpBubbleViewAsh(help_bubble_id, anchor, std::move(params)), + HelpBubbleViewAsh::Create(help_bubble_id, anchor, std::move(params)), element)); for (const auto& accelerator : delegate_->GetPaneNavigationAccelerators(element)) { - result->bubble_view()->GetFocusManager()->RegisterAccelerator( + result->help_bubble_view_->GetFocusManager()->RegisterAccelerator( accelerator, ui::AcceleratorManager::HandlerPriority::kNormalPriority, result.get()); } @@ -243,4 +66,12 @@ return result; } +DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleViewsAsh) + +HelpBubbleViewsAsh::HelpBubbleViewsAsh(user_education::HelpBubbleViewInfo info, + ui::TrackedElement* anchor_element) + : HelpBubbleViews(std::move(info), anchor_element) {} + +HelpBubbleViewsAsh::~HelpBubbleViewsAsh() = default; + } // namespace ash diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_factory_views_ash.h chromium-147.0.7727.137/ash/user_education/views/help_bubble_factory_views_ash.h --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_factory_views_ash.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_factory_views_ash.h 2026-04-27 20:03:22.000000000 +0000 @@ -15,6 +15,8 @@ #include "components/user_education/common/help_bubble/help_bubble.h" #include "components/user_education/common/help_bubble/help_bubble_factory.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_view_info.h" +#include "components/user_education/views/help_bubble_views.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/element_tracker.h" @@ -30,81 +32,10 @@ namespace ash { -class HelpBubbleViewAsh; - namespace internal { struct HelpBubbleAnchorParams; } -// Views-specific implementation of the help bubble. -// -// Because this is a FrameworkSpecificImplementation, you can use: -// help_bubble->AsA()->bubble_view() -// to retrieve the underlying bubble view. -class ASH_EXPORT HelpBubbleViewsAsh : public user_education::HelpBubble, - public views::WidgetObserver, - public ui::AcceleratorTarget { - public: - ~HelpBubbleViewsAsh() override; - - DECLARE_FRAMEWORK_SPECIFIC_METADATA() - - // Retrieve the bubble view. If the bubble has been closed, this may return - // null. - HelpBubbleViewAsh* bubble_view() { return help_bubble_view_; } - const HelpBubbleViewAsh* bubble_view() const { return help_bubble_view_; } - - // HelpBubble: - bool ToggleFocusForAccessibility() override; - void OnAnchorBoundsChanged() override; - gfx::Rect GetBoundsInScreen() const override; - ui::ElementContext GetContext() const override; - - // ui::AcceleratorTarget - bool AcceleratorPressed(const ui::Accelerator& accelerator) override; - bool CanHandleAccelerators() const override; - - private: - friend class HelpBubbleFactoryViewsAsh; - friend class HelpBubbleFactoryMac; - friend class HelpBubbleViewsTest; - - explicit HelpBubbleViewsAsh(HelpBubbleViewAsh* help_bubble_view, - ui::TrackedElement* anchor_element); - - // Clean up properties on the anchor view, if applicable. - void MaybeResetAnchorView(); - - // HelpBubble: - void CloseBubbleImpl() override; - - // views::WidgetObserver: - void OnWidgetDestroying(views::Widget* widget) override; - - void OnElementHidden(ui::TrackedElement* element); - void OnElementBoundsChanged(ui::TrackedElement* element); - - raw_ptr help_bubble_view_; - base::ScopedObservation - scoped_observation_{this}; - - // Track the anchor element to determine if/when it goes away. - raw_ptr anchor_element_; - - // Listens so that the bubble can be closed if the anchor element disappears. - // The specific anchor view is not tracked because in a few cases (e.g. Mac - // native menus) the anchor view is not the anchor element itself but a - // placeholder. - base::CallbackListSubscription anchor_hidden_subscription_; - - // Listens for changes to the anchor bounding rect that are independent of the - // anchor view. Necessary for e.g. WebUI elements, which can be scrolled or - // moved within the web page. - base::CallbackListSubscription anchor_bounds_changed_subscription_; - - base::WeakPtrFactory weak_ptr_factory_{this}; -}; - // Factory implementation for HelpBubbleViews. class ASH_EXPORT HelpBubbleFactoryViewsAsh : public user_education::HelpBubbleFactory { @@ -132,6 +63,22 @@ raw_ptr delegate_; }; +// ChromeOS-specific help bubble. Has its own class metadata mostly just for +// testing purposes. +class ASH_EXPORT HelpBubbleViewsAsh : public user_education::HelpBubbleViews { + public: + DECLARE_FRAMEWORK_SPECIFIC_METADATA() + + ~HelpBubbleViewsAsh() override; + + protected: + HelpBubbleViewsAsh(user_education::HelpBubbleViewInfo info, + ui::TrackedElement* anchor_element); + + private: + friend class HelpBubbleFactoryViewsAsh; +}; + } // namespace ash #endif // ASH_USER_EDUCATION_VIEWS_HELP_BUBBLE_FACTORY_VIEWS_ASH_H_ diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash.cc chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash.cc --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash.cc 2026-04-27 20:03:22.000000000 +0000 @@ -642,7 +642,38 @@ anchor_widget()->GetNativeWindow()->GetRootWindow()->GetChildById( kShellWindowId_HelpBubbleContainer)); - views::Widget* widget = views::BubbleDialogDelegateView::CreateBubble(this); + auto* const anchor_bubble = + anchor.view->GetWidget()->widget_delegate()->AsBubbleDialogDelegate(); + if (anchor_bubble) { + anchor_pin_ = anchor_bubble->PreventCloseOnDeactivate(); + } +} + +HelpBubbleViewAsh::~HelpBubbleViewAsh() { + // NOTE: `controller` may be `nullptr` in testing. + if (auto* controller = UserEducationHelpBubbleController::Get()) { + controller->NotifyHelpBubbleClosed(base::PassKey(), + /*help_bubble_view=*/this); + } +} + +// static +user_education::HelpBubbleViewInfo HelpBubbleViewAsh::Create( + HelpBubbleId id, + const internal::HelpBubbleAnchorParams& anchor, + user_education::HelpBubbleParams params) { + auto bubble = + base::WrapUnique(new HelpBubbleViewAsh(id, anchor, std::move(params))); + auto* const bubble_ptr = bubble.get(); + auto* const widget = views::BubbleDialogDelegateView::CreateBubble( + std::move(bubble), views::Widget::InitParams::CLIENT_OWNS_WIDGET); + bubble_ptr->InitializeAndShow(); + return user_education::HelpBubbleViewInfo(base::WrapUnique(widget), + bubble_ptr); +} + +void HelpBubbleViewAsh::InitializeAndShow() { + views::Widget* widget = GetWidget(); // This gets reset to the platform default when we call `CreateBubble()`, so // we have to change it afterwards. Note that rounded corners are updated @@ -667,11 +698,6 @@ widget->ShowInactive(); } - auto* const anchor_bubble = - anchor.view->GetWidget()->widget_delegate()->AsBubbleDialogDelegate(); - if (anchor_bubble) { - anchor_pin_ = anchor_bubble->PreventCloseOnDeactivate(); - } MaybeStartAutoCloseTimer(); // NOTE: `controller` may be `nullptr` in testing. @@ -681,14 +707,6 @@ } } -HelpBubbleViewAsh::~HelpBubbleViewAsh() { - // NOTE: `controller` may be `nullptr` in testing. - if (auto* controller = UserEducationHelpBubbleController::Get()) { - controller->NotifyHelpBubbleClosed(base::PassKey(), - /*help_bubble_view=*/this); - } -} - void HelpBubbleViewAsh::MaybeStartAutoCloseTimer() { if (timeout_.is_zero()) { return; diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash.h chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash.h --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash.h 2026-04-27 20:03:22.000000000 +0000 @@ -16,6 +16,7 @@ #include "base/time/time.h" #include "base/timer/timer.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/gfx/geometry/rect.h" @@ -57,13 +58,16 @@ DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kBodyIconIdForTesting); DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kBodyTextIdForTesting); - HelpBubbleViewAsh(HelpBubbleId id, - const internal::HelpBubbleAnchorParams& anchor, - user_education::HelpBubbleParams params); HelpBubbleViewAsh(const HelpBubbleViewAsh&) = delete; HelpBubbleViewAsh& operator=(const HelpBubbleViewAsh&) = delete; ~HelpBubbleViewAsh() override; + // Creates a help bubble view. + static user_education::HelpBubbleViewInfo Create( + HelpBubbleId id, + const internal::HelpBubbleAnchorParams& anchor, + user_education::HelpBubbleParams params); + // Returns whether the given dialog is a help bubble. static bool IsHelpBubble(views::DialogDelegate* dialog); @@ -99,6 +103,12 @@ FRIEND_TEST_ALL_PREFIXES(HelpBubbleViewAshTest, RootViewAccessibleName); friend class HelpBubbleViewsTest; + HelpBubbleViewAsh(HelpBubbleId id, + const internal::HelpBubbleAnchorParams& anchor, + user_education::HelpBubbleParams params); + + void InitializeAndShow(); + void MaybeStartAutoCloseTimer(); void OnTimeout(); diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_pixeltest.cc chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_pixeltest.cc --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_pixeltest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_pixeltest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -94,13 +94,13 @@ // help bubble views created with attributes according to test parameterization // against benchmark images. TEST_P(HelpBubbleViewAshPixelTest, Appearance) { - auto* help_bubble_view = + auto help_bubble = CreateHelpBubbleView(HelpBubbleArrow::kNone, with_title_text(), with_body_icon(), with_buttons(), with_progress()); EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( - "appearance", /*revision_number=*/9, help_bubble_view, - help_bubble_view->anchor_widget())); + "appearance", /*revision_number=*/9, help_bubble.bubble_view, + help_bubble.bubble_view->anchor_widget())); } // HelpBubbleViewAshArrowPixelTest --------------------------------------------- @@ -160,13 +160,13 @@ // help bubble views created with arrows according to test parameterization // against benchmark images. TEST_P(HelpBubbleViewAshArrowPixelTest, Placement) { - auto* help_bubble_view = CreateHelpBubbleView( + auto help_bubble = CreateHelpBubbleView( arrow(), /*with_title_text=*/true, /*with_body_icon=*/true, /*with_buttons=*/true, /*with_progress=*/true); EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen( - "placement", /*revision_number=*/9, help_bubble_view, - help_bubble_view->anchor_widget())); + "placement", /*revision_number=*/9, help_bubble.bubble_view, + help_bubble.bubble_view->anchor_widget())); } } // namespace ash diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_test_base.cc chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_test_base.cc --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_test_base.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_test_base.cc 2026-04-27 20:03:22.000000000 +0000 @@ -16,6 +16,7 @@ #include "ash/user_education/views/help_bubble_view_ash.h" #include "base/strings/string_util.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "components/vector_icons/vector_icons.h" #include "ui/compositor/layer.h" #include "ui/gfx/color_palette.h" @@ -42,7 +43,8 @@ // HelpBubbleViewAshTestBase --------------------------------------------------- -HelpBubbleViewAsh* HelpBubbleViewAshTestBase::CreateHelpBubbleView() { +user_education::HelpBubbleViewInfo +HelpBubbleViewAshTestBase::CreateHelpBubbleView() { HelpBubbleParams params; params.arrow = HelpBubbleArrow::kNone; @@ -50,12 +52,12 @@ return CreateHelpBubbleView(std::move(params)); } -HelpBubbleViewAsh* HelpBubbleViewAshTestBase::CreateHelpBubbleView( - HelpBubbleArrow arrow, - bool with_title_text, - bool with_body_icon, - bool with_buttons, - bool with_progress) { +user_education::HelpBubbleViewInfo +HelpBubbleViewAshTestBase::CreateHelpBubbleView(HelpBubbleArrow arrow, + bool with_title_text, + bool with_body_icon, + bool with_buttons, + bool with_progress) { HelpBubbleParams params; params.arrow = arrow; @@ -86,8 +88,8 @@ return CreateHelpBubbleView(std::move(params)); } -HelpBubbleViewAsh* HelpBubbleViewAshTestBase::CreateHelpBubbleView( - HelpBubbleParams params) { +user_education::HelpBubbleViewInfo +HelpBubbleViewAshTestBase::CreateHelpBubbleView(HelpBubbleParams params) { // NOTE: `HelpBubbleViewAsh` will never be created without body text. params.body_text = Repeat(u"Body", /*times=*/50); @@ -96,8 +98,8 @@ anchor_params.view = widget_->GetContentsView(); // NOTE: The returned help bubble view is owned by its widget. - return new HelpBubbleViewAsh(HelpBubbleId::kTest, anchor_params, - std::move(params)); + return HelpBubbleViewAsh::Create(HelpBubbleId::kTest, anchor_params, + std::move(params)); } void HelpBubbleViewAshTestBase::SetUp() { diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_test_base.h chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_test_base.h --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_test_base.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_test_base.h 2026-04-27 20:03:22.000000000 +0000 @@ -8,6 +8,7 @@ #include #include "ash/test/ash_test_base.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "ui/views/widget/unique_widget_ptr.h" namespace user_education { @@ -24,21 +25,22 @@ public: // Creates and returns a pointer to a new `HelpBubbleViewAsh` instance. // Note that the returned help bubble view is owned by its widget. - HelpBubbleViewAsh* CreateHelpBubbleView(); + user_education::HelpBubbleViewInfo CreateHelpBubbleView(); // Creates and returns a pointer to a new `HelpBubbleViewAsh` instance with // the specified attributes. Note that the returned help bubble view is owned // by its widget. - HelpBubbleViewAsh* CreateHelpBubbleView(user_education::HelpBubbleArrow arrow, - bool with_title_text, - bool with_body_icon, - bool with_buttons, - bool with_progress); + user_education::HelpBubbleViewInfo CreateHelpBubbleView( + user_education::HelpBubbleArrow arrow, + bool with_title_text, + bool with_body_icon, + bool with_buttons, + bool with_progress); // Creates and returns a pointer to a new `HelpBubbleViewAsh` instance with // the specified `params`. Note that the returned help bubble view is owned // by its widget. - HelpBubbleViewAsh* CreateHelpBubbleView( + user_education::HelpBubbleViewInfo CreateHelpBubbleView( user_education::HelpBubbleParams params); private: diff -Nru chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_unittest.cc chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_unittest.cc --- chromium-147.0.7727.116/ash/user_education/views/help_bubble_view_ash_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ash/user_education/views/help_bubble_view_ash_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -80,12 +80,12 @@ // Verifies that help bubbles are contained within the correct parent window. TEST_F(HelpBubbleViewAshTest, ParentWindow) { - auto* const help_bubble_view = CreateHelpBubbleView(); - EXPECT_TRUE(help_bubble_view->anchor_widget() + auto const help_bubble = CreateHelpBubbleView(); + EXPECT_TRUE(help_bubble.bubble_view->anchor_widget() ->GetNativeWindow() ->GetRootWindow() ->GetChildById(kShellWindowId_HelpBubbleContainer) - ->Contains(help_bubble_view->GetWidget()->GetNativeWindow())); + ->Contains(help_bubble.widget->GetNativeWindow())); } // HelpBubbleViewAshBodyIconTest ----------------------------------------------- @@ -154,8 +154,8 @@ } // Create `help_bubble_view`. - auto* const help_bubble_view = CreateHelpBubbleView(std::move(params)); - ASSERT_NE(help_bubble_view, nullptr); + auto const help_bubble = CreateHelpBubbleView(std::move(params)); + ASSERT_NE(help_bubble.bubble_view, nullptr); // Cache `expected_body_icon` based on order of precedence. const gfx::VectorIcon& expected_body_icon = @@ -167,7 +167,8 @@ views::ElementTrackerViews::GetInstance() ->GetUniqueViewAs( HelpBubbleViewAsh::kBodyIconIdForTesting, - views::ElementTrackerViews::GetContextForView(help_bubble_view)), + views::ElementTrackerViews::GetContextForView( + help_bubble.bubble_view)), Conditional(&expected_body_icon != &gfx::VectorIcon::EmptyIcon(), Property(&views::ImageView::GetImageModel, Eq(ui::ImageModel::FromVectorIcon( @@ -178,33 +179,33 @@ // Verifies that help bubbles have the appropriate background color. TEST_F(HelpBubbleViewAshTest, BackgroundColor) { - const auto* const help_bubble_view = CreateHelpBubbleView(); - EXPECT_EQ(help_bubble_view->background_color(), + const auto help_bubble = CreateHelpBubbleView(); + EXPECT_EQ(help_bubble.bubble_view->background_color(), cros_tokens::kCrosSysDialogContainer); } // Verifies that help bubbles can activate. TEST_F(HelpBubbleViewAshTest, CanActivate) { - const auto* const help_bubble_view = CreateHelpBubbleView(); - EXPECT_TRUE(help_bubble_view->CanActivate()); + const auto help_bubble = CreateHelpBubbleView(); + EXPECT_TRUE(help_bubble.bubble_view->CanActivate()); } TEST_F(HelpBubbleViewAshTest, RootViewAccessibleName) { - auto* const help_bubble_view = CreateHelpBubbleView(); + auto const help_bubble = CreateHelpBubbleView(); ui::AXNodeData root_view_data; - help_bubble_view->GetWidget() - ->GetRootView() + help_bubble.widget->GetRootView() ->GetViewAccessibility() .GetAccessibleNodeData(&root_view_data); EXPECT_EQ( root_view_data.GetString16Attribute(ax::mojom::StringAttribute::kName), - help_bubble_view->GetAccessibleWindowTitle()); + help_bubble.bubble_view->GetAccessibleWindowTitle()); } // Verifies that help bubbles do not handle events within their shadows. TEST_F(HelpBubbleViewAshTest, HitTest) { - auto* const help_bubble_view = CreateHelpBubbleView(); - auto* const help_bubble_widget = help_bubble_view->GetWidget(); + auto const help_bubble = CreateHelpBubbleView(); + auto* const help_bubble_view = help_bubble.bubble_view.get(); + auto* const help_bubble_widget = help_bubble.widget.get(); auto* const help_bubble_window = help_bubble_widget->GetNativeWindow(); auto* const root_window = help_bubble_window->GetRootWindow(); auto* const root_window_targeter = root_window->targeter(); diff -Nru chromium-147.0.7727.116/build/util/LASTCHANGE chromium-147.0.7727.137/build/util/LASTCHANGE --- chromium-147.0.7727.116/build/util/LASTCHANGE 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/build/util/LASTCHANGE 2026-04-27 20:03:22.000000000 +0000 @@ -1,2 +1,2 @@ -LASTCHANGE=dbcf1b1bfb506cc580859bcb5ff9460a8443af90-refs/branch-heads/7727_111@{#3} +LASTCHANGE=68ba233a543d25e75c30f1228dd3bafa2da96937-refs/branch-heads/7727@{#3862} LASTCHANGE_YEAR=2026 diff -Nru chromium-147.0.7727.116/build/util/LASTCHANGE.committime chromium-147.0.7727.137/build/util/LASTCHANGE.committime --- chromium-147.0.7727.116/build/util/LASTCHANGE.committime 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/build/util/LASTCHANGE.committime 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -1776793858 \ No newline at end of file +1777320202 \ No newline at end of file diff -Nru chromium-147.0.7727.116/chrome/VERSION chromium-147.0.7727.137/chrome/VERSION --- chromium-147.0.7727.116/chrome/VERSION 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/VERSION 2026-04-27 20:03:22.000000000 +0000 @@ -1,4 +1,4 @@ MAJOR=147 MINOR=0 BUILD=7727 -PATCH=116 +PATCH=137 diff -Nru chromium-147.0.7727.116/chrome/app/resources/chromium_strings_az.xtb chromium-147.0.7727.137/chrome/app/resources/chromium_strings_az.xtb --- chromium-147.0.7727.116/chrome/app/resources/chromium_strings_az.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/chromium_strings_az.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -629,7 +629,7 @@ Asan giriş üçün Şəkil Axtarışını bərkidə bilərsiniz Bu hesab ilə Chromium profili artıq bu cihazda mövcuddur Quraşdırdığınız üçün təşəkkür edirik. istifadə etməzdən əvvəl kompüterinizi yenidən başlatmalısınız. -Defolt axtarış sisteminiz Chromium-dan kənarda dəyişdirildi. Sizi qorumaq üçün Chromium onu orijinal defoltuna sıfırladı. +Defolt axtarış sisteminiz Chromium-dan kənarda dəyişdirildi. Chromium sizi qorumaq üçün onu orijinal defoltuna sıfırladı. Chromium-un görünüşünü dəyişmək üçün fərdiləşdirin Ünvan panelinə və ya axtarış xanasına mətn daxil etdiyiniz zaman Chromium sizə daha yaxşı təkliflər təqdim etmək üçün yazdıqlarınızı defolt axtarış sisteminizə göndərir. Bu, Anonim rejimdə deaktivdir. Chromium ən son versiyaya güncəllənə bilmədi. Yeni funksiyaları və təhlükəsizlik həllərini əldən verirsiniz. diff -Nru chromium-147.0.7727.116/chrome/app/resources/chromium_strings_fa.xtb chromium-147.0.7727.137/chrome/app/resources/chromium_strings_fa.xtb --- chromium-147.0.7727.116/chrome/app/resources/chromium_strings_fa.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/chromium_strings_fa.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -347,7 +347,7 @@ ‏سرعت سایت‌ها را با موتورV8 مرورگر Chromium بالاتر می‌برد اما مقاومت Chromium را دربرابر حمله‌ها کمی کمتر می‌کند ‏هشدار: Chromium نمی‌تواند مانع از ثبت سابقه مرورتان توسط افزونه‌ها شود. برای غیرفعال کردن این افزونه در «حالت ناشناس»، این گزینه را لغو انتخاب کنید. ‏اگر گزارش‌های استفاده از Chromium را نیز هم‌رسانی کنید، این گزارش‌ها نشانی‌های وبی را که بازدید می‌کنید دربرمی‌گیرد -‏ظاهراً نمایه توسط فرآیند Chromium دیگری () در رایانه‌ای دیگر () در حال استفاده است. Chromium نمایه را قفل کرده است تا خراب نشود. اگر مطمئنید فرآیندهای دیگری از این نمایه استفاده نمی‌کنند، می‌توانید قفل نمایه را باز کنید و Chromium را مجدداً راه‌اندازی نمایید. +‏ظاهراً نمایه توسط فرایند Chromium دیگری () در رایانه‌ای دیگر () در حال استفاده است. Chromium نمایه را قفل کرده است تا خراب نشود. اگر مطمئنید فرایندهای دیگری از این نمایه استفاده نمی‌کنند، می‌توانید قفل نمایه را باز کنید و Chromium را مجدداً راه‌اندازی کنید. لغو نصب ‏برخی‌از داده‌های Chromium هنوز در «حساب Google» شما ذخیره نشده است. پیش‌از خروج از سیستم، چند دقیقه صبر کنید. اگر اکنون از سیستم خارج شوید، این داده‌ها حذف خواهد شد. ‏اکنون که به سیستم وارد شده‌اید، می‌توانید از گذرواژه‌ها و موارد دیگر «حساب Google» خود در Chromium استفاده کنید. ‫ می‌تواند تنظیمات سرویس‌های Google را تغییر دهد. diff -Nru chromium-147.0.7727.116/chrome/app/resources/chromium_strings_fi.xtb chromium-147.0.7727.137/chrome/app/resources/chromium_strings_fi.xtb --- chromium-147.0.7727.116/chrome/app/resources/chromium_strings_fi.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/chromium_strings_fi.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -113,7 +113,7 @@ Poista tili Chromiumista Pysy turvassa liikkeellä iOS:n Chromiumin Parannetun selaussuojan avulla Vaarallinen sivusto. Chromium poisti ilmoitukset. -Omat tapahtumat +Oma toiminta Kirjaudu sisään Chromiumiin, niin voit käyttää osoitteita ja muita tietoja kaikilla laitteillasi Tämä osoite tallennetaan Google-tilillesi kirjautumisen jälkeen. Jos käytät jaettua tietokonetta, kaverisi ja perheenjäsenesi voivat selata verkkoa omista profiileistaan käsin ja muokata Chromiumista juuri itselleen sopivan. Hae tältä välilehdeltä kuvahaulla diff -Nru chromium-147.0.7727.116/chrome/app/resources/chromium_strings_it.xtb chromium-147.0.7727.137/chrome/app/resources/chromium_strings_it.xtb --- chromium-147.0.7727.116/chrome/app/resources/chromium_strings_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/chromium_strings_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -60,7 +60,7 @@ Per contribuire a migliorare queste funzionalità, Chromium invia le tue interazioni a Google. Questi dati potrebbero essere letti, elaborati e annotati da revisori. In questo modo potrai selezionare i dispositivi disponibili su cui visualizzare i contenuti. A breve Chromium eliminerà i dati di navigazione -Chromium proverà a eseguire l'upgrade delle navigazioni a HTTPS +Chromium proverà a fare l'upgrade delle navigazioni a HTTPS Dati di Chromium cancellati Chromium verrà riavviato tra Quando HTTPS non è disponibile, Chromium utilizza una connessione non sicura senza avvisarti diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_cs.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_cs.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_cs.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_cs.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -8963,7 +8963,7 @@ Oznámení jsou v nastavení systému Mac vypnutá O instalaci webových aplikací žádají weby obvykle proto, aby vám mohly poskytovat lepší prostředí Steam pro Chromebook (beta) na vašem Chromebooku není k dispozici. -Zeptat se Googlu ohledně téhle stránky: +Zeptat se Googlu na tuto stránku: (nenakonfigurováno) Zvýšit jas Zahrnout do archivu soubory protokolů Chromu. @@ -9700,7 +9700,7 @@ Žádné karty z jiných zařízení Správa tisku Teď můžete na jednom místě spravovat skupiny karet, chaty s Gemini a vlákna režimu AI. Tlačítko můžete kdykoli odepnout. -Zeptat se Googlu ohledně této stránky +Zeptat se Googlu na tuto stránku Weby uvedené níže se řídí vlastním nastavením {NUM_PASSWORDS,plural, =1{1 heslo nebylo importováno}few{{NUM_PASSWORDS} hesla nebyla importována}many{{NUM_PASSWORDS} hesla nebylo importováno}other{{NUM_PASSWORDS} hesel nebylo importováno}} Povolit aplikacím pro Android ovládat zařízení USB na tomto Chromebooku diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_da.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_da.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_da.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_da.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -590,7 +590,7 @@ Udvid faner Har tilladelse til at bruge din mikrofon genstartes, når opdateringerne er fuldført. -Du skal gendanne fabriksindstillingerne på denne enhed for at bruge Chrome Education-opgraderingsfunktioner. +Du skal gendanne fabriksindstillingerne på denne enhed for at bruge Chrome Education Upgrade-funktioner. Har tilladelse til at bruge de skrifttyper, der er installeret på din enhed forudinstalleret på din Åbn som almindelig fane @@ -1627,7 +1627,7 @@ Opret en gruppe med fanen Vælg en temafarve Prøv igen -Konstant VPN +Altid aktiv VPN Foreslåede handlinger, når du søger på skærmen Fanen er flyttet op Udskrift: @@ -2558,7 +2558,7 @@ Lyd ved opladning Se udvidelser Indhold fra dette website, herunder dets webadresse, sendes til Google og kan blive gennemgået af menneskelige kontrollanter med henblik på at forbedre denne funktion -Dette er en pakkeenhed, som ikke kan tilmeldes Kiosk & Signage-opgraderingen. +Dette er en pakkeenhed, som ikke kan tilmeldes Kiosk & Signage Upgrade. Pinkodehandlingen mislykkedes med koden . Delt fra Log ind, @@ -3271,7 +3271,7 @@ Administrer isolerede webapps (beta) Ny fane til venstre Sidens sprog: -Din enhed inkluderer en Chrome Education-opgradering, men dit brugernavn er ikke knyttet til en Google for Education-konto. Opret en Google for Education-konto ved at gå til g.co/workspace/edusignup på en sekundær enhed. +Din enhed inkluderer Chrome Education Upgrade, men dit brugernavn er ikke knyttet til en Google for Education-konto. Opret en Google for Education-konto ved at gå til g.co/workspace/edusignup på en sekundær enhed. Tekstskrifttype Vis faner lodret Drej kæben til venstre @@ -4916,7 +4916,7 @@ XML-konfigurationskilde {NUM_SITES,plural, =1{Tilladelserne er blevet fjernet fra 1 website}one{Tilladelserne er blevet fjernet fra {NUM_SITES} website}other{Tilladelserne er blevet fjernet fra {NUM_SITES} websites}} Tilladt -Du skal bruge en terminal- og signeringsopgradering, der kun tillader, at enheden køres i terminal- eller signeringstilstand. Hvis du gerne vil have, at brugere logger ind på enheden, kan du gå tilbage og tilmelde dig via Chrome Enterprise-opgraderingen. +Du skal bruge en terminal- og signeringsopgradering, der kun tillader, at enheden køres i terminal- eller signeringstilstand. Hvis du gerne vil have, at brugere logger ind på enheden, kan du gå tilbage og tilmelde dig via Chrome Enterprise Upgrade. Denne proces kan tage et par minutter. Downloader filer. Du kan installere op til eSIM-kortprofiler på denne enhed. Hvis du vil tilføje en ny profil, skal du først fjerne en eksisterende profil. Ny fanegruppe @@ -6047,7 +6047,7 @@ Smart Lock Administrer Sikkert DNS i indstillingerne for ChromeOS Brug overscroll til at navigere mellem sider -Altid aktiveret +Altid aktiv Spørg, om vil have adgang til din mikrofon vise adgangskoder Aktivér isolerede webapps @@ -6834,7 +6834,7 @@ Gem på konto Luk fanerne nedenfor , -Brugerbesked +Brugermeddelelse Vil du gemme adgangskoden? iCloud-nøglering Liste over websites, du har besøgt for nylig, som kan foreslå annoncer på andre websites, når du er på nettet @@ -7698,7 +7698,7 @@ Land/Region Mobildata aktiveres Elementet kunne ikke gemmes i -Din enhed inkluderer en Chrome Enterprise-opgradering, men dit brugernavn er ikke tilknyttet en virksomhedskonto. Du skal oprette en virksomhedskonto på g.co/ChromeEnterpriseAccount på en sekundær enhed. +Din enhed inkluderer en Chrome Enterprise Upgrade, men dit brugernavn er ikke tilknyttet en virksomhedskonto. Du skal oprette en virksomhedskonto på g.co/ChromeEnterpriseAccount på en sekundær enhed. I adresselinjen kan du bruge denne tastaturgenvej til genveje til søgemaskiner og websitesøgning Aktivér øjeblikkeligt hotspot Websites kan bruge bevægelses- og lyssensorer @@ -11621,7 +11621,7 @@ Argumenter for API-funktion Dette kort bliver kun gemt på denne enhed Gør ikke din browser eller enhed mærkbart langsommere. -Denne Chrome Enterprise-enhed følger med Chrome Enterprise-opgraderingen. Tilmeld denne enhed med en Google-administratorkonto for at få adgang til virksomhedsfunktioner. +Denne Chrome Enterprise-enhed følger med Chrome Enterprise Upgrade. Tilmeld denne enhed med en Google-administratorkonto for at få adgang til virksomhedsfunktioner. Gendan apps og sider Lagerplads Dine gemte præferencer og din aktivitet anvendes på alle de ChromeOS-enheder, hvor du logger ind med din Google-konto. Du kan vælge, hvad der skal synkroniseres, i indstillingerne. diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_fa.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_fa.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_fa.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_fa.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -3131,7 +3131,7 @@ ترجمه کل صفحه حسگر اثر انگشت را در سمت چپ لمس کنید. داده‌های اثر انگشتتان به‌صورت ایمن ذخیره می‌شود و هرگز از شما خارج نمی‌شود. خاموش • این افزونه حاوی بدافزار است -‏برای شروع فرآیند Powerwash، به یک راه‌اندازی مجدد نیاز دارید. پس از راه‌اندازی مجدد، از شما خواسته می‌شود تأیید کنید که می‌خواهید ادامه دهید. +‏برای شروع فرایند Powerwash، به یک راه‌اندازی مجدد نیاز دارید. پس‌از راه‌اندازی مجدد، از شما خواسته می‌شود تأیید کنید که می‌خواهید ادامه دهید. / ذخیره فقط در این دستگاه داده‌های ورود به سیستمتان حذف شد @@ -3146,7 +3146,7 @@ حرکت صفحه درحالی‌که موشواره در مرکز صفحه ثابت می‌ماند سرپرست شما را سنجاق کرده است رهاکردن جهت نصب -‏ممکن است حافظه Chrome تمام شده باشد یا فرآیند مربوط به صفحهٔ وب به دلایل دیگری متوقف شده باشد. برای ادامه، صفحه را مجدداً بار کنید یا به یک صفحه دیگر بروید. +‏ممکن است حافظه Chrome تمام شده باشد یا فرایند مربوط به صفحهٔ وب به دلایل دیگری متوقف شده باشد. برای ادامه، صفحه را مجدداً بار کنید یا به یک صفحه دیگر بروید. سایت می‌تواند از دوربینتان استفاده کند ‏سایت درحال استفاده از MIDI است درحال آماده‌سازی نمایه برنامه... @@ -7111,7 +7111,7 @@ پین باید ۶ رقمی باشد این حساب برای کنترل‌های والدین واجد شرایط نیست این فایل معمولاً بارگیری نمی‌شود و ممکن است خطرناک باشد -بزرگ کردن همه +گستردن همه برای هم‌رسانی، پیوستن، و مشارکت در گروه‌های زبانه، به «تنظیمات» بروید و گروه‌های زبانه خود را همگام‌سازی کنید ‏آدرس X.400 تغییرات درخواستی باعث می‌شود چاپگر غیرقابل استفاده شود. @@ -7627,7 +7627,7 @@ ‏اجازه ندادن به Gemini برای ورود به سیستم ازطرف شما &عادی ‏تنظیمات Kerberos -بزرگ کردن همه... +گستردن همه… افزودن سایت‌ها زبان‌های موردنظر را برای ترجمه خودکار اضافه کنید بیشتر بدانید @@ -8732,7 +8732,7 @@ عدم نمایش هشدار قبل‌از رفتن به سایت‌های ناامن مدیریت افزونه‌ها ‏تقویم Outlook پنهان است -ازهم باز کردن همه +گستردن همه ‏برای هم‌رسانی، پیوستن، و مشارکت در گروه‌های زبانه، تنظیمات را باز کنید و «مجاز کردن ورود به سیستم Chrome» را روشن کنید ‏به‌روزرسانی ChromeOS الزامی است ‏راه‌اندازی سریع با تلفن Android @@ -11070,7 +11070,7 @@ نقطه اتصال تأخیر قبل‌از فعال کردن کلید پیشنهادهای شخصی فقط در حساب شما نمایش داده می‌شوند -برنامه‌های کیوسک زیر «» به‌روز شده‌اند. لطفاً برای تکمیل فرآیند به‌روزرسانی دستگاه را مجدداً راه‌اندازی کنید. +برنامه‌های کیوسک زیر «» به‌روز شده‌اند. لطفاً برای تکمیل فرایند به‌روزرسانی دستگاه را مجدداً راه‌اندازی کنید. سایت‌ها می‌توانند بالاپر ارسال کنند و از هدایت‌ها استفاده کنند ‏بازخورد Google Cast میکروفن: @@ -11096,7 +11096,7 @@ ‏‫ می‌خواهد اطلاعات را در دستگاه Android شما ذخیره کند بازنشانی کلیدهای امنیتی و ایجاد پین بارگیری واژه‌نامه بررسی املا انجام نشد. -پایان دادن به فرآیند +پایان دادن به فرایند همیشه اجازه داشتن برای نشان دادن تصاویر چرخش خودکار ‏برای وارد کردن گذرواژه‌های به ، فایل CSV موردنظر را انتخاب کنید. diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_it.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_it.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -579,7 +579,7 @@ Collegamento automatico alla rete Il testo dettato in viene inviato a Google per essere elaborato Numeri carte di credito -Blocca sulla barra degli strumenti +Fissa sulla barra degli strumenti Gestisci questa estensione Impossibile scaricare i file di contenuti vocali. Riprova più tardi. Apri al termine @@ -1936,7 +1936,7 @@ Rilevamento dispositivi Cerca stampanti Condividi questa pagina -Sblocca dalla barra degli strumenti +Stacca dalla barra degli strumenti Ora puoi riprendere direttamente le chat con Gemini qui. Stacca questo elemento in qualsiasi momento. Puoi ricevere un riepilogo di ciò che le persone dicono nelle loro recensioni Cursore: da a @@ -3462,7 +3462,7 @@ Il tuo indirizzo è stato salvato Impossibile collegarsi a OneDrive. Riprova. Impostazioni di Navigazione sicura (protezione da siti pericolosi) e altre impostazioni di sicurezza -Esegue l'upgrade delle connessioni a HTTPS quando possibile e ti avvisa prima di effettuare connessioni non sicure +Fa l'upgrade delle connessioni a HTTPS quando possibile e ti avvisa prima di effettuare connessioni non sicure Nessuna autorizzazione a offrire assistenza per la scrittura Attiva ritorno a capo automatico Non vedi la stampante? @@ -4121,7 +4121,7 @@ Trasmissione dello schermo Imposta il PIN I siti possono chiedere di cercare dispositivi Bluetooth -Sblocca dalla barra degli strumenti +Stacca dalla barra degli strumenti Strumenti di ricerca Nessun sito trovato Seleziona gruppo @@ -4522,7 +4522,7 @@ &Carattere Dispositivo USB-C (porta posteriore destra) Riproduci l'animazione -Blocca sulla barra degli strumenti +Fissa sulla barra degli strumenti Mostra immagini di anteprima della scheda Gestisci stampanti Non potrai utilizzare le app per Android o il Google Play Store @@ -5407,7 +5407,7 @@ Tema a colori Attiva app Colore predefinito grigio -Consenti a siti e app di eseguire l'upgrade degli account esistenti per utilizzare le passkey +Consenti a siti e app di fare l'upgrade degli account esistenti per utilizzare le passkey Continua da un altro dispositivo La configurazione del container Linux non è stata completata. Riprova. Apri il profilo @@ -6915,7 +6915,7 @@ Invio a in corso… Puoi riaprire le schede che hai chiuso di recente su tutti i dispositivi su cui utilizzi Chrome Lettura dei dati su -Non riceverai più aggiornamenti della sicurezza per questo Chromebook a partire da . È il momento di eseguire l'upgrade all'ultima versione del software e della sicurezza. Si applicano i termini dell'offerta. +Non riceverai più aggiornamenti della sicurezza per questo Chromebook a partire da . È il momento di fare l'upgrade all'ultima versione del software e della sicurezza. Si applicano i termini dell'offerta. Vuoi rimuovere ""? Il file è già in fase di apertura Dettagli Wi-Fi @@ -8211,7 +8211,7 @@ Velocità di scansione Per ottenere questa estensione su tutti i tuoi computer, verifica la tua identità Impossibile importare le password. Le dimensioni file devono essere minori di 1 MB. -Ripristina il tuo dispositivo per eseguire l'upgrade della sicurezza. +Ripristina il tuo dispositivo per fare l'upgrade della sicurezza. Stai navigando come Ospite Continua il download Firma definitiva Microsoft @@ -8907,7 +8907,7 @@ Accedi a dispositivi USB di Accesso smart card Microsoft Crea un Account Google per un bambino -Il tuo Chromebook non riceve più aggiornamenti della sicurezza. È il momento di eseguire l'upgrade all'ultima versione del software e della sicurezza. Si applicano i termini dell'offerta. +Il tuo Chromebook non riceve più aggiornamenti della sicurezza. È il momento di fare l'upgrade all'ultima versione del software e della sicurezza. Si applicano i termini dell'offerta. Autorizzato a salvare i dati sul tuo dispositivo Ulteriori informazioni sull'accesso ai siti Uscirai da questi siti, schede aperte incluse diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_kn.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_kn.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_kn.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_kn.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -239,7 +239,7 @@ ಅನ್ನು ಸ್ಕ್ಯಾನ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ ಗಾಗಿ, ಇದು ಕೊನೆಯ ಸ್ವಯಂಚಾಲಿತ ಸಾಫ್ಟ್‌ವೇರ್ ಹಾಗೂ ಸುರಕ್ಷತಾ ಅಪ್‌ಡೇಟ್ ಆಗಿದೆ. ನಂತರದ ದಿನಗಳಲ್ಲಿ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಪಡೆಯಲು, ಹೊಸ ಮಾಡೆಲ್‌ಗೆ ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ. ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ ಸನ್‌ಗ್ಲಾಸ್‌ಗಳು -ಸೆನ್ಸರ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಗೆ ಯಾವಾಗಲೂ ಅನುಮತಿಸಿ +ಸೆನ್ಸರ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಗೆ ಯಾವಾಗಲೂ ಅನುಮತಿಸಿ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ದೃಢೀಕರಿಸಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಇನ್ನೊಮ್ಮೆ ನಮೂದಿಸಿ. Google ಸುಧಾರಿತ ರಕ್ಷಣೆ ಪ್ರೋಗ್ರಾಂ ಎಕ್ಸ್‌ಟೆನ್ಶನ್‌ಗಳ ಬಳಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ @@ -808,7 +808,7 @@ ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಇನ್‌ಪುಟ್ ಅನ್ನು ಓವರ್‌ರೈಡ್ ಮಾಡಲು ಸೈಟ್‌ಗಳನ್ನು ಅನುಮತಿಸಬೇಡಿ ಯಾವುದೇ ಮೊಬೈಲ್ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ಸೆಟಪ್ ಮಾಡಲಾಗಿಲ್ಲ. ಹೊಸ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ. ChromeOS ಆ್ಯಪ್ ಸೇವೆ -MacOS ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ನಿರ್ವಹಿಸಿ +MacOS ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ನಿರ್ವಹಿಸಿ ಡೆಮೊ ಮೋಡ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ ಯಾವುದೇ ನೆಟ್‌ವರ್ಕ್ ಕಂಡುಬಂದಿಲ್ಲ {0,plural, =0{ಈಗ ಅಳಿಸಲಾಗುತ್ತಿದೆ.}=1{ಅಳಿಸಲಾಗುತ್ತಿದೆ: 1 ಸೆಕೆಂಡಿನಲ್ಲಿ}one{ಅಳಿಸಲಾಗುತ್ತಿದೆ: # ಸೆಕೆಂಡುಗಳಲ್ಲಿ}other{ಅಳಿಸಲಾಗುತ್ತಿದೆ: # ಸೆಕೆಂಡುಗಳಲ್ಲಿ}} @@ -1117,7 +1117,7 @@ ತಿಳಿ ನೀಲಿ ಸಮಯದ ಮಿತಿಯೊಳಗೆ ವಿಸ್ತರಣೆಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ. ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ. ತಾನಾಗಿಯೇ ಅಪ್‌ಡೇಟ್‌‌ ಆಗಲು ಸಾಧ್ಯವಾಗದಿರಬಹುದು -Linux ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ +Linux ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ ಅರ್ಥವಾಯಿತು. ಡೀಫಾಲ್ಟ್ ಆಗಿ, ನೀವು ಭೇಟಿ ನೀಡುವ ಹೊಸ ಸೈಟ್‌ಗಳು ನಿಮಗೆ ಅಧಿಸೂಚನೆಗಳನ್ನು ಕಳುಹಿಸುವುದಿಲ್ಲ. ಫೈಲ್ ಬಗ್ . ಸೈಟ್ ಅನುಮತಿಗಳನ್ನು ಬದಲಾಯಿಸಲು ಆಯ್ಕೆಮಾಡಿ @@ -1549,7 +1549,7 @@ ಪೂರ್ಣ ಪುಟ ಪ್ರದೇಶದ ಧ್ವನಿ ಮೋಷನ್ ಸೆನ್ಸರ್‌ಗಳು ಅಗತ್ಯವಿರುವ ಫೀಚರ್‌ಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ -MacOS ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ +MacOS ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ ಸುರಕ್ಷತೆಯನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲು ಮತ್ತು ಸೈನ್-ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ನಮೂದಿಸಿ ಟ್ರ್ಯಾಕ್‌ಪ್ಯಾಡ್ ಸಾಧನದಿಂದ ಸಬ್‌ಸ್ಕ್ರಿಪ್ಶನ್ ತೆಗೆದುಹಾಕಿ @@ -3593,7 +3593,7 @@ ಗಾತ್ರ: ನಿಮ್ಮ ಬ್ರೌಸರ್ ಅನ್ನು ನಿರ್ವಹಿಸುತ್ತಿದೆ ಮಧ್ಯೆ ಕ್ಲಿಕ್ -ನಿಮ್ಮ ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಮ್‌ನಿಂದ ಆಮದು ಮಾಡಿದ ಸ್ಥಳೀಯ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ಬಳಸಿ +ನಿಮ್ಮ ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಮ್‌ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಸ್ಥಳೀಯ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ಬಳಸಿ ಲೈವ್ ಕ್ಯಾಪ್ಶನ್ - ಯಾವಾಗಲೂ ಪೂರ್ಣ URL ಗಳನ್ನು ತೋರಿಸಿ ಟ್ಯಾಬ್‌ಗಳನ್ನು ಹುಡುಕಿ @@ -3694,7 +3694,7 @@ ಟ್ಯಾಬ್‌ಗಳು ಕಂಡುಬಂದಿವೆ ಸೆಕೆಂಡುಗಳಲ್ಲಿ ನಲ್ಲಿ ತೆರೆಯಲಾಗುತ್ತದೆ ಶೈಲಿಯನ್ನು ಆರಿಸಿ -Windows ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ನಿರ್ವಹಿಸಿ +Windows ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ನಿರ್ವಹಿಸಿ ಧ್ವನಿ ಗುರುತಿಸುವಿಕೆ , ಈ ಕೆಳಗಿನ ಫೈಲ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಬಹುದು ವಾಲ್‌ಪೇಪರ್, ಸ್ಕ್ರೀನ್ ಸೇವರ್, ಡಾರ್ಕ್ ಥೀಮ್ ಮತ್ತು ಇನ್ನಷ್ಟನ್ನು ವೈಯಕ್ತೀಕರಿಸಿ @@ -4316,7 +4316,7 @@ ಪ್ರಸ್ತುತ Google ಖಾತೆಯ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಬಳಸಲಾಗುತ್ತಿದೆ. ಸೈನ್ ಇನ್ ಮಾಡಲು ಸುಲಭಗೊಳಿಸುವುದಕ್ಕಾಗಿ ನೀವು ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಬಹುದು. ಪ್ರಾರಂಭ ಪುಟದಲ್ಲಿ ತೋರಿಸಿ &ಗುಂಪನ್ನು ಮರುಸ್ಥಾಪಿಸಿ - ಗೆ ಆಮದು ಮಾಡಿ ಮತ್ತು ಬೈಂಡ್ ಮಾಡಿ + ಗೆ ಇಂಪೋರ್ಟ್ ಮಾಡಿ ಮತ್ತು ಬೈಂಡ್ ಮಾಡಿ ನೀವು ಈ ಸೈಟ್‌ಗಳಿಗೆ ಭೇಟಿ ನೀಡಿದ್ದೀರಿ ಮತ್ತು ಅವು ಶಾರ್ಟ್‌ಕಟ್ ಆಗಲು ವಿನಂತಿಸಿವೆ. ನೀವು ಅವುಗಳನ್ನು ಆನ್ ಮಾಡಲು ಆಯ್ಕೆಮಾಡಬಹುದು. ರೆಸಲ್ಯೂಶನ್ ನೀವು ಅಪಾಯದಲ್ಲಿರುವ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಹೊಂದಿದ್ದೀರಿ @@ -5975,7 +5975,7 @@ ಪ್ರಮುಖ ನೋಡ್ ಟಿಪ್ಪಣಿ ಫೈಲ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ ಹಿಂದಿನ 15 ನಿಮಿಷಗಳು ವಿಸ್ತರಣೆ ವೆಬ್‌ಸೈಟ್ ಅನ್ನು ತೆರೆಯಿರಿ -MacOS ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ +MacOS ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ ಬೇರೆ ವಿಂಡೋವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ ಮಾತಿನ ಲಾಗಿಂಗ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ ಈ ಲಿಂಕ್ ಕಳುಹಿಸಿದ ವ್ಯಕ್ತಿಯನ್ನು ಸಂಪರ್ಕಿಸಿ @@ -6582,7 +6582,7 @@ ಡಿಸ್ಕ್ ಚಿತ್ರವನ್ನು ರಚಿಸುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. ಸ್ವಯಂಚಾಲಿತ ಅಪ್‌ಡೇಟ್ ಅನ್ನು ಆನ್ ಮಾಡಿ ಅಳಿಸಿ ಮತ್ತು ಮುಂದುವರಿಯಿರಿ -ChromeOS ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ +ChromeOS ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳಲಾಗಿದೆ 10x {NUM_REUSED,plural, =0{ಯಾವುದೇ ಮರುಬಳಕೆ ಮಾಡಲಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳಿಲ್ಲ}=1{1 ಮರುಬಳಕೆ ಮಾಡಲಾದ ಪಾಸ್‌ವರ್ಡ್}one{{NUM_REUSED} ಮರುಬಳಕೆ ಮಾಡಲಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು}other{{NUM_REUSED} ಮರುಬಳಕೆ ಮಾಡಲಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು}} ಓಹ್, ಸೈನ್ ಇನ್ ಮಾಡುವಾಗ ಏನೋ ತಪ್ಪು ಸಂಭವಿಸಿದೆ @@ -6603,7 +6603,7 @@ ಸೈನ್ ಔಟ್ ಆಗಿಯೇ ಇರಿ ಡಿವೊರಾಕ್‌ ನೀವು ಹಲವಾರು ಬಾರಿ ತಪ್ಪಾದ ಪಿನ್ ನಮೂದಿಸಿರುವಿರಿ. ನಿಮ್ಮ ಪಾಸ್‌ಕೀಗಳು ಮತ್ತು ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು, ನಿಮ್ಮ ಪಿನ್ ಅನ್ನು ಬದಲಾಯಿಸಿ. -ಪ್ರಮಾಣಪತ್ರವನ್ನು ಆಮದು ಮಾಡಿಕೊಳ್ಳುವಾಗ ದೋಷ ಉಂಟಾಗಿದೆ +ಪ್ರಮಾಣಪತ್ರವನ್ನು ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳುವಾಗ ದೋಷ ಉಂಟಾಗಿದೆ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡುವುದನ್ನು ಮುಂದುವರಿಸಿ ಸ್ಫೂರ್ತಿ ನಿಮ್ಮ ಫೋನ್‌ನೊಂದಿಗೆ ಕನೆಕ್ಷನ್ ಕಡಿದುಹೋಗಿದೆ @@ -8113,7 +8113,7 @@ ನೀವು ಇನ್ನೊಂದು ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿರದಿದ್ದರೆ Google Chrome ಮೊಬೈಲ್ ಡೇಟಾವನ್ನು ಬಳಸುತ್ತದೆ. ಲಭ್ಯವಿರುವ ಸಾಧನ: ಸಾಧನದ ಕಾನ್ಫಿಗರೇಶನ್‌ ಪರಿಶೀಲನೆಯ ಸಮಸ್ಯೆಯಿಂದಾಗಿ ಸೈನ್-ಇನ್ ವಿಫಲವಾಗಿದೆ. ನಿಮ್ಮ ಅನ್ನು ಪವರ್‌ವಾಷ್ ಮಾಡಿ ಮತ್ತು ಪುನಃ ಪ್ರಯತ್ನಿಸಿ. -Linux ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ +Linux ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ ಆಯ್ಕೆಮಾಡಿದ ಸಾಧನವು ಗೆ ಬದಲಾಗಿದೆ ನನ್ನ ಡ್ರೈವ್‌ನಲ್ಲಿರುವ ನಿಮ್ಮ ಫೈಲ್‌ಗಳು ನಿಮ್ಮ Chromebook ಗೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಿಂಕ್ ಆಗುತ್ತವೆ ಇದರಿಂದ ನೀವು ಇಂಟರ್‌ನೆಟ್ ಕನೆಕ್ಷನ್ ಇಲ್ಲದೆಯೇ ಅವುಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು. ಇದು ಸುಮಾರು ಯಷ್ಟು ಸ್ಥಳಾವಕಾಶ ಬಳಸುತ್ತದೆ. ನೀವು ಪ್ರಸ್ತುತ ಯಷ್ಟು ಸ್ಥಳಾವಕಾಶ ಹೊಂದಿದ್ದೀರಿ. ಕ್ಷಮಿಸಿ, DLC ಅವಲಂಬನೆಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡುವಾಗ ಏನೋ ತಪ್ಪಾಗಿದೆ. ರೀಬೂಟ್ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಿ ಮತ್ತು ಸಮಸ್ಯೆ ಮುಂದುವರಿದರೆ ವಿವರಣೆಯಲ್ಲಿ #bruschetta ನೊಂದಿಗೆ ಫೀಡ್‌ಬ್ಯಾಕ್ ಸಲ್ಲಿಸಿ. ದೋಷ ಕೋಡ್ ಆಗಿದೆ. @@ -8191,7 +8191,7 @@ ಈ () ಪ್ರಕಾರದ ಫೈಲ್ ಅಪಾಯಕಾರಿಯಾಗಬಹುದು. ಮೇಲೆ ನಿಮಗೆ ನಂಬಿಕೆಯಿದ್ದರೆ ಮಾತ್ರ ಈ ಫೈಲ್ ಅನ್ನು ಸೇವ್ ಮಾಡಿ ಡೌನ್‌ಲೋಡ್‌ಗಳ ಡೈರೆಕ್ಟರಿಗೆ ಸಿಸ್ಟಂ ಲಾಗ್‌ಗಳನ್ನು ಸಂಗ್ರಹಿಸಿ. ಫೈಲ್ ಪಾತ್ ಅಥವಾ ಹೆಸರು ತುಂಬಾ ಉದ್ದವಾಗಿದೆ. ದಯವಿಟ್ಟು ಕಿರಿದಾದ ಹೆಸರಿನೊಂದಿಗೆ ಅಥವಾ ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ಸೇವ್‌ ಮಾಡಿ. -Windows ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ +Windows ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಕನೆಕ್ಷನ್ ಅನ್ನು ನಿರ್ವಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಫೋನ್ ಸಮೀಪದಲ್ಲಿದೆ, ಅನ್‌ಲಾಕ್ ಆಗಿದೆ ಮತ್ತು ಅದರಲ್ಲಿ ಬ್ಲೂಟೂತ್ ಹಾಗೂ ವೈ-ಫೈ ಆನ್ ಆಗಿದೆ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ. ಗಾಗಿ ಪಾಸ್‌ಕೀಯನ್ನು ರಚಿಸಲು, Google Password Manager ಆನ್ ಆಗಿರಬೇಕು ನಿಮ್ಮ ಸ್ಥಳೀಯ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಸಾಧನವನ್ನು ಹುಡುಕಲು ಮತ್ತು ಸಂಪರ್ಕಿಸಲು ಸೈಟ್‌ಗಳು ಈ ಫೀಚರ್ ಅನ್ನು ಬಳಸಬಹುದು @@ -9956,7 +9956,7 @@ ಮೂಲಕ ಪಠ್ಯವನ್ನು ಹಂಚಲಾಗಿದೆ Family Link ಸೈಟ್ ಹುಡುಕಾಟವನ್ನು ಸೇರಿಸಿ -ChromeOS ನಿಂದ ಆಮದು ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ +ChromeOS ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿದ ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ವೀಕ್ಷಿಸಿ ನಿಮ್ಮ ಸೈಟ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು ನೀವು ಈಗ ನಿಮ್ಮ ಟ್ಯಾಬ್ ಗುಂಪುಗಳು ಮತ್ತು AI ಮೋಡ್ ಥ್ರೆಡ್‌ಗಳನ್ನು ಒಂದೇ ಸ್ಥಳದಲ್ಲಿ ನಿರ್ವಹಿಸಬಹುದು. ಇದನ್ನು ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ಅನ್‌ಪಿನ್ ಮಾಡಿ. ಐಚ್ಛಿಕ @@ -10409,7 +10409,7 @@ ಕ್ಯಾಮರಾವು ಅಗತ್ಯವಿರುವ ಫೀಚರ್‌ಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ ಬ್ರೌಸರ್ ಸಿಂಕ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ಕಾಪಿ ಮಾಡಲಾಗಿದೆ - ಗೆ ಆಮದು ಮಾಡಿ + ಗೆ ಇಂಪೋರ್ಟ್ ಮಾಡಿ ಈ ಪುಟವನ್ನು ರೀಲೋಡ್ ಮಾಡಿ ಅನುಮತಿಸಲಾಗಿದೆ. ಸಿಸ್ಟಮ್ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಆನ್ ಮಾಡಿ. ಈ ಫೈಲ್ ಪ್ರಕಾರವು ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಹಾನಿಮಾಡಬಹುದು. ಆದರೂ ನೀವು ಅನ್ನು ಇರಿಸಿಕೊಳ್ಳಲು ಬಯಸುವಿರಾ? @@ -11084,7 +11084,7 @@ ಬರೆಯಲು ಸಹಾಯ ಪಡೆಯಿರಿ ಎಂಬುದರ ಕುರಿತು ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ ಸಣ್ಣ ಉಳಿತಾಯಗಳು ಸ್ಕ್ರೀನ್ ರೆಸಲ್ಯೂಷನ್‌‌ -ಪ್ರಮಾಣಪತ್ರವನ್ನು ಆಮದು ಮಾಡಿಕೊಳ್ಳಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ +ಪ್ರಮಾಣಪತ್ರವನ್ನು ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ ನಿಮ್ಮ Android ಸಾಧನದಲ್ಲಿ ಮಾಹಿತಿಯನ್ನು ಸೇವ್ ಮಾಡಲು ಬಯಸುತ್ತದೆ ಸುರಕ್ಷತಾ ಕೀಗಳನ್ನು ಮರುಹೊಂದಿಸಿ ಮತ್ತು ಪಿನ್‌ಗಳನ್ನು ರಚಿಸಿ ಕಾಗುಣಿತ ಪರಿಶೀಲನೆ ನಿಘಂಟು ಡೌನ್‌ಲೋಡ್ ವಿಫಲವಾಗಿದೆ. @@ -11852,7 +11852,7 @@ ವಿವರಗಳ ಪುಟಕ್ಕೆ ಹಿಂದಿರುಗುವ ಬಟನ್ ನಲ್ಲಿ ಎಲ್ಲಾ ವಿಸ್ತರಣೆಗಳನ್ನು ಅನುಮತಿಸಲು ನೀವು ಈ ಹಿಂದೆ ಆಯ್ಕೆ ಮಾಡಿದ್ದೀರಿ ಅಪ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ (%)... -Windows ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ +Windows ನಿಂದ ಇಂಪೋರ್ಟ್ ಮಾಡಿಕೊಳ್ಳಿ ಈ ಪುಟವನ್ನು ಬದಲಾಯಿಸಲು ಬಯಸುತ್ತೀರಾ? ಪಾವತಿ ವಿಧಾನಗಳನ್ನು ಸೇರಿಸಿ ಯಾವಾಗಲೂ ಐಕಾನ್ ತೋರಿಸು diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_lv.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_lv.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_lv.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_lv.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -7573,7 +7573,7 @@ Nav atļauts pārvaldīt logus visos jūsu displejos atpakaļatkāpe Jūsu drošības atslēga nav aizsargāta ar PIN kodu. Lai pārvaldītu pirkstu nospiedumus, vispirms izveidojiet PIN kodu. -Pievienojiet cilnes un citu kontekstu +Pievienot cilnes un citu kontekstu X9.62 ECDSA paraksts ar SHA-384 Rādīt detaļas Lejupielādes apstiprināšana diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_my.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_my.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_my.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_my.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -4786,7 +4786,7 @@ လက်ကွက် အာရုံစိုက်ရန်နေရာ အထူးဖော်ပြရန် ဖြင့် လက်ခံမလား။ အတွက် အထူးသီးသန့်။ - တီထွင်ဖန်တီးမှုဆိုင်ရာ AI ကို ကနဦးစမ်းသပ်ရေးဆွဲနေဆဲဖြစ်ပြီး လောလောဆယ်တွင် ရနိုင်မှုကို ကန့်သတ်ထားသည်။ + Generative AI ကို ကနဦးစမ်းသပ်ရေးဆွဲနေဆဲဖြစ်ပြီး လောလောဆယ်တွင် ရနိုင်မှုကို ကန့်သတ်ထားသည်။ ယုံကြည်ရသော CA ကတ်များကို ပြရန် တစ်ကြိမ်လျှင် ဝဏ္ဏသံတစ်ခု ထည့်ရန် @@ -5799,7 +5799,7 @@ နောက်တစ်ကြိမ်တွင် သတိမပေးပါနှင့် ဖွင့် ဤဝဘ်ဆိုက်သို့ ဝင်ခွင့်ရှိသည် -သင်၏နေရာအတိအကျကို မျှဝေသောအခါ Gemini သည် သင့်မေးခွန်းများကို ပိုမိုတိကျစွာ ဖြေနိုင်သည်။ တီထွင်ဖန်တီးမှုဆိုင်ရာ AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ +သင်၏နေရာအတိအကျကို မျှဝေသောအခါ Gemini သည် သင့်မေးခွန်းများကို ပိုမိုတိကျစွာ ဖြေနိုင်သည်။ Generative AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ ဗီဒီယိုချတ်လုပ်ခြင်းကဲ့သို့ ဝန်ဆောင်မှုများအတွက်က ဝဘ်ဆိုက်များက သင့်မိုက်ခရိုဖုန်းကို အသုံးပြုလေ့ရှိသည် ဆိုက်များ အားလုံး အလုပ် မန်နေဂျာ @@ -6341,7 +6341,7 @@ အန္တရာယ်ရှိသည့် ဝဘ်ဆိုက်များရန်မှ အကာအကွယ် ရယူရန် ‘လုံခြုံစွာ ဖွင့်ကြည့်ခြင်း’ ကိုဖွင့်ပါ သင့်လျှို့ဝှက်ကီးများကို ဖွင့်ရန် ပရင်ထုတ်ရန် မအောင်မြင်ပါ -တဘ်များ ပြောင်းဖွင့်လိုက်သည့်တိုင် လက်ရှိတဘ်ကို Gemini နှင့် မျှဝေသည်။ တီထွင်ဖန်တီးမှုဆိုင်ရာ AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ +တဘ်များ ပြောင်းဖွင့်လိုက်သည့်တိုင် လက်ရှိတဘ်ကို Gemini နှင့် မျှဝေသည်။ Generative AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ Google Drive အသုံးပြုမှု ဖယ်ရှားမလား။ အခြားစက်များမှ တဘ်မရှိပါ သင့်တွင် စသည်တို့ လောလောဆယ်ရှိသည်။ သင့်အဖွဲ့အစည်းဖြစ်သော က ဤဒေတာကို ကြည့်နိုင်၊ စီမံနိုင်မည်။ @@ -7366,7 +7366,7 @@ ရေးသားမှုအကူအညီ ရယူရန် ဤစက်ပစ္စည်းပေါ်တွင်- &အခြေခံ စာလုံးပေါင်းစစ်ဆေးခြင်းကို အသုံးပြုရန် -ရွေးချယ်မှုဖြတ်လမ်းအကြောင်း ပိုမိုလေ့လာရန် +ရွေးချယ်မှုဖြတ်လမ်းအကြောင်း ပိုလေ့လာရန် စကားဝှက်များ စစ်ဆေးရန် {NUM_EXTENSIONS,plural, =1{သင်သည် အန္တရာယ်ရှိနိုင်သော နောက်ဆက်တွဲ ၁ ခုကို ပြန်ဖွင့်လိုက်သည်}other{သင်သည် အန္တရာယ်ရှိနိုင်သော နောက်ဆက်တွဲ {NUM_EXTENSIONS} ခုကို ပြန်ဖွင့်လိုက်သည်}} ဒေတာအစီအစဉ် (သို့) မိုဘိုင်းကွန်ရက် ဒွန်ဂယ်ပါသော Chromebook များ (သို့) ဟော့စပေါ့သို့ မိုဘိုင်းသုံး၍ ချိတ်ဆက်သည့်အခါ ဤရွေးချယ်မှု သက်ရောက်ပါသည် @@ -8235,7 +8235,7 @@ အခြေခံ မောက်စ် ခလုတ် ဖလှယ်ရန် လုံခြုံသော DNS ကို အသုံးပြုပါ ပါနာကိုတာ အချိုပွဲ -ချတ်အသစ် စတင်သောအခါ လက်ရှိတဘ်ကို Gemini နှင့် မျှဝေသည်။ တီထွင်ဖန်တီးမှုဆိုင်ရာ AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ AI နှင့် ကိုယ်ရေးအချက်အလက် လုံခြုံမှုအကြောင်း ပိုလေ့လာရန် +ချတ်အသစ် စတင်သောအခါ လက်ရှိတဘ်ကို Gemini နှင့် မျှဝေသည်။ Generative AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ AI နှင့် ကိုယ်ရေးအချက်အလက် လုံခြုံမှုအကြောင်း ပိုလေ့လာရန် ဖိုင်တွဲအမည်ကို ပြောင်းခြင်း WebRTC ရိုက်ထားသည့် လော့ သင်၏လက်ဗွေကို ထည့်လိုက်ပါပြီ @@ -10398,7 +10398,7 @@ တုံ့ပြန်မှုမရှိခြင်း။ နှေး Google Dashboard မှတစ်ဆင့် စင့်ခ်လုပ်ခြင်းကို ရပ်တန့်လိုက်ပါပြီ။ -အလုပ်တွင် တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သုံးခြင်းအကြောင်း ပိုမိုလေ့လာရန် +အလုပ်တွင် Generative AI သုံးခြင်းအကြောင်း ပိုမိုလေ့လာရန် အပလီကေးရှင်း ID သို့မဟုတ် ဝဘ်စတိုး URL ကို ထည့်ပါ အသုံးပြုသူ လိုင်စင် သဘောတူညီချက် အဓိကနုတ် မှတ်ချက်ဖိုင်များကို ဒေါင်းလုဒ်လုပ်နေသည်… % @@ -11752,7 +11752,7 @@ စတူဒီယိုပုံစံမိုက် ကိရိယာပေါ်တွင် ချို့ယွင်းချက်ရှာဖွေပြင်ဆင်ခြင်း အင်္ဂါရပ်အား အပြည့်အဝ မဖွင့်ထားပါ။ လိပ်စာဘားရှိ တောင်းဆိုချက်အားလုံး လျှော့ပြပါ -Gemini နှင့် ချတ်လုပ်ရန် သင်၏မိုက်ခရိုဖုန်းကို သုံးပါ။ Gemini နှင့် ပြောဆိုသောအခါ အသံကို ‘Gemini Apps အသုံးပြုမှု’ (၎င်းကို ဖွင့်ထားပါက) တွင် သိမ်းထားသည်။ တီထွင်ဖန်တီးမှုဆိုင်ရာ AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ +Gemini နှင့် ချတ်လုပ်ရန် သင်၏မိုက်ခရိုဖုန်းကို သုံးပါ။ Gemini နှင့် ပြောဆိုသောအခါ အသံကို ‘Gemini Apps အသုံးပြုမှု’ (၎င်းကို ဖွင့်ထားပါက) တွင် သိမ်းထားသည်။ Generative AI မိုဒယ်များ ပိုကောင်းအောင်လုပ်ရန် သင်၏ဒေတာကို မသုံးပါ။ ကောင်း ဝဘ်ဆိုက်များက လှုပ်ရှားမှု အာရုံခံစနစ်များ သုံးနိုင်သည် အမှားအယွင်းတစ်ခုကြောင့် ပြန်ယူခြင်းကို အပြီးသတ်၍မရပါ diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_sw.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_sw.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_sw.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_sw.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -4779,7 +4779,7 @@ - Tafuta na Google Inaweka mipangilio ya ... Historia ya utafutaji, inaendeshwa na AI -Ili uweze kuweka mipangilio ya mwanzo, unahitaji kuunganisha kwenye mtandao ili faili ziweze kusawazisha kwenye Chromebook yako +Ili uweze kuweka mipangilio ya mwanzo, unahitaji kuunganisha mtandao ili faili ziweze kusawazisha kwenye Chromebook yako Angazia fokasi ya kibodi Ungependa kupokea kupitia kipengele cha ? Inapatikana kwenye pekee. diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_uk.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_uk.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_uk.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_uk.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -11899,7 +11899,7 @@ &Відкрити в новій вкладці Помилка підключення спільного доступу. Перевірте URL-адресу файлу та повторіть спробу. Щоб швидко знайти щось на певному сайті або перейти в іншу пошукову систему, ви можете вводити в адресному рядку відповідні команди -Показувати Gemini на панелі меню Mac і ввімкнути комбінацію клавіш +Показувати Gemini на смузі меню Mac і ввімкнути клавіатурне скорочення Порівняльні таблиці допомагають відстежувати й звужувати пошук товарів Налаштування клавіатури диктування diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_uz.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_uz.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_uz.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_uz.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1703,7 +1703,7 @@ Kodli ibora xato kiritildi saytida bildirishnomalar chiqmasin Sozlash tugmasi -Quvvat darajasi +Batareya holati Havolani quyidagicha ochish Google Parollar menejeri uchun 6 raqamli zaxiraviy PIN kod yarating, /6 PIN raqami Oxirgi Chrome varaqlarini ochish uchun Chrome Sync funksiyasini yoqing. Batafsil diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_vi.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_vi.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_vi.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_vi.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -8568,7 +8568,7 @@ Trình ghi ảnh hệ thống ChromeOS Flex Cáo Chặn trang web lạ (mặc định) -Mở trong cửa sổ Ẩn danh +Mở trong cửa sổ ẩn danh Tự động đăng nhập vào các trang web bằng thông tin đăng nhập được lưu trữ. Khi tính năng này tắt, bạn sẽ luôn được yêu cầu xác nhận trước khi đăng nhập vào trang web. Trang hiện đã sẵn sàng cho bạn xem Mạnh diff -Nru chromium-147.0.7727.116/chrome/app/resources/generated_resources_zh-HK.xtb chromium-147.0.7727.137/chrome/app/resources/generated_resources_zh-HK.xtb --- chromium-147.0.7727.116/chrome/app/resources/generated_resources_zh-HK.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/generated_resources_zh-HK.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -11143,7 +11143,7 @@ 日期和時間設定 離線時無法開啟「 儲存去「一律啟用這些網站」清單 -目前由 Gemini 接手操作並控制緊 +目前由 Gemini 接手操作同控制緊 管理其他使用者 未選取語言 自動更新日落時間表 diff -Nru chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_cs.xtb chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_cs.xtb --- chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_cs.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_cs.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -639,7 +639,7 @@ Vypnuto • Chrome nedokáže ověřit, odkud toto rozšíření pochází instalační program prohlížeče Chrome můžete použít pokaždé, když kliknete na odkazy ve zprávách, dokumentech a dalších aplikacích -Zeptat se Googlu ohledně této stránky +Zeptat se Googlu na tuto stránku Jiný program ve vašem počítači nainstaloval aplikaci, která může měnit funkce Chromu. diff -Nru chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_fa.xtb chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_fa.xtb --- chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_fa.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_fa.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -659,7 +659,7 @@ ‏برای باز کردن Chrome و شروع به مرور، روی نامتان کلیک کنید. ‏اگر تصویری توضیح مفیدی نداشته باشد، Chrome توضیحی برای شما ارائه خواهد کرد. برای ایجاد توضیحات، تصاویر به Google ارسال می‌شود. هروقت خواستید می‌توانید این گزینه را در تنظیمات خاموش کنید. ‏Chrome برای این سایت به اجازه مکان نیاز دارد -‏ظاهراً نمایه توسط فرآیند Google Chrome دیگری () در رایانه‌ای دیگر () در حال استفاده است. Chrome نمایه را قفل کرده است تا خراب نشود. اگر مطمئنید فرآیندهای دیگری از این نمایه استفاده نمی‌کنند، می‌توانید قفل نمایه را باز کنید و Chrome را مجدداً راه‌اندازی نمایید. +‏ظاهراً نمایه توسط فرایند Google Chrome دیگری () در رایانه‌ای دیگر () درحال استفاده است. Chrome نمایه را قفل کرده است تا خراب نشود. اگر مطمئنید فرایندهای دیگری از این نمایه استفاده نمی‌کنند، می‌توانید قفل نمایه را باز کنید و Chrome را مجدداً راه‌اندازی کنید. ‏مدیریت نمایه‌های Chrome ‏Google Chrome می‌خواهد گذرواژه‌ها را کپی کند. برای اینکه اجازه دهید این کار انجام شود، گذرواژه Windows خود را تایپ کنید. ‏حساب شما در Chrome diff -Nru chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_fi.xtb chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_fi.xtb --- chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_fi.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_fi.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -122,7 +122,7 @@ Virhe päivityksiä tarkistettaessa: . Chromen avulla voit vaikuttaa enemmän näkemiisi mainoksiin ja rajoittaa, mitä tietoa sivustot saavat sinusta, kun ne näyttävät sinulle personoituja mainoksia Jos haluat poistaa Google-tilin Chromesta, kirjaudu ulos -Omat tapahtumat +Oma toiminta Sulje Gemini in Chrome Dev Kun käyt sivustolla, Chrome lähettää osan URL-osoitteesta obfuskoituna Googlen selaussuojalle yksityisyyspalvelimen kautta, jolloin IP-osoitteesi piilotetaan. Jos sivusto tekee jotakin epäilyttävää, koko URL-osoitteet ja osia sivun sisällöstä lähetetään. Googlen sovellusliittymän avaimet puuttuvat. Jotkin Google Chromen toiminnoista poistetaan käytöstä. diff -Nru chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_it.xtb chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_it.xtb --- chromium-147.0.7727.116/chrome/app/resources/google_chrome_strings_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/app/resources/google_chrome_strings_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -144,7 +144,7 @@ Per proteggere i tuoi dati, consenti a Chrome di rimuovere le autorizzazioni dai siti che non hai visitato di recente. Le notifiche non vengono interrotte. La tua organizzazione chiude Chrome quando non viene utilizzato per . I dati di navigazione sono stati eliminati. Potrebbero essere inclusi cronologia, compilazione automatica e download. Vuoi riavviare Chrome? -Chrome proverà a eseguire l'upgrade delle navigazioni a HTTPS +Chrome proverà a fare l'upgrade delle navigazioni a HTTPS La tua organizzazione gestisce Chrome ha già eseguito l'accesso a questo profilo Chrome. Per tenere separata la tua navigazione, Chrome può creare per te il tuo profilo personale. Impossibile installare la stessa versione di Google Chrome attualmente in esecuzione. Chiudi Google Chrome e riprova. diff -Nru chromium-147.0.7727.116/chrome/browser/ash/file_manager/restore_io_task_unittest.cc chromium-147.0.7727.137/chrome/browser/ash/file_manager/restore_io_task_unittest.cc --- chromium-147.0.7727.116/chrome/browser/ash/file_manager/restore_io_task_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ash/file_manager/restore_io_task_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -296,6 +296,97 @@ EXPECT_FALSE(base::PathExists(info_file_path)); } +class TrashServiceMaliciousPath + : public ash::trash_service::mojom::TrashService { + public: + explicit TrashServiceMaliciousPath( + mojo::PendingReceiver receiver) { + receivers_.Add(this, std::move(receiver)); + } + ~TrashServiceMaliciousPath() override = default; + + TrashServiceMaliciousPath(const TrashServiceMaliciousPath&) = delete; + TrashServiceMaliciousPath& operator=(const TrashServiceMaliciousPath&) = + delete; + + void ParseTrashInfoFile( + base::File trash_info_file, + ash::trash_service::ParseTrashInfoCallback callback) override { + // Return a malicious path that traverses directories. + std::move(callback).Run(base::File::FILE_OK, + base::FilePath("/../malicious/path"), + base::Time::UnixEpoch()); + } + + private: + mojo::ReceiverSet receivers_; +}; + +class RestoreIOTaskMaliciousPathTest : public TrashBaseIOTest { + public: + RestoreIOTaskMaliciousPathTest() = default; + + RestoreIOTaskMaliciousPathTest(const RestoreIOTaskMaliciousPathTest&) = + delete; + RestoreIOTaskMaliciousPathTest& operator=( + const RestoreIOTaskMaliciousPathTest&) = delete; + + void SetUp() override { + TrashBaseIOTest::SetUp(); + + ash::trash_service::SetTrashServiceLaunchOverrideForTesting( + base::BindRepeating( + &RestoreIOTaskMaliciousPathTest::CreateMaliciousTrashService, + base::Unretained(this))); + } + + mojo::PendingRemote + CreateMaliciousTrashService() { + mojo::PendingRemote remote; + malicious_service_ = std::make_unique( + remote.InitWithNewPipeAndPassReceiver()); + return remote; + } + + private: + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr malicious_service_; +}; + +TEST_F(RestoreIOTaskMaliciousPathTest, + CompromisedServiceShouldCompleteWithError) { + EnsureTrashDirectorySetup(downloads_dir_); + + std::string foo_contents = base::RandBytesAsString(kTestFileSize); + + const base::FilePath trash_path = + downloads_dir_.Append(trash::kTrashFolderName); + const base::FilePath info_file_path = + trash_path.Append(trash::kInfoFolderName).Append("foo.txt.trashinfo"); + ASSERT_TRUE(base::WriteFile(info_file_path, foo_contents)); + const base::FilePath files_path = + trash_path.Append(trash::kFilesFolderName).Append("foo.txt"); + ASSERT_TRUE(base::WriteFile(files_path, foo_contents)); + + base::RunLoop run_loop; + std::vector source_urls = { + CreateFileSystemURL(info_file_path), + }; + + base::MockRepeatingCallback progress_callback; + base::MockOnceCallback complete_callback; + + EXPECT_CALL(progress_callback, Run(_)).Times(0); + EXPECT_CALL(complete_callback, + Run(Field(&ProgressStatus::state, State::kError))) + .WillOnce(RunClosure(run_loop.QuitClosure())); + + RestoreIOTask task(source_urls, profile_.get(), file_system_context_, + temp_dir_.GetPath()); + task.Execute(progress_callback.Get(), complete_callback.Get()); + run_loop.Run(); +} + class TrashServiceMojoDisconnector : public ash::trash_service::mojom::TrashService { public: diff -Nru chromium-147.0.7727.116/chrome/browser/ash/file_manager/trash_info_validator.cc chromium-147.0.7727.137/chrome/browser/ash/file_manager/trash_info_validator.cc --- chromium-147.0.7727.116/chrome/browser/ash/file_manager/trash_info_validator.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ash/file_manager/trash_info_validator.cc 2026-04-27 20:03:22.000000000 +0000 @@ -165,11 +165,13 @@ } // The restore path that was parsed could be empty, not have a leading "/" or - // only consist of "/". + // only consist of "/". It must also not contain any path traversal components + // (e.g. "..") to prevent escaping the trash directory. if (restore_path.empty() || restore_path.value()[0] != base::FilePath::kSeparators[0] || (restore_path.value().size() == 1 && - restore_path.value()[0] == base::FilePath::kSeparators[0])) { + restore_path.value()[0] == base::FilePath::kSeparators[0]) || + restore_path.ReferencesParent()) { RunCallbackWithError(ValidationError::kInfoFileInvalid, std::move(callback)); return; diff -Nru chromium-147.0.7727.116/chrome/browser/ash/file_system_provider/operations/get_metadata.cc chromium-147.0.7727.137/chrome/browser/ash/file_system_provider/operations/get_metadata.cc --- chromium-147.0.7727.116/chrome/browser/ash/file_system_provider/operations/get_metadata.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ash/file_system_provider/operations/get_metadata.cc 2026-04-27 20:03:22.000000000 +0000 @@ -110,9 +110,13 @@ return false; } - if (fields & ProvidedFileSystemInterface::METADATA_FIELD_SIZE && - !metadata.size) { - return false; + if (fields & ProvidedFileSystemInterface::METADATA_FIELD_SIZE) { + if (!metadata.size) { + return false; + } + if (!base::IsValueInRangeForNumericType(*metadata.size)) { + return false; + } } if (fields & ProvidedFileSystemInterface::METADATA_FIELD_MODIFICATION_TIME) { diff -Nru chromium-147.0.7727.116/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc chromium-147.0.7727.137/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc --- chromium-147.0.7727.116/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc 2026-04-27 20:03:22.000000000 +0000 @@ -263,7 +263,7 @@ } VpnService::VpnConfiguration* configuration = - CreateConfigurationInternal(extension_id, configuration_name); + GetOrCreateConfigurationInternal(extension_id, configuration_name); auto properties = base::DictValue() @@ -333,7 +333,7 @@ } VpnService::VpnConfiguration* configuration = - CreateConfigurationInternal(*extension_id, *configuration_name); + GetOrCreateConfigurationInternal(*extension_id, *configuration_name); RegisterConfiguration(configuration, service_path); } @@ -504,9 +504,14 @@ } } -VpnService::VpnConfiguration* VpnService::CreateConfigurationInternal( +VpnService::VpnConfiguration* VpnService::GetOrCreateConfigurationInternal( const std::string& extension_id, const std::string& configuration_name) { + if (auto* configuration = + LookupConfiguration(extension_id, configuration_name)) { + return configuration; + } + const std::string key = GetKey(extension_id, configuration_name); auto configuration = std::make_unique( extension_id, configuration_name, key, this); @@ -527,6 +532,12 @@ void VpnService::RegisterConfiguration( VpnService::VpnConfiguration* configuration, const std::string& service_path) { + if (VpnConfiguration* existing_configuration = + LookupConfiguration(service_path)) { + CHECK_EQ(existing_configuration, configuration); + return; + } + configuration->set_service_path(service_path); auto [_, inserted] = service_path_to_configuration_map_.emplace(service_path, configuration); diff -Nru chromium-147.0.7727.116/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.h chromium-147.0.7727.137/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.h --- chromium-147.0.7727.116/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.h 2026-04-27 20:03:22.000000000 +0000 @@ -121,7 +121,7 @@ void SendOnConfigRemovedToExtension(const std::string& extension_id, const std::string& configuration_name); - VpnConfiguration* CreateConfigurationInternal( + VpnConfiguration* GetOrCreateConfigurationInternal( const std::string& extension_id, const std::string& configuration_name); diff -Nru chromium-147.0.7727.116/chrome/browser/contextual_cueing/contextual_cueing_enums.h chromium-147.0.7727.137/chrome/browser/contextual_cueing/contextual_cueing_enums.h --- chromium-147.0.7727.116/chrome/browser/contextual_cueing/contextual_cueing_enums.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/contextual_cueing/contextual_cueing_enums.h 2026-04-27 20:03:22.000000000 +0000 @@ -59,6 +59,17 @@ }; // LINT.ThenChange(/tools/metrics/histograms/metadata/contextual_cueing/enums.xml:NudgeDecision) +// LINT.IfChange(GlicAutoOpenResult) +enum class GlicAutoOpenResult { + kSuccess = 0, + kPreventedFromSplitView = 1, + kPreventedFromVerticalTabs = 2, + kPreventedFromExistingSidePanelOpen = 3, + kFailedUnknown = 4, + kMaxValue = kFailedUnknown, +}; +// LINT.ThenChange(/tools/metrics/histograms/metadata/contextual_cueing/enums.xml:GlicAutoOpenResult) + // LINT.IfChange(NudgeInteraction) enum class NudgeInteraction { kUnknown = 0, diff -Nru chromium-147.0.7727.116/chrome/browser/contextual_cueing/contextual_cueing_helper.cc chromium-147.0.7727.137/chrome/browser/contextual_cueing/contextual_cueing_helper.cc --- chromium-147.0.7727.116/chrome/browser/contextual_cueing/contextual_cueing_helper.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/contextual_cueing/contextual_cueing_helper.cc 2026-04-27 20:03:22.000000000 +0000 @@ -28,6 +28,7 @@ #include "chrome/browser/ui/tabs/glic_nudge_controller.h" #include "chrome/browser/ui/tabs/public/tab_features.h" #include "chrome/browser/ui/user_education/browser_user_education_interface.h" +#include "chrome/common/pref_names.h" #include "components/feature_engagement/public/feature_constants.h" #include "components/history/core/browser/features.h" #include "components/optimization_guide/core/hints/hints_processing_util.h" @@ -54,6 +55,8 @@ #if !BUILDFLAG(IS_ANDROID) #include "chrome/browser/glic/public/glic_side_panel_coordinator.h" +#include "chrome/browser/ui/side_panel/side_panel_enums.h" +#include "chrome/browser/ui/side_panel/side_panel_ui.h" #endif namespace contextual_cueing { @@ -394,12 +397,52 @@ return; } - const bool should_open_side_panel = - decision_result->auto_open_eligible && - base::FeatureList::IsEnabled(kEnableAutoOpenGlicSidePanel); + auto* tab_interface = tabs::TabInterface::GetFromContents(web_contents()); + + bool existing_side_panel_open = false; +#if !BUILDFLAG(IS_ANDROID) + auto* bwi = + tab_interface ? tab_interface->GetBrowserWindowInterface() : nullptr; + auto* side_panel_ui = bwi ? bwi->GetFeatures().side_panel_ui() : nullptr; + existing_side_panel_open = + side_panel_ui && + (side_panel_ui->IsSidePanelShowing(SidePanelEntry::PanelType::kContent) || + side_panel_ui->IsSidePanelShowing(SidePanelEntry::PanelType::kToolbar)); +#endif + + bool is_split = tab_interface && tab_interface->IsSplit(); Profile* profile = Profile::FromBrowserContext(web_contents()->GetBrowserContext()); + bool vertical_tabs_enabled = false; +#if !BUILDFLAG(IS_ANDROID) + vertical_tabs_enabled = + profile->GetPrefs()->GetBoolean(::prefs::kVerticalTabsEnabled); +#endif + + bool should_open_side_panel = + decision_result->auto_open_eligible && + base::FeatureList::IsEnabled(kEnableAutoOpenGlicSidePanel); + + if (should_open_side_panel) { + std::optional prevented_reason; + + if (existing_side_panel_open) { + prevented_reason = + GlicAutoOpenResult::kPreventedFromExistingSidePanelOpen; + } else if (is_split) { + prevented_reason = GlicAutoOpenResult::kPreventedFromSplitView; + } else if (vertical_tabs_enabled) { + prevented_reason = GlicAutoOpenResult::kPreventedFromVerticalTabs; + } + + if (prevented_reason) { + base::UmaHistogramEnumeration("ContextualCueing.GlicAutoOpen.Result", + *prevented_reason); + should_open_side_panel = false; + } + } + const bool is_auto_open_pdf_side_panel_cue = should_open_side_panel && web_contents()->GetContentsMimeType() == pdf::kPDFMimeType && @@ -422,7 +465,6 @@ // Handle side panel auto-open case: bypass nudge and open panel directly. // If auto-open fails or is disabled, falls through to standard nudge. if (should_open_side_panel) { - auto* tab_interface = tabs::TabInterface::GetFromContents(web_contents()); auto* glic_service = glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile); if (glic_service && tab_interface) { @@ -438,9 +480,13 @@ options.prompts.push_back(decision_result->prompt_suggestion); } glic_service->Invoke(tab_interface, std::move(options)); + base::UmaHistogramEnumeration("ContextualCueing.GlicAutoOpen.Result", + GlicAutoOpenResult::kSuccess); return; } // Fall through to nudge if side panel open fails. + base::UmaHistogramEnumeration("ContextualCueing.GlicAutoOpen.Result", + GlicAutoOpenResult::kFailedUnknown); } GetGlicNudgeController()->UpdateNudgeLabel( diff -Nru chromium-147.0.7727.116/chrome/browser/flags/android/chrome_feature_list.cc chromium-147.0.7727.137/chrome/browser/flags/android/chrome_feature_list.cc --- chromium-147.0.7727.116/chrome/browser/flags/android/chrome_feature_list.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/flags/android/chrome_feature_list.cc 2026-04-27 20:03:22.000000000 +0000 @@ -653,7 +653,7 @@ BASE_FEATURE(kClankWhatsNew, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kClearIntentWhenRecreated, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kCommandLineOnNonRooted, base::FEATURE_DISABLED_BY_DEFAULT); -BASE_FEATURE(kCompositorViewHolderObscuring, base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kCompositorViewHolderObscuring, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kCompositorViewRemeasureFix, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kContextMenuTranslateWithGoogleLens, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kContextualSearchDisableOnlineDetection, base::FEATURE_DISABLED_BY_DEFAULT); diff -Nru chromium-147.0.7727.116/chrome/browser/glic/glic_context_menu_browsertest.cc chromium-147.0.7727.137/chrome/browser/glic/glic_context_menu_browsertest.cc --- chromium-147.0.7727.116/chrome/browser/glic/glic_context_menu_browsertest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/glic/glic_context_menu_browsertest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -57,6 +57,22 @@ EXPECT_TRUE(menu->IsItemEnabled(IDC_CONTENT_CONTEXT_GLIC)); } +IN_PROC_BROWSER_TEST_F(GlicContextMenuBrowserTest, GlicItemPresentForLink) { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetSimpleTestUrl())); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + content::ContextMenuParams params; + params.page_url = web_contents->GetVisibleURL(); + params.link_url = GURL("https://example.com"); + + auto menu = std::make_unique( + *web_contents->GetPrimaryMainFrame(), params); + menu->Init(); + + EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_GLIC)); +} + IN_PROC_BROWSER_TEST_F(GlicContextMenuBrowserTest, GlicInvokeStandard) { ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetSimpleTestUrl())); auto menu = CreateContextMenu(); @@ -107,6 +123,34 @@ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetSimpleTestUrl())); auto menu = CreateContextMenu(); + // Initially no Glic instance. + EXPECT_EQ(nullptr, GetOnlyGlicInstance()); + + menu->ExecuteCommand(IDC_CONTENT_CONTEXT_GLIC, 0); + + // Now Glic should be open. + ASSERT_TRUE(WaitForGlicOpen()); + EXPECT_NE(nullptr, GetOnlyGlicInstance()); +} + +class GlicContextMenuArm3BrowserTest : public GlicContextMenuBrowserTestBase { + public: + GlicContextMenuArm3BrowserTest() { + feature_list_.InitWithFeaturesAndParameters( + {{features::kGlic, {}}, + {features::kGlicContextMenu, + {{features::kGlicContextMenuArm.name, "arm3"}}}}, + {}); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(GlicContextMenuArm3BrowserTest, GlicInvokeArm3) { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetSimpleTestUrl())); + auto menu = CreateContextMenu(); + // Initially no Glic instance. EXPECT_EQ(nullptr, GetOnlyGlicInstance()); diff -Nru chromium-147.0.7727.116/chrome/browser/glic/glic_metrics_browsertest.cc chromium-147.0.7727.137/chrome/browser/glic/glic_metrics_browsertest.cc --- chromium-147.0.7727.116/chrome/browser/glic/glic_metrics_browsertest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/glic/glic_metrics_browsertest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -55,6 +55,30 @@ std::unique_ptr glic_test_environment_; }; +class GlicMetricsBrowserTestWithMessageFirstFre + : public GlicMetricsBrowserTest { + public: + GlicMetricsBrowserTestWithMessageFirstFre() + : GlicMetricsBrowserTest({features::kGlicMessageFirstFre}, {}) {} +}; + +IN_PROC_BROWSER_TEST_F(GlicMetricsBrowserTestWithMessageFirstFre, + GlicFreShown_MessageFirstFreEnabled) { + base::UserActionTester user_action_tester; + base::HistogramTester histogram_tester; + + SetFRECompletion(browser()->profile(), prefs::FreStatus::kNotStarted); + + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile()) + ->ToggleUI(browser(), /*prevent_close=*/false, + mojom::InvocationSource::kOsButton); + + EXPECT_EQ(user_action_tester.GetActionCount("Glic.Fre.Shown"), 1); + + histogram_tester.ExpectUniqueSample("Glic.Fre.Shown.Entrypoint", + mojom::InvocationSource::kOsButton, 1); +} + IN_PROC_BROWSER_TEST_F(GlicMetricsBrowserTest, GlicFreShown_MultiInstance) { ASSERT_TRUE(GlicEnabling::IsMultiInstanceEnabled()); diff -Nru chromium-147.0.7727.116/chrome/browser/glic/service/glic_instance_impl.cc chromium-147.0.7727.137/chrome/browser/glic/service/glic_instance_impl.cc --- chromium-147.0.7727.116/chrome/browser/glic/service/glic_instance_impl.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/glic/service/glic_instance_impl.cc 2026-04-27 20:03:22.000000000 +0000 @@ -333,7 +333,7 @@ // Look up the current embedder for that tab/key. auto* embedder = GetEmbedderForKey(new_key); - bool should_log_open = + const bool new_embedder_will_show = !embedder || !embedder->IsShowing(); GlicUiEmbedder* embedder_to_show = nullptr; @@ -358,8 +358,10 @@ MaybeShowHostUi(embedder_to_show, options.invocation_source, options.prompt_suggestion, options.auto_send, options.fre_override); - if (should_log_open) { + if (new_embedder_will_show) { instance_metrics()->OnOpen(options.invocation_source, options); + service_->metrics()->OnGlicWindowStartedOpening(/*attached=*/false, + options.invocation_source); } embedder_to_show->Show(options); if (options.focus_on_show) { @@ -429,8 +431,6 @@ return false; } - service_->metrics()->OnGlicWindowStartedOpening(/*attached=*/false, source); - // We assume that a toggle is user initiated so focus on show. options.focus_on_show = true; options.prompt_suggestion = prompt_suggestion; diff -Nru chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager.cc chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager.cc --- chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -561,6 +561,7 @@ // Use insert_or_assign to avoid Use-After-Free on route ID collision. // See crbug.com/500091052. activities_.insert_or_assign(route.media_route_id(), std::move(activity)); + app_activities_.erase(route.media_route_id()); return activity_ptr; } @@ -1183,12 +1184,6 @@ } } -void CastActivityManager::AddMirroringActivityForTest( - const MediaRoute::Id& route_id, - std::unique_ptr mirroring_activity) { - activities_.emplace(route_id, std::move(mirroring_activity)); -} - void CastActivityManager::HandleMissingSinkOnJoin( mojom::MediaRouteProvider::JoinRouteCallback callback, const std::string& sink_id, diff -Nru chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager.h chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager.h --- chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager.h 2026-04-27 20:03:22.000000000 +0000 @@ -143,15 +143,17 @@ MirroringActivity* FindMirroringActivityByRouteId( const std::string& route_id); - void AddMirroringActivityForTest( - const MediaRoute::Id& route_id, - std::unique_ptr mirroring_activity); + using ActivityMap = + base::flat_map>; + using AppActivityMap = base::flat_map; + + const ActivityMap& activities_for_testing() const { return activities_; } + const AppActivityMap& app_activities_for_testing() const { + return app_activities_; + } private: friend class CastActivityManagerTest; - FRIEND_TEST_ALL_PREFIXES(CastActivityManagerTest, AddAppActivityCollision); - FRIEND_TEST_ALL_PREFIXES(CastActivityManagerTest, - AddMirroringActivityCollision); FRIEND_TEST_ALL_PREFIXES(CastActivityManagerWithTerminatingTest, LaunchSessionTerminatesExistingSessionOnSink); FRIEND_TEST_ALL_PREFIXES(CastActivityManagerTest, @@ -162,10 +164,6 @@ FRIEND_TEST_ALL_PREFIXES(CastActivityManagerTest, StartSessionAndRemoveExistingSessionOnSink); - using ActivityMap = - base::flat_map>; - using AppActivityMap = base::flat_map; - void SendRouteJsonMessage(const std::string& media_route_id, const std::string& message, data_decoder::DataDecoder::ValueOrError result); diff -Nru chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc --- chromium-147.0.7727.116/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -181,6 +181,20 @@ CastActivityManager::SetActitityFactoryForTest(nullptr); } + AppActivity* AddAppActivity(const MediaRoute& route, + const std::string& app_id) { + return manager_->AddAppActivity(route, app_id); + } + + CastActivity* AddMirroringActivity( + const MediaRoute& route, + const std::string& app_id, + content::FrameTreeNodeId frame_tree_node_id, + const CastSinkExtraData& cast_data) { + return manager_->AddMirroringActivity(route, app_id, frame_tree_node_id, + cast_data); + } + // from CastActivityFactoryForTest std::unique_ptr MakeAppActivity( const MediaRoute& route, @@ -992,20 +1006,21 @@ true); // First call to AddAppActivity should succeed. - AppActivity* activity1 = manager_->AddAppActivity(route, kAppId1); + AppActivity* activity1 = AddAppActivity(route, kAppId1); ASSERT_TRUE(activity1); EXPECT_EQ(1u, manager_->GetRoutes().size()); // Second call with same route_id should overwrite the existing activity // instead of creating a dangling pointer. - AppActivity* activity2 = manager_->AddAppActivity(route, kAppId1); + AppActivity* activity2 = AddAppActivity(route, kAppId1); ASSERT_TRUE(activity2); EXPECT_NE(activity1, activity2); EXPECT_EQ(1u, manager_->GetRoutes().size()); // Verify that the pointer in app_activities_ has been updated to the new // activity. - EXPECT_EQ(activity2, manager_->app_activities_[route.media_route_id()]); + EXPECT_EQ(activity2, + manager_->app_activities_for_testing().at(route.media_route_id())); } // Regression test for crbug.com/500091052. @@ -1016,20 +1031,46 @@ route.set_controller_type(RouteControllerType::kMirroring); // First call to AddMirroringActivity should succeed. - CastActivity* activity1 = manager_->AddMirroringActivity( + CastActivity* activity1 = AddMirroringActivity( route, cast_streaming_app_id_, kFrameTreeNodeId, sink_.cast_data()); ASSERT_TRUE(activity1); - EXPECT_EQ(1u, manager_->activities_.size()); + EXPECT_EQ(1u, manager_->activities_for_testing().size()); // Second call with same route_id should overwrite the existing activity. - CastActivity* activity2 = manager_->AddMirroringActivity( + CastActivity* activity2 = AddMirroringActivity( route, cast_streaming_app_id_, kFrameTreeNodeId, sink_.cast_data()); ASSERT_TRUE(activity2); EXPECT_NE(activity1, activity2); - EXPECT_EQ(1u, manager_->activities_.size()); + EXPECT_EQ(1u, manager_->activities_for_testing().size()); // Verify that activities_ map has been updated to the new activity. - EXPECT_EQ(activity2, manager_->activities_[route.media_route_id()].get()); + EXPECT_EQ( + activity2, + manager_->activities_for_testing().at(route.media_route_id()).get()); +} + +TEST_F(CastActivityManagerTest, AddMirroringActivityOverwritesAppActivity) { + MediaSource source = MediaSource::ForTab(123); + MediaRoute route(kPresentationId, source, sink_.sink().id(), "description", + true); + route.set_controller_type(RouteControllerType::kGeneric); + + AppActivity* activity1 = AddAppActivity(route, kAppId1); + ASSERT_TRUE(activity1); + EXPECT_EQ(1u, manager_->activities_for_testing().size()); + EXPECT_EQ(activity1, + manager_->app_activities_for_testing().at(route.media_route_id())); + + // Adding a mirroring activity with the same route ID should overwrite the app + // activity and remove it from app_activities_. + CastActivity* activity2 = AddMirroringActivity( + route, cast_streaming_app_id_, kFrameTreeNodeId, sink_.cast_data()); + ASSERT_TRUE(activity2); + EXPECT_NE(activity1, activity2); + EXPECT_EQ(1u, manager_->activities_for_testing().size()); + EXPECT_TRUE( + manager_->app_activities_for_testing().find(route.media_route_id()) == + manager_->app_activities_for_testing().end()); } } // namespace media_router diff -Nru chromium-147.0.7727.116/chrome/browser/renderer_context_menu/render_view_context_menu.cc chromium-147.0.7727.137/chrome/browser/renderer_context_menu/render_view_context_menu.cc --- chromium-147.0.7727.116/chrome/browser/renderer_context_menu/render_view_context_menu.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/renderer_context_menu/render_view_context_menu.cc 2026-04-27 20:03:22.000000000 +0000 @@ -398,6 +398,9 @@ if (!params.selection_text.empty() && !params.link_url.is_empty()) { return "TextSelectionWithLink"; } + if (!params.link_url.is_empty()) { + return "Link"; + } return "TextSelection"; } @@ -1222,7 +1225,7 @@ AppendSearchProvider(); } - if (!params_.selection_text.empty()) { + if (!params_.selection_text.empty() || !params_.link_url.is_empty()) { MaybeAppendOpenGlicItem(); } @@ -4455,6 +4458,11 @@ glic::mojom::InvocationSource::kWebContentsContextMenu); options.fre_override = glic::mojom::FreOverride::kTrustFirstInline; std::string arm = features::kGlicContextMenuArm.Get(); + if (arm == "arm3") { + options.fre_override = glic::mojom::FreOverride::kTrustFirstClick; + } else { + options.fre_override = glic::mojom::FreOverride::kTrustFirstInline; + } if (arm == "arm2") { options.prompts.push_back( l10n_util::GetStringUTF8(IDS_GLIC_SUMMARIZE_PAGE_PROMPT)); diff -Nru chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_ar.xtb chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_ar.xtb --- chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_ar.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_ar.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1,7 +1,7 @@ -يتيح استخدام قفل الشاشة للاطّلاع على نوافذ التصفّح المتخفي المفتوحة +استخدام قفل الشاشة لتفقّد نوافذ التصفّح المتخفي المفتوحة ما مِن كلمات مرور محفوظة على هذا الجهاز النقل إلى هنا تم فتح الإعدادات المفضّلة لـ "وضع القراءة" بطول الشاشة diff -Nru chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_de.xtb chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_de.xtb --- chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_de.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_de.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1,7 +1,7 @@ -Displaysperre verwenden, um geöffnete Inkognitofenster zu sehen +Zum Anzeigen geöffneter Inkognitofenster Displaysperre zum Entsperren verwenden Keine gespeicherten Passwörter auf diesem Gerät Hierhin verschieben Einstellungen für den Lesemodus sind vollständig geöffnet @@ -1281,7 +1281,7 @@ Web-Apps (stumm) Löschen und fortfahren Android-Version nicht unterstützt -Inkognitofenster beim Verlassen von Chrome sperren +Beim Verlassen von Chrome Inkognitofenster sperren Verknüpfung bearbeiten Link zum markierten Text kann nicht erstellt werden Über teilen diff -Nru chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_es.xtb chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_es.xtb --- chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_es.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_es.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -705,7 +705,7 @@ Personalizar y controlar Google Chrome con un elemento destacado {NUM_PASSWORDS,plural, =1{1 contraseña vulnerada en tu cuenta de Google}other{# contraseñas vulneradas en tu cuenta de Google}} No se puede seguir. Se ha producido un error. -Inicia sesión en Chrome para acceder a tus marcadores y otros en todos tus dispositivos +Inicia sesión en Chrome para acceder a tus marcadores y otros elementos en todos tus dispositivos Tu padre o madre gestiona tu navegador Carpeta {HOURS,plural, =1{# h}other{# h}} diff -Nru chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_th.xtb chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_th.xtb --- chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_th.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_th.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1365,7 +1365,7 @@ ปรับปรุงความปลอดภัยให้คุณและทุกคนที่ใช้อินเทอร์เน็ต หากต้องการส่งแท็บนี้ไปยังอุปกรณ์อื่น ให้ลงชื่อเข้าใช้ Chrome ในอุปกรณ์ทั้ง 2 เครื่อง แก้ไขโฟลเดอร์ -ปลดล็อกเซสชันที่ไม่ระบุตัวตน +ปลดล็อกแท็บที่ไม่ระบุตัวตน สำรวจดูการตั้งค่าเพิ่มเติมด้านล่างหรือสิ้นสุดการตรวจสอบเลย Chrome จะส่งข้อมูลการใช้งานและข้อขัดข้องไปยัง Google เพื่อช่วยปรับปรุงแอป จัดการ นำออก diff -Nru chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb --- chromium-147.0.7727.116/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1416,7 +1416,7 @@ Để thiết lập dịch vụ tự động điền mà bạn ưu tiên, trước tiên, hãy thêm dịch vụ đó trong Cài đặt Android . Cửa sổ này sẽ đóng Xem xét thông báo -Mở trong cửa sổ Ẩn danh +Mở trong cửa sổ ẩn danh Trang hiện đã sẵn sàng cho bạn xem {FILE_COUNT,plural, =1{Hình ảnh, 1 hình ảnh trong danh sách}other{Hình ảnh, # hình ảnh trong danh sách}} Đã mở trang Đăng nhập vào Chrome. diff -Nru chromium-147.0.7727.116/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc chromium-147.0.7727.137/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc --- chromium-147.0.7727.116/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -33,11 +33,9 @@ // Aliases. using ::ash::HelpBubbleContext; using ::ash::HelpBubbleViewAsh; -using ::ash::HelpBubbleViewsAsh; using ::ash::kHelpBubbleContextKey; using ::user_education::HelpBubbleParams; using ::user_education::HelpBubbleView; -using ::user_education::HelpBubbleViews; // Helpers --------------------------------------------------------------------- @@ -111,6 +109,5 @@ // The help `bubble` should be Ash-specific depending on `context`. bool is_ash_context = context == HelpBubbleContext::kAsh; - ASSERT_EQ(bubble->IsA(), is_ash_context); - ASSERT_NE(bubble->IsA(), is_ash_context); + ASSERT_EQ(bubble->IsA(), is_ash_context); } diff -Nru chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc --- chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc 2026-04-27 20:03:22.000000000 +0000 @@ -160,7 +160,8 @@ Profile* profile, BookmarkLaunchLocation opened_from, const std::vector>& - selection) + selection, + bool can_paste) : parent_window_(parent_window), delegate_(delegate), browser_(browser), @@ -169,7 +170,8 @@ selection_(selection), bookmark_service_( BookmarkMergedSurfaceServiceFactory::GetForProfile(profile)), - new_nodes_parent_(GetParentForNewNodes(selection)) { + new_nodes_parent_(GetParentForNewNodes(selection)), + can_paste_(can_paste) { DCHECK(profile_); DCHECK(bookmark_service_->loaded()); CheckSelectionIsValid(selection); @@ -594,20 +596,6 @@ return prefs->GetBoolean(bookmarks::prefs::kShowAppsShortcutInBookmarkBar); } -void BookmarkContextMenuController::UpdateCanPaste(base::OnceClosure callback) { - BookmarkUIOperationsHelperMergedSurfaces(bookmark_service_, - new_nodes_parent_.get()) - .CanPasteFromClipboard(base::BindOnce( - [](base::WeakPtr self, - base::OnceClosure callback, bool can_paste) { - if (self) { - self->can_paste_ = can_paste; - } - std::move(callback).Run(); - }, - weak_factory_.GetWeakPtr(), std::move(callback))); -} - bool BookmarkContextMenuController::IsCommandIdEnabled(int command_id) const { PrefService* prefs = profile_->GetPrefs(); diff -Nru chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h --- chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h 2026-04-27 20:03:22.000000000 +0000 @@ -63,7 +63,8 @@ Profile* profile, BookmarkLaunchLocation opened_from, const std::vector>& selection); + VectorExperimental>>& selection, + bool can_paste); BookmarkContextMenuController(const BookmarkContextMenuController&) = delete; BookmarkContextMenuController& operator=( @@ -89,9 +90,6 @@ bool IsItemForCommandIdDynamic(int command_id) const override; std::u16string GetLabelForCommandId(int command_id) const override; - // Updates the `can_paste_` cache and runs `callback` when finished. - void UpdateCanPaste(base::OnceClosure callback); - // Public for testing. // Returns index at which the newly added nodes will be added. size_t GetIndexForNewNodes() const; @@ -142,8 +140,8 @@ const raw_ptr bookmark_service_; std::unique_ptr menu_model_; const std::unique_ptr new_nodes_parent_; - // Cached value for IDC_PASTE. - bool can_paste_ = false; + // Whether IDC_PASTE is enabled. + const bool can_paste_; // Used to detect deletion of |this| executing a command. base::WeakPtrFactory weak_factory_{this}; }; diff -Nru chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc --- chromium-147.0.7727.116/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -160,7 +160,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); GURL url = model_->bookmark_bar_node()->children().front()->url(); ASSERT_TRUE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_REMOVE)); // Delete the URL. @@ -177,7 +177,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_TRUE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -199,7 +199,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_TRUE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -218,7 +218,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_FALSE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -239,7 +239,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_FALSE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -259,7 +259,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_TRUE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -286,7 +286,7 @@ }; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, incognito, - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_INCOGNITO)); EXPECT_FALSE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO)); @@ -299,7 +299,7 @@ model_->other_node()}; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_EDIT)); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_REMOVE)); } @@ -311,7 +311,7 @@ model_->account_other_node(), model_->other_node()}; BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_EDIT)); EXPECT_FALSE(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_REMOVE)); } @@ -335,7 +335,7 @@ SCOPED_TRACE(NodesToString(nodes)); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); const bool has_urls = bookmarks::HasBookmarkURLs(nodes); EXPECT_EQ(controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_OPEN_ALL), has_urls); @@ -380,7 +380,7 @@ SCOPED_TRACE(NodesToString(nodes)); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_FALSE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_ADD_TO_BOOKMARKS_BAR)); EXPECT_TRUE(controller.IsCommandIdEnabled( @@ -399,7 +399,7 @@ BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_TRUE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_ADD_TO_BOOKMARKS_BAR)); EXPECT_FALSE(controller.IsCommandIdEnabled( @@ -412,9 +412,9 @@ model_->bookmark_bar_node()->children()[0].get(), }; std::unique_ptr controller( - new BookmarkContextMenuController(gfx::NativeWindow(), nullptr, nullptr, - profile_.get(), - BookmarkLaunchLocation::kNone, nodes)); + new BookmarkContextMenuController( + gfx::NativeWindow(), nullptr, nullptr, profile_.get(), + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false)); EXPECT_TRUE(controller->IsCommandIdEnabled(IDC_COPY)); EXPECT_TRUE(controller->IsCommandIdEnabled(IDC_CUT)); @@ -423,7 +423,7 @@ controller = base::WrapUnique(new BookmarkContextMenuController( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes)); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/true)); size_t old_count = bb_node->children().size(); controller->ExecuteCommand(IDC_PASTE, 0); @@ -433,7 +433,7 @@ controller = base::WrapUnique(new BookmarkContextMenuController( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes)); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false)); // Cut the URL. controller->ExecuteCommand(IDC_CUT, 0); ASSERT_TRUE(bb_node->children()[0]->is_url()); @@ -445,7 +445,8 @@ ManagedShowAppsShortcutInBookmarksBar) { BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {model_->other_node()}); + BookmarkLaunchLocation::kNone, {model_->other_node()}, + /*can_paste=*/false); // By default, the pref is not managed and the command is enabled. sync_preferences::TestingPrefServiceSyncable* prefs = @@ -471,7 +472,8 @@ TEST_F(BookmarkContextMenuControllerTest, ShowTabGroupsPref) { BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {model_->bookmark_bar_node()}); + BookmarkLaunchLocation::kNone, {model_->bookmark_bar_node()}, + /*can_paste=*/false); EXPECT_TRUE( controller.IsCommandIdEnabled(IDC_BOOKMARK_BAR_TOGGLE_SHOW_TAB_GROUPS)); @@ -504,7 +506,7 @@ BookmarkContextMenuController controller( /*parent_window=*/gfx::NativeWindow(), /*delegate=*/nullptr, /*browser=*/nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - {model_->bookmark_bar_node()}); + {model_->bookmark_bar_node()}, /*can_paste=*/false); ASSERT_TRUE(controller.menu_model()); EXPECT_FALSE( @@ -533,7 +535,7 @@ EXPECT_EQ(*parent.get(), BookmarkParentFolder::BookmarkBarFolder()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); // New nodes added just after page. EXPECT_EQ(controller.GetIndexForNewNodes(), page_index + 1u); } @@ -563,7 +565,7 @@ EXPECT_EQ(*parent.get(), BookmarkParentFolder::OtherFolder()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); // New nodes added just after page. EXPECT_EQ(controller.GetIndexForNewNodes(), other_folder_children_count); } @@ -581,7 +583,7 @@ EXPECT_EQ(*parent.get(), BookmarkParentFolder::FromFolderNode(folder_node)); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes); + BookmarkLaunchLocation::kNone, nodes, /*can_paste=*/false); EXPECT_EQ(controller.GetIndexForNewNodes(), 0u); } @@ -598,7 +600,8 @@ // to focus. BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {child1_node, child2_node}); + BookmarkLaunchLocation::kNone, {child1_node, child2_node}, + /*can_paste=*/false); EXPECT_EQ(nullptr, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -611,7 +614,7 @@ ASSERT_FALSE(model_->account_bookmark_bar_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {l_bb_node}); + BookmarkLaunchLocation::kNone, {l_bb_node}, /*can_paste=*/false); EXPECT_EQ(l_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -623,7 +626,8 @@ { BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {a_bb_node, l_bb_node}); + BookmarkLaunchLocation::kNone, {a_bb_node, l_bb_node}, + /*can_paste=*/false); EXPECT_EQ(a_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -631,7 +635,7 @@ { BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {a_bb_node}); + BookmarkLaunchLocation::kNone, {a_bb_node}, /*can_paste=*/false); EXPECT_EQ(a_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -641,7 +645,7 @@ ASSERT_TRUE(a_bb_node); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {l_bb_node}); + BookmarkLaunchLocation::kNone, {l_bb_node}, /*can_paste=*/false); EXPECT_EQ(a_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } } @@ -664,7 +668,7 @@ ASSERT_TRUE(local_url->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {local_url}); + BookmarkLaunchLocation::kNone, {local_url}, /*can_paste=*/false); EXPECT_EQ(l_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -676,7 +680,7 @@ ASSERT_TRUE(account_url->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {account_url}); + BookmarkLaunchLocation::kNone, {account_url}, /*can_paste=*/false); EXPECT_EQ(a_bb_node, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -687,7 +691,7 @@ ASSERT_TRUE(local_folder->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {local_folder}); + BookmarkLaunchLocation::kNone, {local_folder}, /*can_paste=*/false); EXPECT_EQ(local_folder, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -698,7 +702,7 @@ ASSERT_TRUE(account_folder->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {account_folder}); + BookmarkLaunchLocation::kNone, {account_folder}, /*can_paste=*/false); EXPECT_EQ(account_folder, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -719,7 +723,7 @@ ASSERT_FALSE(f1->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {f1}); + BookmarkLaunchLocation::kNone, {f1}, /*can_paste=*/false); EXPECT_EQ(F1, controller.ComputeNodeToFocusForBookmarkManager()); } @@ -730,7 +734,7 @@ ASSERT_FALSE(F11->parent()->is_permanent_node()); BookmarkContextMenuController controller( gfx::NativeWindow(), nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, {F11}); + BookmarkLaunchLocation::kNone, {F11}, /*can_paste=*/false); EXPECT_EQ(F11, controller.ComputeNodeToFocusForBookmarkManager()); } } diff -Nru chromium-147.0.7727.116/chrome/browser/ui/user_education/show_promo_in_page.cc chromium-147.0.7727.137/chrome/browser/ui/user_education/show_promo_in_page.cc --- chromium-147.0.7727.116/chrome/browser/ui/user_education/show_promo_in_page.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/user_education/show_promo_in_page.cc 2026-04-27 20:03:22.000000000 +0000 @@ -132,15 +132,12 @@ delete this; return; } - help_bubble_closed_subscription_ = help_bubble_->AddOnCloseCallback( + help_bubble_closed_subscription_ = help_bubble_->AddOnClosedCallback( base::BindOnce(&ShowPromoInPageImpl::OnBubbleClosed, GetWeakPtr())); std::move(callback_).Run(this, true); } - void OnBubbleClosed(user_education::HelpBubble*, - user_education::HelpBubble::CloseReason) { - delete this; - } + void OnBubbleClosed(user_education::HelpBubble::CloseReason) { delete this; } void OnTimeout() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); diff -Nru chromium-147.0.7727.116/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc chromium-147.0.7727.137/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc --- chromium-147.0.7727.116/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -98,7 +98,8 @@ ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open()); // Closing the help bubble should destroy the object. - handle->GetHelpBubbleForTesting()->Close(); + handle->GetHelpBubbleForTesting()->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); ASSERT_FALSE(handle); } @@ -143,7 +144,8 @@ ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open()); // Closing the help bubble should destroy the object. - handle->GetHelpBubbleForTesting()->Close(); + handle->GetHelpBubbleForTesting()->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); ASSERT_FALSE(handle); } @@ -180,7 +182,8 @@ ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open()); // Closing the help bubble should destroy the object. - handle->GetHelpBubbleForTesting()->Close(); + handle->GetHelpBubbleForTesting()->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); ASSERT_FALSE(handle); } @@ -217,7 +220,8 @@ ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open()); // Closing the help bubble should destroy the object. - handle->GetHelpBubbleForTesting()->Close(); + handle->GetHelpBubbleForTesting()->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); ASSERT_FALSE(handle); } @@ -361,6 +365,7 @@ ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open()); // Closing the help bubble should destroy the object. - handle->GetHelpBubbleForTesting()->Close(); + handle->GetHelpBubbleForTesting()->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); ASSERT_FALSE(handle); } diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc 2026-04-27 20:03:22.000000000 +0000 @@ -47,6 +47,7 @@ #include "chrome/browser/search/search.h" #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h" #include "chrome/browser/themes/theme_properties.h" +#include "chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h" #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h" #include "chrome/browser/ui/bookmarks/bookmark_ui_operations_helper.h" @@ -1532,14 +1533,51 @@ context_menu_source = apps_page_shortcut_; } } + + std::vector node_ids; + node_ids.reserve(nodes.size()); + for (const auto* node : nodes) { + node_ids.push_back(node->id()); + } + auto parent_folder = BookmarkContextMenuController::GetParentForNewNodes( + ToRawPtrVector(nodes)); + BookmarkUIOperationsHelperMergedSurfaces(bookmark_service_, + parent_folder.get()) + .CanPasteFromClipboard(base::BindOnce( + &BookmarkBarView::RunContextMenuAt, weak_ptr_factory_.GetWeakPtr(), + std::move(node_ids), point, source_type, + context_menu_source ? context_menu_source->GetWeakPtr() : nullptr)); +} + +void BookmarkBarView::RunContextMenuAt( + std::vector node_ids, + const gfx::Point& point, + ui::mojom::MenuSourceType source_type, + base::WeakPtr context_menu_source, + bool can_paste) { // |close_on_remove| only matters for nested menus. We're not nested at this // point, so this value has no effect. const bool close_on_remove = true; + auto* bookmark_model = + BookmarkModelFactory::GetForBrowserContext(browser_->profile()); + std::vector nodes; + for (int64_t node_id : node_ids) { + const BookmarkNode* node = + bookmarks::GetBookmarkNodeByID(bookmark_model, node_id); + if (node) { + nodes.push_back(node); + } + } + if (nodes.empty()) { + return; + } + + context_menu_observation_.Reset(); context_menu_ = std::make_unique( GetWidget(), browser_, browser_->profile(), BookmarkLaunchLocation::kAttachedBar, ToRawPtrVector(nodes), - close_on_remove); + close_on_remove, can_paste); context_menu_observation_.Observe(context_menu_.get()); context_menu_->RunMenuAt(point, source_type); if (context_menu_source) { diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h 2026-04-27 20:03:22.000000000 +0000 @@ -242,6 +242,12 @@ const gfx::Point& point, ui::mojom::MenuSourceType source_type) override; + void RunContextMenuAt(std::vector node_ids, + const gfx::Point& point, + ui::mojom::MenuSourceType source_type, + base::WeakPtr context_menu_source, + bool can_paste); + // BookmarkContextMenuObserver: void WillRemoveBookmarks( const std::vectorShowContextMenuForViewImpl( view, point, ui::mojom::MenuSourceType::kMouse); - EXPECT_EQ(views::InkDropState::ACTIVATED, - views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState()); + if (views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState() != + views::InkDropState::ACTIVATED) { + EXPECT_TRUE(base::test::RunUntil([&]() { + return views::InkDrop::Get(view) + ->GetInkDrop() + ->GetTargetInkDropState() == views::InkDropState::ACTIVATED; + })); + } bookmark_bar()->OnContextMenuClosed(); #if BUILDFLAG(IS_MAC) diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu.cc 2026-04-27 20:03:22.000000000 +0000 @@ -51,7 +51,8 @@ BookmarkLaunchLocation opened_from, const std::vector>& selection, - bool close_on_remove) + bool close_on_remove, + bool can_paste) : controller_(new BookmarkContextMenuController( parent_widget ? parent_widget->GetNativeWindow() : gfx::NativeWindow(), @@ -59,7 +60,8 @@ browser, profile, opened_from, - selection)), + selection, + can_paste)), parent_widget_(parent_widget), menu_(new views::MenuItemView(this)), close_on_remove_(close_on_remove) { @@ -87,27 +89,14 @@ return; } - UpdateCanPaste(base::BindOnce( - [](base::WeakPtr self, const gfx::Point& point, - ui::mojom::MenuSourceType source_type) { - if (!self) { - return; - } - if (!PreRunCallback().is_null()) { - std::move(PreRunCallback()).Run(); - } - - // width/height don't matter here. - self->menu_runner_->RunMenuAt(self->parent_widget_, nullptr, - gfx::Rect(point.x(), point.y(), 0, 0), - views::MenuAnchorPosition::kTopLeft, - source_type); - }, - weak_factory_.GetWeakPtr(), point, source_type)); -} + if (!PreRunCallback().is_null()) { + std::move(PreRunCallback()).Run(); + } -void BookmarkContextMenu::UpdateCanPaste(base::OnceClosure callback) { - controller_->UpdateCanPaste(std::move(callback)); + // width/height don't matter here. + menu_runner_->RunMenuAt(parent_widget_, nullptr, + gfx::Rect(point.x(), point.y(), 0, 0), + views::MenuAnchorPosition::kTopLeft, source_type); } void BookmarkContextMenu::AddObserver(BookmarkContextMenuObserver* observer) { diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu.h 2026-04-27 20:03:22.000000000 +0000 @@ -48,7 +48,8 @@ BookmarkLaunchLocation opened_from, const std::vector>& selection, - bool close_on_remove); + bool close_on_remove, + bool can_paste); BookmarkContextMenu(const BookmarkContextMenu&) = delete; BookmarkContextMenu& operator=(const BookmarkContextMenu&) = delete; @@ -64,8 +65,6 @@ void RunMenuAt(const gfx::Point& point, ui::mojom::MenuSourceType source_type); - void UpdateCanPaste(base::OnceClosure callback); - views::MenuItemView* menu() const { return menu_; } void AddObserver(BookmarkContextMenuObserver* observer); @@ -103,8 +102,6 @@ // Should the menu close when a node is removed. bool close_on_remove_; - - base::WeakPtrFactory weak_factory_{this}; }; #endif // CHROME_BROWSER_UI_VIEWS_BOOKMARKS_BOOKMARK_CONTEXT_MENU_H_ diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -26,6 +26,7 @@ #include "chrome/browser/bookmarks/bookmark_test_helpers.h" #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h" #include "chrome/browser/ui/bookmarks/bookmark_utils_desktop.h" #include "chrome/browser/ui/bookmarks/test_bookmark_navigation_wrapper.h" #include "chrome/common/webui_url_constants.h" @@ -170,7 +171,8 @@ std::vector> nodes = { node_to_delete}; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); GURL url = node_to_delete->url(); ASSERT_TRUE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_REMOVE)); // Delete the URL. @@ -209,7 +211,8 @@ model_->bookmark_bar_node()->children().front().get(), }; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_TRUE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -229,7 +232,8 @@ model_->bookmark_bar_node()->children()[1]->children()[0].get(), }; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_TRUE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -246,7 +250,8 @@ model_->bookmark_bar_node()->children()[2].get(), }; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_FALSE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_FALSE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -266,7 +271,8 @@ model_->bookmark_bar_node()->children()[3].get(), }; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_FALSE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_FALSE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -285,7 +291,8 @@ model_->bookmark_bar_node()->children()[4].get(), }; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_TRUE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL)); EXPECT_TRUE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); @@ -303,7 +310,8 @@ Profile* incognito = profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true); BookmarkContextMenu controller(nullptr, nullptr, incognito, - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_FALSE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_INCOGNITO)); EXPECT_FALSE( controller.IsCommandEnabled(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO)); @@ -314,7 +322,8 @@ std::vector> nodes = { model_->other_node()}; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false); + BookmarkLaunchLocation::kNone, nodes, false, + false); EXPECT_FALSE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_EDIT)); EXPECT_FALSE(controller.IsCommandEnabled(IDC_BOOKMARK_BAR_REMOVE)); } @@ -338,7 +347,7 @@ { BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); + selected_nodes, false, false); ASSERT_TRUE(controller.IsCommandEnabled(IDC_COPY)); controller.ExecuteCommand(IDC_COPY, 0); @@ -351,11 +360,7 @@ { BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); - - base::RunLoop run_loop; - controller.UpdateCanPaste(run_loop.QuitClosure()); - run_loop.Run(); + selected_nodes, false, /*can_paste=*/true); ASSERT_TRUE(controller.IsCommandEnabled(IDC_PASTE)); controller.ExecuteCommand(IDC_PASTE, 0); @@ -370,7 +375,7 @@ { BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); + selected_nodes, false, false); ASSERT_TRUE(controller.IsCommandEnabled(IDC_CUT)); controller.ExecuteCommand(IDC_CUT, 0); @@ -402,7 +407,7 @@ selected_nodes = {bb_node->children()[0].get()}; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); + selected_nodes, false, false); ASSERT_TRUE(controller.IsCommandEnabled(IDC_COPY)); controller.ExecuteCommand(IDC_COPY, 0); @@ -418,13 +423,10 @@ const BookmarkNode* local_bookmark_to_copy = bb_node->children()[0].get(); std::vector> selected_nodes = {local_bookmark_to_copy}; + BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); - - base::RunLoop run_loop; - controller.UpdateCanPaste(run_loop.QuitClosure()); - run_loop.Run(); + selected_nodes, false, /*can_paste=*/true); ASSERT_TRUE(controller.IsCommandEnabled(IDC_PASTE)); controller.ExecuteCommand(IDC_PASTE, 0); @@ -447,7 +449,7 @@ selected_nodes = {account_bb_node->children()[0].get()}; BookmarkContextMenu controller(nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, - selected_nodes, false); + selected_nodes, false, false); ASSERT_TRUE(controller.IsCommandEnabled(IDC_CUT)); controller.ExecuteCommand(IDC_CUT, 0); @@ -466,9 +468,9 @@ std::vector> nodes = { bb_node->children().front().get(), }; - std::unique_ptr controller( - new BookmarkContextMenu(nullptr, nullptr, profile_.get(), - BookmarkLaunchLocation::kNone, nodes, false)); + std::unique_ptr controller(new BookmarkContextMenu( + nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, nodes, + false, false)); // Verify that there are no managed nodes yet. bookmarks::ManagedBookmarkService* managed = @@ -499,8 +501,9 @@ // New context menus now show the "Show managed bookmarks" option. controller = std::make_unique( - nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kNone, nodes, - false); + nullptr, nullptr, profile_.get(), BookmarkLaunchLocation::kAttachedBar, + nodes, false, false); + EXPECT_TRUE(controller->IsCommandVisible(IDC_BOOKMARK_BAR_NEW_FOLDER)); EXPECT_TRUE( controller->IsCommandVisible(IDC_BOOKMARK_BAR_SHOW_MANAGED_BOOKMARKS)); diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4,6 +4,7 @@ #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h" +#include #include #include @@ -21,11 +22,13 @@ #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/bookmarks/bookmark_merged_surface_service.h" #include "chrome/browser/bookmarks/bookmark_merged_surface_service_factory.h" +#include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/bookmarks/bookmark_parent_folder_children.h" #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h" #include "chrome/browser/favicon/favicon_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/themes/theme_properties.h" +#include "chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h" #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" #include "chrome/browser/ui/bookmarks/bookmark_stats.h" #include "chrome/browser/ui/bookmarks/bookmark_ui_operations_helper.h" @@ -40,6 +43,7 @@ #include "chrome/grit/generated_resources.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_node.h" +#include "components/bookmarks/browser/bookmark_utils.h" #include "components/bookmarks/common/bookmark_pref_names.h" #include "components/bookmarks/managed/managed_bookmark_service.h" #include "components/prefs/pref_service.h" @@ -605,12 +609,49 @@ const BookmarkFolderOrURL folder_or_url = menu_id_to_node->second; std::vector> nodes = folder_or_url.GetUnderlyingNodes(GetBookmarkMergedSurfaceService()); + const bool close_on_remove = ShouldCloseOnRemove(folder_or_url); + + std::vector node_ids; + node_ids.reserve(nodes.size()); + for (const BookmarkNode* node : nodes) { + node_ids.push_back(node->id()); + } + auto parent_folder = + BookmarkContextMenuController::GetParentForNewNodes(nodes); + BookmarkUIOperationsHelperMergedSurfaces(GetBookmarkMergedSurfaceService(), + parent_folder.get()) + .CanPasteFromClipboard( + base::BindOnce(&BookmarkMenuDelegate::RunContextMenuAt, + weak_ptr_factory_.GetWeakPtr(), std::move(node_ids), p, + source_type, close_on_remove)); + return true; +} + +void BookmarkMenuDelegate::RunContextMenuAt( + std::vector node_ids, + const gfx::Point& p, + ui::mojom::MenuSourceType source_type, + bool close_on_remove, + bool can_paste) { + auto* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile_); + std::vector> nodes; + for (int64_t node_id : node_ids) { + const BookmarkNode* node = + bookmarks::GetBookmarkNodeByID(bookmark_model, node_id); + if (node) { + nodes.push_back(node); + } + } + if (nodes.empty()) { + return; + } + + bookmark_context_menu_observation_.Reset(); context_menu_ = std::make_unique( - parent_, browser_, profile_, location_, nodes, - ShouldCloseOnRemove(folder_or_url)); + parent_, browser_, profile_, location_, nodes, close_on_remove, + can_paste); bookmark_context_menu_observation_.Observe(context_menu_.get()); context_menu_->RunMenuAt(p, source_type); - return true; } bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) { diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h --- chromium-147.0.7727.116/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h 2026-04-27 20:03:22.000000000 +0000 @@ -131,6 +131,11 @@ int id, const gfx::Point& p, ui::mojom::MenuSourceType source_type); + void RunContextMenuAt(std::vector node_ids, + const gfx::Point& p, + ui::mojom::MenuSourceType source_type, + bool close_on_remove, + bool can_paste); bool CanDrag(views::MenuItemView* menu); void WriteDragData(views::MenuItemView* sender, ui::OSExchangeData* data); int GetDragOperations(views::MenuItemView* sender); @@ -372,6 +377,8 @@ base::ScopedObservation bookmark_merged_service_observation_{this}; + + base::WeakPtrFactory weak_ptr_factory_{this}; }; #endif // CHROME_BROWSER_UI_VIEWS_BOOKMARKS_BOOKMARK_MENU_DELEGATE_H_ diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/toolbar/toolbar_controller.cc chromium-147.0.7727.137/chrome/browser/ui/views/toolbar/toolbar_controller.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/toolbar/toolbar_controller.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/toolbar/toolbar_controller.cc 2026-04-27 20:03:22.000000000 +0000 @@ -256,12 +256,21 @@ if (browser_actions) { auto* root_item = browser_actions->root_action_item(); if (root_item) { + PinnedToolbarActionsModel* const pinned_actions_model = + PinnedToolbarActionsModel::Get(browser->profile()); for (const auto& item : root_item->GetChildren().children()) { auto id = item->GetActionId(); - if (item->GetProperty(actions::kActionItemPinnableKey) == - std::underlying_type_t( - actions::ActionPinnableState::kPinnable) && - id.has_value()) { + // Add an item if it is pinnable and/or pinned. The tab search item may + // be pinned but not pinnable in the event of a race condition after + // action item initialization but before the bubble host has been + // initialized by TabSearchToolbarButtonController. + // TODO(b/471062209): Remove the pinned check as part of + // cleanup of the tab search toolbar button feature. + if (id.has_value() && + (item->GetProperty(actions::kActionItemPinnableKey) == + std::underlying_type_t( + actions::ActionPinnableState::kPinnable) || + pinned_actions_model->Contains(id.value()))) { elements.emplace_back(id.value()); } } diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc chromium-147.0.7727.137/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -324,7 +324,10 @@ CustomWebUIHelpBubble::kWebViewIdForTesting), ClickElement(kWebViewElementId, kCancelButton), WaitForEvent(kToolbarAppMenuButtonElementId, kCallbackEvent), - Do([&help_bubble]() { help_bubble->Close(); }), + Do([&help_bubble]() { + help_bubble->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); + }), WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting), CheckIsAnchor(kToolbarAppMenuButtonElementId, false)); } @@ -430,3 +433,36 @@ WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting), CheckIsDismissed(kCustomWebUIHelpBubbleTestFeature, true)); } + +// Regression tests for https://crbug.com/496456528 + +IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest, ShowPromoAndCloseBrowser) { + RunTestSequence( + MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature, + CustomHelpBubbleShown{ + CustomWebUIHelpBubble::kHelpBubbleIdForTesting}), + Do([this] { browser()->window()->Close(); }), + WaitForHide(kBrowserViewElementId), + WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting)); +} + +IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest, ShowPromoAndCloseBubble) { + RunTestSequence( + MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature, + CustomHelpBubbleShown{ + CustomWebUIHelpBubble::kHelpBubbleIdForTesting}), + WithView(CustomWebUIHelpBubble::kHelpBubbleIdForTesting, + [](views::View* view) { view->GetWidget()->Close(); }), + WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting)); +} + +IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest, + ShowPromoAndCloseBubbleNow) { + RunTestSequence( + MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature, + CustomHelpBubbleShown{ + CustomWebUIHelpBubble::kHelpBubbleIdForTesting}), + WithView(CustomWebUIHelpBubble::kHelpBubbleIdForTesting, + [](views::View* view) { view->GetWidget()->CloseNow(); }), + WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting)); +} diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -77,7 +77,8 @@ ASSERT_TRUE(help_bubble_); ASSERT_TRUE(help_bubble_->IsA()); EXPECT_TRUE(help_bubble_->is_open()); - EXPECT_TRUE(help_bubble_->Close()); + EXPECT_TRUE(help_bubble_->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed)); EXPECT_FALSE(help_bubble_->is_open()); } diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -156,7 +156,10 @@ } auto CloseHelpBubble() { - return Do(base::BindLambdaForTesting([this]() { help_bubble_->Close(); })); + return Do(base::BindLambdaForTesting([this]() { + help_bubble_->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed); + })); } auto CheckHandlerHasHelpBubble(ElementSpecifier anchor, diff -Nru chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc --- chromium-147.0.7727.116/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -12,6 +12,7 @@ #include "chrome/browser/ui/views/user_education/browser_user_education_service.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" #include "components/user_education/views/help_bubble_view.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/views/widget/widget_observer.h" @@ -19,14 +20,7 @@ using user_education::HelpBubbleButtonParams; using user_education::HelpBubbleParams; using user_education::HelpBubbleView; - -namespace { -class TestHelpBubbleView : public HelpBubbleView { - public: - using HelpBubbleView::HelpBubbleView; - using HelpBubbleView::OnWidgetActivationChanged; -}; -} // namespace +using user_education::HelpBubbleViewInfo; // Testing timeouts can be flaky on some platforms without the full browser view // and its message pump, so we do these tests here rather than in the @@ -46,11 +40,17 @@ return params; } - TestHelpBubbleView* CreateHelpBubbleView(HelpBubbleParams params) { - return new TestHelpBubbleView(GetHelpBubbleDelegate(), + [[nodiscard]] HelpBubbleViewInfo CreateHelpBubbleView( + HelpBubbleParams params) { + return HelpBubbleView::Create(GetHelpBubbleDelegate(), {browser_view()->contents_container()}, std::move(params)); } + + void SimulateActivation(const HelpBubbleViewInfo& info, bool active) { + static_cast(info.bubble_view) + ->OnWidgetActivationChanged(info.widget.get(), active); + } }; class MockWidgetObserver : public views::WidgetObserver { @@ -61,10 +61,10 @@ TEST_F(HelpBubbleViewTimeoutTest, DismissOnTimeout) { HelpBubbleParams params = GetBubbleParams(); params.timeout = base::Seconds(30); - HelpBubbleView* const bubble = CreateHelpBubbleView(std::move(params)); + auto info = CreateHelpBubbleView(std::move(params)); MockWidgetObserver dismiss_observer; EXPECT_CALL(dismiss_observer, OnWidgetClosing(testing::_)).Times(1); - bubble->GetWidget()->AddObserver(&dismiss_observer); + info.widget->AddObserver(&dismiss_observer); task_environment()->FastForwardBy(base::Minutes(1)); task_environment()->RunUntilIdle(); } @@ -75,15 +75,15 @@ HelpBubbleButtonParams button_params; button_params.text = u"button"; params.buttons.push_back(std::move(button_params)); - HelpBubbleView* const bubble = CreateHelpBubbleView(std::move(params)); + auto info = CreateHelpBubbleView(std::move(params)); MockWidgetObserver dismiss_observer; EXPECT_CALL(dismiss_observer, OnWidgetClosing(testing::_)).Times(0); - bubble->GetWidget()->AddObserver(&dismiss_observer); + info.widget->AddObserver(&dismiss_observer); task_environment()->FastForwardBy(base::Minutes(1)); task_environment()->RunUntilIdle(); // WidgetObserver checks if it is in an observer list in its destructor. // Need to remove it from widget manually. - bubble->GetWidget()->RemoveObserver(&dismiss_observer); + info.widget->RemoveObserver(&dismiss_observer); } TEST_F(HelpBubbleViewTimeoutTest, TimeoutCallback) { @@ -93,10 +93,10 @@ params.timeout = base::Seconds(10); params.timeout_callback = timeout_callback.Get(); - CreateHelpBubbleView(std::move(params)); + auto bubble = CreateHelpBubbleView(std::move(params)); EXPECT_CALL(timeout_callback, Run()).Times(1); - task_environment()->FastForwardBy(base::Seconds(10)); + task_environment()->FastForwardBy(base::Seconds(11)); } TEST_F(HelpBubbleViewTimeoutTest, NoTimeoutIfSetToZero) { @@ -106,7 +106,7 @@ params.timeout = base::TimeDelta(); params.timeout_callback = timeout_callback.Get(); - CreateHelpBubbleView(std::move(params)); + auto bubble = CreateHelpBubbleView(std::move(params)); EXPECT_CALL(timeout_callback, Run()).Times(0); @@ -121,7 +121,7 @@ params.timeout = base::Seconds(20); params.timeout_callback = timeout_callback.Get(); - CreateHelpBubbleView(std::move(params)); + auto bubble = CreateHelpBubbleView(std::move(params)); EXPECT_CALL(timeout_callback, Run()).Times(0); task_environment()->FastForwardBy(base::Seconds(19)); @@ -139,19 +139,19 @@ EXPECT_CALL(timeout_callback, Run()).Times(0); - TestHelpBubbleView* const bubble = CreateHelpBubbleView(std::move(params)); + auto info = CreateHelpBubbleView(std::move(params)); task_environment()->FastForwardBy(base::Seconds(9)); // Simulate bubble activation. We won't actually activate the bubble since // bubble visibility and activation don't work well in this mock environment. - bubble->OnWidgetActivationChanged(bubble->GetWidget(), true); + SimulateActivation(info, true); // The bubble should not time out since it is active. task_environment()->FastForwardBy(base::Seconds(4)); // Deactivating the widget should restart the timer. - bubble->OnWidgetActivationChanged(bubble->GetWidget(), false); + SimulateActivation(info, false); // Wait most of the timeout, but not all of it. task_environment()->FastForwardBy(base::Seconds(9)); diff -Nru chromium-147.0.7727.116/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc chromium-147.0.7727.137/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc --- chromium-147.0.7727.116/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc 2026-04-27 20:03:22.000000000 +0000 @@ -71,11 +71,10 @@ #include "ui/views/view.h" #include "ui/views/widget/widget.h" -namespace { - -class BookmarkContextMenu : public ui::SimpleMenuModel, - public ui::SimpleMenuModel::Delegate, - public BookmarkContextMenuControllerDelegate { +class BookmarksPageHandler::BookmarkContextMenu + : public ui::SimpleMenuModel, + public ui::SimpleMenuModel::Delegate, + public BookmarkContextMenuControllerDelegate { public: explicit BookmarkContextMenu( BrowserWindowInterface* browser_window, @@ -83,7 +82,8 @@ std::vector> bookmarks, const side_panel::mojom::ActionSource& source, - commerce::ShoppingListContextMenuController* shopping_list_controller) + commerce::ShoppingListContextMenuController* shopping_list_controller, + bool can_paste) : ui::SimpleMenuModel(this), embedder_(embedder), controller_(base::WrapUnique(new BookmarkContextMenuController( @@ -94,7 +94,8 @@ browser_window->GetBrowserForMigrationOnly(), browser_window->GetProfile(), BookmarkLaunchLocation::kSidePanelContextMenu, - bookmarks))), + bookmarks, + can_paste))), shopping_list_controller_(shopping_list_controller), bookmarks_(bookmarks) { if (bookmarks.size() == 0) { @@ -163,8 +164,6 @@ return controller_->IsCommandIdVisible(command_id); } - BookmarkContextMenuController* controller() { return controller_.get(); } - // BookmarkContextMenuControllerDelegate: void CloseMenu() override { if (embedder_) { @@ -188,14 +187,11 @@ bookmarks_; }; -std::unique_ptr ContextMenuFromNodes( - const std::vector node_ids, - base::WeakPtr embedder, - side_panel::mojom::ActionSource source, - commerce::ShoppingListContextMenuController* shopping_list_controller, - BrowserWindowInterface* browser_window) { - bookmarks::BookmarkModel* bookmark_model = - BookmarkModelFactory::GetForBrowserContext(browser_window->GetProfile()); +namespace { + +std::vector> +BookmarksFromNodeIds(const std::vector node_ids, + bookmarks::BookmarkModel* bookmark_model) { std::vector> bookmarks = {}; for (const int64_t id : node_ids) { @@ -205,11 +201,7 @@ bookmarks.push_back(bookmark); } } - - return bookmarks.empty() ? nullptr - : std::make_unique( - browser_window, embedder, bookmarks, source, - shopping_list_controller); + return bookmarks; } // Returns the Side Panel merged ID for permanent folders. @@ -628,22 +620,16 @@ const std::vector& node_ids, side_panel::mojom::ActionSource source, int command_id) { - std::unique_ptr context_menu = ContextMenuFromNodes( + CreateContextMenuForNodes( node_ids, bookmarks_ui_->embedder(), source, - bookmarks_ui_->GetShoppingListContextMenuController(), - browser_window_interface_); - if (!context_menu) { - return; - } - - BookmarkContextMenu* context_menu_ptr = context_menu.get(); - context_menu_ptr->controller()->UpdateCanPaste(base::BindOnce( - [](std::unique_ptr context_menu, int command_id) { - if (context_menu->IsCommandIdEnabled(command_id)) { - context_menu->ExecuteCommand(command_id, 0); - } - }, - std::move(context_menu), command_id)); + base::BindOnce( + [](int command_id, + std::unique_ptr context_menu) { + if (context_menu && context_menu->IsCommandIdEnabled(command_id)) { + context_menu->ExecuteCommand(command_id, 0); + } + }, + command_id)); } void BookmarksPageHandler::OpenBookmark( @@ -768,11 +754,17 @@ auto embedder = bookmarks_ui_->embedder(); if (embedder) { - std::unique_ptr context_menu = ContextMenuFromNodes( + CreateContextMenuForNodes( {id}, embedder, source, - bookmarks_ui_->GetShoppingListContextMenuController(), - browser_window_interface_); - embedder->ShowContextMenu(point, std::move(context_menu)); + base::BindOnce( + [](gfx::Point point, + base::WeakPtr embedder, + std::unique_ptr context_menu) { + if (context_menu && embedder) { + embedder->ShowContextMenu(point, std::move(context_menu)); + } + }, + point, embedder)); } } @@ -870,6 +862,45 @@ std::move(callback).Run(std::move(mojo_nodes)); } +void BookmarksPageHandler::CreateContextMenuForNodes( + const std::vector node_ids, + base::WeakPtr embedder, + side_panel::mojom::ActionSource source, + base::OnceCallback)> callback) { + auto bookmarks = BookmarksFromNodeIds( + node_ids, BookmarkModelFactory::GetForBrowserContext( + browser_window_interface_->GetProfile())); + auto parent = BookmarkContextMenuController::GetParentForNewNodes(bookmarks); + if (!parent) { + std::move(callback).Run(nullptr); + return; + } + BookmarkUIOperationsHelperMergedSurfaces(bookmark_merged_surface_, + parent.get()) + .CanPasteFromClipboard( + base::BindOnce(&BookmarksPageHandler::OnCanPasteFromClipboard, + weak_ptr_factory_.GetWeakPtr(), node_ids, embedder, + source, std::move(callback))); +} + +void BookmarksPageHandler::OnCanPasteFromClipboard( + const std::vector node_ids, + base::WeakPtr embedder, + side_panel::mojom::ActionSource source, + base::OnceCallback)> callback, + bool can_paste) { + auto bookmarks = BookmarksFromNodeIds( + node_ids, BookmarkModelFactory::GetForBrowserContext( + browser_window_interface_->GetProfile())); + std::move(callback).Run( + bookmarks.empty() + ? nullptr + : std::make_unique( + browser_window_interface_, embedder, bookmarks, source, + bookmarks_ui_->GetShoppingListContextMenuController(), + can_paste)); +} + void BookmarksPageHandler::BookmarkNodeAdded(const BookmarkParentFolder& parent, size_t index) { const bookmarks::BookmarkNode* added_node = diff -Nru chromium-147.0.7727.116/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h chromium-147.0.7727.137/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h --- chromium-147.0.7727.116/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h 2026-04-27 20:03:22.000000000 +0000 @@ -6,9 +6,11 @@ #define CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_BOOKMARKS_BOOKMARKS_PAGE_HANDLER_H_ #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "chrome/browser/bookmarks/bookmark_merged_surface_service_observer.h" #include "chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom.h" +#include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" @@ -110,10 +112,25 @@ void BookmarkAllUserNodesRemoved() override {} private: + class BookmarkContextMenu; + // Compute and sends all the bookmark through the input `callback`, // redirecting the values to the TS side. void SendAllBookmarks(GetAllBookmarksCallback callback); + void CreateContextMenuForNodes( + const std::vector node_ids, + base::WeakPtr embedder, + side_panel::mojom::ActionSource source, + base::OnceCallback)> callback); + + void OnCanPasteFromClipboard( + const std::vector node_ids, + base::WeakPtr embedder, + side_panel::mojom::ActionSource source, + base::OnceCallback)> callback, + bool can_paste); + mojo::Receiver receiver_; mojo::Remote page_; const raw_ptr web_ui_; @@ -129,6 +146,8 @@ base::ScopedObservation scoped_bookmark_merged_service_observation_{this}; + + base::WeakPtrFactory weak_ptr_factory_{this}; }; std::string GetFolderSidePanelIDForTesting(const BookmarkParentFolder& folder); diff -Nru chromium-147.0.7727.116/chrome/chrome_branch_deps.json chromium-147.0.7727.137/chrome/chrome_branch_deps.json --- chromium-147.0.7727.116/chrome/chrome_branch_deps.json 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chrome/chrome_branch_deps.json 2026-04-27 20:03:22.000000000 +0000 @@ -1,21 +1,22 @@ { - "src": "refs/branch-heads/7727_111", - "src:src/chrome/browser/glic/e2e_test/internal": "refs/heads/chromium/7727_111", - "src:src/clank": "refs/heads/chromium/7727_111", - "src:src/components/optimization_guide/internal": "refs/heads/chromium/7727_111", - "src:src/internal": "refs/heads/chromium/7727_111", - "src:src/ios_internal": "refs/heads/chromium/7727_111", - "src:src/third_party/angle": "refs/heads/chromium/7727_111", - "src:src/third_party/dawn": "refs/heads/chromium/7727_111", - "src:src/third_party/devtools-frontend/src": "refs/heads/chromium/7727_111", - "src:src/third_party/instrumented_libs": "refs/heads/chromium/7727_111", - "src:src/third_party/litert/src": "refs/heads/chromium/7727_111", - "src:src/third_party/openscreen/src": "refs/heads/chromium/7727_111", - "src:src/third_party/pdfium": "refs/heads/chromium/7727_111", - "src:src/third_party/ruy/src": "refs/heads/chromium/7727_111", - "src:src/third_party/tflite/src": "refs/heads/chromium/7727_111", - "src:src/third_party/vulkan-deps": "refs/heads/chromium/7727_111", - "src:src/third_party/webrtc": "refs/branch-heads/7727_111", - "src:src/third_party/xnnpack/src": "refs/heads/chromium/7727_111", - "src:src/v8": "refs/heads/chromium/7727_111" + "src": "refs/branch-heads/7727", + "src:src/chrome/browser/glic/e2e_test/internal": "refs/heads/chromium/7727", + "src:src/clank": "refs/heads/chromium/7727", + "src:src/components/optimization_guide/internal": "refs/heads/chromium/7727", + "src:src/internal": "refs/heads/chromium/7727", + "src:src/ios_internal": "refs/heads/chromium/7727", + "src:src/third_party/angle": "refs/heads/chromium/7727", + "src:src/third_party/dawn": "refs/heads/chromium/7727", + "src:src/third_party/devtools-frontend/src": "refs/heads/chromium/7727", + "src:src/third_party/instrumented_libs": "refs/heads/chromium/7727", + "src:src/third_party/litert/src": "refs/heads/chromium/7727", + "src:src/third_party/openscreen/src": "refs/heads/chromium/7727", + "src:src/third_party/pdfium": "refs/heads/chromium/7727", + "src:src/third_party/ruy/src": "refs/heads/chromium/7727", + "src:src/third_party/skia": "refs/heads/chrome/m147", + "src:src/third_party/tflite/src": "refs/heads/chromium/7727", + "src:src/third_party/vulkan-deps": "refs/heads/chromium/7727", + "src:src/third_party/webrtc": "refs/branch-heads/7727", + "src:src/third_party/xnnpack/src": "refs/heads/chromium/7727", + "src:src/v8": "refs/heads/chromium/7727" } diff -Nru chromium-147.0.7727.116/chromeos/CHROMEOS_LKGM chromium-147.0.7727.137/chromeos/CHROMEOS_LKGM --- chromium-147.0.7727.116/chromeos/CHROMEOS_LKGM 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/CHROMEOS_LKGM 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -16610.43.0 \ No newline at end of file +16610.50.0 \ No newline at end of file diff -Nru chromium-147.0.7727.116/chromeos/components/onc/onc_utils.cc chromium-147.0.7727.137/chromeos/components/onc/onc_utils.cc --- chromium-147.0.7727.116/chromeos/components/onc/onc_utils.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/components/onc/onc_utils.cc 2026-04-27 20:03:22.000000000 +0000 @@ -694,7 +694,8 @@ if (!base::Base64Decode(salt, &salt) || !base::Base64Decode(iv, &iv) || !base::Base64Decode(ciphertext, &ciphertext) || !base::Base64Decode(hmac, &hmac) || - iv.length() != crypto::aes_cbc::kBlockSize) { + iv.length() != crypto::aes_cbc::kBlockSize || + hmac.length() != crypto::hash::kSha1Size) { NET_LOG(ERROR) << kUnableToDecode; return std::nullopt; } diff -Nru chromium-147.0.7727.116/chromeos/profiles/arm.afdo.newest.txt chromium-147.0.7727.137/chromeos/profiles/arm.afdo.newest.txt --- chromium-147.0.7727.116/chromeos/profiles/arm.afdo.newest.txt 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/profiles/arm.afdo.newest.txt 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -chromeos-chrome-arm-none-147-7727.85-1776047165-benchmark-147.0.7727.109-r1-redacted.afdo.xz +chromeos-chrome-arm-none-147-7727.115-1777256718-benchmark-147.0.7727.136-r1-redacted.afdo.xz diff -Nru chromium-147.0.7727.116/chromeos/profiles/atom.afdo.newest.txt chromium-147.0.7727.137/chromeos/profiles/atom.afdo.newest.txt --- chromium-147.0.7727.116/chromeos/profiles/atom.afdo.newest.txt 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/profiles/atom.afdo.newest.txt 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -chromeos-chrome-amd64-atom-147-7727.85-1776045726-benchmark-147.0.7727.109-r1-redacted.afdo.xz +chromeos-chrome-amd64-atom-147-7727.105-1776655455-benchmark-147.0.7727.131-r1-redacted.afdo.xz diff -Nru chromium-147.0.7727.116/chromeos/profiles/bigcore.afdo.newest.txt chromium-147.0.7727.137/chromeos/profiles/bigcore.afdo.newest.txt --- chromium-147.0.7727.116/chromeos/profiles/bigcore.afdo.newest.txt 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/profiles/bigcore.afdo.newest.txt 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -chromeos-chrome-amd64-bigcore-147-7727.85-1776044354-benchmark-147.0.7727.109-r1-redacted.afdo.xz +chromeos-chrome-amd64-bigcore-147-7727.105-1776656268-benchmark-147.0.7727.131-r1-redacted.afdo.xz diff -Nru chromium-147.0.7727.116/chromeos/strings/chromeos_strings_my.xtb chromium-147.0.7727.137/chromeos/strings/chromeos_strings_my.xtb --- chromium-147.0.7727.116/chromeos/strings/chromeos_strings_my.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/strings/chromeos_strings_my.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -146,7 +146,7 @@ AI ဖြင့် ပြုလုပ်သောအခါ Google ကိုယ်ရေးအချက်အလက်လုံခြုံမှုဆိုင်ရာ မူဝါဒ နှင့်အညီ ပုံများထုတ်လုပ်ပြီး ထုတ်ကုန်မွမ်းမံရန်အတွက် ညွှန်ကြားချက်ကို Google AI ဆာဗာများသို့ ပို့သည်။ - တီထွင်ဖန်တီးမှုဆိုင်ရာ AI ကို ကနဦးစမ်းသပ်ရေးဆွဲနေဆဲဖြစ်ပြီး လောလောဆယ်တွင် ရနိုင်မှုကို ကန့်သတ်ထားသည်။ + Generative AI ကို ကနဦးစမ်းသပ်ရေးဆွဲနေဆဲဖြစ်ပြီး လောလောဆယ်တွင် ရနိုင်မှုကို ကန့်သတ်ထားသည်။ အပြာရောင် တို့ထိရန် အပြား နောက်ခံမီးအရောင် @@ -744,7 +744,7 @@ ကိုယ်ပိုင်နောက်ခံ ပြုလုပ်သောအခါ Google ၏ ဝန်ဆောင်မှုစည်းမျဉ်းများနှင့် ကိုယ်ရေးအချက်အလက်လုံခြုံမှုဆိုင်ရာ မူဝါဒ တို့နှင့်အညီ ပုံများထုတ်လုပ်ပြီး ထုတ်ကုန်မွမ်းမံရန်အတွက် ညွှန်ကြားချက်ကို Google ဆာဗာများသို့ ပို့သည်။ ကိုယ်ရေးကိုယ်တာ၊ သတိထားရမည့် (သို့) လျှို့ဝှက်အချက်အလက်များကို မထည့်ပါနှင့်။ - တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ။ ပိုမိုလေ့လာရန် + Generative AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ။ ပိုမိုလေ့လာရန် နေရာလွတ် HTTP တုံ့ပြန်ချိန် ချည်လုံး @@ -836,7 +836,7 @@ ညာဘက် shift နေ့စဉ် ပြောင်းရန် ပုံကို နောက်ခံအဖြစ် သတ်မှတ်ပြီးပါပြီ -တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ +Generative AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာ စိစစ်ပါ အိပ်မက်ဆန် ဤအပ်ဒိတ်ကို စက်ထုတ်လုပ်သူက ပံ့ပိုးထားပြီး Google က မစိစစ်ရသေးပါ သားမွေး @@ -1446,7 +1446,7 @@ A3 အစိမ်းနှင့် စိမ်းပြာရင့် ပရဒိသုငှက်ပန်း -သင်သည် အနည်းဆုံးအသက် ၁၈ နှစ် ရှိရမည်ဖြစ်ပြီး AI နောက်ခံများကို အသုံးပြုခြင်းသည် Google ဝန်ဆောင်မှုစည်းမျဉ်းများ နှင့် တီထွင်ဖန်တီးမှုဆိုင်ရာ AI ထပ်ဆောင်းဝန်ဆောင်မှုစည်းမျဉ်းများ ကို လိုက်နာရမည်ဖြစ်ကြောင်း သင်သဘောတူသည်။ +သင်သည် အနည်းဆုံးအသက် ၁၈ နှစ် ရှိရမည်ဖြစ်ပြီး AI နောက်ခံများကို အသုံးပြုခြင်းသည် Google ဝန်ဆောင်မှုစည်းမျဉ်းများ နှင့် Generative AI ထပ်ဆောင်းဝန်ဆောင်မှုစည်းမျဉ်းများ ကို လိုက်နာရမည်ဖြစ်ကြောင်း သင်သဘောတူသည်။ တစ်ကိုယ်ရေ အသုံးပြုမှုနှင့် စီးပွားဖြစ်မဟုတ်သော အသုံးပြုမှုအတွက်သာ နောက်ခံများကို AI ဖြင့် ဖန်တီးနိုင်သည်။ နောက်ခံအတွက် အကူအညီ ရယူသည့်အခါ နောက်ခံဆိုင်ရာ အကြံပြုချက်များ ထုတ်ပေးရန် စာသားကို Google ကိုယ်ရေးအချက်အလက်လုံခြုံမှုဆိုင်ရာ မူဝါဒ နှင့်အညီ Google AI ဆာဗာများသို့ ပို့သည်။ ပိုမိုလေ့လာရန် @@ -1595,7 +1595,7 @@ {NUM_ROOL_APPS,plural, =1{"" ကို အလိုအလျောက် စတင်ထားသည်}other{အက်ပ် # ခုကို အလိုအလျောက် စတင်ထားသည်}} EAP နည်းလမ်း ဘရောင်ဇာ -‘တီထွင်ဖန်တီးမှုဆိုင်ရာ AI’ အကြောင်း ပိုမိုလေ့လာရန် +Generative AI အကြောင်း ပိုမိုလေ့လာရန် ဖြတ်လမ်းလင့်ခ်ကို တည်းဖြတ်ပြီးပါပြီ ဒေါင်းလုဒ်လုပ်မှု မအောင်မြင်ပါ ရွေးထားသောနောက်ခံအကြောင်း ပိုမိုလေ့လာရန် @@ -1720,7 +1720,7 @@ Google AI က ပံ့ပိုးထားသည် ကီးဘုတ်ကီးများဖြင့် ကစားရန်အတွက် ဂိမ်းလုပ်ဆောင်ချက်များတွင် သတ်မှတ်ချက်များ ထည့်ပါ WPA2 -တီထွင်ဖန်တီးမှုဆိုင်ရာ AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာစိစစ်ပါ။ +Generative AI သည် လူများအကြောင်းအပါအဝင် အမှားများ ပြုလုပ်နိုင်သဖြင့် ၎င်းကို သေချာစွာစိစစ်ပါ။ သင့်မိုဘိုင်းဖုန်းကုမ္ပဏီသည် ၎င်း၏ကွန်ရက်သို့ ချိတ်ဆက်ရန် ဤရွေးချယ်စရာကို သတ်မှတ်ထားနိုင်သည်။ အသေးစိတ်အတွက် သင့်မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။ စတင်မထားပါ အတွက် ဖာမ်းဝဲဗားရှင်း ထည့်သွင်းမှုကို အပြီးသတ်ရန်အတွက် ပြန်စရန်လိုအပ်သည်။ သင့်ကွန်ပျူတာကို အကြိမ်များစွာ ပြန်လည်စတင်နိုင်သည်။ ဤလုပ်ဆောင်ချက်သည် ပုံမှန်ဖြစ်သည်။ diff -Nru chromium-147.0.7727.116/chromeos/strings/chromeos_strings_uz.xtb chromium-147.0.7727.137/chromeos/strings/chromeos_strings_uz.xtb --- chromium-147.0.7727.116/chromeos/strings/chromeos_strings_uz.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/chromeos/strings/chromeos_strings_uz.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -954,7 +954,7 @@ O‘zgartirilgan sanasi Operativ xotira hisobingizga kiring -Quvvat darajasi +Batareya holati &Jildda ko‘rsatish Yangilashda davom etish quvvat kabelini uzib, qayta ulang baobablar diff -Nru chromium-147.0.7727.116/components/browser_ui/strings/android/translations/browser_ui_strings_ar.xtb chromium-147.0.7727.137/components/browser_ui/strings/android/translations/browser_ui_strings_ar.xtb --- chromium-147.0.7727.116/components/browser_ui/strings/android/translations/browser_ui_strings_ar.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/browser_ui/strings/android/translations/browser_ui_strings_ar.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -126,7 +126,7 @@ السماح للمواقع الإلكترونية بتشغيل المحتوى المحمي تم حظر ملفات تعريف الارتباط التابعة لجهات خارجية. عمليات البحث المقترَحة استنادًا إلى علامة التبويب الأخيرة -البطاقات في صفحة "علامة تبويب جديدة" +البطاقات في صفحة علامة التبويب الجديدة مرة واحدة فقط إزالة عدم السماح للمواقع الإلكترونية باستخدام أدوات استشعار الإضاءة والحركة diff -Nru chromium-147.0.7727.116/components/certificate_transparency/data/log_list.json chromium-147.0.7727.137/components/certificate_transparency/data/log_list.json --- chromium-147.0.7727.116/components/certificate_transparency/data/log_list.json 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/certificate_transparency/data/log_list.json 2026-04-27 20:03:22.000000000 +0000 @@ -1,6 +1,6 @@ { - "version": "85.51", - "log_list_timestamp": "2026-04-20T13:40:24Z", + "version": "85.59", + "log_list_timestamp": "2026-04-27T13:37:53Z", "operators": [ { "name": "Google", diff -Nru chromium-147.0.7727.116/components/contextual_tasks/public/features.cc chromium-147.0.7727.137/components/contextual_tasks/public/features.cc --- chromium-147.0.7727.116/components/contextual_tasks/public/features.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/contextual_tasks/public/features.cc 2026-04-27 20:03:22.000000000 +0000 @@ -80,7 +80,7 @@ BASE_FEATURE(kContextualTasksEnableFileHint, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kContextualTasksComposeboxJumpFix, - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); // Enables the use of a rounded clip-path for the composebox. BASE_FEATURE(kContextualTasksRoundedClipPath, base::FEATURE_ENABLED_BY_DEFAULT); diff -Nru chromium-147.0.7727.116/components/exo/data_device.cc chromium-147.0.7727.137/components/exo/data_device.cc --- chromium-147.0.7727.116/components/exo/data_device.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/exo/data_device.cc 2026-04-27 20:03:22.000000000 +0000 @@ -61,6 +61,13 @@ } DataDevice::~DataDevice() { + while (!window_tracker_.windows().empty()) { + aura::Window* window = window_tracker_.Pop(); + if (aura::client::GetDragDropDelegate(window) == this) { + aura::client::SetDragDropDelegate(window, nullptr); + } + } + delegate_->OnDataDeviceDestroying(this); ui::ClipboardMonitor::GetInstance()->RemoveObserver(this); @@ -155,6 +162,7 @@ void DataDevice::OnSurfaceCreated(Surface* surface) { if (delegate_->CanAcceptDataEventsForSurface(surface)) { aura::client::SetDragDropDelegate(surface->window(), this); + window_tracker_.Add(surface->window()); } } diff -Nru chromium-147.0.7727.116/components/exo/data_device.h chromium-147.0.7727.137/components/exo/data_device.h --- chromium-147.0.7727.116/components/exo/data_device.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/exo/data_device.h 2026-04-27 20:03:22.000000000 +0000 @@ -15,6 +15,7 @@ #include "components/exo/surface_observer.h" #include "ui/aura/client/drag_drop_client.h" #include "ui/aura/client/drag_drop_delegate.h" +#include "ui/aura/window_tracker.h" #include "ui/base/clipboard/clipboard_observer.h" #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h" @@ -106,6 +107,10 @@ std::unique_ptr data_offer_; std::unique_ptr focused_surface_; + // Tracker for aura::Window's whose DragDropDelegate is `this` to avoid a + // dangling kDragDropDelegateKey property after `this` is destroyed. + aura::WindowTracker window_tracker_; + base::OnceClosure quit_closure_; bool drop_succeeded_; base::WeakPtrFactory drop_weak_factory_{this}; diff -Nru chromium-147.0.7727.116/components/exo/data_device_unittest.cc chromium-147.0.7727.137/components/exo/data_device_unittest.cc --- chromium-147.0.7727.116/components/exo/data_device_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/exo/data_device_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -352,6 +352,16 @@ EXPECT_EQ(test::DataEvent::kSelection, events[1]); } +TEST_F(DataDeviceTest, UnsetDragDropDelegateAfterRelease) { + auto surface = std::make_unique(); + seat_->NotifySurfaceCreated(surface.get()); + + ASSERT_EQ(aura::client::GetDragDropDelegate(surface->window()), + static_cast(device_.get())); + device_.reset(); + ASSERT_EQ(aura::client::GetDragDropDelegate(surface->window()), nullptr); +} + TEST_F(DataDeviceTest, ClipboardFocusedSurfaceDestroyed) { device_->OnSurfaceFocused(surface_.get(), nullptr, true); surface_.reset(); diff -Nru chromium-147.0.7727.116/components/exo/wayland/wayland_display_observer.cc chromium-147.0.7727.137/components/exo/wayland/wayland_display_observer.cc --- chromium-147.0.7727.116/components/exo/wayland/wayland_display_observer.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/exo/wayland/wayland_display_observer.cc 2026-04-27 20:03:22.000000000 +0000 @@ -109,7 +109,12 @@ void WaylandDisplayHandler::OnXdgOutputCreated( wl_resource* xdg_output_resource) { - DCHECK(!xdg_output_resource_); + if (xdg_output_resource_) { + wl_resource_post_error(output_resource_, WL_DISPLAY_ERROR_INVALID_OBJECT, + "wl_output already has xdg_output"); + return; + } + xdg_output_resource_ = xdg_output_resource; display::Display display; diff -Nru chromium-147.0.7727.116/components/feedback/feedback_util.cc chromium-147.0.7727.137/components/feedback/feedback_util.cc --- chromium-147.0.7727.116/components/feedback/feedback_util.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/feedback/feedback_util.cc 2026-04-27 20:03:22.000000000 +0000 @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "base/files/file_util.h" +#include "base/files/safe_base_name.h" #include "base/files/scoped_temp_dir.h" #include "base/functional/bind.h" #include "base/json/json_reader.h" @@ -32,6 +33,12 @@ std::optional ZipString(const base::FilePath& filename, std::string_view data) { + std::optional safe_name = + base::SafeBaseName::Create(filename); + if (!safe_name || safe_name->path() != filename) { + return std::nullopt; + } + base::ScopedTempDir temp_dir; base::FilePath zip_file; @@ -40,7 +47,7 @@ if (!temp_dir.CreateUniqueTempDir()) { return std::nullopt; } - if (!base::WriteFile(temp_dir.GetPath().Append(filename), data)) { + if (!base::WriteFile(temp_dir.GetPath().Append(safe_name->path()), data)) { return std::nullopt; } if (!base::CreateTemporaryFile(&zip_file)) { diff -Nru chromium-147.0.7727.116/components/feedback/feedback_util_unittest.cc chromium-147.0.7727.137/components/feedback/feedback_util_unittest.cc --- chromium-147.0.7727.116/components/feedback/feedback_util_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/feedback/feedback_util_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -153,4 +153,43 @@ EXPECT_EQ(autofill_data_str, expected_autofill_data_str); } +TEST_F(FeedbackUtilTest, ZipStringTraversal) { + // Create a temp directory, and target a file within it: + base::ScopedTempDir root_dir; + ASSERT_TRUE(root_dir.CreateUniqueTempDir()); + base::FilePath sensitive_file = + root_dir.GetPath().AppendASCII("sensitive.txt"); + + // Construct a traversal back to that file in a platform-dependent way: + std::string sensitive_path_str = sensitive_file.AsUTF8Unsafe(); +#if BUILDFLAG(IS_WIN) + // Remove "C:" if present + if (sensitive_path_str.size() >= 2 && sensitive_path_str[1] == ':') { + sensitive_path_str = sensitive_path_str.substr(2); + } + // Remove leading backslash + if (!sensitive_path_str.empty() && sensitive_path_str[0] == '\\') { + sensitive_path_str = sensitive_path_str.substr(1); + } + base::FilePath traversal( + FILE_PATH_LITERAL("..\\..\\..\\..\\..\\..\\..\\..\\")); +#else + // Remove leading slash + if (!sensitive_path_str.empty() && sensitive_path_str[0] == '/') { + sensitive_path_str = sensitive_path_str.substr(1); + } + base::FilePath traversal(FILE_PATH_LITERAL("../../../../../../../../")); +#endif + + base::FilePath malicious_filename = + traversal.Append(base::FilePath::FromUTF8Unsafe(sensitive_path_str)); + + // Call ZipString. + std::optional result = + feedback_util::ZipString(malicious_filename, "maliciousness"); + + EXPECT_FALSE(result.has_value()); + EXPECT_FALSE(base::PathExists(sensitive_file)); +} + } // namespace feedback_util diff -Nru chromium-147.0.7727.116/components/js_injection/renderer/js_binding.cc chromium-147.0.7727.137/components/js_injection/renderer/js_binding.cc --- chromium-147.0.7727.116/components/js_injection/renderer/js_binding.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/js_injection/renderer/js_binding.cc 2026-04-27 20:03:22.000000000 +0000 @@ -275,6 +275,13 @@ return receiver_.Bind(std::move(receiver)); } +void JsBinding::Dispose() { + // Explicitly reset the receiver to prevent IPC messages from being dispatched + // to this object while it is awaiting lazy sweeping. This prevents a UAF if + // synchronous JS execution triggers a nested GC. See crbug.com/503889643. + receiver_.reset(); +} + gin::ObjectTemplateBuilder JsBinding::GetObjectTemplateBuilder( v8::Isolate* isolate) { return gin::Wrappable::GetObjectTemplateBuilder(isolate) diff -Nru chromium-147.0.7727.116/components/js_injection/renderer/js_binding.h chromium-147.0.7727.137/components/js_injection/renderer/js_binding.h --- chromium-147.0.7727.116/components/js_injection/renderer/js_binding.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/js_injection/renderer/js_binding.h 2026-04-27 20:03:22.000000000 +0000 @@ -19,6 +19,7 @@ #include "mojo/public/cpp/bindings/associated_receiver.h" #include "third_party/blink/public/common/messaging/string_message_codec.h" #include "v8/include/cppgc/persistent.h" +#include "v8/include/cppgc/prefinalizer.h" #include "v8/include/v8.h" namespace v8 { @@ -39,6 +40,8 @@ // to the page. JsBinding is owned by v8. class JsBinding final : public gin::Wrappable, public mojom::BrowserToJsMessaging { + CPPGC_USING_PRE_FINALIZER(JsBinding, Dispose); + public: static constexpr gin::WrapperInfo kWrapperInfo = {{gin::kEmbedderNativeGin}, gin::kJsBinding}; @@ -75,6 +78,8 @@ mojo::PendingAssociatedReceiver receiver); private: + void Dispose(); + // gin::WrappableBase implementation. gin::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff -Nru chromium-147.0.7727.116/components/omnibox/resources/translations/omnibox_pedal_synonyms_it.xtb chromium-147.0.7727.137/components/omnibox/resources/translations/omnibox_pedal_synonyms_it.xtb --- chromium-147.0.7727.116/components/omnibox/resources/translations/omnibox_pedal_synonyms_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/omnibox/resources/translations/omnibox_pedal_synonyms_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -11,7 +11,7 @@ impostazioni dei cookie di chrome, impostazioni cookie di chrome, impostazioni dei cookie chrome, impostazioni cookie chrome, uso dei cookie, utilizzo dei cookie, uso di cookie, utilizzo di cookie, vietare tutti i cookie di terze parti, vieta tutti i cookie di terze parti, vietare tutti cookie di terze parti, vieta tutti cookie di terze parti, gestire le impostazioni dei cookie, gestisci le impostazioni dei cookie, gestire impostazioni cookie, gestisci impostazioni cookie, gestire i cookie, gestisci i cookie, gestire cookie, gestisci cookie tradurre questa pagina, traduci questa pagina, traduzione di questa pagina, tradurre pagina web, traduci pagina web, tradurre la pagina web, traduci la pagina web regolare le autorizzazioni dei siti, regola le autorizzazioni dei siti, regolare autorizzazioni dei siti, regola autorizzazioni dei siti, cambiare le autorizzazioni, cambia le autorizzazioni, modificare le autorizzazioni, modifica le autorizzazioni, controllare le impostazioni dei siti, controlla le impostazioni dei siti, controllare impostazioni siti, controlla impostazioni siti, gestire le impostazioni dei siti, gestisci le impostazioni dei siti, gestire impostazioni siti, gestisci impostazioni siti, impostazioni siti chrome, impostazioni siti di chrome -aggiornare chrome, aggiorna chrome, aggiornamento di chrome, eseguire l'upgrade del browser, upgrade del browser, upgrade browser +aggiornare chrome, aggiorna chrome, aggiornamento di chrome, fare l'upgrade del browser, upgrade del browser, upgrade browser personalizzare l'accessibilità di chrome, gestire le impostazioni di accessibilità creare sito google, crea sito google, creare un sito google, crea un sito google, creare sito web google, crea sito web google, creare un sito web google, crea un sito web google, nuovo sito google, nuovo sito di google, aprire nuovo sito google, apri nuovo sito google, aprire un nuovo sito google, apri un nuovo sito google creare modulo google, crea modulo google, creare un modulo google, crea un modulo google, creare nuovo modulo google, crea nuovo modulo google, creare un nuovo modulo google, crea un nuovo modulo google, nuovo modulo google, nuovo modulo di google, aprire sondaggio google, apri sondaggio google, aprire un sondaggio google, apri un sondaggio google diff -Nru chromium-147.0.7727.116/components/policy/resources/policy_templates_it.xtb chromium-147.0.7727.137/components/policy/resources/policy_templates_it.xtb --- chromium-147.0.7727.116/components/policy/resources/policy_templates_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/policy/resources/policy_templates_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1815,7 +1815,7 @@ Se la policy viene impostata su , è necessario configurare questa policy o la policy e il campo non può essere lasciato vuoto. -Se la policy viene impostata su e questa policy viene configurata, verranno usati i modelli URI specificati; se questa policy non viene configurata, verranno usate le mappature impostate come hardcoded per cercare di eseguire l'upgrade del resolver DNS attuale dell'utente a un resolver DoH gestito dallo stesso fornitore. +Se la policy viene impostata su e questa policy viene configurata, verranno usati i modelli URI specificati; se questa policy non viene configurata, verranno usate le mappature impostate come hardcoded per cercare di fare l'upgrade del resolver DNS attuale dell'utente a un resolver DoH gestito dallo stesso fornitore. Se il modello URI contiene una variabile , per le richieste al resolver verrà usato il metodo ; in caso contrario verrà usato il metodo . @@ -2016,7 +2016,7 @@ Se questa policy viene impostata su False, disattiva queste protezioni di sicurezza per le connessioni autenticate con certificati CA installati a livello locale. Queste protezioni sono sempre attive per le connessioni autenticate con certificati CA considerati pubblicamente attendibili. - Il valore predefinito di questa policy è stato modificato in 81 da False a True. Il codice di errore previsto per il problema di connessione dei proxy interessati è ERR_TLS13_DOWNGRADE_DETECTED. Gli amministratori a cui occorre più tempo per eseguire l'upgrade dei proxy interessati potrebbero usare questa policy per disattivare temporaneamente questa funzionalità di sicurezza. Questa policy è stata rimossa nella versione 86. + Il valore predefinito di questa policy è stato modificato in 81 da False a True. Il codice di errore previsto per il problema di connessione dei proxy interessati è ERR_TLS13_DOWNGRADE_DETECTED. Gli amministratori a cui occorre più tempo per fare l'upgrade dei proxy interessati potrebbero usare questa policy per disattivare temporaneamente questa funzionalità di sicurezza. Questa policy è stata rimossa nella versione 86. Disattiva l'accesso al menu contestuale del provider di ricerca predefinito Impostazioni per i gruppi suggeriti @@ -4869,7 +4869,7 @@ Se questa policy non viene impostata, per tutti i siti viene usato il valore predefinito globale, configurato dalla policy (Impostazione predefinita rilevamento inattività), se impostato, o altrimenti dalla configurazione personale dell'utente. Consenti la riproduzione automatica dei contenuti multimediali per una lista consentita di pattern URL No -Chrome tenta di eseguire l'upgrade di alcuni tipi di sottorisorse di contenuto misto (HTTP su un sito HTTPS) su iOS. +Chrome tenta di fare l'upgrade di alcuni tipi di sottorisorse di contenuto misto (HTTP su un sito HTTPS) su iOS. Per informazioni dettagliate, visita la pagina https://chromium.googlesource.com/chromium/src/+/main/docs/security/autoupgrade-mixed.md. Questa policy è stata utilizzata per disattivare l'upgrade automatico dei contenuti misti su iOS. La policy è deprecata e non è supportata. @@ -8395,7 +8395,7 @@ Europa. Impedisci l'inclusione dei dati di nei backup Comportamento predefinito di LBS - tenta di eseguire l'upgrade di alcune navigazioni da HTTP a HTTPS, se possibile. È possibile utilizzare questa policy per disabilitare questo comportamento. Se viene impostata su "true" o non viene impostata, questa funzionalità verrà attivata per impostazione predefinita. + tenta di fare l'upgrade di alcune navigazioni da HTTP a HTTPS, se possibile. È possibile utilizzare questa policy per disabilitare questo comportamento. Se viene impostata su "true" o non viene impostata, questa funzionalità verrà attivata per impostazione predefinita. La policy separata può essere utilizzata per escludere determinati nomi host o pattern dei nomi host dall'upgrade a HTTPS da parte di questa funzionalità. @@ -12495,7 +12495,7 @@ Se la policy DnsOverHttpsMode viene impostata su , questa policy deve essere impostata e non può essere vuota. Soltanto su è necessario impostare questa policy o la policy , altrimenti la risoluzione DNS non andrà a buon fine. - Se la policy DnsOverHttpsMode viene impostata su e questa policy viene configurata, verranno usati i modelli URI specificati; se questa policy non viene configurata, verranno usate le mappature impostate come hardcoded per cercare di eseguire l'upgrade del resolver DNS attuale dell'utente a un resolver DoH gestito dallo stesso fornitore. + Se la policy DnsOverHttpsMode viene impostata su e questa policy viene configurata, verranno usati i modelli URI specificati; se questa policy non viene configurata, verranno usate le mappature impostate come hardcoded per cercare di fare l'upgrade del resolver DNS attuale dell'utente a un resolver DoH gestito dallo stesso fornitore. Se il modello URI contiene una variabile , per le richieste al resolver verrà usato il metodo ; in caso contrario verrà usato il metodo per le richieste. diff -Nru chromium-147.0.7727.116/components/strings/components_strings_ar.xtb chromium-147.0.7727.137/components/strings/components_strings_ar.xtb --- chromium-147.0.7727.116/components/strings/components_strings_ar.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_ar.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -922,7 +922,7 @@ سجلّ التصفُّح ملفات تعريف الارتباط وبيانات المواقع الإلكترونية - المعلومات التي تم إدخالها في النماذج + المعلومات التي أدخلتها في النماذج الأجهزة الملحقة المُرفقة بهذا الجهاز عند تسجيل الدخول ‏طُرق الدفع ومعلومات أخرى من "محفظة Google" diff -Nru chromium-147.0.7727.116/components/strings/components_strings_cs.xtb chromium-147.0.7727.137/components/strings/components_strings_cs.xtb --- chromium-147.0.7727.116/components/strings/components_strings_cs.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_cs.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1549,7 +1549,7 @@ chce získat přístup k textu a obrázkům zkopírovaným do schránky Zvýrazňovač Skrýt rozšířené -, stisknutím klávesy Enter se Googlu zeptáte ohledně této stránky +, stisknutím klávesy Enter se Googlu zeptáte na tuto stránku Tento soubor PDF není přístupný. Text byl extrahován pomocí technologie Google AI Chemie , aktuálně otevřeno, na otevřenou kartu přejdete tak, že stisknete tabulátor a poté Enter @@ -2941,7 +2941,7 @@ Spravovat hesla Uložte si své údaje, ať můžete ve službách Google pracovat rychleji. Vaše informace se ukládají do účtu uživatele . Načítání nastavení zásady se nezdařilo -Tlačítko Zeptat se Googlu ohledně této stránky, aktivací Googlu zeptáte ohledně této stránky +Tlačítko Zeptat se Googlu na tuto stránku, aktivací se Googlu zeptáte na tuto stránku Neplatný token správy zařízení Ukázat postup Byl zablokován klamavý obsah. @@ -4203,7 +4203,7 @@ Méně než Web momentálně tento požadavek nemůže zpracovat. Časový limit stahování vypršel -Zeptat se Googlu ohledně této stránky +Zeptat se Googlu na tuto stránku Modely Gemini 3 Hodnota je mimo rozsah. Silný diff -Nru chromium-147.0.7727.116/components/strings/components_strings_da.xtb chromium-147.0.7727.137/components/strings/components_strings_da.xtb --- chromium-147.0.7727.116/components/strings/components_strings_da.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_da.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -3761,7 +3761,7 @@ Nu kan du udfylde loyalitetskort automatisk fra Google Wallet Jobs inden for regnskab og finans . Nogle websites indlæses muligvis langsommere, næste gang du besøger dem. -Dette er en pakkeenhed, som ikke kan tilmeldes Kiosk & Signage-opgraderingen. +Dette er en pakkeenhed, som ikke kan tilmeldes Kiosk & Signage Upgrade. Kommentarer – Strategispil Du indtastede din adgangskode på et website, der ikke administreres af . Du kan beskytte din konto ved at undgå at bruge din adgangskode i andre apps og på andre websites. diff -Nru chromium-147.0.7727.116/components/strings/components_strings_es.xtb chromium-147.0.7727.137/components/strings/components_strings_es.xtb --- chromium-147.0.7727.116/components/strings/components_strings_es.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_es.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -710,7 +710,7 @@ . Se muestran en orden aleatorio. Puedes cambiar tu buscador predeterminado cuando quieras. Esperando permiso... -Marcadores móvil +Marcadores de móvil Editar los datos introducidos Paga en 4 cuotas o mensualmente No hay temas activos @@ -4439,7 +4439,7 @@ Vuelve a cargar esta página para aplicar la configuración actualizada a este sitio Grapado en el borde superior Preguntar en persona -Marcadores móvil +Marcadores de móvil Nunca Máquina de la plataforma Tipo de cuenta de tarea diff -Nru chromium-147.0.7727.116/components/strings/components_strings_fi.xtb chromium-147.0.7727.137/components/strings/components_strings_fi.xtb --- chromium-147.0.7727.116/components/strings/components_strings_fi.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_fi.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -785,7 +785,7 @@ Tummanharmaa 2 {COUNT,plural, =1{1 välilehti}other{# välilehteä}} CD-levy -Omat tapahtumat +Oma toiminta Lähetä nyt Lisää oikaisunumero Tiedosto on ladattu diff -Nru chromium-147.0.7727.116/components/strings/components_strings_it.xtb chromium-147.0.7727.137/components/strings/components_strings_it.xtb --- chromium-147.0.7727.116/components/strings/components_strings_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -1912,7 +1912,7 @@ Se utilizzi una rete pubblica, è più sicuro visitare questo sito in un secondo momento. Con una rete affidabile, come il Wi-Fi di casa o del lavoro, corri meno rischi. - Puoi anche contattare il proprietario del sito e suggerire di eseguire l'upgrade a HTTPS. Scopri di più sull'avviso + Puoi anche contattare il proprietario del sito e suggerire di fare l'upgrade a HTTPS. Scopri di più sull'avviso &Annulla aggiunta Quando l'opzione è attiva, un elenco di argomenti viene visualizzato qui in base alla tua cronologia di navigazione recente Microfono consentito @@ -4115,7 +4115,7 @@ Modulo non sicuro Specifica il motivo della copia (obbligatorio) Specifica il motivo del trasferimento (obbligatorio) -Puoi anche contattare il proprietario del sito e suggerire di eseguire l'upgrade a HTTPS. +Puoi anche contattare il proprietario del sito e suggerire di fare l'upgrade a HTTPS. No, grazie Avanti Busta Italian diff -Nru chromium-147.0.7727.116/components/strings/components_strings_uz.xtb chromium-147.0.7727.137/components/strings/components_strings_uz.xtb --- chromium-147.0.7727.116/components/strings/components_strings_uz.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/strings/components_strings_uz.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -4443,7 +4443,7 @@ Muddatli toʻlov taklif qiladigan hozir olib, keyin toʻlash emitentlari. Klaviatura yashirildi. Limitdan oshib ketdi Chiqarish qurilmasi -{TIME_DIMENSION,plural, =0{ daq oldin}=1{ st oldin}other{ kun oldin}} +{TIME_DIMENSION,plural, =0{ dq oldin}=1{ st oldin}other{ kun oldin}} Taom xizmati Virtual reallik qurilmalari va axborotlardan foydalanish uchun ruxsat soʻrashi mumkin Yoʻl tanlamas avtomobillar diff -Nru chromium-147.0.7727.116/components/user_education/common/feature_promo/feature_promo_lifecycle_unittest.cc chromium-147.0.7727.137/components/user_education/common/feature_promo/feature_promo_lifecycle_unittest.cc --- chromium-147.0.7727.116/components/user_education/common/feature_promo/feature_promo_lifecycle_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/feature_promo/feature_promo_lifecycle_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -137,8 +137,8 @@ auto result = std::make_unique(&element_, HelpBubbleParams()); help_bubble_subscriptions_.emplace_back( - result->AddOnCloseCallback(base::BindLambdaForTesting( - [this](HelpBubble*, HelpBubble::CloseReason) { + result->AddOnClosingCallback(base::BindLambdaForTesting( + [this](const HelpBubble*, HelpBubble::CloseReason) { --num_open_bubbles_; }))); return result; diff -Nru chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.cc chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.cc --- chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.cc 2026-04-27 20:03:22.000000000 +0000 @@ -1081,9 +1081,9 @@ } // Listen for the bubble being closed. - bubble_closed_subscription_ = help_bubble->AddOnCloseCallback( + bubble_closed_subscription_ = help_bubble->AddOnClosedCallback( base::BindOnce(&FeaturePromoControllerImpl::OnHelpBubbleClosed, - base::Unretained(this))); + base::Unretained(this), help_bubble->GetWeakPtr())); } return help_bubble; @@ -1099,14 +1099,14 @@ } void FeaturePromoControllerImpl::OnHelpBubbleClosed( - HelpBubble* bubble, + base::WeakPtr bubble, HelpBubble::CloseReason reason) { // Since we're in the middle of processing callbacks we can't reset our // subscription but since it's a weak pointer (internally) and since we should // should only get called here once, it's not a big deal if we don't reset // it. bool closed_unexpectedly = false; - if (bubble == promo_bubble()) { + if (bubble && bubble.get() == promo_bubble()) { if (current_promo_->OnPromoBubbleClosed(reason)) { current_promo_.reset(); closed_unexpectedly = true; diff -Nru chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h --- chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h 2026-04-27 20:03:22.000000000 +0000 @@ -308,7 +308,8 @@ void FinishContinuedPromo(const base::Feature& iph_feature) override; // Callback that cleans up a help bubble when it is closed. - void OnHelpBubbleClosed(HelpBubble* bubble, HelpBubble::CloseReason reason); + void OnHelpBubbleClosed(base::WeakPtr bubble, + HelpBubble::CloseReason reason); // Callback when the help bubble times out. void OnHelpBubbleTimedOut(const base::Feature* feature); diff -Nru chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl_unittest.cc chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl_unittest.cc --- chromium-147.0.7727.116/components/user_education/common/feature_promo/impl/feature_promo_controller_impl_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/feature_promo/impl/feature_promo_controller_impl_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -200,8 +200,9 @@ promo_context()); }); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, @@ -226,7 +227,9 @@ { anchor_element().Show(); }); // The second promo can show right away. - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run, GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run, + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, @@ -300,8 +303,9 @@ promo_controller().MaybeShowStartupPromo(std::move(params2), promo_context()); }); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, QueueLowThenMidPriority) { @@ -323,8 +327,9 @@ promo_controller().MaybeShowStartupPromo(std::move(params), promo_context()); }); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, QueueHighThenLowPriority) { @@ -344,8 +349,9 @@ promo_controller().MaybeShowStartupPromo(std::move(params2), promo_context()); }); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, QueueLowThenHighPriority) { @@ -367,8 +373,9 @@ promo_controller().MaybeShowStartupPromo(std::move(params), promo_context()); }); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, DemoOverridesOtherPromos) { @@ -417,8 +424,9 @@ }); promo_controller().MaybeShowStartupPromo(std::move(params2), promo_context()); - EXPECT_ASYNC_CALL_IN_SCOPE(result2, Run(FeaturePromoResult::Success()), - GetHelpBubble()->Close()); + EXPECT_ASYNC_CALL_IN_SCOPE( + result2, Run(FeaturePromoResult::Success()), + GetHelpBubble()->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(FeaturePromoControllerQueueTest, ShowLowThenQueueHighPriority) { diff -Nru chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble.cc chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble.cc --- chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble.cc 2026-04-27 20:03:22.000000000 +0000 @@ -9,8 +9,24 @@ namespace user_education { +HelpBubble::ScopedNotifyOnClosed::ScopedNotifyOnClosed() = default; +HelpBubble::ScopedNotifyOnClosed::ScopedNotifyOnClosed( + const HelpBubble* bubble, + CloseReason reason, + std::unique_ptr callbacks) + : reason_(reason), callbacks_(std::move(callbacks)) {} +HelpBubble::ScopedNotifyOnClosed::ScopedNotifyOnClosed( + ScopedNotifyOnClosed&&) noexcept = default; +HelpBubble::ScopedNotifyOnClosed& HelpBubble::ScopedNotifyOnClosed::operator=( + ScopedNotifyOnClosed&&) noexcept = default; +HelpBubble::ScopedNotifyOnClosed::~ScopedNotifyOnClosed() { + if (callbacks_) { + std::move(*callbacks_).Notify(reason_); + } +} + HelpBubble::HelpBubble() - : on_close_callbacks_(std::make_unique()) {} + : on_closed_callbacks_(std::make_unique()) {} HelpBubble::~HelpBubble() { // Derived classes must call Close() in destructor lest the bubble be @@ -21,31 +37,10 @@ CHECK(is_closed()); } -bool HelpBubble::Close(CloseReason close_reason) { - // This prevents us from re-entrancy during CloseBubbleImpl() or after the - // bubble is closed. - if (is_closed()) { - return false; - } - - // We can't destruct the callback list during callbacks, so ensure that it - // sticks around until the callbacks are all finished. This also has the side - // effect of making is_closed() true since it resets the value of - // `on_close_callbacks_`. - std::unique_ptr callbacks = std::move(on_close_callbacks_); - - // Note: any of the following could destroy `this`. - - // Actually close the help bubble. For some implementations, this may trigger - // additional events. - CloseBubbleImpl(); - - // Call any on-close callbacks. - if (callbacks) { - callbacks->Notify(this, close_reason); - } - - return true; +HelpBubble::ScopedNotifyOnClosed HelpBubble::BeginClose(CloseReason reason) { + auto on_closed = std::move(on_closed_callbacks_); + on_closing_callbacks_.Notify(this, reason); + return ScopedNotifyOnClosed(this, reason, std::move(on_closed)); } void HelpBubble::OnAnchorBoundsChanged() {} @@ -54,13 +49,22 @@ return gfx::Rect(); } -base::CallbackListSubscription HelpBubble::AddOnCloseCallback( +base::CallbackListSubscription HelpBubble::AddOnClosingCallback( + ClosingCallback callback) { + if (is_closed()) { + NOTREACHED(); + } + + return on_closing_callbacks_.Add(std::move(callback)); +} + +base::CallbackListSubscription HelpBubble::AddOnClosedCallback( ClosedCallback callback) { if (is_closed()) { NOTREACHED(); } - return on_close_callbacks_->Add(std::move(callback)); + return on_closed_callbacks_->Add(std::move(callback)); } } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble.h chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble.h --- chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble.h 2026-04-27 20:03:22.000000000 +0000 @@ -8,6 +8,8 @@ #include "base/callback_list.h" #include "base/compiler_specific.h" #include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/element_tracker.h" #include "ui/base/interaction/framework_specific_implementation.h" @@ -33,11 +35,6 @@ kBubbleDestroyed, }; - // Callback to be notified when the help bubble is closed. Note that the - // pointer passed in is entirely for reference and should not be dereferenced - // as another callback may have deleted the bubble itself. - using ClosedCallback = base::OnceCallback; - HelpBubble(); ~HelpBubble() override; @@ -45,8 +42,12 @@ virtual bool ToggleFocusForAccessibility() = 0; // Closes the bubble if it is not already closed. Returns whether the bubble - // was open. - bool Close(CloseReason close_reason = CloseReason::kProgrammaticallyClosed); + // was open. Usual pattern is to call `BeginClose()` at the top and return the + // result of `ScopedNotifyOnClose::is_valid()`. + // + // `Close()` should return true at most once and return false (and be a no-op) + // on any subsequent call. + virtual bool Close(CloseReason close_reason) = 0; // Notify that the element the help bubble is anchored to may have moved. // Default is no-op. @@ -60,23 +61,67 @@ // Returns the context of this help bubble (if there is one). virtual ui::ElementContext GetContext() const = 0; - // Add a callback to know when a bubble is going away. - [[nodiscard]] base::CallbackListSubscription AddOnCloseCallback( + // Add a callback to know when a help bubble is about to close. The help + // bubble will still be valid during this call. + using ClosingCallback = + base::OnceCallback; + [[nodiscard]] base::CallbackListSubscription AddOnClosingCallback( + ClosingCallback callback); + + // Add a callback for when the help bubble has been fully torn down. + // + // The caller can release the help bubble and other resources at this point if + // that hasn't already been done (but note that the help bubble may no longer + // exist at this point). + using ClosedCallback = base::OnceCallback; + [[nodiscard]] base::CallbackListSubscription AddOnClosedCallback( ClosedCallback callback); bool is_open() const { return !is_closed(); } + base::WeakPtr GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + protected: - // Actually close the bubble. - virtual void CloseBubbleImpl() = 0; + using ClosingCallbackList = base::OnceCallbackList; + using ClosedCallbackList = base::OnceCallbackList; + + // Created from `this` with the contents of `on_close_callbacks_`, calls + // the callbacks (if any) when it goes out of scope. + class [[nodiscard]] ScopedNotifyOnClosed { + public: + ScopedNotifyOnClosed(); + ScopedNotifyOnClosed(const HelpBubble* bubble, + CloseReason reason, + std::unique_ptr callbacks); + ScopedNotifyOnClosed(ScopedNotifyOnClosed&&) noexcept; + ScopedNotifyOnClosed& operator=(ScopedNotifyOnClosed&&) noexcept; + ~ScopedNotifyOnClosed(); + + bool is_valid() const { return callbacks_ != nullptr; } + + private: + CloseReason reason_ = CloseReason::kProgrammaticallyClosed; + std::unique_ptr callbacks_; + }; + + // Call when you are tearing down the bubble; the resulting object will ensure + // that - if the bubble was not previously closed - the proper close callbacks + // will be triggered when the resulting object goes out of scope. + // + // If called a second time, `ScopedNotifyOnClose::is_valid()` will be false + // and the call and destructor are a no-op. + ScopedNotifyOnClosed BeginClose(CloseReason reason); private: // Closed callbacks are cleared out on close, so this keeps us from having to // store extra data about closed status that could become out of sync. - bool is_closed() const { return !on_close_callbacks_; } + bool is_closed() const { return !on_closed_callbacks_; } - using CallbackList = base::OnceCallbackList; - std::unique_ptr on_close_callbacks_; + ClosingCallbackList on_closing_callbacks_; + std::unique_ptr on_closed_callbacks_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry.cc chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry.cc --- chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry.cc 2026-04-27 20:03:22.000000000 +0000 @@ -24,7 +24,7 @@ // Unsubscribe from the bubble before trying to close it so we don't try to // modify the map while we're iterating it. pr.second = base::CallbackListSubscription(); - pr.first->Close(); + pr.first->Close(HelpBubble::CloseReason::kBubbleDestroyed); } } @@ -78,14 +78,14 @@ CHECK(help_bubble); CHECK(help_bubble->is_open()); help_bubbles_.emplace(help_bubble, - help_bubble->AddOnCloseCallback(base::BindOnce( - &HelpBubbleFactoryRegistry::OnHelpBubbleClosed, + help_bubble->AddOnClosingCallback(base::BindOnce( + &HelpBubbleFactoryRegistry::OnHelpBubbleClosing, base::Unretained(this)))); } -void HelpBubbleFactoryRegistry::OnHelpBubbleClosed(HelpBubble* bubble, - HelpBubble::CloseReason) { - const auto result = help_bubbles_.erase(bubble); +void HelpBubbleFactoryRegistry::OnHelpBubbleClosing(const HelpBubble* bubble, + HelpBubble::CloseReason) { + const auto result = help_bubbles_.erase(const_cast(bubble)); DCHECK(result); } diff -Nru chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry.h chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry.h --- chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry.h 2026-04-27 20:03:22.000000000 +0000 @@ -68,13 +68,14 @@ } private: - void OnHelpBubbleClosed(HelpBubble* help_bubble, HelpBubble::CloseReason); + void OnHelpBubbleClosing(const HelpBubble* help_bubble, + HelpBubble::CloseReason); // The list of known factories. ui::FrameworkSpecificRegistrationList factories_; // The list of known help bubbles. - std::map help_bubbles_; + std::map, base::CallbackListSubscription> help_bubbles_; }; } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry_unittest.cc chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry_unittest.cc --- chromium-147.0.7727.116/components/user_education/common/help_bubble/help_bubble_factory_registry_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/help_bubble/help_bubble_factory_registry_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -111,7 +111,7 @@ auto bubble = help_bubble_factory_registry_.CreateHelpBubble( &test_element_, std::move(params)); - bubble->Close(); + bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_FALSE(bubble->is_open()); EXPECT_FALSE(help_bubble_factory_registry_.is_any_bubble_showing()); } @@ -228,9 +228,9 @@ &test_element_, GetBubbleParams()); auto bubble2 = help_bubble_factory_registry_.CreateHelpBubble( &test_element_, GetBubbleParams()); - bubble->Close(); + bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_TRUE(help_bubble_factory_registry_.is_any_bubble_showing()); - bubble2->Close(); + bubble2->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_FALSE(help_bubble_factory_registry_.is_any_bubble_showing()); } @@ -238,12 +238,12 @@ auto bubble = help_bubble_factory_registry_.CreateHelpBubble( &test_element_, GetBubbleParams()); EXPECT_TRUE(help_bubble_factory_registry_.is_any_bubble_showing()); - bubble->Close(); + bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_FALSE(help_bubble_factory_registry_.is_any_bubble_showing()); auto bubble2 = help_bubble_factory_registry_.CreateHelpBubble( &test_element_, GetBubbleParams()); EXPECT_TRUE(help_bubble_factory_registry_.is_any_bubble_showing()); - bubble2->Close(); + bubble2->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_FALSE(help_bubble_factory_registry_.is_any_bubble_showing()); } @@ -253,7 +253,7 @@ help_bubble_factory_registry_.AddHelpBubble(bubble.get()); EXPECT_EQ(bubble.get(), help_bubble_factory_registry_.GetHelpBubble( test_element_.context())); - bubble->Close(); + bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); EXPECT_EQ(nullptr, help_bubble_factory_registry_.GetHelpBubble( test_element_.context())); } diff -Nru chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_service.cc chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_service.cc --- chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_service.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_service.cc 2026-04-27 20:03:22.000000000 +0000 @@ -213,9 +213,9 @@ } } -void TutorialService::OnNonFinalBubbleClosed(HelpBubble* bubble, +void TutorialService::OnNonFinalBubbleClosed(base::WeakPtr bubble, HelpBubble::CloseReason) { - if (bubble != currently_displayed_bubble_.get()) { + if (!bubble || bubble.get() != currently_displayed_bubble_.get()) { return; } @@ -251,16 +251,17 @@ if (is_last_step) { is_final_bubble_ = true; bubble_closed_subscription_ = - currently_displayed_bubble_->AddOnCloseCallback(base::BindOnce( - [](TutorialService* service, HelpBubble*, HelpBubble::CloseReason) { + currently_displayed_bubble_->AddOnClosedCallback(base::BindOnce( + [](TutorialService* service, HelpBubble::CloseReason) { service->CompleteTutorial(); }, base::Unretained(this))); } else { is_final_bubble_ = false; bubble_closed_subscription_ = - currently_displayed_bubble_->AddOnCloseCallback(base::BindOnce( - &TutorialService::OnNonFinalBubbleClosed, base::Unretained(this))); + currently_displayed_bubble_->AddOnClosedCallback(base::BindOnce( + &TutorialService::OnNonFinalBubbleClosed, base::Unretained(this), + currently_displayed_bubble_->GetWeakPtr())); } } diff -Nru chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_service.h chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_service.h --- chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_service.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_service.h 2026-04-27 20:03:22.000000000 +0000 @@ -118,7 +118,7 @@ // Called when a non-final bubble is closed. Used to trigger the broken // tutorial timeout. - void OnNonFinalBubbleClosed(HelpBubble* bubble, + void OnNonFinalBubbleClosed(base::WeakPtr bubble, HelpBubble::CloseReason reason); // Calls the completion code for the running tutorial. diff -Nru chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_unittest.cc chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_unittest.cc --- chromium-147.0.7727.116/components/user_education/common/tutorial/tutorial_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/common/tutorial/tutorial_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -873,7 +873,8 @@ ClearEventQueue(); EXPECT_ASYNC_CALL_IN_SCOPE( completed, Run, - service.currently_displayed_bubble_for_testing()->Close()); + service.currently_displayed_bubble_for_testing()->Close( + HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(TutorialTest, TimeoutBeforeFirstBubble) { diff -Nru chromium-147.0.7727.116/components/user_education/test/test_help_bubble.cc chromium-147.0.7727.137/components/user_education/test/test_help_bubble.cc --- chromium-147.0.7727.116/components/user_education/test/test_help_bubble.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/test/test_help_bubble.cc 2026-04-27 20:03:22.000000000 +0000 @@ -84,10 +84,14 @@ return kNoButtonWithTextIndex; } -void TestHelpBubble::CloseBubbleImpl() { +bool TestHelpBubble::Close(CloseReason reason) { + auto scoped_callbacks = BeginClose(reason); + bubble_element_.reset(); anchor_element_ = nullptr; element_hidden_subscription_ = base::CallbackListSubscription(); + + return scoped_callbacks.is_valid(); } ui::ElementContext TestHelpBubble::GetContext() const { diff -Nru chromium-147.0.7727.116/components/user_education/test/test_help_bubble.h chromium-147.0.7727.137/components/user_education/test/test_help_bubble.h --- chromium-147.0.7727.116/components/user_education/test/test_help_bubble.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/test/test_help_bubble.h 2026-04-27 20:03:22.000000000 +0000 @@ -58,10 +58,9 @@ // called. int focus_count() const { return focus_count_; } - protected: // HelpBubble: bool ToggleFocusForAccessibility() override; - void CloseBubbleImpl() override; + bool Close(CloseReason reason) override; ui::ElementContext GetContext() const override; private: diff -Nru chromium-147.0.7727.116/components/user_education/views/BUILD.gn chromium-147.0.7727.137/components/user_education/views/BUILD.gn --- chromium-147.0.7727.116/components/user_education/views/BUILD.gn 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/BUILD.gn 2026-04-27 20:03:22.000000000 +0000 @@ -15,6 +15,8 @@ "help_bubble_factory_views.h", "help_bubble_view.cc", "help_bubble_view.h", + "help_bubble_view_info.cc", + "help_bubble_view_info.h", "help_bubble_views.cc", "help_bubble_views.h", "new_badge_label.cc", diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_mac.mm chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_mac.mm --- chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_mac.mm 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_mac.mm 2026-04-27 20:03:22.000000000 +0000 @@ -54,7 +54,7 @@ } return base::WrapUnique(new HelpBubbleViews( - new HelpBubbleView(delegate_, anchor, std::move(params)), element)); + HelpBubbleView::Create(delegate_, anchor, std::move(params)), element)); } bool HelpBubbleFactoryMac::CanBuildBubbleForTrackedElement( diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_views.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_views.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_views.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_views.cc 2026-04-27 20:03:22.000000000 +0000 @@ -66,10 +66,9 @@ const internal::HelpBubbleAnchorParams& anchor, HelpBubbleParams params, std::unique_ptr event_relay) { - anchor.view->SetProperty(kHasInProductHelpPromoKey, true); auto result = base::WrapUnique(new HelpBubbleViews( - new HelpBubbleView(delegate_, anchor, std::move(params), - std::move(event_relay)), + HelpBubbleView::Create(delegate_, anchor, std::move(params), + std::move(event_relay)), element)); for (const auto& accelerator : delegate_->GetPaneNavigationAccelerators(element)) { @@ -77,9 +76,6 @@ accelerator, ui::AcceleratorManager::HandlerPriority::kNormalPriority, result.get()); } - if (result) { - MaybeApplyAttentionStateToTrackedElement(anchor.view); - } return result; } diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_views_unittest.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_views_unittest.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_factory_views_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_factory_views_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -10,6 +10,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" #include "base/test/mock_callback.h" +#include "build/build_config.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" #include "components/user_education/views/help_bubble_view.h" #include "components/user_education/views/help_bubble_views.h" @@ -17,6 +18,7 @@ #include "components/user_education/views/view_subregion_anchor.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/expect_call_in_scope.h" +#include "ui/base/interaction/interaction_sequence_test_util.h" #include "ui/base/interaction/interaction_test_util.h" #include "ui/views/interaction/element_tracker_views.h" #include "ui/views/interaction/interaction_test_util_views.h" @@ -92,22 +94,28 @@ ASSERT_TRUE(help_bubble); } -TEST_F(HelpBubbleFactoryViewsTest, HelpBubbleDismissedOnAnchorHidden) { +// TODO(https://crbug.com/502638609): In Fuchsia, this test causes an unrelated +// crash on the GPU thread. +#if BUILDFLAG(IS_FUCHSIA) +#define MAYBE_HelpBubbleDismissedOnAnchorHidden \ + DISABLED_HelpBubbleDismissedOnAnchorHidden +#else +#define MAYBE_HelpBubbleDismissedOnAnchorHidden \ + HelpBubbleDismissedOnAnchorHidden +#endif +TEST_F(HelpBubbleFactoryViewsTest, MAYBE_HelpBubbleDismissedOnAnchorHidden) { + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); auto help_bubble = CreateHelpBubble(anchor_view_.get(), base::DoNothing()); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); // Wait for the help bubble to close. - base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); - EXPECT_CALL(closed, Run) - .WillOnce([&](HelpBubble* bubble, HelpBubble::CloseReason reason) { - EXPECT_EQ(help_bubble.get(), bubble); - EXPECT_EQ(HelpBubble::CloseReason::kAnchorHidden, reason); - run_loop.Quit(); - }); - anchor_view_->SetVisible(false); - run_loop.Run(); + EXPECT_ASYNC_CALLS_IN_SCOPE_2( + closing, Run(help_bubble.get(), HelpBubble::CloseReason::kAnchorHidden), + closed, Run(HelpBubble::CloseReason::kAnchorHidden), + anchor_view_->SetVisible(false)); } class HelpBubbleFactoryViewsSubregionAnchorTest @@ -245,23 +253,29 @@ EXPECT_GT(new_bounds.y(), old_bounds.y()); } +// TODO(https://crbug.com/502638609): In Fuchsia, this test causes an unrelated +// crash on the GPU thread. +#if BUILDFLAG(IS_FUCHSIA) +#define MAYBE_HelpBubbleDismissedOnAnchorHidden \ + DISABLED_HelpBubbleDismissedOnAnchorHidden +#else +#define MAYBE_HelpBubbleDismissedOnAnchorHidden \ + HelpBubbleDismissedOnAnchorHidden +#endif TEST_F(HelpBubbleFactoryViewsSubregionAnchorTest, - HelpBubbleDismissedOnAnchorHidden) { + MAYBE_HelpBubbleDismissedOnAnchorHidden) { + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); auto help_bubble = CreateHelpBubble(base::DoNothing()); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); // Wait for the help bubble to close. - base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); - EXPECT_CALL(closed, Run) - .WillOnce([&](HelpBubble* bubble, HelpBubble::CloseReason reason) { - EXPECT_EQ(help_bubble.get(), bubble); - EXPECT_EQ(HelpBubble::CloseReason::kAnchorHidden, reason); - run_loop.Quit(); - }); - anchor_view_->SetVisible(false); - run_loop.Run(); + EXPECT_ASYNC_CALLS_IN_SCOPE_2( + closing, Run(help_bubble.get(), HelpBubble::CloseReason::kAnchorHidden), + closed, Run(HelpBubble::CloseReason::kAnchorHidden), + anchor_view_->SetVisible(false)); } } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_view.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_view.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_view.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_view.cc 2026-04-27 20:03:22.000000000 +0000 @@ -12,6 +12,7 @@ #include "base/callback_list.h" #include "base/functional/bind.h" +#include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/metrics/user_metrics.h" #include "base/notreached.h" @@ -320,6 +321,26 @@ base::ScopedObservation observation_{this}; }; +// static +HelpBubbleViewInfo HelpBubbleView::Create( + const HelpBubbleDelegate* delegate, + const internal::HelpBubbleAnchorParams& anchor, + HelpBubbleParams params, + std::unique_ptr event_relay) { + const bool visible_arrow = + anchor.show_arrow && params.arrow != HelpBubbleArrow::kNone; + const bool show_active = + params.focus_on_show_hint.value_or(!params.buttons.empty()) && + !event_relay; + auto bubble = base::WrapUnique(new HelpBubbleView( + delegate, anchor, std::move(params), std::move(event_relay))); + auto* const bubble_ptr = bubble.get(); + auto* const widget = views::BubbleDialogDelegateView::CreateBubble( + std::move(bubble), views::Widget::InitParams::CLIENT_OWNS_WIDGET); + bubble_ptr->InitializeAndShow(visible_arrow, show_active); + return HelpBubbleViewInfo(base::WrapUnique(widget), bubble_ptr); +} + HelpBubbleView::HelpBubbleView( const HelpBubbleDelegate* delegate, const internal::HelpBubbleAnchorParams& anchor, @@ -671,24 +692,23 @@ set_close_on_deactivate(false); set_focus_traversable_from_anchor_view(false); - const bool suppress_events = - event_relay_ && !event_relay_->ShouldHelpBubbleProcessEvents(); - if (suppress_events) { + if (event_relay_ && !event_relay_->ShouldHelpBubbleProcessEvents()) { CHECK_LE(params.buttons.size(), 1U) << "Help bubbles that cannot activate cannot have multiple interactive " "buttons due to accessibility constraints."; SetCanActivate(false); set_accept_events(false); } +} - views::Widget* widget = views::BubbleDialogDelegateView::CreateBubble(this); +void HelpBubbleView::InitializeAndShow(bool visible_arrow, bool show_active) { + views::Widget* const widget = GetWidget(); // This gets reset to the platform default when we call CreateBubble(), so we // have to change it afterwards: set_adjust_if_offscreen(true); auto* const frame_view = GetBubbleFrameView(); - frame_view->SetDisplayVisibleArrow(anchor.show_arrow && - params.arrow != HelpBubbleArrow::kNone); + frame_view->SetDisplayVisibleArrow(visible_arrow); // If the primary window widget is not the anchor widget, do not use the // window anchor bounds. @@ -706,7 +726,7 @@ InvalidateLayout(); // Setup that should happen after the widget is constructed: - if (suppress_events) { + if (event_relay_ && !event_relay_->ShouldHelpBubbleProcessEvents()) { // This is required on Windows because of the way events are routed. GetBubbleFrameView()->set_hit_test_transparent(true); } @@ -718,9 +738,6 @@ } // Most help bubbles with buttons take focus when they show. - const bool show_active = - params.focus_on_show_hint.value_or(!params.buttons.empty()) && - !event_relay_; if (show_active) { widget->Show(); } else { @@ -750,10 +767,15 @@ } void HelpBubbleView::OnTimeout() { + // The callback could destroy the widget, so grab a weak pointer. + base::WeakPtr widget = + GetWidget() ? GetWidget()->GetWeakPtr() : nullptr; if (timeout_callback_) { std::move(timeout_callback_).Run(); } - GetWidget()->Close(); + if (widget) { + widget->Close(); + } } std::u16string HelpBubbleView::GetAccessibleWindowTitle() const { diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_view.h chromium-147.0.7727.137/components/user_education/views/help_bubble_view.h --- chromium-147.0.7727.116/components/user_education/views/help_bubble_view.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_view.h 2026-04-27 20:03:22.000000000 +0000 @@ -16,12 +16,16 @@ #include "base/timer/timer.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" #include "components/user_education/views/help_bubble_event_relay.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/gfx/geometry/rect.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/label_button.h" +#include "ui/views/widget/widget.h" + +class HelpBubbleViewTimeoutTest; namespace views { class ImageView; @@ -72,14 +76,17 @@ // Maximum width of the bubble. Longer strings will cause wrapping. static constexpr int kMaxWidthDip = 340; - HelpBubbleView(const HelpBubbleDelegate* delegate, - const internal::HelpBubbleAnchorParams& anchor, - HelpBubbleParams params, - std::unique_ptr event_relay = nullptr); HelpBubbleView(const HelpBubbleView&) = delete; HelpBubbleView& operator=(const HelpBubbleView&) = delete; ~HelpBubbleView() override; + // Creates a help bubble view. + static HelpBubbleViewInfo Create( + const HelpBubbleDelegate* delegate, + const internal::HelpBubbleAnchorParams& anchor, + HelpBubbleParams params, + std::unique_ptr event_relay = nullptr); + // Returns whether the given dialog is a help bubble. static bool IsHelpBubble(views::DialogDelegate* dialog); @@ -100,14 +107,20 @@ views::Widget* widget) const override; private: - FRIEND_TEST_ALL_PREFIXES(HelpBubbleViewTimeoutTest, - RespectsProvidedTimeoutAfterActivate); FRIEND_TEST_ALL_PREFIXES(HelpBubbleViewsTest, RootViewAccessibleName); friend class HelpBubbleViewsTest; friend class HelpBubbleEventRelay; + friend HelpBubbleViewTimeoutTest; class AnchorViewObserver; + HelpBubbleView(const HelpBubbleDelegate* delegate, + const internal::HelpBubbleAnchorParams& anchor, + HelpBubbleParams params, + std::unique_ptr event_relay); + + void InitializeAndShow(bool visible_arrow, bool show_active); + void MaybeStartAutoCloseTimer(); void OnTimeout(); diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_view_info.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_view_info.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_view_info.cc 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_view_info.cc 2026-04-27 20:03:22.000000000 +0000 @@ -0,0 +1,20 @@ +// Copyright 2026 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/user_education/views/help_bubble_view_info.h" + +namespace user_education { + +HelpBubbleViewInfo::HelpBubbleViewInfo( + std::unique_ptr widget_, + views::BubbleDialogDelegateView* bubble_view_) + : widget(std::move(widget_)), bubble_view(bubble_view_) {} + +HelpBubbleViewInfo::HelpBubbleViewInfo() = default; +HelpBubbleViewInfo::HelpBubbleViewInfo(HelpBubbleViewInfo&&) noexcept = default; +HelpBubbleViewInfo& HelpBubbleViewInfo::operator=( + HelpBubbleViewInfo&&) noexcept = default; +HelpBubbleViewInfo::~HelpBubbleViewInfo() = default; + +} // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_view_info.h chromium-147.0.7727.137/components/user_education/views/help_bubble_view_info.h --- chromium-147.0.7727.116/components/user_education/views/help_bubble_view_info.h 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_view_info.h 2026-04-27 20:03:22.000000000 +0000 @@ -0,0 +1,28 @@ +// Copyright 2026 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_USER_EDUCATION_VIEWS_HELP_BUBBLE_VIEW_INFO_H_ +#define COMPONENTS_USER_EDUCATION_VIEWS_HELP_BUBBLE_VIEW_INFO_H_ + +#include "base/memory/raw_ptr.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" + +namespace user_education { + +// Object that holds a reference to a help bubble view and its widget. +struct HelpBubbleViewInfo { + HelpBubbleViewInfo(); + HelpBubbleViewInfo(std::unique_ptr widget_, + views::BubbleDialogDelegateView* bubble_view_); + HelpBubbleViewInfo(HelpBubbleViewInfo&&) noexcept; + HelpBubbleViewInfo& operator=(HelpBubbleViewInfo&&) noexcept; + ~HelpBubbleViewInfo(); + + std::unique_ptr widget; + raw_ptr bubble_view = nullptr; +}; + +} // namespace user_education + +#endif // COMPONENTS_USER_EDUCATION_VIEWS_HELP_BUBBLE_VIEW_INFO_H_ diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_view_unittest.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_view_unittest.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_view_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_view_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -17,6 +17,7 @@ #include "components/user_education/common/user_education_events.h" #include "components/user_education/views/help_bubble_delegate.h" #include "components/user_education/views/help_bubble_factory_views.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "components/user_education/views/help_bubble_views.h" #include "components/user_education/views/help_bubble_views_test_util.h" #include "testing/gmock/include/gmock/gmock.h" @@ -80,18 +81,19 @@ return widget_->GetClientAreaBoundsInScreen(); } - HelpBubbleView* CreateHelpBubbleView( + HelpBubbleViewInfo CreateHelpBubbleView( HelpBubbleParams params, std::optional bounds = std::nullopt, std::optional view = std::nullopt) { internal::HelpBubbleAnchorParams anchor_params; anchor_params.view = view.value_or(view_); anchor_params.rect = bounds; - return new HelpBubbleView(&test_delegate_, anchor_params, - std::move(params)); + return HelpBubbleView::Create(&test_delegate_, anchor_params, + std::move(params)); } - HelpBubbleView* CreateHelpBubbleView(base::RepeatingClosure button_callback) { + HelpBubbleViewInfo CreateHelpBubbleView( + base::RepeatingClosure button_callback) { HelpBubbleParams params; params.body_text = u"To X, do Y"; params.arrow = HelpBubbleArrow::kTopRight; @@ -107,6 +109,10 @@ return CreateHelpBubbleView(std::move(params)); } + static HelpBubbleView* GetBubbleView(const HelpBubbleViewInfo& info) { + return views::AsViewClass(info.bubble_view); + } + test::TestHelpBubbleDelegate test_delegate_; raw_ptr view_; std::unique_ptr widget_; @@ -127,9 +133,9 @@ button2.text = u"button2"; params.buttons.emplace_back(std::move(button2)); - HelpBubbleView* const bubble = CreateHelpBubbleView(std::move(params)); - EXPECT_EQ(HelpBubbleView::kMaxWidthDip, bubble->GetPreferredSize().width()); - bubble->GetWidget()->Close(); + const auto info = CreateHelpBubbleView(std::move(params)); + EXPECT_EQ(HelpBubbleView::kMaxWidthDip, + info.bubble_view->GetPreferredSize().width()); } TEST_F(HelpBubbleViewTest, ExpandedMaxWidth) { @@ -147,39 +153,35 @@ button2.text = u"button2"; params.buttons.emplace_back(std::move(button2)); - HelpBubbleView* const bubble = CreateHelpBubbleView(std::move(params)); - EXPECT_GT(bubble->GetPreferredSize().width(), HelpBubbleView::kMaxWidthDip); - bubble->GetWidget()->Close(); + const auto info = CreateHelpBubbleView(std::move(params)); + EXPECT_GT(info.bubble_view->GetPreferredSize().width(), + HelpBubbleView::kMaxWidthDip); } TEST_F(HelpBubbleViewTest, CallButtonCallback_Mouse) { UNCALLED_MOCK_CALLBACK(base::RepeatingClosure, mock_callback); - HelpBubbleView* const bubble = CreateHelpBubbleView(mock_callback.Get()); + const auto info = CreateHelpBubbleView(mock_callback.Get()); // Simulate clicks on dismiss button. EXPECT_CALL_IN_SCOPE( mock_callback, Run, views::test::InteractionTestUtilSimulatorViews::PressButton( - bubble->GetDefaultButtonForTesting(), + GetBubbleView(info)->GetDefaultButtonForTesting(), ui::test::InteractionTestUtil::InputType::kMouse)); - - bubble->GetWidget()->Close(); } TEST_F(HelpBubbleViewTest, CallButtonCallback_Keyboard) { UNCALLED_MOCK_CALLBACK(base::RepeatingClosure, mock_callback); - HelpBubbleView* const bubble = CreateHelpBubbleView(mock_callback.Get()); + const auto info = CreateHelpBubbleView(mock_callback.Get()); // Simulate clicks on dismiss button. EXPECT_CALL_IN_SCOPE( mock_callback, Run, views::test::InteractionTestUtilSimulatorViews::PressButton( - bubble->GetDefaultButtonForTesting(), + GetBubbleView(info)->GetDefaultButtonForTesting(), ui::test::InteractionTestUtil::InputType::kKeyboard)); - - bubble->GetWidget()->Close(); } TEST_F(HelpBubbleViewTest, StableButtonOrder) { @@ -206,9 +208,10 @@ button3.is_default = false; params.buttons.push_back(std::move(button3)); - auto* bubble = new HelpBubbleView( + const auto info = HelpBubbleView::Create( &test_delegate_, internal::HelpBubbleAnchorParams{view_.get()}, std::move(params)); + auto* const bubble = GetBubbleView(info); EXPECT_EQ(kButton1Text, bubble->GetNonDefaultButtonForTesting(0)->GetText()); EXPECT_EQ(kButton2Text, bubble->GetDefaultButtonForTesting()->GetText()); EXPECT_EQ(kButton3Text, bubble->GetNonDefaultButtonForTesting(1)->GetText()); @@ -223,8 +226,8 @@ gfx::Rect anchor_bounds = widget_bounds; anchor_bounds.Inset(50); - HelpBubbleView* const bubble = - CreateHelpBubbleView(std::move(params), anchor_bounds); + const auto info = CreateHelpBubbleView(std::move(params), anchor_bounds); + auto* const bubble = GetBubbleView(info); // CreateHelpBubbleView() will trigger an asynchronous autosize task. views::test::RunScheduledLayout(bubble->GetWidget()); @@ -250,8 +253,8 @@ gfx::Rect anchor_bounds = widget_bounds; anchor_bounds.Inset(50); - HelpBubbleView* const bubble = - CreateHelpBubbleView(std::move(params), anchor_bounds); + const auto info = CreateHelpBubbleView(std::move(params), anchor_bounds); + auto* const bubble = GetBubbleView(info); // CreateHelpBubbleView() will trigger an asynchronous autosize task. views::test::RunScheduledLayout(bubble->GetWidget()); @@ -328,9 +331,9 @@ std::unique_ptr CreateHelpBubble( HelpBubbleParams params, ui::TrackedElement* element) { - HelpBubbleView* const bubble_view = - CreateHelpBubbleView(std::move(params), element->GetScreenBounds()); - return base::WrapUnique(new HelpBubbleViews(bubble_view, element)); + return base::WrapUnique(new HelpBubbleViews( + CreateHelpBubbleView(std::move(params), element->GetScreenBounds()), + element)); } void SetUp() override { diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_views.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_views.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_views.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_views.cc 2026-04-27 20:03:22.000000000 +0000 @@ -34,14 +34,24 @@ DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleViews) -HelpBubbleViews::HelpBubbleViews( - views::BubbleDialogDelegateView* help_bubble_view, - ui::TrackedElement* anchor_element) - : help_bubble_view_(help_bubble_view), anchor_element_(anchor_element) { - CHECK(help_bubble_view); - CHECK(help_bubble_view->GetWidget()); - CHECK(anchor_element); - scoped_observation_.Observe(help_bubble_view->GetWidget()); +HelpBubbleViews::HelpBubbleViews(HelpBubbleViewInfo info, + ui::TrackedElement* anchor_element) + : help_bubble_widget_(std::move(info.widget)), + help_bubble_view_(info.bubble_view), + anchor_element_(anchor_element) { + CHECK(help_bubble_widget_); + CHECK(help_bubble_view_); + CHECK(anchor_element_); + CHECK_EQ(help_bubble_widget_.get(), help_bubble_view_->GetWidget()); + + // If the anchor is a view, ensure it gets the proper state. + if (auto* const anchor = GetAnchorView()) { + anchor->SetProperty(kHasInProductHelpPromoKey, true); + MaybeApplyAttentionStateToTrackedElement(anchor); + } + + help_bubble_widget_->MakeCloseSynchronous(base::BindOnce( + &HelpBubbleViews::OnHelpBubbleClosing, base::Unretained(this))); anchor_hidden_subscription_ = ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback( @@ -56,8 +66,9 @@ } HelpBubbleViews::~HelpBubbleViews() { - // Needs to be called here while we still have access to HelpBubbleViews- - // specific logic. + // Needs to be called here while we still have access to class-specific logic. + // Safe to call even if `Close()` has already been called; see the + // implementation of `Close()`. Close(CloseReason::kBubbleDestroyed); } @@ -109,40 +120,38 @@ return true; } - auto* const anchor = help_bubble_view_->GetAnchorView(); - if (!anchor) { - return false; - } - bool set_focus = false; - if (anchor->GetViewAccessibility().IsAccessibilityFocusable()) { + + if (auto* const anchor = GetAnchorView()) { + if (anchor->GetViewAccessibility().IsAccessibilityFocusable()) { #if BUILDFLAG(IS_MAC) - // Mac does not automatically pass activation on focus, so we have to do it - // manually. - anchor->GetWidget()->Activate(); + // Mac does not automatically pass activation on focus, so we have to do + // it manually. + anchor->GetWidget()->Activate(); #else - // Focus the anchor. We can't request focus for an accessibility-only view - // until we turn on keyboard accessibility for its focus manager. - anchor->GetFocusManager()->SetKeyboardAccessible(true); + // Focus the anchor. We can't request focus for an accessibility-only view + // until we turn on keyboard accessibility for its focus manager. + anchor->GetFocusManager()->SetKeyboardAccessible(true); #endif - anchor->RequestFocus(); - set_focus = true; - } else if (views::IsViewClass(anchor)) { - // An AccessiblePaneView can receive focus, but is not necessarily itself - // accessibility focusable. Use the built-in functionality for focusing - // elements of AccessiblePaneView instead. + anchor->RequestFocus(); + set_focus = true; + } else if (views::IsViewClass(anchor)) { + // An AccessiblePaneView can receive focus, but is not necessarily itself + // accessibility focusable. Use the built-in functionality for focusing + // elements of AccessiblePaneView instead. #if BUILDFLAG(IS_MAC) - // Mac does not automatically pass activation on focus, so we have to do it - // manually. - anchor->GetWidget()->Activate(); + // Mac does not automatically pass activation on focus, so we have to do + // it manually. + anchor->GetWidget()->Activate(); #else - // You can't focus an accessible pane if it's already in accessibility - // mode, so avoid doing that; the SetPaneFocus() call will go back into - // accessibility navigation mode. - anchor->GetFocusManager()->SetKeyboardAccessible(false); + // You can't focus an accessible pane if it's already in accessibility + // mode, so avoid doing that; the SetPaneFocus() call will go back into + // accessibility navigation mode. + anchor->GetFocusManager()->SetKeyboardAccessible(false); #endif - set_focus = - static_cast(anchor)->SetPaneFocus(nullptr); + set_focus = static_cast(anchor)->SetPaneFocus( + nullptr); + } } return set_focus; @@ -180,36 +189,42 @@ help_bubble_view_->GetWidget()->IsActive(); } -void HelpBubbleViews::MaybeResetAnchorView() { - if (!help_bubble_view_) { +void HelpBubbleViews::DestroyWidget() { + if (!has_widget()) { return; } - auto* const anchor_view = help_bubble_view_->GetAnchorView(); - if (!anchor_view) { - return; - } - anchor_view->SetProperty(kHasInProductHelpPromoKey, false); - MaybeRemoveAttentionStateFromTrackedElement(anchor_view); -} -void HelpBubbleViews::CloseBubbleImpl() { anchor_hidden_subscription_ = base::CallbackListSubscription(); anchor_bounds_changed_subscription_ = base::CallbackListSubscription(); - scoped_observation_.Reset(); - MaybeResetAnchorView(); - // Reset the anchor view. Closing the widget could cause callbacks which could - // theoretically destroy `this`, so - auto* const help_bubble_view = help_bubble_view_.get(); + // Maybe clean up anchor state. + if (auto* anchor_view = GetAnchorView()) { + anchor_view->SetProperty(kHasInProductHelpPromoKey, false); + MaybeRemoveAttentionStateFromTrackedElement(anchor_view); + } + help_bubble_view_ = nullptr; anchor_element_ = nullptr; - if (help_bubble_view && help_bubble_view->GetWidget()) { - help_bubble_view->GetWidget()->Close(); - } + help_bubble_widget_.reset(); +} + +bool HelpBubbleViews::Close(CloseReason reason) { + // All of these are no-ops if called a second time. + auto on_close = BeginClose(reason); + DestroyWidget(); + return on_close.is_valid(); +} + +views::View* HelpBubbleViews::GetAnchorView() { + return help_bubble_view_ ? help_bubble_view_->GetAnchorView() : nullptr; } -void HelpBubbleViews::OnWidgetDestroying(views::Widget* widget) { - Close(CloseReason::kBubbleElementDestroyed); +void HelpBubbleViews::OnHelpBubbleClosing( + views::Widget::ClosedReason closed_reason) { + // At this point, everything is cleared and `this` may be safely deleted. + // Call `Close()` [again]; this is a no-op if the help bubble is already + // marked as closing. + Close(CloseReason::kBubbleDestroyed); } void HelpBubbleViews::OnElementHidden(ui::TrackedElement* element) { @@ -245,81 +260,66 @@ ui::TrackedElement* anchor_element, std::optional accept_button_action, std::optional cancel_button_action) - : HelpBubbleViews(bubble, anchor_element), + : HelpBubbleViews(HelpBubbleViewInfo(std::move(widget), bubble), + anchor_element), CustomHelpBubble(ui), - help_bubble_widget_(std::move(widget)), accept_button_action_(accept_button_action), cancel_button_action_(cancel_button_action) { - CHECK(help_bubble_widget_); - // Help bubbles should not close on deactivate. bubble->set_close_on_deactivate(false); // Help bubbles should always send "ESC Pressed" on escape key, not cancel. bubble->set_esc_should_cancel_dialog_override(false); - - bubble->GetWidget()->MakeCloseSynchronous(base::BindOnce( - &CustomHelpBubbleViews::OnHelpBubbleClosing, base::Unretained(this))); - - // Custom help bubble should always have an anchor view. - auto* const anchor_view = bubble->GetAnchorView(); - anchor_view->SetProperty(user_education::kHasInProductHelpPromoKey, true); - user_education::MaybeApplyAttentionStateToTrackedElement(anchor_view); -} - -CustomHelpBubbleViews::~CustomHelpBubbleViews() { - // Ensure that all closing of help bubbles goes through the same logic path. - // - // Due to upstream logic in HelpBubbleViews, `OnHelpBubbleClosing()` ends up - // getting called in a state where the widget cannot correctly be destroyed, - // leading to a CHECK(). - // - // This will be unnecessary when HelpBubbleViews is migrated to ownership mode - // CLIENT_OWNS_WIDGET. - if (help_bubble_widget_) { - help_bubble_widget_->CloseWithReason( - views::Widget::ClosedReason::kUnspecified); - } } +// Note that the `Close()` call in the base-class destructor will not trigger +// `OnHelpBubbleClosing()` for this class; however, since the close reason is +// "unspecified" there is no additional action to be taken in this class' +// override, so it's safe to just call it there. +CustomHelpBubbleViews::~CustomHelpBubbleViews() = default; + void CustomHelpBubbleViews::OnHelpBubbleClosing( views::Widget::ClosedReason reason) { - // The calls below could also destroy `this`, so save off widget in a local. - // This both guarantees that the widget will get properly destroyed at the end - // of this method (as is required by `MakeCloseSynchronous()`) and also - // prevents re-entrancy in the destructor as `help_bubble_widget_` will be - // null. - std::unique_ptr widget = std::move(help_bubble_widget_); - - if (auto* const ui = custom_bubble_ui()) { - switch (reason) { - case views::Widget::ClosedReason::kAcceptButtonClicked: - if (accept_button_action_) { - ui->NotifyUserAction(*accept_button_action_); - } - break; - - case views::Widget::ClosedReason::kCancelButtonClicked: - if (cancel_button_action_) { - ui->NotifyUserAction(*cancel_button_action_); - } - break; - - case views::Widget::ClosedReason::kCloseButtonClicked: - case views::Widget::ClosedReason::kEscKeyPressed: - ui->NotifyUserAction(UserAction::kCancel); - break; - - case views::Widget::ClosedReason::kLostFocus: - case views::Widget::ClosedReason::kUnspecified: - // Do nothing. - break; - } + // If called during teardown, ignore. + if (!has_widget()) { + return; } - // This is required when responding to `OnHelpBubbleClosing()`; the widget - // must be destroyed before this method returns. - widget.reset(); + std::optional action; + CloseReason actual_reason = CloseReason::kProgrammaticallyClosed; + + switch (reason) { + case views::Widget::ClosedReason::kAcceptButtonClicked: + action = accept_button_action_; + break; + + case views::Widget::ClosedReason::kCancelButtonClicked: + action = cancel_button_action_; + break; + + case views::Widget::ClosedReason::kCloseButtonClicked: + case views::Widget::ClosedReason::kEscKeyPressed: + action = UserAction::kCancel; + break; + + case views::Widget::ClosedReason::kLostFocus: + case views::Widget::ClosedReason::kUnspecified: + actual_reason = CloseReason::kBubbleDestroyed; + break; + } + + auto on_close = BeginClose(actual_reason); + + // The following call may result in `this` being destroyed. + auto weak_this = GetWeakPtr(); + if (auto* const ui = custom_bubble_ui(); ui && action) { + ui->NotifyUserAction(*action); + } + + // If `this` survived the call, destroy the widget. + if (weak_this) { + DestroyWidget(); + } } } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_views.h chromium-147.0.7727.137/components/user_education/views/help_bubble_views.h --- chromium-147.0.7727.116/components/user_education/views/help_bubble_views.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_views.h 2026-04-27 20:03:22.000000000 +0000 @@ -9,11 +9,11 @@ #include #include "base/callback_list.h" -#include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "components/user_education/common/help_bubble/custom_help_bubble.h" #include "components/user_education/common/help_bubble/help_bubble.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/interaction/element_tracker.h" #include "ui/color/color_id.h" @@ -23,6 +23,10 @@ #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" +namespace ash { +class HelpBubbleFactoryViewsAsh; +} + namespace user_education { // Views-specific implementation of the help bubble. @@ -57,31 +61,32 @@ static views::BubbleBorder::Arrow TranslateArrow(HelpBubbleArrow arrow); + // HelpBubble: + bool Close(CloseReason reason) override; + protected: - HelpBubbleViews(views::BubbleDialogDelegateView* help_bubble_view, - ui::TrackedElement* anchor_element); + HelpBubbleViews(HelpBubbleViewInfo info, ui::TrackedElement* anchor_element); - // HelpBubble: - void CloseBubbleImpl() override; + bool has_widget() const { return help_bubble_widget_ != nullptr; } + + void DestroyWidget(); + + virtual void OnHelpBubbleClosing(views::Widget::ClosedReason closed_reason); + + views::View* GetAnchorView(); private: friend class HelpBubbleFactoryViews; friend class HelpBubbleFactoryMac; friend class HelpBubbleViewsTest; friend class HelpBubbleViewsCustomBubbleTest; - - // Clean up properties on the anchor view, if applicable. - void MaybeResetAnchorView(); - - // views::WidgetObserver: - void OnWidgetDestroying(views::Widget* widget) override; + friend ash::HelpBubbleFactoryViewsAsh; void OnElementHidden(ui::TrackedElement* element); void OnElementBoundsChanged(ui::TrackedElement* element); + std::unique_ptr help_bubble_widget_; raw_ptr help_bubble_view_; - base::ScopedObservation - scoped_observation_{this}; // Track the anchor element to determine if/when it goes away. raw_ptr anchor_element_ = nullptr; @@ -96,8 +101,6 @@ // anchor view. Necessary for e.g. WebUI elements, which can be scrolled or // moved within the web page. base::CallbackListSubscription anchor_bounds_changed_subscription_; - - base::WeakPtrFactory weak_ptr_factory_{this}; }; // Help bubble that wraps a custom help bubble view. @@ -143,9 +146,8 @@ std::optional cancel_button_action); private: - void OnHelpBubbleClosing(views::Widget::ClosedReason closed_reason); + void OnHelpBubbleClosing(views::Widget::ClosedReason closed_reason) override; - std::unique_ptr help_bubble_widget_; std::optional accept_button_action_; std::optional cancel_button_action_; }; diff -Nru chromium-147.0.7727.116/components/user_education/views/help_bubble_views_unittest.cc chromium-147.0.7727.137/components/user_education/views/help_bubble_views_unittest.cc --- chromium-147.0.7727.116/components/user_education/views/help_bubble_views_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/views/help_bubble_views_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -8,13 +8,16 @@ #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" +#include "build/build_config.h" #include "components/user_education/common/help_bubble/custom_help_bubble.h" #include "components/user_education/test/test_custom_help_bubble_view.h" +#include "components/user_education/views/help_bubble_view_info.h" #include "components/user_education/views/help_bubble_views_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/element_tracker.h" #include "ui/base/interaction/expect_call_in_scope.h" +#include "ui/base/interaction/interaction_sequence_test_util.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" @@ -86,23 +89,27 @@ contents_view_->RemoveChildViewT(anchor); } - test::TestCustomHelpBubbleView* CreateBubble() { + HelpBubbleViewInfo CreateBubble() { auto bubble = std::make_unique( anchor_view_, views::BubbleBorder::TOP_RIGHT); auto* const result = bubble.get(); - auto* const widget = - views::BubbleDialogDelegateView::CreateBubble(std::move(bubble)); + auto* const widget = views::BubbleDialogDelegateView::CreateBubble( + std::move(bubble), views::Widget::InitParams::CLIENT_OWNS_WIDGET); widget->Show(); - return result; + return HelpBubbleViewInfo(base::WrapUnique(widget), result); } views::View* anchor_view() const { return anchor_view_; } - auto BuildHelpBubble(views::BubbleDialogDelegateView* bubble) { - CHECK(bubble); + auto BuildHelpBubble(HelpBubbleViewInfo info) { return base::WrapUnique(new HelpBubbleViews( - bubble, views::ElementTrackerViews::GetInstance()->GetElementForView( - anchor_view_))); + std::move(info), + views::ElementTrackerViews::GetInstance()->GetElementForView( + anchor_view_))); + } + + test::TestCustomHelpBubbleView* GetBubble(const HelpBubbleViewInfo& info) { + return views::AsViewClass(info.bubble_view); } private: @@ -112,8 +119,9 @@ }; TEST_F(HelpBubbleViewsCustomBubbleTest, CreateHelpBubble) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); ASSERT_EQ(bubble, help_bubble->bubble_view_for_testing()); ASSERT_EQ(bubble->GetWidget()->GetWindowBoundsInScreen(), help_bubble->GetBoundsInScreen()); @@ -123,8 +131,8 @@ } TEST_F(HelpBubbleViewsCustomBubbleTest, CloseHelpBubble) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto help_bubble = BuildHelpBubble(std::move(info)); UNCALLED_MOCK_CALLBACK(ui::ElementTracker::Callback, hidden); auto subscription = ui::ElementTracker::GetElementTracker() @@ -134,49 +142,68 @@ EXPECT_CALL(hidden, Run).WillOnce([&](ui::TrackedElement*) { run_loop.Quit(); }); - help_bubble->Close(); + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); run_loop.Run(); EXPECT_FALSE(help_bubble->is_open()); EXPECT_EQ(nullptr, help_bubble->bubble_view_for_testing()); } -TEST_F(HelpBubbleViewsCustomBubbleTest, CloseHelpBubbleWidget) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); +// TODO(https://crbug.com/502638609): In Fuchsia, this test causes an unrelated +// crash on the GPU thread. +#if BUILDFLAG(IS_FUCHSIA) +#define MAYBE_CloseHelpBubbleWidget DISABLED_CloseHelpBubbleWidget +#else +#define MAYBE_CloseHelpBubbleWidget CloseHelpBubbleWidget +#endif +TEST_F(HelpBubbleViewsCustomBubbleTest, MAYBE_CloseHelpBubbleWidget) { + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); - base::RunLoop run_loop; - EXPECT_CALL(closed, Run).WillOnce([&](HelpBubble*, HelpBubble::CloseReason) { - run_loop.Quit(); - }); - bubble->GetWidget()->Close(); - run_loop.Run(); + // Wait for the help bubble to close. + EXPECT_ASYNC_CALLS_IN_SCOPE_2( + closing, + Run(help_bubble.get(), HelpBubble::CloseReason::kBubbleDestroyed), closed, + Run(HelpBubble::CloseReason::kBubbleDestroyed), + bubble->GetWidget()->Close()); EXPECT_FALSE(help_bubble->is_open()); EXPECT_EQ(nullptr, help_bubble->bubble_view_for_testing()); } -TEST_F(HelpBubbleViewsCustomBubbleTest, AnchorViewHidden) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); +// TODO(https://crbug.com/502638609): In Fuchsia, this test causes an unrelated +// crash on the GPU thread. +#if BUILDFLAG(IS_FUCHSIA) +#define MAYBE_AnchorViewHidden DISABLED_AnchorViewHidden +#else +#define MAYBE_AnchorViewHidden AnchorViewHidden +#endif +TEST_F(HelpBubbleViewsCustomBubbleTest, MAYBE_AnchorViewHidden) { + auto info = CreateBubble(); + auto help_bubble = BuildHelpBubble(std::move(info)); + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); - base::RunLoop run_loop; - EXPECT_CALL(closed, Run).WillOnce([&](HelpBubble*, HelpBubble::CloseReason) { - run_loop.Quit(); - }); - anchor_view()->SetVisible(false); - run_loop.Run(); + // Wait for the help bubble to close. + EXPECT_ASYNC_CALLS_IN_SCOPE_2( + closing, Run(help_bubble.get(), HelpBubble::CloseReason::kAnchorHidden), + closed, Run(HelpBubble::CloseReason::kAnchorHidden), + anchor_view()->SetVisible(false)); EXPECT_FALSE(help_bubble->is_open()); EXPECT_EQ(nullptr, help_bubble->bubble_view_for_testing()); } TEST_F(HelpBubbleViewsCustomBubbleTest, CustomBubbleSendsCancel) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); UNCALLED_MOCK_CALLBACK(CustomHelpBubbleUi::UserActionCallback, user_action); auto subscription = bubble->AddUserActionCallback(user_action.Get()); EXPECT_CALL_IN_SCOPE( @@ -186,8 +213,9 @@ } TEST_F(HelpBubbleViewsCustomBubbleTest, CustomBubbleSendsDismiss) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); UNCALLED_MOCK_CALLBACK(CustomHelpBubbleUi::UserActionCallback, user_action); auto subscription = bubble->AddUserActionCallback(user_action.Get()); EXPECT_CALL_IN_SCOPE( @@ -197,8 +225,9 @@ } TEST_F(HelpBubbleViewsCustomBubbleTest, CustomBubbleSendsAction) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); UNCALLED_MOCK_CALLBACK(CustomHelpBubbleUi::UserActionCallback, user_action); auto subscription = bubble->AddUserActionCallback(user_action.Get()); EXPECT_CALL_IN_SCOPE( @@ -208,8 +237,9 @@ } TEST_F(HelpBubbleViewsCustomBubbleTest, CustomBubbleSendsSnooze) { - auto* const bubble = CreateBubble(); - auto help_bubble = BuildHelpBubble(bubble); + auto info = CreateBubble(); + auto* const bubble = GetBubble(info); + auto help_bubble = BuildHelpBubble(std::move(info)); UNCALLED_MOCK_CALLBACK(CustomHelpBubbleUi::UserActionCallback, user_action); auto subscription = bubble->AddUserActionCallback(user_action.Get()); EXPECT_CALL_IN_SCOPE( diff -Nru chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler.cc chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler.cc --- chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler.cc 2026-04-27 20:03:22.000000000 +0000 @@ -177,7 +177,7 @@ HelpBubbleHandlerBase::~HelpBubbleHandlerBase() { for (auto& [id, data] : element_data_) { if (data.help_bubble) { - data.help_bubble->Close(); + data.help_bubble->Close(HelpBubble::CloseReason::kBubbleDestroyed); } } } @@ -213,7 +213,7 @@ LOG(WARNING) << "A help bubble is already being shown for " << identifier; auto weak_ptr = weak_ptr_factory_.GetWeakPtr(); if (data.help_bubble) { - data.help_bubble->Close(); + data.help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); if (!weak_ptr) { return nullptr; } @@ -263,8 +263,9 @@ if (it == element_data_.end()) { NOTREACHED() << "Identifier " << anchor_id << " was never registered."; } - if (!it->second.closing) + if (!it->second.closing) { GetClient()->HideHelpBubble(anchor_id.GetName()); + } it->second.help_bubble = nullptr; it->second.params.reset(); // If this anchor element was only considered visible because it still had a @@ -440,11 +441,13 @@ if (!weak_ptr) return; - if (data->help_bubble) - data->help_bubble->Close(); + if (data->help_bubble) { + data->help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); + } - if (!weak_ptr) + if (!weak_ptr) { return; + } data->closing = false; } @@ -487,7 +490,7 @@ // This could also theoretically trigger callbacks. if (data->help_bubble) { - data->help_bubble->Close(); + data->help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed); } if (!weak_ptr) @@ -527,14 +530,14 @@ return; } DCHECK(!it->second.external_bubble_subscription); - it->second.external_bubble_subscription = help_bubble->AddOnCloseCallback( + it->second.external_bubble_subscription = help_bubble->AddOnClosingCallback( base::BindOnce(&HelpBubbleHandlerBase::OnFloatingHelpBubbleClosed, weak_ptr_factory_.GetWeakPtr(), anchor_id)); } void HelpBubbleHandlerBase::OnFloatingHelpBubbleClosed( ui::ElementIdentifier anchor_id, - HelpBubble* help_bubble, + const HelpBubble* help_bubble, HelpBubble::CloseReason) { const auto it = element_data_.find(anchor_id); if (it == element_data_.end()) { diff -Nru chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler.h chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler.h --- chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler.h 2026-04-27 20:03:22.000000000 +0000 @@ -142,7 +142,7 @@ void OnFloatingHelpBubbleCreated(ui::ElementIdentifier anchor_id, HelpBubble* help_bubble); void OnFloatingHelpBubbleClosed(ui::ElementIdentifier anchor_id, - HelpBubble* help_bubble, + const HelpBubble* help_bubble, HelpBubble::CloseReason); void OnWebContentsVisibilityChanged(std::optional visibility); diff -Nru chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler_unittest.cc chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler_unittest.cc --- chromium-147.0.7727.116/components/user_education/webui/help_bubble_handler_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/webui/help_bubble_handler_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -322,7 +322,8 @@ EXPECT_CALL( test_handler_->mock(), HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName())); - EXPECT_TRUE(help_bubble->Close()); + EXPECT_TRUE( + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); EXPECT_CALL(test_handler_->mock(), HideHelpBubble).Times(0); EXPECT_FALSE(help_bubble->is_open()); @@ -413,7 +414,8 @@ EXPECT_CALL( test_handler_->mock(), HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName())); - EXPECT_TRUE(help_bubble->Close()); + EXPECT_TRUE( + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); EXPECT_FALSE(help_bubble->is_open()); } @@ -443,7 +445,8 @@ EXPECT_CALL( test_handler_->mock(), HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName())); - EXPECT_TRUE(help_bubble->Close()); + EXPECT_TRUE( + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(HelpBubbleHandlerTest, ExternalHelpBubbleUpdated) { @@ -478,7 +481,8 @@ EXPECT_CALL( test_handler_->mock(), ExternalHelpBubbleUpdated(element->identifier().GetName(), false)); - EXPECT_TRUE(help_bubble->Close()); + EXPECT_TRUE( + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenVisibilityChanges) { @@ -513,6 +517,7 @@ } TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenClosedRemotely) { + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); tracked_element_handler()->TrackedElementVisibilityChanged( @@ -528,12 +533,15 @@ EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_)); auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble( element, std::move(params)); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); EXPECT_TRUE( test_handler_->IsHelpBubbleShowingForTesting(element->identifier())); - EXPECT_CALL_IN_SCOPE( - closed, Run, + EXPECT_CALLS_IN_SCOPE_2( + closing, + Run(help_bubble.get(), HelpBubble::CloseReason::kProgrammaticallyClosed), + closed, Run(HelpBubble::CloseReason::kProgrammaticallyClosed), handler()->HelpBubbleClosed( kHelpBubbleHandlerTestElementIdentifier.GetName(), help_bubble::mojom::HelpBubbleClosedReason::kPageChanged)); @@ -556,6 +564,7 @@ // Asserts that closing the HelpBubble handle to a bubble instance destroys // the bubble. TEST_F(HelpBubbleHandlerTest, DestroyBubbleWrapperClosesHelpBubble) { + UNCALLED_MOCK_CALLBACK(HelpBubble::ClosingCallback, closing); UNCALLED_MOCK_CALLBACK(HelpBubble::ClosedCallback, closed); tracked_element_handler()->TrackedElementVisibilityChanged( @@ -571,12 +580,16 @@ EXPECT_CALL(test_handler_->mock(), ShowHelpBubble(testing::_)); auto help_bubble = help_bubble_factory_registry_.CreateHelpBubble( element, std::move(params)); - auto subscription = help_bubble->AddOnCloseCallback(closed.Get()); + auto subscription1 = help_bubble->AddOnClosingCallback(closing.Get()); + auto subscription2 = help_bubble->AddOnClosedCallback(closed.Get()); EXPECT_CALL( test_handler_->mock(), HideHelpBubble(kHelpBubbleHandlerTestElementIdentifier.GetName())); - EXPECT_CALL_IN_SCOPE(closed, Run, help_bubble.reset()); + EXPECT_CALLS_IN_SCOPE_2( + closing, + Run(help_bubble.get(), HelpBubble::CloseReason::kBubbleDestroyed), closed, + Run(HelpBubble::CloseReason::kBubbleDestroyed), help_bubble.reset()); } TEST_F(HelpBubbleHandlerTest, HelpBubbleClosedWhenClosedByUserCallsDismiss) { @@ -882,7 +895,9 @@ EXPECT_TRUE(help_bubble->is_open()); EXPECT_CALL(test_handler_->mock(), HideHelpBubble(testing::_)); - EXPECT_CALL_IN_SCOPE(element_hidden, Run, help_bubble->Close()); + EXPECT_CALL_IN_SCOPE( + element_hidden, Run, + help_bubble->Close(HelpBubble::CloseReason::kProgrammaticallyClosed)); } } // namespace user_education diff -Nru chromium-147.0.7727.116/components/user_education/webui/help_bubble_webui.cc chromium-147.0.7727.137/components/user_education/webui/help_bubble_webui.cc --- chromium-147.0.7727.116/components/user_education/webui/help_bubble_webui.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/webui/help_bubble_webui.cc 2026-04-27 20:03:22.000000000 +0000 @@ -37,8 +37,12 @@ return handler_->context(); } -void HelpBubbleWebUI::CloseBubbleImpl() { - handler_->OnHelpBubbleClosing(anchor_id_); +bool HelpBubbleWebUI::Close(CloseReason reason) { + auto on_close = BeginClose(reason); + if (on_close.is_valid()) { + handler_->OnHelpBubbleClosing(anchor_id_); + } + return on_close.is_valid(); } DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleWebUI) diff -Nru chromium-147.0.7727.116/components/user_education/webui/help_bubble_webui.h chromium-147.0.7727.137/components/user_education/webui/help_bubble_webui.h --- chromium-147.0.7727.116/components/user_education/webui/help_bubble_webui.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/user_education/webui/help_bubble_webui.h 2026-04-27 20:03:22.000000000 +0000 @@ -46,7 +46,7 @@ ui::ElementIdentifier anchor_id); // HelpBubble: - void CloseBubbleImpl() override; + bool Close(CloseReason reason) override; const raw_ptr handler_; const ui::ElementIdentifier anchor_id_; diff -Nru chromium-147.0.7727.116/components/viz/service/frame_sinks/compositor_frame_sink_support.cc chromium-147.0.7727.137/components/viz/service/frame_sinks/compositor_frame_sink_support.cc --- chromium-147.0.7727.116/components/viz/service/frame_sinks/compositor_frame_sink_support.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/frame_sinks/compositor_frame_sink_support.cc 2026-04-27 20:03:22.000000000 +0000 @@ -1718,10 +1718,15 @@ directive.sequence_id()); } + // Subtle: the iterator `it` may be invalidated after the call to + // `CacheSurfaceAnimationManager` due to new SurfaceAnimationManager being + // created and put into the map. This can happen due to the FrameSinkObserver + // getting notified of the view transition saving surface being activated. if (directive.maybe_cross_frame_sink()) { frame_sink_manager_->CacheSurfaceAnimationManager( directive.transition_token(), std::move(it->second)); - view_transition_token_to_animation_manager_.erase(it); + view_transition_token_to_animation_manager_.erase( + directive.transition_token()); } } diff -Nru chromium-147.0.7727.116/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc chromium-147.0.7727.137/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc --- chromium-147.0.7727.116/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -34,6 +34,7 @@ #include "components/viz/common/surfaces/surface_info.h" #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h" #include "components/viz/service/surfaces/surface.h" +#include "components/viz/service/transitions/surface_animation_manager.h" #include "components/viz/test/begin_frame_args_test.h" #include "components/viz/test/compositor_frame_helpers.h" #include "components/viz/test/fake_compositor_frame_sink_client.h" @@ -298,6 +299,12 @@ return !support->view_transition_token_to_animation_manager_.empty(); } + void OnSaveTransitionDirectiveProcessed( + CompositorFrameSinkSupport* support, + const CompositorFrameTransitionDirective& directive) { + support->OnSaveTransitionDirectiveProcessed(directive); + } + protected: TestSharedImageInterfaceProvider shared_image_interface_provider_; std::unique_ptr now_src_; @@ -2727,4 +2734,70 @@ kVideoInterval); } +// Regression test for https://crbug.com/497047552. +TEST_F(CompositorFrameSinkSupportTestBase, + OnSaveTransitionDirectiveProcessedReentryUAF) { + // This test ensures we don't crash when processing a transition completion + // that triggers a chain reaction of new transition requests. + // + // 1. We set up an initial transition. + blink::ViewTransitionToken token_x; + + // Create Surface A by submitting a frame. + SubmitCompositorFrameWithResources({}); + Surface* surface_a = support_->GetLastCreatedSurfaceForTesting(); + ASSERT_TRUE(surface_a); + + // 2. We submit a request to save the transition for our token. + auto save_directive = CompositorFrameTransitionDirective::CreateSave( + token_x, /*maybe_cross_frame_sink=*/true, 1, {}, {}, false); + ProcessCompositorFrameTransitionDirective(support_.get(), save_directive, + surface_a); + ASSERT_TRUE(SupportHasSurfaceAnimationManager(support_.get())); + + // 3. We prepare a second frame that's waiting on this transition. This + // frame is special because it also asks for many more new transitions. + LocalSurfaceId local_surface_id_b( + local_surface_id_.parent_sequence_number() + 1, + local_surface_id_.embed_token()); + + CompositorFrame frame_2 = + MakeDefaultInteractiveCompositorFrame(kBeginFrameSourceId); + // Add the requirement that token_x must be finished before this frame can + // be displayed. + frame_2.metadata.transition_directives.push_back( + CompositorFrameTransitionDirective::CreateAnimate(token_x, true, 2, + true)); + + // Add MANY more new transition requests. This will force the system to + // reorganize its internal storage when they are processed. + for (int i = 0; i < 100; ++i) { + frame_2.metadata.transition_directives.push_back( + CompositorFrameTransitionDirective::CreateSave( + blink::ViewTransitionToken(), true, 100 + i, {}, {}, false)); + } + + // Submit the second frame. It will stay "pending" because it is waiting + // for the first transition to complete. + support_->SubmitCompositorFrame(local_surface_id_b, std::move(frame_2)); + + SurfaceId surface_id_b(support_->frame_sink_id(), local_surface_id_b); + Surface* surface_b = + manager_->surface_manager()->GetSurfaceForId(surface_id_b); + ASSERT_TRUE(surface_b); + ASSERT_TRUE(surface_b->HasPendingFrame()); + + // 4. We now signal that the first transition is complete. + // This triggers a chain reaction: + // - The system marks the first transition as finished. + // - This allows the second frame to finally become active. + // - As the second frame becomes active, it registers all its many new + // transition requests. + // - These new requests cause the internal storage to be reallocated, + // invalidating current iterators. + // - Finally, we finish the cleanup for the original transition. If we were + // still using an outdated reference to the storage, we would crash here. + OnSaveTransitionDirectiveProcessed(support_.get(), save_directive); +} + } // namespace viz diff -Nru chromium-147.0.7727.116/components/viz/service/frame_sinks/frame_sink_manager_impl.cc chromium-147.0.7727.137/components/viz/service/frame_sinks/frame_sink_manager_impl.cc --- chromium-147.0.7727.116/components/viz/service/frame_sinks/frame_sink_manager_impl.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/frame_sinks/frame_sink_manager_impl.cc 2026-04-27 20:03:22.000000000 +0000 @@ -1525,4 +1525,13 @@ return gpu_service_; } +bool FrameSinkManagerImpl::IsChildOf(const FrameSinkId& parent, + const FrameSinkId& child) const { + auto it = frame_sink_source_map_.find(parent); + if (it == frame_sink_source_map_.end()) { + return false; + } + return it->second.children.contains(child); +} + } // namespace viz diff -Nru chromium-147.0.7727.116/components/viz/service/frame_sinks/frame_sink_manager_impl.h chromium-147.0.7727.137/components/viz/service/frame_sinks/frame_sink_manager_impl.h --- chromium-147.0.7727.116/components/viz/service/frame_sinks/frame_sink_manager_impl.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/frame_sinks/frame_sink_manager_impl.h 2026-04-27 20:03:22.000000000 +0000 @@ -279,6 +279,10 @@ void RemoveHitTestRegionObserver(HitTestRegionObserver* observer) override; const DisplayHitTestQueryMap& GetDisplayHitTestQuery() const override; + // HitTestAggregatorDelegate and HitTestManager::Delegate implementation: + bool IsChildOf(const FrameSinkId& parent, + const FrameSinkId& child) const override; + // CompositorFrameSinkSupport, hierarchy, and BeginFrameSource can be // registered and unregistered in any order with respect to each other. // diff -Nru chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator.cc chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator.cc --- chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4,13 +4,19 @@ #include "components/viz/service/hit_test/hit_test_aggregator.h" +#include "base/feature_list.h" #include "base/trace_event/trace_event.h" +#include "base/types/expected.h" #include "components/viz/common/hit_test/hit_test_region_list.h" #include "components/viz/service/hit_test/hit_test_aggregator_delegate.h" #include "components/viz/service/surfaces/latest_local_surface_id_lookup_delegate.h" #include "ui/gfx/geometry/rect_conversions.h" namespace viz { +namespace { +// TODO (crbug.com/495852034): Remove once M150 hits Stable. +BASE_FEATURE(kRejectInvalidChildRegions, base::FEATURE_ENABLED_BY_DEFAULT); +} // namespace HitTestAggregator::HitTestAggregator( const HitTestManager* hit_test_manager, @@ -106,8 +112,16 @@ for (const auto& region : hit_test_region_list->regions) { if (region_index >= hit_test_data_capacity_ - 1) break; - region_index = AppendRegion(region_index, region); - DCHECK_EQ(referenced_child_regions_.size(), 1u); + // In the call to `AppendRegion` the invalid child regions are not + // added to `hit_test_data_`. We need to complete the processing of the + // root itself. Otherwise we will have an invalid map to send to the Viz + // host. + if (auto result = + AppendRegion(region_index, region, surface_id.frame_sink_id()); + result.has_value()) { + region_index = result.value(); + DCHECK_EQ(referenced_child_regions_.size(), 1u); + } } referenced_child_regions_.erase(referenced_child_regions_.begin()); @@ -119,8 +133,10 @@ child_count); } -size_t HitTestAggregator::AppendRegion(size_t region_index, - const HitTestRegion& region) { +base::expected +HitTestAggregator::AppendRegion(size_t region_index, + const HitTestRegion& region, + const FrameSinkId& submitting_frame_sink_id) { size_t parent_index = region_index++; if (region_index >= hit_test_data_capacity_ - 1) { if (hit_test_data_capacity_ > max_region_size_) { @@ -136,8 +152,17 @@ gfx::Transform transform = region.transform; if (region.flags & HitTestRegionFlags::kHitTestChildSurface) { - if (referenced_child_regions_.count(region.frame_sink_id)) + if (referenced_child_regions_.count(region.frame_sink_id)) { + // This detects potential cycles within the HitTestRegions. We want to + // keep the single entry as a valid region. return parent_index; + } + + // Verify that the child is actually a child of the submitting frame sink. + if (base::FeatureList::IsEnabled(kRejectInvalidChildRegions) && + !delegate_->IsChildOf(submitting_frame_sink_id, region.frame_sink_id)) { + return base::unexpected(AggregationError::INVALID_CHILD_REGION); + } referenced_child_regions_.insert(region.frame_sink_id); @@ -187,9 +212,17 @@ } for (const auto& child_region : hit_test_region_list->regions) { - region_index = AppendRegion(region_index, child_region); - if (region_index >= hit_test_data_capacity_ - 1) + if (auto result = + AppendRegion(region_index, child_region, region.frame_sink_id); + result.has_value()) { + region_index = result.value(); + if (region_index >= hit_test_data_capacity_ - 1) { + break; + } + } else { + // Invalid child region break; + } } } referenced_child_regions_.erase(region.frame_sink_id); @@ -198,7 +231,7 @@ int32_t child_count = region_index - parent_index - 1; SetRegionAt(parent_index, region.frame_sink_id, flags, reasons, region.rect, transform, child_count); - return region_index; + return base::ok(region_index); } void HitTestAggregator::SetRegionAt(size_t index, diff -Nru chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator.h chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator.h --- chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator.h 2026-04-27 20:03:22.000000000 +0000 @@ -9,6 +9,7 @@ #include #include "base/memory/raw_ptr.h" +#include "base/types/expected.h" #include "components/viz/common/hit_test/aggregated_hit_test_region.h" #include "components/viz/common/hit_test/hit_test_query.h" #include "components/viz/common/quads/aggregated_render_pass.h" @@ -58,6 +59,11 @@ private: friend class TestHitTestAggregator; + // TODO(jonross): add the capacity error to this and handle that. + enum class AggregationError { + INVALID_CHILD_REGION, + }; + void SendHitTestData(); // Appends the root element to the AggregatedHitTestRegion array. @@ -65,8 +71,12 @@ // Appends a |region| to the HitTestRegionList structure to recursively // build the tree. |region_index| indicates the current index of the end of - // the list. - size_t AppendRegion(size_t region_index, const HitTestRegion& region); + // the list. |submitting_frame_sink_id| is the FrameSinkId that submitted the + // HitTestRegionList containing |region|. + base::expected AppendRegion( + size_t region_index, + const HitTestRegion& region, + const FrameSinkId& submitting_frame_sink_id); // Populates the HitTestRegion element at the given element |index|. void SetRegionAt(size_t index, diff -Nru chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator_delegate.h chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator_delegate.h --- chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator_delegate.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator_delegate.h 2026-04-27 20:03:22.000000000 +0000 @@ -5,6 +5,8 @@ #ifndef COMPONENTS_VIZ_SERVICE_HIT_TEST_HIT_TEST_AGGREGATOR_DELEGATE_H_ #define COMPONENTS_VIZ_SERVICE_HIT_TEST_HIT_TEST_AGGREGATOR_DELEGATE_H_ +#include + #include "components/viz/common/hit_test/aggregated_hit_test_region.h" namespace viz { @@ -16,6 +18,10 @@ const FrameSinkId& frame_sink_id, const std::vector& hit_test_data) = 0; + // Check's the hierarchy of FrameSinkIds as registered by the Viz Host. + virtual bool IsChildOf(const FrameSinkId& parent, + const FrameSinkId& child) const = 0; + protected: // The dtor is protected so that HitTestAggregator does not take ownership. virtual ~HitTestAggregatorDelegate() = default; diff -Nru chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator_unittest.cc chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator_unittest.cc --- chromium-147.0.7727.116/components/viz/service/hit_test/hit_test_aggregator_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/hit_test/hit_test_aggregator_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -164,6 +164,8 @@ if (depth > 0) { hit_test_region.flags = HitTestRegionFlags::kHitTestChildSurface; + frame_sink_manager()->RegisterFrameSinkHierarchy( + surface_id.frame_sink_id(), hit_test_region.frame_sink_id); client_id = CreateAndSubmitHitTestRegionListWith8Children(client_id, depth - 1); } else { @@ -353,6 +355,11 @@ SurfaceId c1_surface_id = MakeSurfaceId(kDisplayClientId + 1); SurfaceId c2_surface_id = MakeSurfaceId(kDisplayClientId + 2); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c1_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c2_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -438,6 +445,9 @@ SurfaceId e_surface_id = MakeSurfaceId(kDisplayClientId); SurfaceId c_surface_id = MakeSurfaceId(kDisplayClientId + 1); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -518,6 +528,9 @@ SurfaceId e_surface_id = MakeSurfaceId(kDisplayClientId); SurfaceId c_surface_id = MakeSurfaceId(kDisplayClientId + 1); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -601,6 +614,13 @@ SurfaceId a_surface_id = MakeSurfaceId(kDisplayClientId + 2); SurfaceId b_surface_id = MakeSurfaceId(kDisplayClientId + 3); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + c_surface_id.frame_sink_id(), a_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + c_surface_id.frame_sink_id(), b_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -728,6 +748,13 @@ SurfaceId c2_surface_id = MakeSurfaceId(kDisplayClientId + 2); SurfaceId c3_surface_id = MakeSurfaceId(kDisplayClientId + 3); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c1_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + c1_surface_id.frame_sink_id(), c2_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + c2_surface_id.frame_sink_id(), c3_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -848,6 +875,9 @@ SurfaceId e_surface_id = MakeSurfaceId(kDisplayClientId); SurfaceId c_surface_id = MakeSurfaceId(kDisplayClientId + 1); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -956,6 +986,9 @@ SurfaceId e_surface_id = MakeSurfaceId(kDisplayClientId); SurfaceId c_surface_id = MakeSurfaceId(kDisplayClientId + 1); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -1041,6 +1074,13 @@ SurfaceId c2_surface_id = MakeSurfaceId(kDisplayClientId + 2); SurfaceId d1_surface_id = MakeSurfaceId(kDisplayClientId + 3); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c1_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + e_surface_id.frame_sink_id(), c2_surface_id.frame_sink_id()); + frame_sink_manager()->RegisterFrameSinkHierarchy( + c1_surface_id.frame_sink_id(), d1_surface_id.frame_sink_id()); + HitTestRegionList e_hit_test_region_list; e_hit_test_region_list.flags = HitTestRegionFlags::kHitTestMine; e_hit_test_region_list.bounds.SetRect(0, 0, 1024, 768); @@ -1175,4 +1215,68 @@ EXPECT_NE(last_index, aggregator->GetLastSubmitHitTestRegionListIndex()); } +TEST_F(HitTestAggregatorTest, InvalidChildFrameSinkIdRejected) { + // Setup: Parent (P) and Child (C). + // Browser registers C as child of P. + // BUT we will also have an unrelated Sibling (S). + FrameSinkId parent_id(1, 1); + FrameSinkId child_id(1, 2); + FrameSinkId sibling_id(1, 3); + + // Register legitimate hierarchy. + frame_sink_manager()->RegisterFrameSinkId(parent_id, true); + frame_sink_manager()->RegisterFrameSinkId(sibling_id, true); + frame_sink_manager()->RegisterFrameSinkHierarchy(parent_id, child_id); + frame_sink_manager()->RegisterFrameSinkHierarchy(parent_id, sibling_id); + + // Now submit hit-test data. + HitTestRegionList hit_test_region_list; + hit_test_region_list.bounds = gfx::Rect(0, 0, 100, 100); + + // Invalid data for a sibling. + HitTestRegion spoofed_sibling; + spoofed_sibling.frame_sink_id = sibling_id; + spoofed_sibling.flags = HitTestRegionFlags::kHitTestChildSurface | + HitTestRegionFlags::kHitTestMine; + spoofed_sibling.rect = gfx::Rect(50, 50, 50, 50); + hit_test_region_list.regions.push_back(spoofed_sibling); + + SurfaceId child_surface_id( + child_id, LocalSurfaceId(1, 1, base::UnguessableToken::Create())); + + // Register child in delegate. + local_surface_id_lookup_delegate()->SetSurfaceIdMap(child_surface_id); + + // Submit CompositorFrame which also submits hit-test data. + // This should be accepted, because the full FrameSink hierarchy arrives + // asynchronously. We can only validate during aggregation. + auto child_support = std::make_unique( + nullptr, frame_sink_manager(), child_id, /*is_root=*/false); + child_support->SubmitCompositorFrame(child_surface_id.local_surface_id(), + MakeDefaultCompositorFrame(), + std::move(hit_test_region_list), 0); + + // We should have a list active before aggregation. + const HitTestRegionList* active_list = + hit_test_manager()->GetActiveHitTestRegionList( + local_surface_id_lookup_delegate(), child_id); + EXPECT_NE(nullptr, active_list); + + // Aggregation will detect the non-child and omit that region. + // The root region itself is still added. + hit_test_aggregator()->Aggregate(child_surface_id); + EXPECT_EQ(1, hit_test_aggregator()->GetRegionCount()); + + // The HitTestRegionList for child_id should still exist in HitTestManager, + // AND its regions should still be there, so that it can be aggregated if + // the hierarchy changes. + const HitTestRegionList* post_aggregation_list = + hit_test_manager()->GetActiveHitTestRegionList( + local_surface_id_lookup_delegate(), child_id); + EXPECT_NE(nullptr, post_aggregation_list); + EXPECT_FALSE(post_aggregation_list->regions.empty()); + EXPECT_EQ(1u, post_aggregation_list->regions.size()); + EXPECT_EQ(sibling_id, post_aggregation_list->regions[0].frame_sink_id); +} + } // namespace viz diff -Nru chromium-147.0.7727.116/components/viz/service/surfaces/surface_manager.cc chromium-147.0.7727.137/components/viz/service/surfaces/surface_manager.cc --- chromium-147.0.7727.116/components/viz/service/surfaces/surface_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/surfaces/surface_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -166,7 +166,9 @@ void SurfaceManager::InvalidateFrameSinkId(const FrameSinkId& frame_sink_id) { auto it = frame_sink_id_to_allocation_groups_.find(frame_sink_id); if (it != frame_sink_id_to_allocation_groups_.end()) { - for (SurfaceAllocationGroup* group : it->second) { + // Copy allocation group vector since it can be modified while iterating. + auto allocation_groups = it->second; + for (SurfaceAllocationGroup* group : allocation_groups) { group->WillNotRegisterNewSurfaces(); } } diff -Nru chromium-147.0.7727.116/components/viz/service/surfaces/surface_unittest.cc chromium-147.0.7727.137/components/viz/service/surfaces/surface_unittest.cc --- chromium-147.0.7727.116/components/viz/service/surfaces/surface_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/components/viz/service/surfaces/surface_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -2,12 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/viz/service/surfaces/surface.h" + #include #include "base/functional/bind.h" #include "base/run_loop.h" #include "base/test/scoped_feature_list.h" #include "base/test/simple_test_tick_clock.h" +#include "base/unguessable_token.h" #include "cc/test/scheduler_test_common.h" #include "components/viz/common/features.h" #include "components/viz/common/frame_sinks/copy_output_result.h" @@ -16,7 +19,6 @@ #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h" #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h" #include "components/viz/service/surfaces/pending_copy_output_request.h" -#include "components/viz/service/surfaces/surface.h" #include "components/viz/test/begin_frame_args_test.h" #include "components/viz/test/compositor_frame_helpers.h" #include "components/viz/test/fake_external_begin_frame_source.h" @@ -398,5 +400,66 @@ EXPECT_TRUE(surface->activation_dependencies().empty()); } +// Checks that modifying surface activation group vector while iterating through +// the existing entries doesn't cause problems. +TEST_F(SurfaceTest, RentrantSurfaceActivationGroups) { + SurfaceManager* surface_manager = frame_sink_manager_.surface_manager(); + + auto will_invalidate_support = std::make_unique( + nullptr, &frame_sink_manager_, kArbitraryFrameSinkId, /*is_root=*/false); + auto y1_support = std::make_unique( + nullptr, &frame_sink_manager_, FrameSinkId(3, 1), /*is_root=*/false); + auto y2_support = std::make_unique( + nullptr, &frame_sink_manager_, FrameSinkId(4, 1), /*is_root=*/false); + + // Builds a frame with dependencies and a long deadline for activation. + auto build_frame = [](std::vector deps, + std::vector refs) { + return CompositorFrameBuilder() + .AddRenderPass(gfx::Rect(10, 10), gfx::Rect(10, 10)) + .SetActivationDependencies(std::move(deps)) + .SetReferencedSurfaces(std::move(refs)) + .SetDeadline(FrameDeadline(base::TimeTicks::Now(), 10000u, + base::Milliseconds(16), false)) + .Build(); + }; + + // Each of these SurfaceIds are from the same FrameSinkId but have different + // embed_tokens therefore different SurfaceAllocationGroups. + std::vector malicious_refs; + malicious_refs.reserve(100); + for (int i = 0; i < 100; i++) { + SurfaceId sid(kArbitraryFrameSinkId, + LocalSurfaceId(i, 1, base::UnguessableToken::Create())); + malicious_refs.emplace_back(sid); + } + + // A SurfaceAllocationGroup for `dep1` is added immediately but groups for + // `malicious_refs` are only added once the CompositorFrame activates. + SurfaceId dep1(kArbitraryFrameSinkId, + LocalSurfaceId(1, 1, base::UnguessableToken::Create())); + LocalSurfaceId y1_lsid(1, 1, base::UnguessableToken::Create()); + y1_support->SubmitCompositorFrame(y1_lsid, + build_frame({dep1}, malicious_refs)); + + SurfaceId dep2(kArbitraryFrameSinkId, + LocalSurfaceId(2, 1, base::UnguessableToken::Create())); + LocalSurfaceId y2_lsid(1, 1, base::UnguessableToken::Create()); + y2_support->SubmitCompositorFrame(y2_lsid, build_frame({dep2}, {})); + + // There will be two SurfaceAllocationGroups for `kArbitraryFrameSinkId` at + // this point. WillNotRegisterNewSurfaces() will be called on each allocation + // groups, first for `y1_lsid` group which activates the surface and add 100 + // more SurfaceAllocationGroups to the vector. This tests that modifying + // the vector being iterated doesn't cause problems. + surface_manager->InvalidateFrameSinkId(kArbitraryFrameSinkId); + + // Both y1 and y2 surfaces are now active. + EXPECT_TRUE(surface_manager->GetSurfaceForId( + SurfaceId(y1_support->frame_sink_id(), y1_lsid))); + EXPECT_TRUE(surface_manager->GetSurfaceForId( + SurfaceId(y2_support->frame_sink_id(), y2_lsid))); +} + } // namespace } // namespace viz diff -Nru chromium-147.0.7727.116/content/browser/devtools/devtools_renderer_channel.cc chromium-147.0.7727.137/content/browser/devtools/devtools_renderer_channel.cc --- chromium-147.0.7727.116/content/browser/devtools/devtools_renderer_channel.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/content/browser/devtools/devtools_renderer_channel.cc 2026-04-27 20:03:22.000000000 +0000 @@ -187,8 +187,6 @@ case blink::mojom::DevToolsExecutionContextType::kDedicatedWorker: { // WorkerDevToolsAgentHost for dedicated workers is already created in the // browser process. - CHECK(content::DevToolsAgentHost::GetForId( - devtools_worker_token.ToString())); DedicatedWorkerDevToolsAgentHost* dedicated_worker_agent_host = WorkerDevToolsManager::GetInstance().GetDevToolsHostFromToken( devtools_worker_token); diff -Nru chromium-147.0.7727.116/content/browser/download/mhtml_generation_manager.cc chromium-147.0.7727.137/content/browser/download/mhtml_generation_manager.cc --- chromium-147.0.7727.116/content/browser/download/mhtml_generation_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/content/browser/download/mhtml_generation_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -12,6 +12,7 @@ #include "base/files/file.h" #include "base/functional/bind.h" #include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" #include "base/stl_util.h" @@ -250,8 +251,8 @@ // choices. MHTMLGenerationParams params_; - // The IDs of frames that still need to be processed. - base::queue pending_frame_tree_node_ids_; + // The documents that still need to be processed. + base::queue> pending_render_frame_hosts_; // Identifies a frame to which we've sent through // MhtmlFileWriter::SerializeAsMHTML but for which we didn't yet process @@ -330,13 +331,12 @@ // Skip inner tree placeholder nodes. continue; } - pending_frame_tree_node_ids_.push(node->frame_tree_node_id()); + pending_render_frame_hosts_.push(node->current_frame_host()->GetWeakPtr()); } // Main frame needs to be processed first. - DCHECK(!pending_frame_tree_node_ids_.empty()); - DCHECK(FrameTreeNode::GloballyFindByID(pending_frame_tree_node_ids_.front()) - ->parent() == nullptr); + CHECK(!pending_render_frame_hosts_.empty()); + CHECK(pending_render_frame_hosts_.front().get()->is_main_frame()); // Save off any extra data. auto* extra_parts = static_cast( @@ -367,16 +367,14 @@ } mojom::MhtmlSaveStatus MHTMLGenerationManager::Job::SendToNextRenderFrame() { - DCHECK(browser_file_.IsValid()); - DCHECK(!pending_frame_tree_node_ids_.empty()); + CHECK(browser_file_.IsValid()); + CHECK(!pending_render_frame_hosts_.empty()); - FrameTreeNodeId frame_tree_node_id = pending_frame_tree_node_ids_.front(); - pending_frame_tree_node_ids_.pop(); - - FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id); - if (!ftn) // The contents went away. + RenderFrameHostImpl* rfh = pending_render_frame_hosts_.front().get(); + pending_render_frame_hosts_.pop(); + if (!rfh) { // The contents went away. return mojom::MhtmlSaveStatus::kFrameNoLongerExists; - RenderFrameHost* rfh = ftn->current_frame_host(); + } if (writer_) { // If we reached here, means the work for previous frame is done, so it is @@ -401,7 +399,8 @@ // Send a Mojo request to Renderer to serialize its frame. DCHECK(frame_tree_node_id_of_busy_frame_.is_null()); - frame_tree_node_id_of_busy_frame_ = frame_tree_node_id; + frame_tree_node_id_of_busy_frame_ = + rfh->frame_tree_node()->frame_tree_node_id(); auto response_callback = base::BindOnce(&Job::SerializeAsMHTMLResponse, weak_factory_.GetWeakPtr()); @@ -540,7 +539,7 @@ // If current operation is successful and there are more frames to process, // let save status depend on the result of sending the next request. if (save_status == mojom::MhtmlSaveStatus::kSuccess && - !pending_frame_tree_node_ids_.empty() && CurrentFrameDone()) { + !pending_render_frame_hosts_.empty() && CurrentFrameDone()) { save_status = SendToNextRenderFrame(); } @@ -553,8 +552,9 @@ // Otherwise report completion if there are no more frames to process // and Job is done processing the current frame. - if (pending_frame_tree_node_ids_.empty() && CurrentFrameDone()) + if (pending_render_frame_hosts_.empty() && CurrentFrameDone()) { Finalize(mojom::MhtmlSaveStatus::kSuccess); + } } bool MHTMLGenerationManager::Job::CurrentFrameDone() const { diff -Nru chromium-147.0.7727.116/content/browser/renderer_host/navigator.cc chromium-147.0.7727.137/content/browser/renderer_host/navigator.cc --- chromium-147.0.7727.116/content/browser/renderer_host/navigator.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/content/browser/renderer_host/navigator.cc 2026-04-27 20:03:22.000000000 +0000 @@ -526,9 +526,16 @@ #endif // BUILDFLAG(IS_ANDROID) // Run tasks that must execute just before the commit. + base::WeakPtr weak_rfh = render_frame_host->GetWeakPtr(); delegate_->DidNavigateAnyFramePreCommit(navigation_request.get(), was_within_same_document); + // NOTE: the pre commit tasks may result in the destruction of the render + // frame host, in which case we should exit this method early. + if (!weak_rfh) { + return; + } + if (ui::PageTransitionIsMainFrame(params.transition)) { delegate_->DidNavigateMainFramePreCommit(navigation_request.get(), was_within_same_document); diff -Nru chromium-147.0.7727.116/content/browser/web_contents/web_contents_impl.cc chromium-147.0.7727.137/content/browser/web_contents/web_contents_impl.cc --- chromium-147.0.7727.116/content/browser/web_contents/web_contents_impl.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/content/browser/web_contents/web_contents_impl.cc 2026-04-27 20:03:22.000000000 +0000 @@ -7807,7 +7807,13 @@ } if (should_exit_fullscreen) { + base::WeakPtr weak_this = weak_factory_.GetWeakPtr(); ExitFullscreen(false); + + // If `this` gets destructed due to ExitFullscreen(), we need to exit early. + if (!weak_this) { + return; + } CancelKeyboardLock(keyboard_lock_widget_); } } diff -Nru chromium-147.0.7727.116/debian/changelog chromium-147.0.7727.137/debian/changelog --- chromium-147.0.7727.116/debian/changelog 2026-04-24 00:10:12.000000000 +0000 +++ chromium-147.0.7727.137/debian/changelog 2026-04-29 08:36:38.000000000 +0000 @@ -1,3 +1,57 @@ +chromium (147.0.7727.137-1~deb13u1) trixie-security; urgency=high + + [ Andres Salomon ] + * New upstream security release. + - CVE-2026-7363: Use after free in Canvas. Reported by heapracer. + - CVE-2026-7361: Use after free in iOS. Reported by Google. + - CVE-2026-7344: Use after free in Accessibility. Reported by Google. + - CVE-2026-7343: Use after free in Views. Reported by Google. + - CVE-2026-7333: Use after free in GPU. + Reported by c6eed09fc8b174b0f3eebedcceb1e792. + - CVE-2026-7360: Insufficient validation of untrusted input in Compositing. + Reported by Google. + - CVE-2026-7359: Use after free in ANGLE. Reported by Google. + - CVE-2026-7358: Use after free in Animation. Reported by Google. + - CVE-2026-7334: Use after free in Views. Reported by Batuhan Eşref KOÇ. + - CVE-2026-7357: Use after free in GPU. Reported by Google. + - CVE-2026-7356: Use after free in Navigation. Reported by Google. + - CVE-2026-7354: Out of bounds read and write in Angle. Reported by Google. + - CVE-2026-7353: Heap buffer overflow in Skia. Reported by Google. + - CVE-2026-7352: Use after free in Media. Reported by Google. + - CVE-2026-7351: Race in MHTML. Reported by Google. + - CVE-2026-7350: Use after free in WebMIDI. Reported by Google. + - CVE-2026-7349: Use after free in Cast. Reported by Google. + - CVE-2026-7348: Use after free in Codecs. Reported by Google. + - CVE-2026-7335: Use after free in media. + Reported by Jungwoo Lee (@physicube) and Wongi Lee (@_qwerty_po). + - CVE-2026-7336: Use after free in WebRTC. Reported by Mozilla. + - CVE-2026-7337: Type Confusion in V8. Reported by q@calif.io. + - CVE-2026-7347: Use after free in Chromoting. Reported by Google. + - CVE-2026-7346: Inappropriate implementation in Tint. Reported by Google. + - CVE-2026-7345: Insufficient validation of untrusted input in Feedback. + Reported by Google. + - CVE-2026-7338: Use after free in Cast. Reported by Krace. + - CVE-2026-7342: Use after free in WebView. Reported by Google. + - CVE-2026-7341: Use after free in WebRTC. Reported by Google. + - CVE-2026-7339: Heap buffer overflow in WebRTC. + Reported by c6eed09fc8b174b0f3eebedcceb1e792. + - CVE-2026-7340: Integer overflow in ANGLE. + Reported by 86ac1f1587b71893ed2ad792cd7dde32. + - CVE-2026-7355: Use after free in Media. Reported by Google. + + [ Jianfeng Liu ] + * d/patches: + - upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch: + Fixes upstream issue https://crbug.com/501115509. This issue is + introduced in v147, and unfortunately the fix won't get into v147. This + issue affects both vaapi and v4l2 decoding under ozone wayland. + - fixes/enable-widevine-on-arm64-linux-platform.patch: Enable widevine + support on arm64. There is no official support for widevine on arm64 + linux while there are libwidevine binaries extracted from chromeos, + which can work on linux (closes: #1052440). + + -- Andres Salomon Wed, 29 Apr 2026 04:36:38 -0400 + chromium (147.0.7727.116-1~deb13u1) trixie-security; urgency=high [ Andres Salomon ] diff -Nru chromium-147.0.7727.116/debian/patches/fixes/enable-widevine-on-arm64-linux-platform.patch chromium-147.0.7727.137/debian/patches/fixes/enable-widevine-on-arm64-linux-platform.patch --- chromium-147.0.7727.116/debian/patches/fixes/enable-widevine-on-arm64-linux-platform.patch 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/debian/patches/fixes/enable-widevine-on-arm64-linux-platform.patch 2026-04-29 08:36:38.000000000 +0000 @@ -0,0 +1,25 @@ +From abd301efade3b34762a7d977b803240ce4c910b5 Mon Sep 17 00:00:00 2001 +From: Jianfeng Liu +Date: Thu, 18 Jul 2024 11:10:21 +0800 +Subject: [PATCH] enable widevine on arm64 linux platform + +--- + third_party/widevine/cdm/widevine.gni | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/third_party/widevine/cdm/widevine.gni b/third_party/widevine/cdm/widevine.gni +index e31de0a552..b932be1e74 100644 +--- a/third_party/widevine/cdm/widevine.gni ++++ b/third_party/widevine/cdm/widevine.gni +@@ -27,7 +27,7 @@ if (is_chromeos && !is_chromeos_device) { + library_widevine_cdm_available = + (is_chromeos && + (target_cpu == "x64" || target_cpu == "arm" || target_cpu == "arm64")) || +- (target_os == "linux" && target_cpu == "x64") || ++ (target_os == "linux" && (target_cpu == "x64" || target_cpu == "arm64")) || + (target_os == "mac" && (target_cpu == "x64" || target_cpu == "arm64")) || + (target_os == "win" && + (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm64")) +-- +2.47.3 + diff -Nru chromium-147.0.7727.116/debian/patches/series chromium-147.0.7727.137/debian/patches/series --- chromium-147.0.7727.116/debian/patches/series 2026-04-09 15:16:50.000000000 +0000 +++ chromium-147.0.7727.137/debian/patches/series 2026-04-29 08:36:38.000000000 +0000 @@ -9,6 +9,7 @@ fixes/ps-print.patch fixes/widevine-locations.patch +fixes/enable-widevine-on-arm64-linux-platform.patch fixes/rust-clanglib.patch fixes/material-utils.patch fixes/gentoo-stylesheet.patch @@ -32,6 +33,7 @@ fixes/bytemuck.patch fixes/missing-dep.patch +upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch disable/tests.patch disable/tests-swiftshader.patch diff -Nru chromium-147.0.7727.116/debian/patches/upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch chromium-147.0.7727.137/debian/patches/upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch --- chromium-147.0.7727.116/debian/patches/upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/debian/patches/upstream/Fix-GL-native-pixmap-import-support-reset-in-GpuInit.patch 2026-04-29 08:36:38.000000000 +0000 @@ -0,0 +1,80 @@ +From 322bec355809269d85a8a5f277ce3e64a2e868d1 Mon Sep 17 00:00:00 2001 +From: Saifuddin Hitawala +Date: Wed, 22 Apr 2026 11:36:01 -0700 +Subject: [PATCH] Fix GL native pixmap import support reset in GpuInit + +In GpuInit::InitializeAndStartSandbox(), the support for GL native +pixmap import was being set on gpu_feature_info_ before being +overwritten by ComputeGpuFeatureInfo(). This caused these flags to +be reset to false, leading to media pipeline failures on certain +Wayland configurations. + +This CL fixes the issue by moving setting of these flags after the +block where gpu_feature_info_ is written by ComputeGpuFeatureInfo(). + +Bug: 501115509 +Change-Id: I9e18eacf949a2377dd98a2cce9cd43e0ae7cc2a5 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7776335 +Commit-Queue: Saifuddin Hitawala +Reviewed-by: Kyle Charbonneau +Reviewed-by: Mingjing Zhang +Cr-Commit-Position: refs/heads/main@{#1618991} +--- + gpu/ipc/service/gpu_init.cc | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc +index 5f017dc4e1..4a0532581e 100644 +--- a/gpu/ipc/service/gpu_init.cc ++++ b/gpu/ipc/service/gpu_init.cc +@@ -795,23 +795,6 @@ bool GpuInit::InitializeAndStartSandbox(base::CommandLine* command_line, + } + } + +-#if BUILDFLAG(IS_OZONE) +- // We need to get supported formats before sandboxing to avoid an known +- // issue which breaks the camera preview. (b/166850715) +- { +- TRACE_EVENT("gpu,startup", "ui::ozone::IsFormatSupportedForTexturing"); +- auto* surface_factory = +- ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone(); +- auto* gl_ozone = surface_factory->GetCurrentGLOzone(); +- if (gl_ozone) { +- gpu_feature_info_.supports_nv12_gl_native_pixmap = +- gl_ozone->CanImportNativePixmap(viz::MultiPlaneFormat::kNV12); +- gpu_feature_info_.supports_p010_gl_native_pixmap = +- gl_ozone->CanImportNativePixmap(viz::MultiPlaneFormat::kP010); +- } +- } +-#endif // BUILDFLAG(IS_OZONE) +- + #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + // Driver may create a compatibility profile context when collect graphics + // information on Linux platform. Try to collect graphics information +@@ -834,6 +817,23 @@ bool GpuInit::InitializeAndStartSandbox(base::CommandLine* command_line, + } + #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + ++#if BUILDFLAG(IS_OZONE) ++ // We need to get supported formats before sandboxing to avoid an known ++ // issue which breaks the camera preview. (b/166850715) ++ { ++ TRACE_EVENT("gpu,startup", "ui::ozone::CanImportNativePixmap"); ++ auto* surface_factory = ++ ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone(); ++ auto* gl_ozone = surface_factory->GetCurrentGLOzone(); ++ if (gl_ozone) { ++ gpu_feature_info_.supports_nv12_gl_native_pixmap = ++ gl_ozone->CanImportNativePixmap(viz::MultiPlaneFormat::kNV12); ++ gpu_feature_info_.supports_p010_gl_native_pixmap = ++ gl_ozone->CanImportNativePixmap(viz::MultiPlaneFormat::kP010); ++ } ++ } ++#endif // BUILDFLAG(IS_OZONE) ++ + if (gl_use_swiftshader_) { + AdjustInfoToSwiftShader(); + } +-- +2.54.0 + diff -Nru chromium-147.0.7727.116/extensions/common/BUILD.gn chromium-147.0.7727.137/extensions/common/BUILD.gn --- chromium-147.0.7727.116/extensions/common/BUILD.gn 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/extensions/common/BUILD.gn 2026-04-27 20:03:22.000000000 +0000 @@ -768,7 +768,10 @@ ] if (is_chromeos) { - sources += [ "manifest_handlers/file_handler_manifest_unittest.cc" ] + sources += [ + "manifest_handlers/file_handler_manifest_unittest.cc", + "manifest_handlers/input_components_handler_unittest.cc", + ] } data = [ diff -Nru chromium-147.0.7727.116/extensions/common/manifest_handlers/input_components_handler.cc chromium-147.0.7727.137/extensions/common/manifest_handlers/input_components_handler.cc --- chromium-147.0.7727.116/extensions/common/manifest_handlers/input_components_handler.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/extensions/common/manifest_handlers/input_components_handler.cc 2026-04-27 20:03:22.000000000 +0000 @@ -100,13 +100,19 @@ if (layouts_value) { for (size_t j = 0; j < layouts_value->size(); ++j) { const base::Value& layout = (*layouts_value)[j]; - if (!layout.is_string()) { + const std::string* layout_str = layout.GetIfString(); + if (!layout_str || + !base::ContainsOnlyChars(*layout_str, + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "_()-:")) { *error = ErrorUtils::FormatErrorMessageUTF16( errors::kInvalidInputComponentLayoutName, base::NumberToString(i), base::NumberToString(j)); return false; } - layouts.insert(layout.GetString()); + layouts.insert(*layout_str); } } diff -Nru chromium-147.0.7727.116/extensions/common/manifest_handlers/input_components_handler_unittest.cc chromium-147.0.7727.137/extensions/common/manifest_handlers/input_components_handler_unittest.cc --- chromium-147.0.7727.116/extensions/common/manifest_handlers/input_components_handler_unittest.cc 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/extensions/common/manifest_handlers/input_components_handler_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -0,0 +1,99 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/manifest_handlers/input_components_handler.h" + +#include +#include +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension_builder.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace errors = manifest_errors; + +class InputComponentsManifestTest : public ManifestTest { + protected: + std::u16string GetInvalidLayoutError(int component_index, int layout_index) { + return ErrorUtils::FormatErrorMessageUTF16( + errors::kInvalidInputComponentLayoutName, + base::NumberToString(component_index), + base::NumberToString(layout_index)); + } +}; + +TEST_F(InputComponentsManifestTest, ValidLayouts) { + base::DictValue manifest; + manifest.Set("name", "test"); + manifest.Set("version", "1"); + manifest.Set("manifest_version", 3); + + base::DictValue component; + component.Set("name", "test component"); + component.Set("id", "test_id"); + base::ListValue layouts; + layouts.Append("us"); + layouts.Append("us(intl)"); + layouts.Append("be"); + component.Set("layouts", std::move(layouts)); + + base::ListValue input_components; + input_components.Append(std::move(component)); + manifest.Set("input_components", std::move(input_components)); + + scoped_refptr extension = + ExtensionBuilder().SetManifest(std::move(manifest)).Build(); + + ASSERT_TRUE(extension.get()); + const std::vector* components = + InputComponents::GetInputComponents(extension.get()); + ASSERT_TRUE(components); + ASSERT_EQ(1u, components->size()); + EXPECT_EQ(3u, (*components)[0].layouts.size()); +} + +TEST_F(InputComponentsManifestTest, InvalidLayouts) { + const struct { + const char* layout; + } kInvalidTestCases[] = { + {"../../evil"}, {"us/../../evil"}, {"us(../../evil)"}, + {"us-../../evil"}, {"us$"}, {"us(dvo*ak)"}, + }; + + for (const auto& test_case : kInvalidTestCases) { + SCOPED_TRACE(test_case.layout); + base::DictValue manifest; + manifest.Set("name", "test"); + manifest.Set("version", "1"); + manifest.Set("manifest_version", 3); + + base::DictValue component; + component.Set("name", "test component"); + component.Set("id", "test_id"); + base::ListValue layouts; + layouts.Append(test_case.layout); + component.Set("layouts", std::move(layouts)); + + base::ListValue input_components; + input_components.Append(std::move(component)); + manifest.Set("input_components", std::move(input_components)); + + std::u16string error; + scoped_refptr extension = + Extension::Create(base::FilePath(), mojom::ManifestLocation::kInternal, + manifest, Extension::NO_FLAGS, &error); + EXPECT_FALSE(extension.get()); + EXPECT_EQ(GetInvalidLayoutError(0, 0), error); + } +} + +} // namespace extensions diff -Nru chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_factory.cc chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_factory.cc --- chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_factory.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_factory.cc 2026-04-27 20:03:22.000000000 +0000 @@ -850,28 +850,16 @@ !is_angle_metal && !is_skia_graphite; shared_image_caps.supports_r16_shared_images = is_angle_metal || is_skia_graphite; - if (context_state_) { -#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) - auto* surface_factory = - ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone(); - shared_image_caps.supports_ycbcr_nv12_sampling = - surface_factory->IsFormatSupportedForTexturing( - viz::MultiPlaneFormat::kNV12) && - IsNativeBufferSupported(viz::MultiPlaneFormat::kNV12, - gfx::BufferUsage::GPU_READ_CPU_READ_WRITE, - gpu_extra_info_); - shared_image_caps.supports_ycbcr_p010_sampling = - surface_factory->IsFormatSupportedForTexturing( - viz::MultiPlaneFormat::kP010); -#elif BUILDFLAG(IS_APPLE) - shared_image_caps.supports_ycbcr_nv12_sampling = true; - shared_image_caps.supports_ycbcr_p010_sampling = true; -#endif - } shared_image_caps.disable_r8_shared_images = workarounds_.r8_egl_images_broken; shared_image_caps.disable_webgpu_shared_images = workarounds_.disable_webgpu_shared_images; + if (context_state_) { + shared_image_caps.supports_ycbcr_nv12_sampling = + shared_image_manager_->SupportsNV12TextureSampling(); + shared_image_caps.supports_ycbcr_p010_sampling = + shared_image_manager_->SupportsP010TextureSampling(); + } if (!context_state_) { shared_image_caps.is_r16f_supported = false; } else if (is_skia_graphite || gr_context_type_ == GrContextType::kVulkan) { diff -Nru chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_manager.cc chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_manager.cc --- chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -39,6 +39,7 @@ #include "components/viz/common/gpu/vulkan_context_provider.h" #include "gpu/config/gpu_finch_features.h" #include "ui/ozone/public/ozone_platform.h" +#include "ui/ozone/public/surface_factory_ozone.h" #endif #if DCHECK_IS_ON() @@ -645,6 +646,49 @@ #else return false; #endif +} + +void SharedImageManager::QueryMultiplanarTextureSamplingSupport() { +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) + auto* ozone_platform = ui::OzonePlatform::GetInstance(); + auto* surface_factory = ozone_platform->GetSurfaceFactoryOzone(); + supports_ycbcr_nv12_sampling_ = + surface_factory->IsFormatSupportedForTexturing( + viz::MultiPlaneFormat::kNV12) && + ozone_platform->IsNativePixmapConfigSupported( + viz::MultiPlaneFormat::kNV12, + gfx::BufferUsage::GPU_READ_CPU_READ_WRITE); + supports_ycbcr_p010_sampling_ = + surface_factory->IsFormatSupportedForTexturing( + viz::MultiPlaneFormat::kP010); + is_texture_sampling_queried_ = true; +#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) +} + +bool SharedImageManager::SupportsNV12TextureSampling() { +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) + if (!is_texture_sampling_queried_) { + QueryMultiplanarTextureSamplingSupport(); + } + return supports_ycbcr_nv12_sampling_; +#elif BUILDFLAG(IS_APPLE) + return true; +#else + return false; +#endif +} + +bool SharedImageManager::SupportsP010TextureSampling() { +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) + if (!is_texture_sampling_queried_) { + QueryMultiplanarTextureSamplingSupport(); + } + return supports_ycbcr_p010_sampling_; +#elif BUILDFLAG(IS_APPLE) + return true; +#else + return false; +#endif } } // namespace gpu diff -Nru chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_manager.h chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_manager.h --- chromium-147.0.7727.116/gpu/command_buffer/service/shared_image/shared_image_manager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/command_buffer/service/shared_image/shared_image_manager.h 2026-04-27 20:03:22.000000000 +0000 @@ -175,6 +175,9 @@ #endif bool SupportsScanoutImages(); + void QueryMultiplanarTextureSamplingSupport(); + bool SupportsNV12TextureSampling(); + bool SupportsP010TextureSampling(); // Returns the NativePixmap backing |mailbox|. Returns null if the SharedImage // doesn't exist or is not backed by a NativePixmap. The caller is not @@ -211,10 +214,16 @@ scoped_refptr io_runner_; #endif +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) + bool supports_ycbcr_nv12_sampling_ = false; + bool supports_ycbcr_p010_sampling_ = false; + bool is_texture_sampling_queried_ GUARDED_BY(lock_) = false; +#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) + #if BUILDFLAG(IS_OZONE) bool supports_overlays_on_ozone_ = false; scoped_refptr vulkan_context_provider_; -#endif +#endif // BUILDFLAG(IS_OZONE) THREAD_CHECKER(thread_checker_); }; diff -Nru chromium-147.0.7727.116/gpu/config/gpu_lists_version.h chromium-147.0.7727.137/gpu/config/gpu_lists_version.h --- chromium-147.0.7727.116/gpu/config/gpu_lists_version.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/config/gpu_lists_version.h 2026-04-27 20:03:22.000000000 +0000 @@ -3,6 +3,6 @@ #ifndef GPU_CONFIG_GPU_LISTS_VERSION_H_ #define GPU_CONFIG_GPU_LISTS_VERSION_H_ -#define GPU_LISTS_VERSION "dbcf1b1bfb506cc580859bcb5ff9460a8443af90" +#define GPU_LISTS_VERSION "68ba233a543d25e75c30f1228dd3bafa2da96937" #endif // GPU_CONFIG_GPU_LISTS_VERSION_H_ diff -Nru chromium-147.0.7727.116/gpu/webgpu/DAWN_VERSION chromium-147.0.7727.137/gpu/webgpu/DAWN_VERSION --- chromium-147.0.7727.116/gpu/webgpu/DAWN_VERSION 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/webgpu/DAWN_VERSION 2026-04-27 20:03:22.000000000 +0000 @@ -1 +1 @@ -ff7b4f6c5d964879b5f4356ef6e732adeed2f627 \ No newline at end of file +049880d58d6636a819168c00f44f8a4ed1e33e51 \ No newline at end of file diff -Nru chromium-147.0.7727.116/gpu/webgpu/dawn_commit_hash.h chromium-147.0.7727.137/gpu/webgpu/dawn_commit_hash.h --- chromium-147.0.7727.116/gpu/webgpu/dawn_commit_hash.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/gpu/webgpu/dawn_commit_hash.h 2026-04-27 20:03:22.000000000 +0000 @@ -3,6 +3,6 @@ #ifndef GPU_WEBGPU_DAWN_COMMIT_HASH_H_ #define GPU_WEBGPU_DAWN_COMMIT_HASH_H_ -#define DAWN_COMMIT_HASH "ff7b4f6c5d964879b5f4356ef6e732adeed2f627" +#define DAWN_COMMIT_HASH "049880d58d6636a819168c00f44f8a4ed1e33e51" #endif // GPU_WEBGPU_DAWN_COMMIT_HASH_H_ diff -Nru chromium-147.0.7727.116/media/filters/source_buffer_stream.cc chromium-147.0.7727.137/media/filters/source_buffer_stream.cc --- chromium-147.0.7727.116/media/filters/source_buffer_stream.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/filters/source_buffer_stream.cc 2026-04-27 20:03:22.000000000 +0000 @@ -1029,12 +1029,11 @@ } if (current_range->GetMemoryUsage() == 0) { - DCHECK_NE(current_range, selected_range_); - DCHECK(range_for_next_append_ == ranges_.end() || - range_for_next_append_->get() != current_range); - - // Delete |current_range| by popping it out of |ranges_|. - reverse_direction ? ranges_.pop_back() : ranges_.pop_front(); + CHECK_NE(current_range, selected_range_); + auto range_to_delete = + reverse_direction ? std::prev(ranges_.end()) : ranges_.begin(); + current_range = nullptr; + DeleteAndRemoveRange(&range_to_delete); } if (reverse_direction && new_range_for_append) { diff -Nru chromium-147.0.7727.116/media/filters/source_buffer_stream_unittest.cc chromium-147.0.7727.137/media/filters/source_buffer_stream_unittest.cc --- chromium-147.0.7727.116/media/filters/source_buffer_stream_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/filters/source_buffer_stream_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -5747,4 +5747,38 @@ EXPECT_TRUE(IsRangeListSorted()); } +TEST_F(SourceBufferStreamTest, GarbageCollectionUpdatesRangeForNextAppend) { + // Set memory limit to 10 buffers. + SetMemoryLimit(10); + + // 1. Append 10 buffers to create Range A [0, 90ms]. + NewCodedFrameGroupAppend("0K 10K 20K 30K 40K 50K 60K 70K 80K 90K"); + + // 2. Append 10 buffers to create Range B [1000ms, 1090ms]. + // This exceeds the memory limit and triggers GC, but Range A is kept because + // it was recently appended. + NewCodedFrameGroupAppend( + "1000K 1010K 1020K 1030K 1040K 1050K 1060K 1070K 1080K 1090K"); + + // 3. Start a new coded frame group that overlaps Range A. + // This sets range_for_next_append_ to Range A and + // last_appended_buffer_timestamp_ to kNoTimestamp. + stream_->OnStartOfCodedFrameGroup(base::Milliseconds(0)); + + // 4. Trigger Garbage Collection. + // We want to free enough data that Range A is deleted. + // Set memory limit very low so GC must evict something. + SetMemoryLimit(5); + + // Garbage collect with media time at Range B (1000ms). + // This should evict Range A from the front since it is far behind media time. + EXPECT_TRUE(GarbageCollect(base::Milliseconds(1000), 0)); + + // 5. Append data. If the bug exists, range_for_next_append_ is dangling and + // dereferencing it will cause a UAF or hit a CHECK. + // With the fix, range_for_next_append_ is reset to ranges_.end() when + // Range A is deleted. + AppendBuffers("0K 10K"); +} + } // namespace media diff -Nru chromium-147.0.7727.116/media/midi/midi_manager.cc chromium-147.0.7727.137/media/midi/midi_manager.cc --- chromium-147.0.7727.116/media/midi/midi_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/midi/midi_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -164,7 +164,7 @@ bool MidiManager::HasOpenSession() { base::AutoLock auto_lock(lock_); - return clients_.size() != 0u; + return !clients_.empty() || !pending_clients_.empty(); } void MidiManager::DispatchSendMidiData(MidiManagerClient* client, diff -Nru chromium-147.0.7727.116/media/midi/midi_manager_unittest.cc chromium-147.0.7727.137/media/midi/midi_manager_unittest.cc --- chromium-147.0.7727.116/media/midi/midi_manager_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/midi/midi_manager_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -219,6 +219,8 @@ base::WeakPtr factory() { return factory_; } + MidiService* service() { return service_.get(); } + private: base::test::TaskEnvironment env_; base::WeakPtr factory_; @@ -337,6 +339,31 @@ EXPECT_FALSE(test_future.IsReady()); } +TEST_F(MidiManagerTest, ReproduceLifecycleRace) { + base::test::TestFuture test_future; + std::unique_ptr client = + std::make_unique(test_future.GetCallback()); + + // Start a session. This will put the client in pending_clients_. + StartSession(client.get()); + ASSERT_TRUE(factory()->manager()); + EXPECT_EQ(1U, factory()->manager()->GetPendingClientCount()); + EXPECT_EQ(0U, factory()->manager()->GetClientCount()); + + // FIXED: HasOpenSession() now checks both clients_ and pending_clients_, + // so it should return true while initialization is ongoing. + EXPECT_TRUE(factory()->manager()->HasOpenSession()); + + // If we end the session now, EndSession calls HasOpenSession to decide if it + // should delete the manager. Since the client is still in pending_clients_ + // until removed, EndSession correctly removes it. + EXPECT_TRUE(service()->EndSession(client.get())); + + // Now that the last client is gone (even from pending_clients_), + // HasOpenSession should return false and the manager should be deleted. + EXPECT_FALSE(factory()->manager()); +} + class PlatformMidiManagerTest : public ::testing::Test { public: PlatformMidiManagerTest() diff -Nru chromium-147.0.7727.116/media/midi/midi_manager_win.cc chromium-147.0.7727.137/media/midi/midi_manager_win.cc --- chromium-147.0.7727.116/media/midi/midi_manager_win.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/midi/midi_manager_win.cc 2026-04-27 20:03:22.000000000 +0000 @@ -726,6 +726,11 @@ if (instance_id_ == kInvalidInstanceId) return; + // Behind the lock below, we can safely access all members for finalization + // even on the I/O thread. This also ensures that no bound task runs on + // TaskRunner concurrently while destructing the instance. + base::AutoLock lock(*GetTaskLock()); + // Unregisters on the I/O thread. OnDevicesChanged() won't be called any more. CHECK(thread_runner_->BelongsToCurrentThread()); base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); @@ -741,20 +746,12 @@ // Invalidate instance bound tasks. { - base::AutoLock lock(*GetInstanceIdLock()); + base::AutoLock lock_id(*GetInstanceIdLock()); CHECK_EQ(instance_id_, g_active_instance_id); g_active_instance_id = kInvalidInstanceId; CHECK_EQ(this, g_manager_instance); g_manager_instance = nullptr; } - - // Ensures that no bound task runs on TaskRunner so to destruct the instance - // safely. - // Tasks that did not started yet will do nothing after invalidate the - // instance ID above. - // Behind the lock below, we can safely access all members for finalization - // even on the I/O thread. - base::AutoLock lock(*GetTaskLock()); } void MidiManagerWin::StartInitialization() { diff -Nru chromium-147.0.7727.116/media/remoting/stream_provider.cc chromium-147.0.7727.137/media/remoting/stream_provider.cc --- chromium-147.0.7727.116/media/remoting/stream_provider.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/remoting/stream_provider.cc 2026-04-27 20:03:22.000000000 +0000 @@ -546,6 +546,11 @@ DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); DCHECK(message->has_acquire_demuxer_rpc()); + if (audio_stream_ || video_stream_) { + VLOG(1) << __func__ << " Demuxer streams already acquired, ignoring."; + return; + } + int32_t audio_demuxer_handle = message->acquire_demuxer_rpc().audio_demuxer_handle(); int32_t video_demuxer_handle = diff -Nru chromium-147.0.7727.116/media/remoting/stream_provider_unittest.cc chromium-147.0.7727.137/media/remoting/stream_provider_unittest.cc --- chromium-147.0.7727.116/media/remoting/stream_provider_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/remoting/stream_provider_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4,6 +4,7 @@ #include "media/remoting/stream_provider.h" +#include "base/functional/callback_helpers.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/task/single_thread_task_runner.h" @@ -331,5 +332,27 @@ EXPECT_EQ(GetVideoCurrentFrameCount(), flush_video_count); } +TEST_F(StreamProviderTest, DuplicateAcquireDemuxer) { + InitializeDemuxer(); + SendRpcAcquireDemuxer(); + task_environment_.RunUntilIdle(); + EXPECT_TRUE(stream_provider_initialized_); + + // Cache raw pointers. + std::vector streams = stream_provider_->GetAllStreams(); + ASSERT_EQ(streams.size(), 2u); + DemuxerStream* cached_audio = + streams[0]->type() == DemuxerStream::AUDIO ? streams[0] : streams[1]; + + // Second acquisition. + SendRpcAcquireDemuxer(); + task_environment_.RunUntilIdle(); + + // The first streams should still be valid and not destroyed. + // If they were destroyed, this call would trigger a UAF. + cached_audio->Read(1, base::DoNothing()); + task_environment_.RunUntilIdle(); +} + } // namespace remoting } // namespace media diff -Nru chromium-147.0.7727.116/media/webrtc/audio_processor.cc chromium-147.0.7727.137/media/webrtc/audio_processor.cc --- chromium-147.0.7727.116/media/webrtc/audio_processor.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/media/webrtc/audio_processor.cc 2026-04-27 20:03:22.000000000 +0000 @@ -287,7 +287,7 @@ CHECK(input_format_.IsValid()); CHECK(output_format_.IsValid()); if (webrtc_audio_processing_) { - DCHECK_EQ( + CHECK_EQ( webrtc::AudioProcessing::GetFrameSize(output_format_.sample_rate()), output_format_.frames_per_buffer()); } diff -Nru chromium-147.0.7727.116/net/http/transport_security_state_static.pins chromium-147.0.7727.137/net/http/transport_security_state_static.pins --- chromium-147.0.7727.116/net/http/transport_security_state_static.pins 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/net/http/transport_security_state_static.pins 2026-04-27 20:03:22.000000000 +0000 @@ -43,9 +43,9 @@ # hash function for preloaded entries again (we have already done so once). # -# Last updated: 2026-04-19 13:39 UTC +# Last updated: 2026-04-26 13:36 UTC PinsListTimestamp -1776605998 +1777210598 TestSPKI sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= diff -Nru chromium-147.0.7727.116/net/http/transport_security_state_static_pins.json chromium-147.0.7727.137/net/http/transport_security_state_static_pins.json --- chromium-147.0.7727.116/net/http/transport_security_state_static_pins.json 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/net/http/transport_security_state_static_pins.json 2026-04-27 20:03:22.000000000 +0000 @@ -31,7 +31,7 @@ // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets' // refer to, and the timestamp at which the pins list was last updated. // -// Last updated: 2026-04-19 13:39 UTC +// Last updated: 2026-04-26 13:36 UTC // { "pinsets": [ diff -Nru chromium-147.0.7727.116/remoting/protocol/BUILD.gn chromium-147.0.7727.137/remoting/protocol/BUILD.gn --- chromium-147.0.7727.116/remoting/protocol/BUILD.gn 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/remoting/protocol/BUILD.gn 2026-04-27 20:03:22.000000000 +0000 @@ -417,6 +417,7 @@ "ssl_hmac_channel_authenticator_unittest.cc", "validating_authenticator_unittest.cc", "webrtc_event_log_data_unittest.cc", + "webrtc_video_renderer_adapter_unittest.cc", ] if (enable_remoting_host) { diff -Nru chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter.cc chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter.cc --- chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter.cc 2026-04-27 20:03:22.000000000 +0000 @@ -71,31 +71,30 @@ // Needed for ConnectionTest unittests which set up a fake connection without // starting any video. This video adapter is instantiated when the incoming // video-stats data channel is created. - if (!media_stream_) { - return; + if (video_track_) { + video_track_->RemoveSink(this); } - - webrtc::VideoTrackVector video_tracks = media_stream_->GetVideoTracks(); - DCHECK(!video_tracks.empty()); - video_tracks[0]->RemoveSink(this); } void WebrtcVideoRendererAdapter::SetMediaStream( webrtc::scoped_refptr media_stream) { DCHECK_EQ(media_stream->id(), label()); - media_stream_ = std::move(media_stream); + if (video_track_) { + video_track_->RemoveSink(this); + } - webrtc::VideoTrackVector video_tracks = media_stream_->GetVideoTracks(); + webrtc::VideoTrackVector video_tracks = media_stream->GetVideoTracks(); // Caller must verify that the media stream contains video tracks. - DCHECK(!video_tracks.empty()); + CHECK(!video_tracks.empty()); if (video_tracks.size() > 1U) { LOG(WARNING) << "Received media stream with multiple video tracks."; } - video_tracks[0]->AddOrUpdateSink(this, webrtc::VideoSinkWants()); + video_track_ = video_tracks[0]; + video_track_->AddOrUpdateSink(this, webrtc::VideoSinkWants()); } void WebrtcVideoRendererAdapter::SetVideoStatsChannel( diff -Nru chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter.h chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter.h --- chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter.h 2026-04-27 20:03:22.000000000 +0000 @@ -77,7 +77,7 @@ std::string label_; - webrtc::scoped_refptr media_stream_; + webrtc::scoped_refptr video_track_; raw_ptr video_renderer_; std::unique_ptr video_stats_dispatcher_; diff -Nru chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter_unittest.cc chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter_unittest.cc --- chromium-147.0.7727.116/remoting/protocol/webrtc_video_renderer_adapter_unittest.cc 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/remoting/protocol/webrtc_video_renderer_adapter_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -0,0 +1,109 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/protocol/webrtc_video_renderer_adapter.h" + +#include +#include +#include +#include + +#include "base/memory/scoped_refptr.h" +#include "base/test/task_environment.h" +#include "remoting/protocol/fake_video_renderer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/webrtc/api/make_ref_counted.h" +#include "third_party/webrtc/api/media_stream_interface.h" +#include "third_party/webrtc/api/test/mock_media_stream_interface.h" +#include "third_party/webrtc/api/test/mock_video_track.h" + +using testing::_; +using testing::Return; + +namespace remoting::protocol { + +class WebrtcVideoRendererAdapterTest : public testing::Test { + public: + WebrtcVideoRendererAdapterTest() + : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {} + + protected: + base::test::TaskEnvironment task_environment_; + FakeVideoRenderer video_renderer_; +}; + +TEST_F(WebrtcVideoRendererAdapterTest, DanglingSinkAfterTrackSwap) { + std::string label = "test_stream"; + auto adapter = + std::make_unique(label, &video_renderer_); + + auto track0 = webrtc::MockVideoTrack::Create(); + auto track1 = webrtc::MockVideoTrack::Create(); + auto stream = webrtc::make_ref_counted(); + + EXPECT_CALL(*stream, id()).WillRepeatedly(Return(label)); + + // Initial tracks: [track0] + EXPECT_CALL(*stream, GetVideoTracks()) + .WillRepeatedly(Return( + std::vector>{ + track0})); + + // Expect registration on track0 + EXPECT_CALL(*track0, AddOrUpdateSink(adapter.get(), _)).Times(1); + + adapter->SetMediaStream(stream); + + // Swap tracks: [track1] + EXPECT_CALL(*stream, GetVideoTracks()) + .WillRepeatedly(Return( + std::vector>{ + track1})); + + // On destruction, the code should unregister from the ORIGINAL track + // (track0). + EXPECT_CALL(*track1, RemoveSink(adapter.get())).Times(0); + EXPECT_CALL(*track0, RemoveSink(adapter.get())).Times(1); + + adapter.reset(); +} + +TEST_F(WebrtcVideoRendererAdapterTest, DanglingSinkAfterSetMediaStreamReplace) { + std::string label = "test_stream"; + auto adapter = + std::make_unique(label, &video_renderer_); + + auto track0 = webrtc::MockVideoTrack::Create(); + auto stream0 = webrtc::make_ref_counted(); + EXPECT_CALL(*stream0, id()).WillRepeatedly(Return(label)); + EXPECT_CALL(*stream0, GetVideoTracks()) + .WillRepeatedly(Return( + std::vector>{ + track0})); + + auto track1 = webrtc::MockVideoTrack::Create(); + auto stream1 = webrtc::make_ref_counted(); + EXPECT_CALL(*stream1, id()).WillRepeatedly(Return(label)); + EXPECT_CALL(*stream1, GetVideoTracks()) + .WillRepeatedly(Return( + std::vector>{ + track1})); + + // First SetMediaStream + EXPECT_CALL(*track0, AddOrUpdateSink(adapter.get(), _)).Times(1); + adapter->SetMediaStream(stream0); + + // Second SetMediaStream. The code should unregister from track0 before + // registering on track1. + EXPECT_CALL(*track0, RemoveSink(adapter.get())).Times(1); + EXPECT_CALL(*track1, AddOrUpdateSink(adapter.get(), _)).Times(1); + adapter->SetMediaStream(stream1); + + // On destruction, it will unregister from track1. + EXPECT_CALL(*track1, RemoveSink(adapter.get())).Times(1); + adapter.reset(); +} + +} // namespace remoting::protocol diff -Nru chromium-147.0.7727.116/remoting/resources/remoting_strings_fa.xtb chromium-147.0.7727.137/remoting/resources/remoting_strings_fa.xtb --- chromium-147.0.7727.116/remoting/resources/remoting_strings_fa.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/remoting/resources/remoting_strings_fa.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -16,7 +16,7 @@ حذف اتصال ناموفق بود میزبان در حال راه‌اندازی مجدد است تا یک تغییر در خط‌مشی را اعمال کند. -فرآیند میزبان +فرایند میزبان حالت پد مسیریابی ‏برای استفاده از ، باید اجازه «ضبط صفحه‌نمایش» را اعطا کنید تا بتوان محتوای صفحه در این Mac را به ماشین کنترل ازراه‌دور ارسال کرد. @@ -178,12 +178,12 @@ ‏جلسه‌ای را برای راه‌اندازی در محیط «کنترل کامپیوتر از راه دور Chrome» انتخاب کنید. (توجه داشته باشید که ممکن است اجرای برخی انواع جلسه‌ها در «کنترل کامپیوتر از راه دور Chrome» و کنسول محلی به‌طور هم‌زمان پشتیبانی نشود) تنظیمات ‏مشاهده در فروشگاه Google Play -فرآیند یکپارچگی با دسک‌تاپ +فرایند یکپارچگی با دسک‌تاپ میزبان پیام‌رسانی بومی برای کنترل ازراه‌دور مدیریت میزبان تمام صفحه نظر می‌خواهید به اجازه دهید که رایانه‌تان را ببیند و کنترل کند؟ -‏جلسه خراب شد یا شروع نشد. درصورت وجود ~/.chrome-remote-desktop-session در رایانه راه‌ دور، مطمئن شوید که یک فرآیند پیش‌زمینه طولانی‌مدت مثل محیط دسک‌تاپ یا مدیر پنجره را راه‌اندازی می‌کند. +‏جلسه خراب شد یا شروع نشد. درصورت وجود ~/.chrome-remote-desktop-session در رایانه راه‌ دور، مطمئن شوید که یک فرایند پیش‌زمینه طولانی‌مدت مثل محیط دسک‌تاپ یا مدیر پنجره را راه‌اندازی می‌کند. خط‌مشی رازداری ‏برای ضبط کردن صدا و جاری‌سازی آن در کارخواه «رایانه ازدور Chrome»، دسترسی به میکروفون ضروری است. به دستگاه راه دورتان وصل شده‌اید. برای باز کردن منو، لطفاً با چهار انگشت روی صفحه تک‌ضرب بزنید. diff -Nru chromium-147.0.7727.116/skia/ext/skia_commit_hash.h chromium-147.0.7727.137/skia/ext/skia_commit_hash.h --- chromium-147.0.7727.116/skia/ext/skia_commit_hash.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/skia/ext/skia_commit_hash.h 2026-04-27 20:03:22.000000000 +0000 @@ -3,6 +3,6 @@ #ifndef SKIA_EXT_SKIA_COMMIT_HASH_H_ #define SKIA_EXT_SKIA_COMMIT_HASH_H_ -#define SKIA_COMMIT_HASH "f8cd2da0256752afb0059bf17e4bf2bec422967e" +#define SKIA_COMMIT_HASH "6e0fbe154ccaf018b2dd1f0e42eec285e7d79d00" #endif // SKIA_EXT_SKIA_COMMIT_HASH_H_ diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Buffer.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Buffer.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Buffer.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Buffer.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -18,7 +18,7 @@ { namespace { -constexpr size_t kInvalidContentsObserverIndex = std::numeric_limits::max(); +constexpr size_t kInvalidContentsObserverIndex = std::numeric_limits::max(); } // anonymous namespace // VertexArrayBufferBindingMaskAndContext implementation @@ -95,8 +95,7 @@ Buffer::Buffer(rx::GLImplFactory *factory, BufferID id) : RefCountObject(factory->generateSerial(), id), mImpl(factory->createBuffer(mState)) -{ -} +{} Buffer::~Buffer() { @@ -107,9 +106,16 @@ { mContentsObservers.clear(); + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onBufferDestroy(this); + } + // In tests, mImpl might be null. if (mImpl) + { mImpl->destroy(context); + } } void Buffer::onBind(const Context *context, BufferBinding target) diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Context.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Context.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Context.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Context.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -732,7 +732,8 @@ mFrameCapture(new angle::FrameCapture), mRefCount(0), mOverlay(mImplementation.get()), - mIsDestroyed(false) + mIsDestroyed(false), + mDestroyedManagers(false) { for (angle::SubjectIndex uboIndex = kUniformBuffer0SubjectIndex; uboIndex < kUniformBufferMaxSubjectIndex; ++uboIndex) @@ -1031,19 +1032,40 @@ void Context::releaseSharedObjects() { + mDestroyedManagers = true; + mState.mBufferManager->release(this); + mState.mBufferManager = nullptr; + // mProgramPipelineManager must be before mShaderProgramManager to give each // PPO the chance to release any references they have to the Programs that // are bound to them before the Programs are released()'ed. mState.mProgramPipelineManager->release(this); + mState.mProgramPipelineManager = nullptr; + mState.mShaderProgramManager->release(this); + mState.mShaderProgramManager = nullptr; + mState.mTextureManager->release(this); + mState.mTextureManager = nullptr; + mState.mRenderbufferManager->release(this); + mState.mRenderbufferManager = nullptr; + mState.mSamplerManager->release(this); + mState.mSamplerManager = nullptr; + mState.mSyncManager->release(this); + mState.mSyncManager = nullptr; + mState.mFramebufferManager->release(this); + mState.mFramebufferManager = nullptr; + mState.mMemoryObjectManager->release(this); + mState.mMemoryObjectManager = nullptr; + mState.mSemaphoreManager->release(this); + mState.mSemaphoreManager = nullptr; } Context::~Context() {} @@ -10185,6 +10207,51 @@ mPrivateStateCache.invalidateCachedBasicDrawElementsError(); } +bool Context::retainIdUntilObjectDestroyed() const +{ + // If BindGeneratesResource is disabled, then we can defer recycling the handle ID until the + // object has had the `onDestroy` method called, preventing ID reuse bugs. + // + // If the context is being destroyed however, we don't want to try to recycle as the handle + // manager may be gone. + return !mDestroyedManagers && !mState.isBindGeneratesResourceEnabled(); +} + +void Context::onBufferDestroy(const Buffer *buffer) const +{ + mState.mBufferManager->recycleHandle(buffer->id()); +} + +void Context::onTextureDestroy(const Texture *texture) const +{ + mState.mTextureManager->recycleHandle(texture->id()); +} + +void Context::onRenderbufferDestroy(const Renderbuffer *renderBuffer) const +{ + mState.mRenderbufferManager->recycleHandle(renderBuffer->id()); +} + +void Context::onSamplerDestroy(const Sampler *sampler) const +{ + mState.mSamplerManager->recycleHandle(sampler->id()); +} + +void Context::onSyncDestroy(const Sync *sync) const +{ + mState.mSyncManager->recycleHandle(sync->id()); +} + +void Context::onFramebufferDestroy(const Framebuffer *framebuffer) const +{ + mState.mFramebufferManager->recycleHandle(framebuffer->id()); +} + +void Context::onProgramPipelineDestroy(const ProgramPipeline *programPipeline) const +{ + mState.mProgramPipelineManager->recycleHandle(programPipeline->id()); +} + // ErrorSet implementation. ErrorSet::ErrorSet(Debug *debug, const angle::FrontendFeatures &frontendFeatures, diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Context.h chromium-147.0.7727.137/third_party/angle/src/libANGLE/Context.h --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Context.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Context.h 2026-04-27 20:03:22.000000000 +0000 @@ -981,6 +981,16 @@ GLint64 getInstancedVertexElementLimit() const; void onActiveTransformFeedbackChange(); + bool retainIdUntilObjectDestroyed() const; + + void onBufferDestroy(const Buffer *buffer) const; + void onTextureDestroy(const Texture *texture) const; + void onRenderbufferDestroy(const Renderbuffer *renderBuffer) const; + void onSamplerDestroy(const Sampler *sampler) const; + void onSyncDestroy(const Sync *sync) const; + void onFramebufferDestroy(const Framebuffer *framebuffer) const; + void onProgramPipelineDestroy(const ProgramPipeline *programPipeline) const; + private: void initializeDefaultResources(); void releaseSharedObjects(); @@ -1148,6 +1158,7 @@ OverlayType mOverlay; bool mIsDestroyed; + bool mDestroyedManagers; std::unique_ptr mDefaultFramebuffer; }; diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Fence.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Fence.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Fence.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Fence.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -11,6 +11,7 @@ #include "angle_gl.h" #include "common/utilities.h" +#include "libANGLE/Context.h" #include "libANGLE/renderer/FenceNVImpl.h" #include "libANGLE/renderer/GLImplFactory.h" #include "libANGLE/renderer/SyncImpl.h" @@ -74,6 +75,11 @@ void Sync::onDestroy(const Context *context) { ASSERT(mFence); + + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onSyncDestroy(this); + } mFence->onDestroy(context); } diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Framebuffer.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Framebuffer.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Framebuffer.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Framebuffer.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -865,6 +865,11 @@ mPixelLocalStorage->onFramebufferDestroyed(context); } + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onFramebufferDestroy(this); + } + mImpl->destroy(context); } @@ -2327,7 +2332,9 @@ // In some error cases there may be no bound program or executable. if (!executable) + { return false; + } const ActiveTextureMask &activeTextures = executable->getActiveSamplersMask(); const ActiveTextureTypeArray &textureTypes = executable->getActiveSamplerTypes(); diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/ProgramPipeline.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/ProgramPipeline.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/ProgramPipeline.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/ProgramPipeline.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -183,6 +183,11 @@ } } + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onProgramPipelineDestroy(this); + } + getImplementation()->destroy(context); UninstallExecutable(context, &mState.mExecutable); diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Renderbuffer.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Renderbuffer.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Renderbuffer.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Renderbuffer.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -112,6 +112,11 @@ egl::RefCountObjectReleaser releaseImage; (void)orphanImages(context, &releaseImage); + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onRenderbufferDestroy(this); + } + if (mImplementation) { mImplementation->onDestroy(context); diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/ResourceManager.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/ResourceManager.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/ResourceManager.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/ResourceManager.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -97,8 +97,13 @@ return; } - // Requires an explicit this-> because of C++ template rules. - this->mHandleAllocator.release(GetIDValue(handle)); + // if `BindGeneratesResource` is disabled then we do not recycle the handle ID until the object + // has had the `onDestroy` method called. + if (!context->retainIdUntilObjectDestroyed()) + { + // Requires an explicit this-> because of C++ template rules. + this->mHandleAllocator.release(GetIDValue(handle)); + } if (resource) { diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/ResourceManager.h chromium-147.0.7727.137/third_party/angle/src/libANGLE/ResourceManager.h --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/ResourceManager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/ResourceManager.h 2026-04-27 20:03:22.000000000 +0000 @@ -75,6 +75,17 @@ return GetIDValue(handle) == 0 || mObjectMap.contains(handle); } + void recycleHandle(IDType handle) + { + if (isHandleGenerated(handle)) + { + return; + } + + // Requires an explicit this-> because of C++ template rules. + this->mHandleAllocator.release(GetIDValue(handle)); + } + const ResourceMap &getResourcesForCapture() const { return mObjectMap; } protected: diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Sampler.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Sampler.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Sampler.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Sampler.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -8,6 +8,7 @@ // sampler object. Sampler objects store some state needed to sample textures. #include "libANGLE/Sampler.h" +#include "libANGLE/Context.h" #include "libANGLE/angletypes.h" #include "libANGLE/renderer/GLImplFactory.h" #include "libANGLE/renderer/SamplerImpl.h" @@ -30,6 +31,11 @@ void Sampler::onDestroy(const Context *context) { + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onSamplerDestroy(this); + } + if (mSampler) { mSampler->onDestroy(context); diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/Texture.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/Texture.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/Texture.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/Texture.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -639,8 +639,8 @@ Optional expectedSize; for (size_t enabledLevel = baseLevel; enabledLevel <= maxLevel; ++enabledLevel, ++levelCount) { - size_t descIndex = GetImageDescIndex(target, enabledLevel); - const Extents &levelSize = mImageDescs[descIndex].size; + size_t descIndex = GetImageDescIndex(target, enabledLevel); + const Extents &levelSize = mImageDescs[descIndex].size; const Format &levelFormat = mImageDescs[descIndex].format; if (levelSize.empty()) @@ -870,6 +870,11 @@ mState.mBuffer.set(context, nullptr, 0, 0); + if (context && context->retainIdUntilObjectDestroyed()) + { + context->onTextureDestroy(this); + } + if (mTexture) { mTexture->onDestroy(context); @@ -990,7 +995,9 @@ void Texture::setWrapT(const Context *context, GLenum wrapT) { if (mState.mSamplerState.getWrapT() == wrapT) + { return; + } if (mState.mSamplerState.setWrapT(wrapT)) { signalDirtyState(DIRTY_BIT_WRAP_T); @@ -1875,8 +1882,8 @@ imageCreateInfoPNext)); mState.mIsExternalMemoryTexture = true; - mState.mImmutableFormat = true; - mState.mImmutableLevels = static_cast(levels); + mState.mImmutableFormat = true; + mState.mImmutableLevels = static_cast(levels); mState.clearImageDescs(); mState.setImageDescChain(0, static_cast(levels - 1), size, Format(internalFormat), InitState::Initialized); diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -947,9 +947,23 @@ const auto &formatInfo = gl::GetSizedInternalFormatInfo(image->getInternalFormat()); GLuint imageBytes = 0; - ANGLE_CHECK_GL_MATH(contextD3D, formatInfo.computeRowPitch(formatInfo.type, image->getWidth(), - 1, 0, &imageBytes)); - imageBytes *= image->getHeight() * image->getDepth(); + if (formatInfo.compressed) + { + ANGLE_CHECK_GL_MATH( + contextD3D, formatInfo.computeCompressedImageSize( + gl::Extents(image->getWidth(), image->getHeight(), image->getDepth()), + &imageBytes)); + } + else + { + ANGLE_CHECK_GL_MATH(contextD3D, formatInfo.computeRowPitch( + formatInfo.type, image->getWidth(), 1, 0, &imageBytes)); + + angle::CheckedNumeric checkedImageBytes(imageBytes); + checkedImageBytes *= image->getHeight(); + checkedImageBytes *= image->getDepth(); + ANGLE_CHECK_GL_MATH(contextD3D, checkedImageBytes.AssignIfValid(&imageBytes)); + } gl::PixelUnpackState zeroDataUnpackState; zeroDataUnpackState.alignment = 1; diff -Nru chromium-147.0.7727.116/third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp chromium-147.0.7727.137/third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp --- chromium-147.0.7727.116/third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -529,7 +529,8 @@ UtilsVk::ConvertIndexParameters params = {}; params.srcOffset = static_cast(offsetIntoSrcData); params.dstOffset = 0; - params.maxIndex = static_cast(bufferVk->getSize()); + // Remaining space in buffer was already computed above. + params.maxIndex = static_cast(srcDataSize); ANGLE_TRY(contextVk->getUtils().convertIndexBuffer(contextVk, dst, src, params)); mTranslatedByteIndexData.clearDirty(); diff -Nru chromium-147.0.7727.116/third_party/angle/src/tests/angle_end2end_tests.gni chromium-147.0.7727.137/third_party/angle/src/tests/angle_end2end_tests.gni --- chromium-147.0.7727.116/third_party/angle/src/tests/angle_end2end_tests.gni 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/tests/angle_end2end_tests.gni 2026-04-27 20:03:22.000000000 +0000 @@ -41,6 +41,7 @@ "gl_tests/BPTCCompressedTextureTest.cpp", "gl_tests/BaseInstanceOverflowTest.cpp", "gl_tests/BindGeneratesResourceTest.cpp", + "gl_tests/BindRecyclesResourceTest.cpp", "gl_tests/BindUniformLocationTest.cpp", "gl_tests/BlendFuncExtendedTest.cpp", "gl_tests/BlendIntegerTest.cpp", diff -Nru chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/BPTCCompressedTextureTest.cpp chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/BPTCCompressedTextureTest.cpp --- chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/BPTCCompressedTextureTest.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/BPTCCompressedTextureTest.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -452,3 +452,55 @@ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BPTCCompressedTextureTestES3); ANGLE_INSTANTIATE_TEST_ES3(BPTCCompressedTextureTestES3); + +class BPTCCompressedTextureTestES3WebGL : public BPTCCompressedTextureTestES3 +{ + protected: + BPTCCompressedTextureTestES3WebGL() + { + setWebGLCompatibilityEnabled(true); + setRobustResourceInit(true); + } +}; + +// Test that initializing a large 3D BPTC texture doesn't overflow the size calculation. +// This is a regression test for a bug where the size was computed as (width/4 * 16) * height * +// depth instead of (width/4 * 16) * (height/4) * depth. +TEST_P(BPTCCompressedTextureTestES3WebGL, DeferredInit3DOverflow) +{ + ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc")); + + // The overflow happens in the D3D11 backend. + // The dimensions 2048x2048x320 were reported to trigger it. + // 2048/4 * 16 = 8192 (row pitch) + // 8192 * 2048 * 320 = 5,368,709,120, which wraps to 1,073,741,824 in 32-bit GLuint. + // The correct size is 1,342,177,280 (1.25 GB). + // Since the wrapped buggy size is smaller than the correct size, it triggers an OOB read. + // 1.25 GB is large enough that it might trigger GL_OUT_OF_MEMORY on some systems. + + GLTexture tex; + glBindTexture(GL_TEXTURE_3D, tex); + { + ScopedIgnorePlatformMessages ignore; + glTexStorage3D(GL_TEXTURE_3D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 2048, 2048, 320); + } + GLenum err = glGetError(); + // Allow GL_OUT_OF_MEMORY as the texture is large. + ASSERT_TRUE(err == GL_NO_ERROR || err == GL_OUT_OF_MEMORY); + + if (err != GL_OUT_OF_MEMORY) + { + // Trigger deferred initialization by updating a small sub-region. + std::vector data(16, 0); + glCompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 1, + GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 16, data.data()); + err = glGetError(); + EXPECT_TRUE(err == GL_NO_ERROR || err == GL_OUT_OF_MEMORY); + } +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BPTCCompressedTextureTestES3WebGL); +// The overflow happens in the "slow path" of initializeContents, which is only called if +// robust resource initialization is enabled. Since it is always enabled in WebGL, we +// enable it here to reproduce the bug. +ANGLE_INSTANTIATE_TEST(BPTCCompressedTextureTestES3WebGL, ES3_D3D11()); diff -Nru chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/BindRecyclesResourceTest.cpp chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/BindRecyclesResourceTest.cpp --- chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/BindRecyclesResourceTest.cpp 1970-01-01 00:00:00.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/BindRecyclesResourceTest.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -0,0 +1,48 @@ +// +// Copyright 2015 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +// BindRecyclesResourceTest.cpp : Tests of the GL_CHROMIUM_bind_generates_resource extension. + +#include "test_utils/ANGLETest.h" + +namespace angle +{ + +class BindRecyclesResourceTest : public ANGLETest<> +{ + protected: + BindRecyclesResourceTest() { setBindGeneratesResource(false); } +}; + +// crbug.com/496284494 +TEST_P(BindRecyclesResourceTest, BufferRecycling) +{ + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint idA; + glGenBuffers(1, &idA); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idA); + + // Unbind the VAO + glBindVertexArray(0); + + glDeleteBuffers(1, &idA); + + GLuint idB; + glGenBuffers(1, &idB); + EXPECT_NE(idA, idB); + + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &idB); +} + +// Use this to select which configurations (e.g. which renderer, which GLES major version) these +// tests should be run against. +ANGLE_INSTANTIATE_TEST_ES3_AND_ES31_AND_ES32(BindRecyclesResourceTest); + +} // namespace angle diff -Nru chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/VertexAttributeTest.cpp chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/VertexAttributeTest.cpp --- chromium-147.0.7727.116/third_party/angle/src/tests/gl_tests/VertexAttributeTest.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/angle/src/tests/gl_tests/VertexAttributeTest.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -8,6 +8,7 @@ # pragma allow_unsafe_buffers #endif +#include #include "anglebase/numerics/safe_conversions.h" #include "common/mathutil.h" #include "test_utils/ANGLETest.h" @@ -5759,6 +5760,111 @@ # define EMULATED_VAO_CONFIGS #endif +class VertexAttributeUint8Test : public VertexAttributeTestES3 +{}; + +// Regression test for a bug in emulation of 8-bit indices, when the end of +// the index buffer is used. +TEST_P(VertexAttributeUint8Test, ConvertUint8IndexAtEndOfBuffer) +{ + ANGLE_GL_PROGRAM(prog, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); + ANGLE_GL_PROGRAM(prog2, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue()); + glUseProgram(prog); + + GLVertexArray vao; + glBindVertexArray(vao); + + // Vertex buffer: 256 vertices so any uint8 index value is valid for + // robust-access vertex fetch (avoids unrelated OOB on the draw side). + GLBuffer vbo; + glBindBuffer(GL_ARRAY_BUFFER, vbo); + std::vector verts(256 * 2); + for (int i = 0; i < 256; i++) + { + float x, y; + // Vertices 0, 1, 2: cover the right half of the framebuffer + // Vertices 3, 4, 5: cover the left half of the framebuffer + switch (i % 6) + { + case 0: + x = 0; + y = -2; + break; + case 1: + x = 2; + y = 0; + break; + case 2: + x = 0; + y = 2; + break; + case 3: + x = 0; + y = -2; + break; + case 4: + x = 0; + y = 2; + break; + case 5: + x = -2; + y = 0; + break; + } + verts[i * 2] = x; + verts[i * 2 + 1] = y; + } + glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW); + + GLint aLoc = glGetAttribLocation(prog, "a_position"); + glEnableVertexAttribArray(aLoc); + glVertexAttribPointer(aLoc, 2, GL_FLOAT, GL_FALSE, 0, 0); + + // Create a large index buffer, contents of which don't really matter. + // Filled with low values so any vertex fetch is in-range. + const size_t kEboSize = 65536; + GLBuffer ebo; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + std::vector indices(kEboSize); + for (size_t i = 0; i < kEboSize - 3; i++) + { + indices[i] = i % 3; + } + // Set indices 65533, 65534, 65535 to 3, 4, 5 to cover the left + // half of the framebuffer. + indices[kEboSize - 3] = 3; + indices[kEboSize - 2] = 4; + indices[kEboSize - 1] = 5; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, kEboSize, indices.data(), GL_STATIC_DRAW); + + // Draw red to the right half of the framebuffer. This draw call ensures that the + // index buffer is in use by the GPU, so the emulation, if any, would prefer the GPU path. + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, 0); + + // Draw blue to the left half of the framebuffer. The draw call uses an offset to the + // end of the index buffer. This is a regression test for a bug where the 8-bit index + // emulation path miscalculated the range to emulate. + const size_t kOffset = 65533; + const size_t kCount = 3; + glUseProgram(prog2); + GLint aLoc2 = glGetAttribLocation(prog, "a_position"); + glEnableVertexAttribArray(aLoc2); + glVertexAttribPointer(aLoc2, 2, GL_FLOAT, GL_FALSE, 0, 0); + glDrawElements(GL_TRIANGLES, kCount, GL_UNSIGNED_BYTE, reinterpret_cast(kOffset)); + + const int w = getWindowWidth(); + const int h = getWindowHeight(); + + EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h, GLColor::blue); + EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w / 2 - 2, h, GLColor::red); + ASSERT_GL_NO_ERROR(); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VertexAttributeUint8Test); +ANGLE_INSTANTIATE_TEST_ES3_AND(VertexAttributeUint8Test, + ES3_VULKAN().disable(Feature::SupportsIndexTypeUint8), + ES3_VULKAN_SWIFTSHADER().disable(Feature::SupportsIndexTypeUint8)); + ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND( VertexAttributeTest, ES2_VULKAN().enable(Feature::ForceFallbackFormat), diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/animation/animation_trigger.cc chromium-147.0.7727.137/third_party/blink/renderer/core/animation/animation_trigger.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/animation/animation_trigger.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/animation/animation_trigger.cc 2026-04-27 20:03:22.000000000 +0000 @@ -249,7 +249,8 @@ } void AnimationTrigger::PerformActivate() { - for (auto [animation, behaviors] : animation_behavior_map_) { + auto map_copy = animation_behavior_map_; + for (auto [animation, behaviors] : map_copy) { if (HasPausedCSSPlayState(animation) || (compositor_trigger_ && IsTriggeredOnCompositor(animation))) { continue; @@ -259,7 +260,8 @@ } void AnimationTrigger::PerformDeactivate() { - for (auto [animation, behaviors] : animation_behavior_map_) { + auto map_copy = animation_behavior_map_; + for (auto [animation, behaviors] : map_copy) { if (HasPausedCSSPlayState(animation) || (compositor_trigger_ && IsTriggeredOnCompositor(animation))) { continue; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/dom/pseudo_element.cc chromium-147.0.7727.137/third_party/blink/renderer/core/dom/pseudo_element.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/dom/pseudo_element.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/dom/pseudo_element.cc 2026-04-27 20:03:22.000000000 +0000 @@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/html_menu_item_element.h" #include "third_party/blink/renderer/core/html/html_quote_element.h" +#include "third_party/blink/renderer/core/input/event_handler.h" #include "third_party/blink/renderer/core/input_type_names.h" #include "third_party/blink/renderer/core/layout/generated_children.h" #include "third_party/blink/renderer/core/layout/layout_counter.h" @@ -470,6 +471,9 @@ DetachLayoutTree(); Element* parent = ParentOrShadowHostElement(); + if (LocalFrame* frame = GetDocument().GetFrame()) { + frame->GetEventHandler().HandlePseudoElementRemoval(*this); + } GetDocument().AdoptIfNeeded(*this); SetParentNode(nullptr); RemovedFrom(*parent); diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/events/pointer_event_factory.cc chromium-147.0.7727.137/third_party/blink/renderer/core/events/pointer_event_factory.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/events/pointer_event_factory.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/events/pointer_event_factory.cc 2026-04-27 20:03:22.000000000 +0000 @@ -7,6 +7,7 @@ #include "base/notreached.h" #include "base/trace_event/trace_event.h" #include "third_party/blink/renderer/bindings/core/v8/v8_pointer_event_init.h" +#include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/events/pointer_event_util.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" @@ -813,6 +814,31 @@ return nullptr; } +void PointerEventFactory::HandlePseudoElementRemoved(PseudoElement& pseudo) { + Element* originating_element = pseudo.ParentOrShadowHostElement(); + if (!originating_element) { + return; + } + for (auto& entry : pointer_id_to_attributes_) { + PointerAttributes* attributes = entry.value; + if (attributes->pointer_down_target && + attributes->pointer_down_target->node) { + Node* target = attributes->pointer_down_target->node; + if (target->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*target)) { + attributes->pointer_down_target->node = originating_element; + } + } + if (attributes->pointer_up_target && attributes->pointer_up_target->node) { + Node* target = attributes->pointer_up_target->node; + if (target->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*target)) { + attributes->pointer_up_target->node = originating_element; + } + } + } +} + PointerEventFactory::PointerAttributes* PointerEventFactory::GetPointerAttributesForId(PointerId pointer_id) const { auto it = pointer_id_to_attributes_.find(pointer_id); diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/events/pointer_event_factory.h chromium-147.0.7727.137/third_party/blink/renderer/core/events/pointer_event_factory.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/events/pointer_event_factory.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/events/pointer_event_factory.h 2026-04-27 20:03:22.000000000 +0000 @@ -133,6 +133,7 @@ PointerTarget* GetPointerDownTarget(PointerId) const; PointerTarget* GetPointerUpTarget(PointerId) const; void RemovePointerTargets(PointerId); + void HandlePseudoElementRemoved(PseudoElement& pseudo); void Trace(Visitor*) const; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/event_handler.h chromium-147.0.7727.137/third_party/blink/renderer/core/input/event_handler.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/event_handler.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/event_handler.h 2026-04-27 20:03:22.000000000 +0000 @@ -93,6 +93,10 @@ mouse_event_manager_->NodeWillBeRemoved(node); pointer_event_manager_->NodeWillBeRemoved(node); } + void HandlePseudoElementRemoval(PseudoElement& pseudo) { + mouse_event_manager_->HandlePseudoElementRemoval(pseudo); + pointer_event_manager_->HandlePseudoElementRemoval(pseudo); + } void UpdateSelectionForMouseDrag(); void StartMiddleClickAutoscroll(LayoutObject*); diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/mouse_event_manager.cc chromium-147.0.7727.137/third_party/blink/renderer/core/input/mouse_event_manager.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/mouse_event_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/mouse_event_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -14,6 +14,7 @@ #include "third_party/blink/renderer/core/clipboard/data_transfer_access_policy.h" #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/focus_params.h" +#include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" @@ -462,13 +463,37 @@ HandleRemoveSubtree(node, /*include_root=*/true); } +void MouseEventManager::HandlePseudoElementRemoval(PseudoElement& pseudo) { + Element* parent = pseudo.ParentOrShadowHostElement(); + if (mousedown_element_ && mousedown_element_->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*mousedown_element_)) { + mousedown_element_ = parent; + } + if (mouse_press_node_ && mouse_press_node_->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*mouse_press_node_)) { + mouse_press_node_ = parent; + } + if (element_under_mouse_ && element_under_mouse_->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*element_under_mouse_)) { + element_under_mouse_ = parent; + original_element_under_mouse_removed_ = true; + } +} + void MouseEventManager::HandleRemoveSubtree(Node& node, bool include_root) { Node* remaining_node = include_root ? node.parentNode() : &node; if (mousedown_element_ && (include_root || mousedown_element_ != node) && node.IsShadowIncludingInclusiveAncestorOf(*mousedown_element_)) { // We don't dispatch click events if the mousedown node is removed // before a mouseup event. It is compatible with IE and Firefox. - mousedown_element_ = nullptr; + // However, if the removed node is a pseudo-element, it's just a style + // change, so we should fallback to its originating element so that + // click events can still be dispatched. + if (node.IsPseudoElement()) { + mousedown_element_ = node.ParentOrShadowHostElement(); + } else { + mousedown_element_ = nullptr; + } } if (mouse_press_node_ && (include_root || mouse_press_node_ != node) && node.IsShadowIncludingInclusiveAncestorOf(*mouse_press_node_)) { diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/mouse_event_manager.h chromium-147.0.7727.137/third_party/blink/renderer/core/input/mouse_event_manager.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/mouse_event_manager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/mouse_event_manager.h 2026-04-27 20:03:22.000000000 +0000 @@ -93,6 +93,7 @@ void NodeChildrenWillBeRemoved(ContainerNode&); void NodeWillBeRemoved(Node&); + void HandlePseudoElementRemoval(PseudoElement&); void SendBoundaryEvents(EventTarget* exited_target, bool original_exited_target_removed, @@ -218,7 +219,8 @@ bool HoverStateDirty(); // NOTE: If adding a new field to this class please ensure that it is - // cleared in |MouseEventManager::clear()|. + // cleared in |MouseEventManager::clear()| and updated in + // |MouseEventManager::HandlePseudoElementRemoval()| if it's a target. const Member frame_; Member scroll_manager_; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/pointer_event_manager.cc chromium-147.0.7727.137/third_party/blink/renderer/core/input/pointer_event_manager.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/pointer_event_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/pointer_event_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -9,6 +9,7 @@ #include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h" #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/events/event_path.h" +#include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/event_type_names.h" #include "third_party/blink/renderer/core/events/mouse_event.h" #include "third_party/blink/renderer/core/frame/event_handler_registry.h" @@ -309,6 +310,39 @@ HandleRemoveSubtree(node, /*include_root=*/true); } +void PointerEventManager::HandlePseudoElementRemoval(PseudoElement& pseudo) { + Element* parent = pseudo.ParentOrShadowHostElement(); + for (auto& entry : element_under_pointer_) { + if (entry.value && entry.value->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*entry.value)) { + entry.value = parent; + original_element_under_pointer_removed_.insert(entry.key); + } + } + + for (auto& entry : pointer_capture_target_) { + if (entry.value && entry.value->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*entry.value)) { + entry.value = parent; + } + } + + for (auto& entry : pending_pointer_capture_target_) { + if (entry.value && entry.value->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*entry.value)) { + entry.value = parent; + } + } + + if (pointer_event_factory_) { + pointer_event_factory_->HandlePseudoElementRemoved(pseudo); + } + + if (touch_event_manager_) { + touch_event_manager_->HandlePseudoElementRemoval(pseudo); + } +} + void PointerEventManager::HandleRemoveSubtree(Node& node, bool include_root) { if (!RuntimeEnabledFeatures:: BoundaryEventDispatchTracksNodeRemovalEnabled()) { @@ -1230,8 +1264,13 @@ if (consider_click_dispatch) { ProcessPendingPointerCapture(pointer_event); + Element* click_mouse_target = mouse_target; + if (click_mouse_target && click_mouse_target->IsPseudoElement() && + !click_mouse_target->isConnected()) { + click_mouse_target = mouse_event_manager_->GetElementUnderMouse(); + } mouse_event_manager_->DispatchMouseClickIfNeeded( - mouse_target, captured_click_target, mouse_event, + click_mouse_target, captured_click_target, mouse_event, pointer_event->pointerId(), pointer_event->pointerType(), pointer_event_factory_->GetPointerDownTarget( pointer_event->pointerId()), diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/pointer_event_manager.h chromium-147.0.7727.137/third_party/blink/renderer/core/input/pointer_event_manager.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/pointer_event_manager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/pointer_event_manager.h 2026-04-27 20:03:22.000000000 +0000 @@ -84,6 +84,7 @@ void NodeChildrenWillBeRemoved(ContainerNode&); void NodeWillBeRemoved(Node&); + void HandlePseudoElementRemoval(PseudoElement&); void SetHandwritingRadius(int handwriting_radius); // Starts capturing of all events with the given |PointerId| to the given @@ -278,6 +279,9 @@ // Set upon scrolling starts when sending a pointercancel, prevents PE // dispatches for non-hovering pointers until all of them become inactive. + // NOTE: If adding a new field to this class please ensure that it is + // updated in |PointerEventManager::HandlePseudoElementRemoval()| if it's a + // target. bool non_hovering_pointers_canceled_ = false; Deque touch_ids_for_canceled_pointerdowns_; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/touch_event_manager.cc chromium-147.0.7727.137/third_party/blink/renderer/core/input/touch_event_manager.cc --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/touch_event_manager.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/touch_event_manager.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4,7 +4,6 @@ #include "third_party/blink/renderer/core/input/touch_event_manager.h" -#include #include #include @@ -12,6 +11,7 @@ #include "third_party/blink/public/common/input/web_touch_event.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" +#include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/events/touch_event.h" #include "third_party/blink/renderer/core/frame/event_handler_registry.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" @@ -673,6 +673,16 @@ should_enforce_vertical_scroll_ = false; } +void TouchEventManager::HandlePseudoElementRemoval(PseudoElement& pseudo) { + Element* parent = pseudo.ParentOrShadowHostElement(); + for (auto& entry : touch_attribute_map_) { + if (entry.value->target_ && entry.value->target_->IsPseudoElement() && + pseudo.IsShadowIncludingInclusiveAncestorOf(*entry.value->target_)) { + entry.value->target_ = parent; + } + } +} + bool TouchEventManager::IsAnyTouchActive() const { return !touch_attribute_map_.empty(); } diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/input/touch_event_manager.h chromium-147.0.7727.137/third_party/blink/renderer/core/input/touch_event_manager.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/input/touch_event_manager.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/input/touch_event_manager.h 2026-04-27 20:03:22.000000000 +0000 @@ -45,6 +45,7 @@ // Resets the internal state of this object. void Clear(); + void HandlePseudoElementRemoval(PseudoElement&); // Returns whether there is any touch on the screen. bool IsAnyTouchActive() const; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/core/style/computed_style.h chromium-147.0.7727.137/third_party/blink/renderer/core/style/computed_style.h --- chromium-147.0.7727.116/third_party/blink/renderer/core/style/computed_style.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/core/style/computed_style.h 2026-04-27 20:03:22.000000000 +0000 @@ -1468,7 +1468,9 @@ EBorderStyle style, EBorderStyle other_style, int width, int other_width) -> bool { if (style == EBorderStyle::kNone && other_style == EBorderStyle::kNone) { - return true; + if (!HasBorderShape() && !o.HasBorderShape()) { + return true; + } } if (style == EBorderStyle::kHidden && other_style == EBorderStyle::kHidden) { @@ -1494,7 +1496,8 @@ BorderLeftStyle(), o.BorderLeftStyle(), BorderLeftWidthInternal(), o.BorderLeftWidthInternal()) && - BorderImage() == o.BorderImage(); + BorderImage() == o.BorderImage() && + base::ValuesEquivalent(BorderShape(), o.BorderShape()); } bool BorderVisualOverflowEqual(const ComputedStyle& o) const { diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc chromium-147.0.7727.137/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc --- chromium-147.0.7727.116/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc 2026-04-27 20:03:22.000000000 +0000 @@ -173,15 +173,13 @@ } void BaseRenderingContext2D::DispatchContextLostEvent(TimerBase*) { - // If `need_dispatch_context_restored_` is `true`, the context has been - // restored already (e.g. by fixing a `kInvalidCanvasSize` context loss), but - // the oncontextrestored event was postponed until the oncontextlost event was - // dispatched first. This is happening now, so irrespective of how this - // function returns, `need_dispatch_context_restored_` should be cleared. - absl::Cleanup cleanup = [this] { need_dispatch_context_restored_ = false; }; + CanvasRenderingContextHost* host = GetCanvasRenderingContextHost(); + if (!host) { + return; + } Event* event = Event::CreateCancelable(event_type_names::kContextlost); - GetCanvasRenderingContextHost()->HostDispatchEvent(event); + host->HostDispatchEvent(event); UseCounter::Count(GetTopExecutionContext(), WebFeature::kCanvasRenderingContext2DContextLostEvent); @@ -193,7 +191,8 @@ return; } - if (need_dispatch_context_restored_) { + if (context_lost_mode_ == CanvasRenderingContext::kInvalidCanvasSize && + host->IsValidImageSize()) { // The context is already restored (an invalid canvas size was probably // fixed). We can send the restored event right away. dispatch_context_restored_event_timer_.StartOneShot(base::TimeDelta(), @@ -292,16 +291,20 @@ DCHECK(!GetResourceProvider()); if (host->IsValidImageSize()) { - if (dispatch_context_lost_event_timer_.IsActive()) { - // An oncontextlost event is still pending. We can't send the - // oncontextrestored right away because the oncontextlost callback could - // choose to prevent restoration. Thus, we need to delay queuing the - // restored event to after the lost event completed. - need_dispatch_context_restored_ = true; - } else { + // The size was restored. Fire a contextrestored event, but only if there's + // no pending contextlost. contextlost needs to run first and it will take + // care of running contextrestored if the size is still valid at that point. + if (!dispatch_context_lost_event_timer_.IsActive()) { dispatch_context_restored_event_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); } + } else { + // The canvas was given another invalid size. Abort any pending + // contextrestored event, these would have to wait until the canvas is given + // a valid size. + if (dispatch_context_restored_event_timer_.IsActive()) { + dispatch_context_restored_event_timer_.Stop(); + } } } diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h chromium-147.0.7727.137/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h --- chromium-147.0.7727.116/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h 2026-04-27 20:03:22.000000000 +0000 @@ -324,7 +324,6 @@ std::unique_ptr resource_provider_from_webgpu_access_; Canvas2DColorParams color_params_; - bool need_dispatch_context_restored_ = false; base::RepeatingClosure on_restore_failed_callback_for_testing_; }; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/graphics/image_decoding_store.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/graphics/image_decoding_store.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/graphics/image_decoding_store.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/graphics/image_decoding_store.cc 2026-04-27 20:03:22.000000000 +0000 @@ -111,7 +111,9 @@ generator, 0, std::move(decoder), client_id); base::AutoLock lock(lock_); - DCHECK(!decoder_cache_map_.Contains(new_cache_entry->CacheKey())); + // Note: duplicate insertions can happen if multiple threads experience a + // cache miss for the same key and both attempt to insert a decoder. + // InsertCacheInternal handles this safely. InsertCacheInternal(std::move(new_cache_entry), &decoder_cache_map_, &decoder_cache_key_map_); } @@ -228,18 +230,27 @@ U* cache_map, V* identifier_map) { lock_.AssertAcquired(); - const size_t cache_entry_bytes = cache_entry->MemoryUsageInBytes(); - heap_memory_usage_in_bytes_ += cache_entry_bytes; + const typename U::KeyType key = cache_entry->CacheKey(); - // m_orderedCacheList is used to support LRU operations to reorder cache + // Attempt to insert into the cache map first. If the key already exists, + // the unique_ptr is not consumed and will be destroyed, which is correct + // for a duplicate entry. + auto result = cache_map->insert(key, std::move(cache_entry)); + if (!result.is_new_entry) { + return; + } + + // Only add to the LRU list and update memory usage if this is a new entry. + T* entry_ptr = result.stored_value->value.get(); + + // ordered_cache_list_ is used to support LRU operations to reorder cache // entries quickly. - ordered_cache_list_.Append(cache_entry.get()); + ordered_cache_list_.Append(entry_ptr); + heap_memory_usage_in_bytes_ += entry_ptr->MemoryUsageInBytes(); - typename U::KeyType key = cache_entry->CacheKey(); - typename V::AddResult result = identifier_map->insert( - cache_entry->Generator(), typename V::MappedType()); - result.stored_value->value.insert(key); - cache_map->insert(key, std::move(cache_entry)); + typename V::AddResult id_result = + identifier_map->insert(entry_ptr->Generator(), typename V::MappedType()); + id_result.stored_value->value.insert(key); TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink.image_decoding"), "ImageDecodingStoreHeapMemoryUsageBytes", diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/graphics/image_decoding_store_test.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/graphics/image_decoding_store_test.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/graphics/image_decoding_store_test.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/graphics/image_decoding_store_test.cc 2026-04-27 20:03:22.000000000 +0000 @@ -235,4 +235,68 @@ EXPECT_EQ(image_decoding_store_.CacheEntries(), 0); } +// Regression test for crbug.com/500104917. +TEST_F(ImageDecodingStoreTest, DuplicateInsert) { + auto decoder1 = std::make_unique(this); + decoder1->SetSize(1, 1); + image_decoding_store_.InsertDecoder(generator_.get(), + cc::PaintImage::kDefaultGeneratorClientId, + std::move(decoder1)); + EXPECT_EQ(1, image_decoding_store_.CacheEntries()); + + auto decoder2 = std::make_unique(this); + decoder2->SetSize(1, 1); + + // Duplicate insertion should be handled safely. + image_decoding_store_.InsertDecoder(generator_.get(), + cc::PaintImage::kDefaultGeneratorClientId, + std::move(decoder2)); + + // Should still have only 1 entry. + EXPECT_EQ(1, image_decoding_store_.CacheEntries()); + + // Pruning should work correctly and not crash. + image_decoding_store_.SetCacheLimitInBytes(0); + EXPECT_EQ(0, image_decoding_store_.CacheEntries()); +} + +// Regression test for crbug.com/500104917. +// Simulates a race condition where multiple threads experience a cache miss +// for the same key and both attempt to insert a decoder. +TEST_F(ImageDecodingStoreTest, LockDecoderMissRace) { + const SkISize size = SkISize::Make(1, 1); + const ImageDecoder::AlphaOption alpha = ImageDecoder::kAlphaPremultiplied; + const cc::PaintImage::GeneratorClientId client_id = + cc::PaintImage::kDefaultGeneratorClientId; + + ImageDecoder* decoder; + // Thread 1: experiences a cache miss. + EXPECT_FALSE(image_decoding_store_.LockDecoder(generator_.get(), size, alpha, + client_id, &decoder)); + + // Thread 2: also experiences a cache miss for the same key because Thread 1 + // hasn't called InsertDecoder() yet. + EXPECT_FALSE(image_decoding_store_.LockDecoder(generator_.get(), size, alpha, + client_id, &decoder)); + + // Thread 1: completes decoding and inserts the decoder. + auto decoder1 = std::make_unique(this); + decoder1->SetSize(1, 1); + image_decoding_store_.InsertDecoder(generator_.get(), client_id, + std::move(decoder1)); + EXPECT_EQ(1, image_decoding_store_.CacheEntries()); + + // Thread 2: also completes decoding and attempts to insert its own decoder + // for the same key. This is a duplicate insertion. + auto decoder2 = std::make_unique(this); + decoder2->SetSize(1, 1); + image_decoding_store_.InsertDecoder(generator_.get(), client_id, + std::move(decoder2)); + + // The store should handle the duplicate safely and maintain consistency. + EXPECT_EQ(1, image_decoding_store_.CacheEntries()); + image_decoding_store_.SetCacheLimitInBytes(0); + EXPECT_EQ(0, image_decoding_store_.CacheEntries()); +} + } // namespace blink diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc 2026-04-27 20:03:22.000000000 +0000 @@ -948,11 +948,12 @@ return scale_denominator; } - // Downsample according to the maximum decoded size. - return static_cast(floor(sqrt( - // MSVC needs explicit parameter type for sqrt(). - static_cast(max_decoded_bytes) / original_bytes * - scale_denominator * scale_denominator))); + // Downsample according to the maximum decoded size. Use double to prevent + // precision loss that can trigger redundant decoder creation + // (crbug.com/500104917). + return static_cast( + floor(sqrt(static_cast(max_decoded_bytes) / original_bytes) * + scale_denominator)); } bool JPEGImageDecoder::ShouldGenerateAllSizes() const { diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc 2026-04-27 20:03:22.000000000 +0000 @@ -421,6 +421,23 @@ ASSERT_EQ(numerator_overflow, static_cast(7)); } +// Regression test for crbug.com/500104917. +TEST(JPEGImageDecoderTest, DesiredScaleNumeratorPrecision) { + // 16777216 = 2^24. Single-precision float can represent integers exactly + // up to this value. 16777217 is rounded to 16777216.0f. + wtf_size_t max_decoded_bytes = 16777216; + wtf_size_t original_bytes = 16777217; + unsigned scale_denominator = 8; + + // With float: + // floor(sqrt(16777216.0f / 16777216.0f) * 8) = 8 + // With double: + // floor(sqrt(16777216.0 / 16777217.0) * 8) = 7 + auto numerator = JPEGImageDecoder::DesiredScaleNumerator( + max_decoded_bytes, original_bytes, scale_denominator); + EXPECT_EQ(numerator, 7u); +} + struct ColorSpaceTestParam { std::string file; bool expected_success = false; diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.cc 2026-04-27 20:03:22.000000000 +0000 @@ -19,6 +19,7 @@ #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h" #include "third_party/webrtc/api/frame_transformer_interface.h" #include "third_party/webrtc/rtc_base/ref_counted_object.h" @@ -36,26 +37,20 @@ // shortcircuiting/setting transforms. const size_t kMaxBufferedFrames = 60; -// This delegate class exists to work around the fact that -// RTCEncodedVideoStreamTransformer cannot derive from webrtc::RefCountedObject -// and post tasks referencing itself as an webrtc::scoped_refptr. Instead, -// RTCEncodedVideoStreamTransformer creates a delegate using -// webrtc::RefCountedObject and posts tasks referencing the delegate, which -// invokes the RTCEncodedVideoStreamTransformer via callbacks. -class RTCEncodedVideoStreamTransformerDelegate - : public webrtc::FrameTransformerInterface { +// This class handles the metronome-related tasks for the transformer delegate. +// It is ref-counted and decoupled from the delegate's lifecycle to avoid +// circular references and ensure cross-thread safety. +class VideoMetronomeWorker : public ThreadSafeRefCounted { public: - RTCEncodedVideoStreamTransformerDelegate( - scoped_refptr realm_task_runner, + VideoMetronomeWorker( + std::unique_ptr metronome, scoped_refptr transformer_broker, - std::unique_ptr metronome) - : source_task_runner_(realm_task_runner), + scoped_refptr source_task_runner) + : source_task_runner_(source_task_runner), transformer_broker_(std::move(transformer_broker)), - metronome_(std::move(metronome)) { - DCHECK(source_task_runner_->BelongsToCurrentThread()); - DETACH_FROM_SEQUENCE(metronome_sequence_checker_); - } + use_metronome_(!!metronome), + metronome_(std::move(metronome)) {} void SetSourceTaskRunner( scoped_refptr task_runner) { @@ -63,77 +58,155 @@ source_task_runner_ = std::move(task_runner); } - // webrtc::FrameTransformerInterface void RegisterTransformedFrameSinkCallback( webrtc::scoped_refptr send_frame_to_sink_callback, - uint32_t ssrc) override { + uint32_t ssrc) { transformer_broker_->RegisterTransformedFrameSinkCallback( std::move(send_frame_to_sink_callback), ssrc); } - void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) override { + void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) { transformer_broker_->UnregisterTransformedFrameSinkCallback(ssrc); } void Transform( - std::unique_ptr frame) override { - auto video_frame = - base::WrapUnique(static_cast( - frame.release())); - if (metronome_) { - DCHECK_CALLED_ON_VALID_SEQUENCE(metronome_sequence_checker_); - queued_frames_.emplace_back(std::move(video_frame)); - if (!tick_scheduled_) { - tick_scheduled_ = true; + std::unique_ptr frame) { + if (use_metronome_) { + bool should_schedule_tick = false; + { + base::AutoLock locker(metronome_lock_); + queued_frames_.emplace_back(std::move(frame)); + if (!tick_scheduled_) { + tick_scheduled_ = true; + should_schedule_tick = true; + } + } + + if (should_schedule_tick) { // Using a lambda here instead of a OnceClosure as // RequestCallOnNextTick() requires an absl::AnyInvocable. metronome_->RequestCallOnNextTick( - [delegate = weak_factory_.GetWeakPtr()] { - if (delegate) { - delegate->InvokeQueuedTransforms(); - } + [worker = scoped_refptr(this)] { + worker->InvokeQueuedTransforms(); }); } - } else { + return; + } + + scoped_refptr task_runner; + { base::AutoLock locker(source_task_runner_lock_); + task_runner = source_task_runner_; + } + if (task_runner) { PostCrossThreadTask( - *source_task_runner_, FROM_HERE, + *task_runner, FROM_HERE, CrossThreadBindOnce(&RTCEncodedVideoStreamTransformer::Broker:: TransformFrameOnSourceTaskRunner, - transformer_broker_, std::move(video_frame))); + transformer_broker_, std::move(frame))); } } - private: void InvokeQueuedTransforms() { - DCHECK_CALLED_ON_VALID_SEQUENCE(metronome_sequence_checker_); - base::AutoLock locker(source_task_runner_lock_); - tick_scheduled_ = false; + Vector> frames; + { + base::AutoLock locker(metronome_lock_); + tick_scheduled_ = false; + frames = std::move(queued_frames_); + } + + scoped_refptr task_runner; + { + base::AutoLock locker(source_task_runner_lock_); + task_runner = source_task_runner_; + } + if (!task_runner) { + return; + } for (std::unique_ptr& frame : - queued_frames_) { + frames) { PostCrossThreadTask( - *source_task_runner_, FROM_HERE, + *task_runner, FROM_HERE, CrossThreadBindOnce(&RTCEncodedVideoStreamTransformer::Broker:: TransformFrameOnSourceTaskRunner, transformer_broker_, std::move(frame))); } - queued_frames_.clear(); } + void Disconnect() { + base::AutoLock locker(source_task_runner_lock_); + source_task_runner_.reset(); + } + + private: + friend class ThreadSafeRefCounted; + ~VideoMetronomeWorker() = default; + base::Lock source_task_runner_lock_; scoped_refptr source_task_runner_ GUARDED_BY(source_task_runner_lock_); + scoped_refptr transformer_broker_; + const bool use_metronome_; + const std::unique_ptr metronome_; - std::unique_ptr metronome_; - SEQUENCE_CHECKER(metronome_sequence_checker_); - bool tick_scheduled_ GUARDED_BY_CONTEXT(metronome_sequence_checker_) = false; + base::Lock metronome_lock_; + bool tick_scheduled_ GUARDED_BY(metronome_lock_) = false; Vector> - queued_frames_ GUARDED_BY_CONTEXT(metronome_sequence_checker_); + queued_frames_ GUARDED_BY(metronome_lock_); +}; - base::WeakPtrFactory weak_factory_{ - this}; +// This delegate class exists to work around the fact that +// RTCEncodedVideoStreamTransformer cannot derive from webrtc::RefCountedObject +// and post tasks referencing itself as an webrtc::scoped_refptr. Instead, +// RTCEncodedVideoStreamTransformer creates a delegate using +// webrtc::RefCountedObject and posts tasks referencing the delegate, which +// invokes the RTCEncodedVideoStreamTransformer via callbacks. +class RTCEncodedVideoStreamTransformerDelegate + : public webrtc::FrameTransformerInterface { + public: + RTCEncodedVideoStreamTransformerDelegate( + scoped_refptr realm_task_runner, + scoped_refptr + transformer_broker, + std::unique_ptr metronome) + : metronome_worker_(base::MakeRefCounted( + std::move(metronome), + std::move(transformer_broker), + realm_task_runner)) {} + + ~RTCEncodedVideoStreamTransformerDelegate() override { + metronome_worker_->Disconnect(); + } + + void SetSourceTaskRunner( + scoped_refptr task_runner) { + metronome_worker_->SetSourceTaskRunner(std::move(task_runner)); + } + + // webrtc::FrameTransformerInterface + void RegisterTransformedFrameSinkCallback( + webrtc::scoped_refptr + send_frame_to_sink_callback, + uint32_t ssrc) override { + metronome_worker_->RegisterTransformedFrameSinkCallback( + std::move(send_frame_to_sink_callback), ssrc); + } + + void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) override { + metronome_worker_->UnregisterTransformedFrameSinkCallback(ssrc); + } + + void Transform( + std::unique_ptr frame) override { + metronome_worker_->Transform( + base::WrapUnique(static_cast( + frame.release()))); + } + + private: + scoped_refptr metronome_worker_; }; } // namespace diff -Nru chromium-147.0.7727.116/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer_test.cc chromium-147.0.7727.137/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer_test.cc --- chromium-147.0.7727.116/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer_test.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer_test.cc 2026-04-27 20:03:22.000000000 +0000 @@ -288,4 +288,41 @@ CrossThreadUnretained(&mock_transformer_callback_holder_))); } +TEST_P(RTCEncodedVideoStreamTransformerTest, WorkerOutlivesDelegate) { + if (!GetParam()) { + return; + } + + MockTransformerCallbackHolder lifecycle_callback_holder; + scoped_refptr main_runner = + blink::scheduler::GetSingleThreadTaskRunnerForTesting(); + auto* mock_metronome = new NiceMock(); + // Using AnyInvocable as that's what the libwebrtc Metronome + // interface requires. + absl::AnyInvocable metronome_callback; + EXPECT_CALL(*mock_metronome, RequestCallOnNextTick) + .WillOnce([&](absl::AnyInvocable c) { + metronome_callback = std::move(c); + }); + + auto transformer = std::make_unique( + main_runner, absl::WrapUnique(mock_metronome)); + transformer->SetTransformerCallback(CrossThreadBindRepeating( + &MockTransformerCallbackHolder::OnEncodedFrame, + CrossThreadUnretained(&lifecycle_callback_holder))); + + // Send a frame to schedule a tick. + transformer->Delegate()->Transform(CreateMockFrame()); + ASSERT_TRUE(metronome_callback); + + // Destroy the transformer. This should call Disconnect() on the delegate's + // worker. + transformer.reset(); + + // Now fire the metronome tick. It should NOT crash and should NOT call + // the callback (since the transformer is gone). + EXPECT_CALL(lifecycle_callback_holder, OnEncodedFrame).Times(0); + std::move(metronome_callback)(); +} + } // namespace blink diff -Nru chromium-147.0.7727.116/third_party/dawn/src/dawn/native/CommandEncoder.cpp chromium-147.0.7727.137/third_party/dawn/src/dawn/native/CommandEncoder.cpp --- chromium-147.0.7727.116/third_party/dawn/src/dawn/native/CommandEncoder.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/dawn/native/CommandEncoder.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -1080,13 +1080,6 @@ DAWN_TRY(device->GetQueue()->WriteBuffer(paramsBuffer.Get(), 0, ¶ms, sizeof(params))); - // In the internal shader to convert timestamps to nanoseconds, we can ensure no uninitialized - // data will be read and the full buffer range will be filled with valid data. - if (!destination->IsInitialized() && - destination->IsFullBufferRange(firstQuery, sizeof(uint64_t) * queryCount)) { - destination->SetInitialized(true); - } - return EncodeConvertTimestampsToNanoseconds(encoder, destination, availabilityBuffer.Get(), paramsBuffer.Get()); } @@ -2189,7 +2182,8 @@ // Encode internal compute pipeline for timestamp query if (querySet->GetQueryType() == wgpu::QueryType::Timestamp && !GetDevice()->IsToggleEnabled(Toggle::DisableTimestampQueryConversion) && - GetDevice()->GetTimestampPeriodInNS() != 1.0f) { + (GetDevice()->GetTimestampPeriodInNS() != 1.0f || + GetDevice()->IsToggleEnabled(Toggle::TimestampQueryConversionEvenIf1NS))) { // The below function might create new resources. Need to lock the Device. // TODO(crbug.com/dawn/1618): In future, all temp resources should be created at // Command Submit time, so the locking would be removed from here at that point. diff -Nru chromium-147.0.7727.116/third_party/dawn/src/dawn/native/Toggles.cpp chromium-147.0.7727.137/third_party/dawn/src/dawn/native/Toggles.cpp --- chromium-147.0.7727.116/third_party/dawn/src/dawn/native/Toggles.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/dawn/native/Toggles.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -259,6 +259,11 @@ {"disable_timestamp_query_conversion", "Resolve timestamp queries into ticks instead of nanoseconds.", "https://crbug.com/dawn/1305", ToggleStage::Device}}, + {Toggle::TimestampQueryConversionEvenIf1NS, + {"timestamp_query_conversion_even_if_1ns", + "Force timestamp query conversion to run even if it isn't needed (unless " + "disable_timestamp_query_conversion).", + "https://crbug.com/499211666", ToggleStage::Device}}, {Toggle::TimestampQuantization, {"timestamp_quantization", "Enable timestamp queries quantization to reduce the precision of timers that can be created " diff -Nru chromium-147.0.7727.116/third_party/dawn/src/dawn/native/Toggles.h chromium-147.0.7727.137/third_party/dawn/src/dawn/native/Toggles.h --- chromium-147.0.7727.116/third_party/dawn/src/dawn/native/Toggles.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/dawn/native/Toggles.h 2026-04-27 20:03:22.000000000 +0000 @@ -83,6 +83,7 @@ FxcOptimizations, RecordDetailedTimingInTraceEvents, DisableTimestampQueryConversion, + TimestampQueryConversionEvenIf1NS, TimestampQuantization, ClearBufferBeforeResolveQueries, VulkanUseZeroInitializeWorkgroupMemoryExtension, diff -Nru chromium-147.0.7727.116/third_party/dawn/src/dawn/tests/end2end/BufferZeroInitTests.cpp chromium-147.0.7727.137/third_party/dawn/src/dawn/tests/end2end/BufferZeroInitTests.cpp --- chromium-147.0.7727.116/third_party/dawn/src/dawn/tests/end2end/BufferZeroInitTests.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/dawn/tests/end2end/BufferZeroInitTests.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -1309,25 +1309,19 @@ // Test the buffer will be lazily initialized correctly when its first use is in resolveQuerySet TEST_P(BufferZeroInitTest, ResolveQuerySet) { - // Timestamp query is not supported on OpenGL - DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); + // Skip if timestamp feature is not supported on device + DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::TimestampQuery})); // TODO(crbug.com/dawn/545): Crash occurs if we only call WriteTimestamp in a command encoder // without any copy commands on Metal on AMD GPU. DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD()); - // Skip if timestamp feature is not supported on device - DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::TimestampQuery})); - - // crbug.com/dawn/940: Does not work on Mac 11.0+. Backend validation changed. - DAWN_TEST_UNSUPPORTED_IF(IsMacOS() && !IsMacOS(10)); - // TODO(crbug.com/451389800): [Capture] implement query set. DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled()); constexpr uint64_t kBufferSize = 16u; constexpr wgpu::BufferUsage kBufferUsage = - wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopyDst; + wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc; wgpu::QuerySetDescriptor descriptor; descriptor.count = 2u; @@ -1349,6 +1343,25 @@ EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commands)); } + // Resolve data to the whole buffer requires lazy initialization if the buffer is read without + // being submitted. + { + constexpr uint32_t kQueryCount = 2u; + constexpr uint64_t kDestinationOffset = 0u; + + wgpu::Buffer destination = CreateBuffer(kBufferSize, kBufferUsage); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.WriteTimestamp(querySet, 0); + encoder.WriteTimestamp(querySet, 1); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, kDestinationOffset); + /* no submit*/ encoder.Finish(); + + // Lazy clear occurs on read. + constexpr std::array kExpectedData = {{0, 0}}; + EXPECT_LAZY_CLEAR(1u, EXPECT_BUFFER_U64_RANGE_EQ(kExpectedData.data(), destination, 0, + kExpectedData.size())); + } + // Resolve data to partial of the buffer needs lazy initialization. // destinationOffset == 0 and destinationOffset + 8 * queryCount < kBufferSize { @@ -1380,17 +1393,26 @@ } DAWN_INSTANTIATE_TEST(BufferZeroInitTest, - D3D11Backend({"nonzero_clear_resources_on_creation_for_testing"}), + D3D11Backend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), D3D11Backend({"auto_map_backend_buffer", "d3d11_disable_cpu_buffers", - "nonzero_clear_resources_on_creation_for_testing"}), - D3D12Backend({"nonzero_clear_resources_on_creation_for_testing"}), - D3D12Backend({"nonzero_clear_resources_on_creation_for_testing"}, + "nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + D3D12Backend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + D3D12Backend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}, {"d3d12_create_not_zeroed_heap"}), - MetalBackend({"nonzero_clear_resources_on_creation_for_testing"}), - OpenGLBackend({"nonzero_clear_resources_on_creation_for_testing"}), - OpenGLESBackend({"nonzero_clear_resources_on_creation_for_testing"}), - VulkanBackend({"nonzero_clear_resources_on_creation_for_testing"}), - WebGPUBackend({"nonzero_clear_resources_on_creation_for_testing"})); + MetalBackend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + OpenGLBackend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + OpenGLESBackend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + VulkanBackend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"}), + WebGPUBackend({"nonzero_clear_resources_on_creation_for_testing", + "timestamp_query_conversion_even_if_1ns"})); } // anonymous namespace } // namespace dawn diff -Nru chromium-147.0.7727.116/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis.cc chromium-147.0.7727.137/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis.cc --- chromium-147.0.7727.116/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis.cc 2026-04-27 20:03:22.000000000 +0000 @@ -190,7 +190,7 @@ [&](Bitcast*) { return true; }, // [&](Binary*) { return true; }, // [&](If* i) { - if (IsBreakIfOnIndex(loop, i, index)) { + if (IsBreakIfOnIndex(i, index)) { // The loop is finite. has_break_if = true; } @@ -206,7 +206,7 @@ } /// @returns `true` if @p is a break-if construct that exits the loop based on @p index. - bool IsBreakIfOnIndex(const Loop& loop, If* i, Var& index) { + bool IsBreakIfOnIndex(If* i, Var& index) { // Returns `true` if the given value is a load of the index variable. auto is_index = [&index](Value* v) { if (auto* load = As(UnwrapBitcast(v))) { @@ -214,18 +214,6 @@ } return false; }; - // Returns `true` if the given value an immutable value declared before the loop body. - auto is_immutable_before_body = [&loop](Value* v) { - return tint::Switch( - UnwrapBitcast(v), // - [](ir::Constant*) { return true; }, // - [](ir::FunctionParam*) { return true; }, // - [&](ir::InstructionResult* r) { - auto* let = r->Instruction()->As(); - return let && let->Block() != loop.Body(); - } // - ); - }; auto is_constant_i32_or_u32 = [](Value* v) { auto* constant_value = v->As(); if (!constant_value) { @@ -235,10 +223,53 @@ }; auto is_capable_binary_for_loop_exit = [&](Binary* binary) { switch (binary->Op()) { - case BinaryOp::kLessThan: + case BinaryOp::kLessThan: { + if (is_index(binary->LHS()) && is_constant_i32_or_u32(binary->RHS())) { + // index < kConstantValue + // If `kConstantValue` is the lowest possible value then the expression is + // always false. + auto* constant_value = binary->RHS()->As()->Value(); + if (constant_value->Type()->Is()) { + return constant_value->ValueAs() > i32::kLowestValue; + } + TINT_ASSERT(constant_value->Type()->Is()); + return constant_value->ValueAs() > u32::kLowestValue; + } else if (is_index(binary->RHS()) && is_constant_i32_or_u32(binary->LHS())) { + // kConstantValue < index + // If `kConstantValue` is the highest possible value then the expression is + // always false. + auto* constant_value = binary->LHS()->As()->Value(); + if (constant_value->Type()->Is()) { + return constant_value->ValueAs() < i32::kHighestValue; + } + TINT_ASSERT(constant_value->Type()->Is()); + return constant_value->ValueAs() < u32::kHighestValue; + } + return false; + } case BinaryOp::kGreaterThan: { - return (is_index(binary->LHS()) && is_immutable_before_body(binary->RHS())) || - (is_index(binary->RHS()) && is_immutable_before_body(binary->LHS())); + if (is_index(binary->LHS()) && is_constant_i32_or_u32(binary->RHS())) { + // index > kConstantValue + // If `kConstantValue` is the highest possible value then the expression is + // always false. + auto* constant_value = binary->RHS()->As()->Value(); + if (constant_value->Type()->Is()) { + return constant_value->ValueAs() < i32::kHighestValue; + } + TINT_ASSERT(constant_value->Type()->Is()); + return constant_value->ValueAs() < u32::kHighestValue; + } else if (is_index(binary->RHS()) && is_constant_i32_or_u32(binary->LHS())) { + // kConstantValue > index + // If `kConstantValue` is the lowest possible value then the expression is + // always false. + auto* constant_value = binary->LHS()->As()->Value(); + if (constant_value->Type()->Is()) { + return constant_value->ValueAs() > i32::kLowestValue; + } + TINT_ASSERT(constant_value->Type()->Is()); + return constant_value->ValueAs() > u32::kLowestValue; + } + return false; } case BinaryOp::kLessThanEqual: { if (is_index(binary->LHS()) && is_constant_i32_or_u32(binary->RHS())) { diff -Nru chromium-147.0.7727.116/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis_test.cc chromium-147.0.7727.137/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis_test.cc --- chromium-147.0.7727.116/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis_test.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/tint/lang/core/ir/analysis/loop_analysis_test.cc 2026-04-27 20:03:22.000000000 +0000 @@ -27,7 +27,7 @@ #include "src/tint/lang/core/ir/analysis/loop_analysis.h" -#include +#include #include "src/tint/lang/core/ir/ir_helper_test.h" #include "src/tint/lang/core/ir/validator.h" @@ -40,387 +40,337 @@ class IR_LoopAnalysisTest : public IRTestHelper {}; -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanConstant_IncByOne) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.LessThan(b.Load(idx), 10_u)); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lt %3, 10u - if %4 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_ConstantLessThanIndex_IncByOne) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.LessThan(10_u, b.Load(idx))); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lt 10u, %3 - if %4 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_IndexGreaterThanConstant_IncByOne) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.GreaterThan(b.Load(idx), 10_u)); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = gt %3, 10u - if %4 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } +enum class Type : uint8_t { + kI32, + kU32, +}; +enum class Direction : uint8_t { + kIndexOpBound, + kBoundOpIndex, +}; +enum class BoundKind : uint8_t { + kConstant, + kFunctionParam, + kLet, + kVar, +}; +struct BoundDescriptor { + Type type; + Direction dir; + BoundKind kind; + int64_t const_value; + BinaryOp op; + bool is_finite; +}; + +// Empty structure used to describe the position of the index variable in an expression. +struct IndexValue { +} Index; + +template +concept I32OrU32 = std::same_as || std::same_as; + +// Helper to declare a BoundDescriptor with a concise expression. +// e.g. +// Bound(true) +// Bound(true) +// +template +BoundDescriptor Bound(bool is_infinite) { + BoundDescriptor desc; + desc.op = op; + desc.is_finite = is_infinite; + if constexpr (std::is_same_v) { + desc.type = Type::kI32; + } else if constexpr (std::is_same_v) { + desc.type = Type::kU32; + } + + if constexpr (std::is_same_v) { + // Index OP Bound + desc.dir = Direction::kIndexOpBound; + if constexpr (std::is_same_v) { + desc.kind = RHS; + } else { + desc.kind = BoundKind::kConstant; + desc.const_value = RHS; + } + } else { + // Bound OP Index + desc.dir = Direction::kBoundOpIndex; + if constexpr (std::is_same_v) { + desc.kind = LHS; + } else { + desc.kind = BoundKind::kConstant; + desc.const_value = LHS; + } + } + return desc; +} + +void PrintKind(tint::StringStream& stream, const BoundDescriptor& bound) { + switch (bound.kind) { + case BoundKind::kConstant: + if (bound.type == Type::kI32) { + int64_t i = bound.const_value; + if (i < 0) { + stream << "n"; + i = -i; + } + stream << i << "i"; + } else if (bound.type == Type::kU32) { + stream << bound.const_value << "u"; + } + break; + case BoundKind::kFunctionParam: + stream << "param"; + break; + case BoundKind::kLet: + stream << "let"; + break; + case BoundKind::kVar: + stream << "var"; + break; + } +} + +// These parameterized tests cover the many different ways of constructing the loop exit condition: +// +// if (LHS OP RHS) { +// break; +// } +// +// Either LHS or RHS will be the loop index. +// The other will be either a constant value or a selection from other possible value sources. +// +using IR_LoopAnalysisBoundTest = IRTestParamHelper; +TEST_P(IR_LoopAnalysisBoundTest, Bound) { + auto params = GetParam(); + + const core::type::Type* type = nullptr; + core::ir::Constant* zero = nullptr; + core::ir::Constant* one = nullptr; + if (params.type == Type::kI32) { + type = ty.i32(); + zero = b.Constant(0_i); + one = b.Constant(1_i); + } else { + type = ty.u32(); + zero = b.Constant(0_u); + one = b.Constant(1_u); } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanParam_IncByOne) { Var* idx = nullptr; Loop* loop = nullptr; - auto* end = b.FunctionParam("end", ty.u32()); auto* func = b.Function("func", ty.void_()); - func->AppendParam(end); b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.LessThan(b.Load(idx), end)); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func(%end:u32):void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %4:u32 = load %idx - %5:bool = lt %4, %end - if %5 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %6:u32 = load %idx - %7:u32 = add %6, 1u - store %idx, %7 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); + auto* param = b.FunctionParam("param", type); + func->AppendParam(param); + auto* let = b.Let("let", param); + auto* var = b.Var("var", param); + + loop = b.Loop(); + b.Append(loop->Initializer(), [&] { // + idx = b.Var("idx", zero); + b.NextIteration(loop); + }); + b.Append(loop->Body(), [&] { + Instruction* cond = nullptr; + + core::ir::Value* bound = nullptr; + switch (params.kind) { + case BoundKind::kConstant: + if (params.type == Type::kI32) { + bound = b.Constant(i32(params.const_value)); + } else { + bound = b.Constant(u32(params.const_value)); + } + break; + case BoundKind::kFunctionParam: + bound = param; + break; + case BoundKind::kLet: + bound = let->Result(); + break; + case BoundKind::kVar: + bound = b.Load(var)->Result(); + break; + } - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} + switch (params.dir) { + case Direction::kIndexOpBound: + cond = b.Binary(params.op, ty.bool_(), b.Load(idx), bound); + break; + case Direction::kBoundOpIndex: + cond = b.Binary(params.op, ty.bool_(), bound, b.Load(idx)); + break; + } -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanLet_IncByOne) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - auto* end = b.Let("end", 10_u); - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.LessThan(b.Load(idx), end)); + auto* ifelse = b.If(cond); b.Append(ifelse->True(), [&] { // b.ExitLoop(loop); }); b.Continue(loop); }); b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); + b.Store(idx, b.Add(b.Load(idx), one)); b.NextIteration(loop); }); b.Return(func); }); - auto* src = R"( -%func = func():void { - $B1: { - %end:u32 = let 10u - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %4:u32 = load %idx - %5:bool = lt %4, %end - if %5 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %6:u32 = load %idx - %7:u32 = add %6, 1u - store %idx, %7 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); + EXPECT_EQ(Validate(mod), Success); LoopAnalysis analysis(*func); auto* info = analysis.GetInfo(*loop); ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanConstant_IncByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* ifelse = b.If(b.LessThan(b.Load(idx), 10_i)); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = lt %3, 10i - if %4 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = add %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); + if (params.is_finite) { + EXPECT_TRUE(info->IsFinite()); + EXPECT_EQ(info->index_var, idx); + } else { + EXPECT_FALSE(info->IsFinite()); + EXPECT_EQ(info->index_var, nullptr); + } +} + +using enum BinaryOp; +using enum BoundKind; +INSTANTIATE_TEST_SUITE_P(, + IR_LoopAnalysisBoundTest, + testing::Values( + // Comparing the index to a constant that is not a limit is always OK. + Bound(true), + Bound(true), + Bound(true), + Bound(true), + + Bound(true), + Bound(true), + Bound(true), + Bound(true), + + Bound(true), + Bound(true), + Bound(true), + Bound(true), + + Bound(true), + Bound(true), + Bound(true), + Bound(true), + + // Comparing the index to a constant that is at a limit can result in + // an always-true or always-false result, which is not OK. + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + // Using other immutable values for the bound is not OK since that + // value could result in an always-true or always-false outcome. + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + Bound(false), + + // Using a var for the bound is never OK. + Bound(false), + Bound(false), + Bound(false), + Bound(false) + + // + ), + [](const testing::TestParamInfo& desc) { + tint::StringStream name; + + switch (desc.param.type) { + case Type::kI32: + name << "i32_"; + break; + case Type::kU32: + name << "u32_"; + break; + } + + if (desc.param.dir == Direction::kIndexOpBound) { + name << "index"; + } else { + PrintKind(name, desc.param); + } + + switch (desc.param.op) { + case kLessThan: + name << "_lt_"; + break; + case kGreaterThan: + name << "_gt_"; + break; + case kLessThanEqual: + name << "_lte_"; + break; + case kGreaterThanEqual: + name << "_gte_"; + break; + default: + break; + } + + if (desc.param.dir == Direction::kBoundOpIndex) { + name << "index"; + } else { + PrintKind(name, desc.param); + } - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} + return name.str(); + }); TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanConstant_OnePlusIndex) { Var* idx = nullptr; @@ -1190,70 +1140,6 @@ EXPECT_EQ(info->index_var, nullptr); } -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_EndBoundIsVar) { - Var* idx = nullptr; - Loop* loop = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { // - idx = b.Var("idx", 0_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - auto* load = b.Load(idx); - auto* ifelse = b.If(b.LessThan(load, load)); - b.Append(ifelse->True(), [&] { // - b.ExitLoop(loop); - }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { // - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 0u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lt %3, %3 - if %4 [t: $B5] { # if_1 - $B5: { # true - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - TEST_F(IR_LoopAnalysisTest, MaybeInfinite_EndBoundIsLetInBody) { Var* idx = nullptr; Loop* loop = nullptr; @@ -2416,1141 +2302,5 @@ EXPECT_EQ(info_inner_2->index_var, idx_inner_2); } -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanEqualConstant_IncByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx <= 10 - binary = b.LessThanEqual(b.Load(idx), 10_i); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = lte %3, 10i - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = add %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_IndexLessThanEqualConstant_IncByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx <= 10 - binary = b.LessThanEqual(b.Load(idx), 10_u); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lte %3, 10u - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_IndexLessThanEqualHighestValue_IncByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx <= i32::kHighestValue - binary = b.LessThanEqual(b.Load(idx), i32::Highest()); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = lte %3, 2147483647i - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = add %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_IndexLessThanEqualHighestValue_IncByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx <= u32::kHighestValue - binary = b.LessThanEqual(b.Load(idx), u32::Highest()); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lte %3, 4294967295u - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, Finite_ConstantLessThanEqualIndex_DecByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // 1 <= idx - binary = b.LessThanEqual(1_i, b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = lte 1i, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = sub %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_ConstantLessThanEqualIndex_DecByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // 1 <= idx - binary = b.LessThanEqual(1_u, b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lte 1u, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = sub %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_LowestValueLessThanEqualIndex_DecByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // i32::kLowestValue <= idx - binary = b.LessThanEqual(i32::Lowest(), b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = lte -2147483648i, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = sub %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_LowestValueLessThanEqualIndex_DecByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // u32::kLowestValue <= idx - binary = b.LessThanEqual(u32::Lowest(), b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = lte 0u, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = sub %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, Finite_IndexGreaterThanEqualConstant_DecByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx >= 1 - binary = b.GreaterThanEqual(b.Load(idx), 1_i); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = gte %3, 1i - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = sub %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_IndexGreaterThanEqualConstant_DecByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx <= 1 - binary = b.GreaterThanEqual(b.Load(idx), 1_u); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = gte %3, 1u - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = sub %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_IndexGreaterThanEqualLowestValue_DecByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx >= i32::kLowestValue - binary = b.GreaterThanEqual(b.Load(idx), i32::Lowest()); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = gte %3, -2147483648i - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = sub %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_IndexGreaterThanEqualLowestValue_DecByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // idx >= u32::kLowestValue - binary = b.GreaterThanEqual(b.Load(idx), u32::Lowest()); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx-- - b.Store(idx, b.Subtract(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = gte %3, 0u - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = sub %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, Finite_ConstantGreaterThanEqualIndex_IncByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // 10 >= idx - binary = b.GreaterThanEqual(10_i, b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = gte 10i, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = add %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, Finite_ConstantGreaterThanEqualIndex_IncByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 1 - idx = b.Var("idx", 1_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // 10 >= idx - binary = b.GreaterThanEqual(10_u, b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 1u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = gte 10u, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_TRUE(info->IsFinite()); - EXPECT_EQ(info->index_var, idx); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_HighestValueGreaterThanEqualIndex_IncByOne_I32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_i); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // i32::kHighestValue >= idx - binary = b.GreaterThanEqual(i32::Highest(), b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_i)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10i - next_iteration # -> $B3 - } - $B3: { # body - %3:i32 = load %idx - %4:bool = gte 2147483647i, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:i32 = load %idx - %6:i32 = add %5, 1i - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - -TEST_F(IR_LoopAnalysisTest, MaybeInfinite_HighestValueGreaterThanEqualIndex_IncByOne_U32) { - Var* idx = nullptr; - Loop* loop = nullptr; - Binary* binary = nullptr; - auto* func = b.Function("func", ty.void_()); - b.Append(func->Block(), [&] { - loop = b.Loop(); - b.Append(loop->Initializer(), [&] { - // idx = 10 - idx = b.Var("idx", 10_u); - b.NextIteration(loop); - }); - b.Append(loop->Body(), [&] { - // u32::kHighestValue >= idx - binary = b.GreaterThanEqual(u32::Highest(), b.Load(idx)); - auto* ifelse = b.If(binary); - b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); }); - b.Append(ifelse->False(), [&] { b.ExitLoop(loop); }); - b.Continue(loop); - }); - b.Append(loop->Continuing(), [&] { - // idx++ - b.Store(idx, b.Add(b.Load(idx), 1_u)); - b.NextIteration(loop); - }); - b.Return(func); - }); - - auto* src = R"( -%func = func():void { - $B1: { - loop [i: $B2, b: $B3, c: $B4] { # loop_1 - $B2: { # initializer - %idx:ptr = var 10u - next_iteration # -> $B3 - } - $B3: { # body - %3:u32 = load %idx - %4:bool = gte 4294967295u, %3 - if %4 [t: $B5, f: $B6] { # if_1 - $B5: { # true - exit_if # if_1 - } - $B6: { # false - exit_loop # loop_1 - } - } - continue # -> $B4 - } - $B4: { # continuing - %5:u32 = load %idx - %6:u32 = add %5, 1u - store %idx, %6 - next_iteration # -> $B3 - } - } - ret - } -} -)"; - - EXPECT_EQ(src, str()); - EXPECT_EQ(ValidateBefore(mod), Success); - - LoopAnalysis analysis(*func); - auto* info = analysis.GetInfo(*loop); - ASSERT_NE(info, nullptr); - EXPECT_FALSE(info->IsFinite()); - EXPECT_EQ(info->index_var, nullptr); -} - } // namespace } // namespace tint::core::ir::analysis diff -Nru chromium-147.0.7727.116/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout.cc chromium-147.0.7727.137/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout.cc --- chromium-147.0.7727.116/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout.cc 2026-04-27 20:03:22.000000000 +0000 @@ -196,7 +196,12 @@ const core::type::Type* new_elem_type = nullptr; auto* vec = arr->ElemType()->As(); if (vec && vec->Width() == 3) { - new_elem_type = GetPackedVec3ArrayElementStruct(vec->Type()); + // A vec3 is always converted to a vec3 + if (vec->Type()->Is()) { + new_elem_type = GetPackedVec3ArrayElementStruct(ty.u32()); + } else { + new_elem_type = GetPackedVec3ArrayElementStruct(vec->Type()); + } } else { new_elem_type = RewriteType(arr->ElemType()); } diff -Nru chromium-147.0.7727.116/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc chromium-147.0.7727.137/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc --- chromium-147.0.7727.116/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/dawn/src/tint/lang/msl/writer/raise/fix_type_layout_test.cc 2026-04-27 20:03:22.000000000 +0000 @@ -4306,5 +4306,58 @@ EXPECT_EQ(expect, str()); } +// Test that an array> handles the `u32` conversion correctly. +// https://crbug.com/505317119 +TEST_F(MslWriter_FixTypeLayoutTest, ArrayVec3Bool) { + auto* var = b.Var, 1>>("v"); + mod.root_block->Append(var); + + auto* func = b.Function("foo", ty.void_()); + b.Append(func->Block(), [&] { // + auto* ptr = b.Access(ty.ptr(workgroup, ty.vec3(), read_write), var, 0_u); + b.LoadVectorElement(ptr, 0_u); + b.Return(func); + }); + + auto* src = R"( +$B1: { # root + %v:ptr, 1>, read_write> = var undef +} + +%foo = func():void { + $B2: { + %3:ptr, read_write> = access %v, 0u + %4:bool = load_vector_element %3, 0u + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +tint_packed_vec3_u32_array_element = struct @align(16) { + packed:__packed_vec3 @offset(0) +} + +$B1: { # root + %v:ptr, read_write> = var undef +} + +%foo = func():void { + $B2: { + %3:ptr, read_write> = access %v, 0u, 0u + %4:u32 = load_vector_element %3, 0u + %5:bool = convert %4 + ret + } +} +)"; + + options.replace_bool_with_u32 = false; + Run(); + + EXPECT_EQ(expect, str()); +} + } // namespace } // namespace tint::msl::writer::raise diff -Nru chromium-147.0.7727.116/third_party/skia/include/core/SkRegion.h chromium-147.0.7727.137/third_party/skia/include/core/SkRegion.h --- chromium-147.0.7727.116/third_party/skia/include/core/SkRegion.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/skia/include/core/SkRegion.h 2026-04-27 20:03:22.000000000 +0000 @@ -447,8 +447,9 @@ #endif /** \class SkRegion::Iterator - Returns sequence of rectangles, sorted along y-axis, then x-axis, that make - up SkRegion. + Goes through the region one rectangle at a time. For each "strip" of one or more contiguous + Y values (scanlines) in ascending order, the iterator returns each rectangle in that strip + (from left to right) before advancing to the next strip (which may or may not have a gap). */ class SK_API Iterator { public: @@ -494,9 +495,12 @@ bool done() const { return fDone; } /** Advances SkRegion::Iterator to next SkIRect in SkRegion if it is not done. - - example: https://fiddle.skia.org/c/@Region_Iterator_next - */ + * This moves to the next rectangle to the right within the current + * horizontal strip. If the end of the strip is reached, it automatically + * advances to the first rectangle in the next strip, skipping any vertical gaps. + * + * example: https://fiddle.skia.org/c/@Region_Iterator_next + */ void next(); /** Returns SkIRect element in SkRegion. Does not return predictable results if SkRegion @@ -514,6 +518,7 @@ private: const SkRegion* fRgn; + // See top of SkRegion.cpp for more on the RLE encoding const SkRegion::RunType* fRuns; SkIRect fRect = {0, 0, 0, 0}; bool fDone; diff -Nru chromium-147.0.7727.116/third_party/skia/src/core/SkRegion.cpp chromium-147.0.7727.137/third_party/skia/src/core/SkRegion.cpp --- chromium-147.0.7727.116/third_party/skia/src/core/SkRegion.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/skia/src/core/SkRegion.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -23,14 +23,30 @@ using namespace skia_private; -/* Region Layout +/* SkRegion Run-Length Encoding (RLE) Format: * - * TOP + * A region is stored as a series of non-overlapping horizontal strips (scanlines). + * All rectangles in a strip share the same Top and Bottom Y-coordinates. * - * [ Bottom, X-Intervals, [Left, Right]..., X-Sentinel ] - * ... + * Data layout: + * [Top-Y] + * [Bottom-Y, Interval-Count (N), Left-1, Right-1, ..., Left-N, Right-N, X-Sentinel] + * [Bottom-Y, Interval-Count (M), Left-1, Right-1, ..., Left-M, Right-M, X-Sentinel] + * ... + * [Y-Sentinel] * - * Y-Sentinel + * - Sentinels are 0x7FFFFFFF (SkRegion_kRunTypeSentinel). + * - Interval-Count can be 0 (representing a vertical gap between strips). + * - Strips are sorted by Y; Intervals within a strip are sorted by X. + * + * Example: Two rects [10, 10, 20, 20] and [30, 10, 40, 20] on one line, + * then a gap until Y=30, then [15, 30, 25, 40] would be: + * + * 10 // Global Top-Y + * 20, 2, 10, 20, 30, 40, S // Strip 1: Y 10-20, 2 rects, X-Sentinel + * 30, 0, S // Strip 2: Y 20-30, 0 rects (gap), X-Sentinel + * 40, 1, 15, 25, S // Strip 3: Y 30-40, 1 rect, X-Sentinel + * S // Y-Sentinel */ ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -98,6 +114,10 @@ return const_cast(runs); } +static inline void assert_sentinel(int32_t value, bool isSentinel) { + SkASSERT(SkRegionValueIsSentinel(value) == isSentinel); +} + bool SkRegion::RunsAreARect(const SkRegion::RunType runs[], int count, SkIRect* bounds) { assert_sentinel(runs[0], false); // top @@ -299,7 +319,7 @@ if (runs[3] == SkRegion_kRunTypeSentinel) { // should be first left... runs += 3; // skip empty initial span runs[0] = runs[-2]; // set new top to prev bottom - assert_sentinel(runs[1], false); // bot: a sentinal would mean two in a row + assert_sentinel(runs[1], false); // bot: a sentinel would mean two in a row assert_sentinel(runs[2], false); // intervalcount assert_sentinel(runs[3], false); // left assert_sentinel(runs[4], false); // right @@ -879,7 +899,7 @@ int flush() { (*fArray)[fStartDst] = fTop; - // Previously reserved enough for TWO sentinals. + // Previously reserved enough for TWO sentinels. SkASSERT(fArray->count() > SkToInt(fPrevDst + fPrevLen)); (*fArray)[fPrevDst + fPrevLen] = SkRegion_kRunTypeSentinel; return (int)(fPrevDst - fStartDst + fPrevLen + 1); @@ -1050,20 +1070,18 @@ // swith to using pointers, so we can swap them as needed const SkRegion* rgna = &rgnaOrig; const SkRegion* rgnb = &rgnbOrig; - // after this point, do not refer to rgnaOrig or rgnbOrig!!! - // collaps difference and reverse-difference into just difference + // collapse difference and reverse-difference into just difference if (kReverseDifference_Op == op) { - using std::swap; - swap(rgna, rgnb); + std::swap(rgna, rgnb); op = kDifference_Op; } SkIRect bounds; - bool a_empty = rgna->isEmpty(); - bool b_empty = rgnb->isEmpty(); - bool a_rect = rgna->isRect(); - bool b_rect = rgnb->isRect(); + bool a_empty = rgna->isEmpty(); + bool b_empty = rgnb->isEmpty(); + bool a_rect = rgna->isRect(); + bool b_rect = rgnb->isRect(); switch (op) { case kDifference_Op: @@ -1188,9 +1206,11 @@ } SkSafeMath safeMath; int sum = 2; + // 3 bytes per ySpan (stop Y, how many intervals, sentinel) sum = safeMath.addInt(sum, ySpanCount); sum = safeMath.addInt(sum, ySpanCount); sum = safeMath.addInt(sum, ySpanCount); + // 2 bytes per interval (startX, stop X) sum = safeMath.addInt(sum, intervalCount); sum = safeMath.addInt(sum, intervalCount); return safeMath && sum == runCount; @@ -1218,6 +1238,7 @@ const int32_t* const end = runs + runCount; SkIRect bounds = {0, 0, 0 ,0}; // calulated bounds SkIRect rect = {0, 0, 0, 0}; // current rect + bool prevWasEmpty = true; // If we start with an empty slice, that's corrupted data. rect.fTop = *runs++; if (rect.fTop == SkRegion_kRunTypeSentinel) { return false; // no rect can contain SkRegion_kRunTypeSentinel @@ -1246,6 +1267,15 @@ if (xIntervals < 0 || xIntervals > intervalCount || runs + 1 + 2 * xIntervals > end) { return false; } + if (xIntervals == 0) { + if (prevWasEmpty) { + // back to back empty spans are invalid; our serialization always has them together + return false; + } + prevWasEmpty = true; + } else { + prevWasEmpty = false; + } intervalCount -= xIntervals; bool firstInterval = true; int32_t lastRight = 0; // check that x-intervals are distinct and ordered. @@ -1263,7 +1293,7 @@ bounds.join(rect); } if (*runs++ != SkRegion_kRunTypeSentinel) { - return false; // required check sentinal. + return false; // required check sentinel. } rect.fTop = rect.fBottom; SkASSERT(runs < end); diff -Nru chromium-147.0.7727.116/third_party/skia/src/core/SkRegionPriv.h chromium-147.0.7727.137/third_party/skia/src/core/SkRegionPriv.h --- chromium-147.0.7727.116/third_party/skia/src/core/SkRegionPriv.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/skia/src/core/SkRegionPriv.h 2026-04-27 20:03:22.000000000 +0000 @@ -38,9 +38,6 @@ return value == (int32_t)SkRegion_kRunTypeSentinel; } -#define assert_sentinel(value, isSentinel) \ - SkASSERT(SkRegionValueIsSentinel(value) == isSentinel) - #ifdef SK_DEBUG // Given the first interval (just past the interval-count), compute the // interval count, by search for the x-sentinel diff -Nru chromium-147.0.7727.116/third_party/skia/tests/RegionTest.cpp chromium-147.0.7727.137/third_party/skia/tests/RegionTest.cpp --- chromium-147.0.7727.116/third_party/skia/tests/RegionTest.cpp 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/skia/tests/RegionTest.cpp 2026-04-27 20:03:22.000000000 +0000 @@ -33,7 +33,7 @@ #define TEST_INTERSECT(rgn, rect) REPORTER_ASSERT(reporter, rgn.intersects(rect)) #define TEST_NO_CONTAINS(rgn, rect) REPORTER_ASSERT(reporter, !rgn.contains(rect)) -// inspired by http://code.google.com/p/skia/issues/detail?id=958 +// inspired by https://issues.skia.org/issues/40032017 // static void test_fromchrome(skiatest::Reporter* reporter) { SkRegion r; @@ -588,3 +588,113 @@ REPORTER_ASSERT(reporter, smallRegion.contains(499, 0)); REPORTER_ASSERT(reporter, smallRegion.contains(499, 499)); } + +DEF_TEST(SkRegion_Iterator_StepsThroughAllScanlines, reporter) { + SkRegion rgn1; + rgn1.op({12, 10, 17, 20}, SkRegion::kUnion_Op); + rgn1.op({31, 10, 39, 25}, SkRegion::kUnion_Op); + rgn1.op({16, 30, 23, 40}, SkRegion::kUnion_Op); + + int32_t buffer[32]; + memset(&buffer, 0, 128); + size_t len = rgn1.writeToMemory(&buffer); + SkASSERT_RELEASE(len < 128); + SkRegion rgn2; + size_t len2 = rgn2.readFromMemory(&buffer, len); + REPORTER_ASSERT(reporter, len == len2); + // TODO(kjlubick) make sure fuzzer uses the iterators + + // Make sure the serialized/deserialzed version is the same as the original. + for (const auto& rgn : {rgn1, rgn2}) { + SkRegion::Iterator iter(rgn); + + // The first scanline strip starts at Y=10 and ends at Y=20. + // The first rectangle there starts at X=12 and ends at X=17 + REPORTER_ASSERT(reporter, !iter.done()); + REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(12, 10, 17, 20)); + + // There's a second rectangle in that section from X=31 to X=39. + iter.next(); + REPORTER_ASSERT(reporter, !iter.done()); + REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 10, 39, 20)); + + // The next scanline strip continues at Y=20 and goes til Y=25 + // The one and only rectangle here starts at X=31 and goes to X=39 + iter.next(); + REPORTER_ASSERT(reporter, !iter.done()); + REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 20, 39, 25)); + + // There's a jump to the final scanline strip from Y=30 to Y=40 + // The one and only rectangle here starts at X=16 and goes to X=23 + iter.next(); + REPORTER_ASSERT(reporter, !iter.done()); + REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(16, 30, 23, 40)); + + // Call next() -> no more rectangles + iter.next(); + REPORTER_ASSERT(reporter, iter.done()); + } +} + +DEF_TEST(SkRegion_ReadFromMemory_ConsecutiveEmptySlices_Invalid, reporter) { + constexpr int32_t kSentinel = 0x7FFFFFFF; + const int32_t corrupt[] = { + 42, // number of int32s in the RLE portion (after 7 metadata int32s) + 0, 0, 100, 100, // bounds + 12, // 12 spans (10 are empty) + 2, // 2 rectangles + 0, 5, // first stripe is from Y=0 to Y=5, + 1, // 1 rectangle + 0, 100, // from x = 0 to 100 (arbitrary) + kSentinel, + 10, 0, kSentinel, // Empty from 5-10 + 20, 0, kSentinel, // Empty from 10-20... + 30, 0, kSentinel, + 40, 0, kSentinel, + 50, 0, kSentinel, + 60, 0, kSentinel, + 70, 0, kSentinel, + 80, 0, kSentinel, + 90, 0, kSentinel, + 95, 0, kSentinel, + 100, 1, 20, 30, kSentinel, // one final real rectangle until Y=100 + kSentinel, // final sentinal + }; + + SkRegion rgn; + size_t len = rgn.readFromMemory(&corrupt, sizeof(corrupt)); + REPORTER_ASSERT(reporter, len == 0); // len == 0 means "could not read" + + // When there was a buggy version of this, the following iteration caused a crash. + SkRegion::Iterator iter(rgn); + while (!iter.done()) { + iter.next(); + } +} + +DEF_TEST(SkRegion_ReadFromMemory_SingleEmptySlice_Valid, reporter) { + constexpr int32_t kSentinel = 0x7FFFFFFF; + const int32_t valid[] = { + 15, // number of int32s in the RLE portion (after 7 metadata int32s) + 0, 0, 100, 100, // bounds + 3, // 3 spans (1 is empty) + 2, // 2 rectangles + 0, 5, // first stripe is from Y=0 to Y=5, + 1, // 1 rectangle + 0, 100, // from x = 0 to 100 (arbitrary) + kSentinel, + 80, 0, kSentinel, // Empty from 5-80 + 100, 1, 20, 30, kSentinel, // one final real rectangle + kSentinel, // final sentinal + }; + + SkRegion rgn; + size_t len = rgn.readFromMemory(&valid, sizeof(valid)); + REPORTER_ASSERT(reporter, len > 0); // len == 0 means "could not read" + + // Make sure we don't read any memory we aren't supposed to. + SkRegion::Iterator iter(rgn); + while (!iter.done()) { + iter.next(); + } +} diff -Nru chromium-147.0.7727.116/third_party/webrtc/call/BUILD.gn chromium-147.0.7727.137/third_party/webrtc/call/BUILD.gn --- chromium-147.0.7727.116/third_party/webrtc/call/BUILD.gn 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/webrtc/call/BUILD.gn 2026-04-27 20:03:22.000000000 +0000 @@ -554,6 +554,7 @@ "../api/video:video_codec_constants", "../api/video:video_frame", "../api/video:video_frame_type", + "../api/video:video_layers_allocation", "../api/video:video_rtp_headers", "../api/video_codecs:video_codecs_api", "../audio", diff -Nru chromium-147.0.7727.116/third_party/webrtc/call/rtp_video_sender.cc chromium-147.0.7727.137/third_party/webrtc/call/rtp_video_sender.cc --- chromium-147.0.7727.116/third_party/webrtc/call/rtp_video_sender.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/webrtc/call/rtp_video_sender.cc 2026-04-27 20:03:22.000000000 +0000 @@ -695,6 +695,11 @@ transport_queue_.PostTask( SafeTask(safety_.flag(), [this, sending = std::move(sending)] { RTC_DCHECK_RUN_ON(&transport_checker_); + // It's possible for another task to be scheduled on the transport + // checker ahead of this call that makes the sender not active. + if (!IsActive()) { + return; + } RTC_CHECK_EQ(sending.size(), rtp_streams_.size()); for (size_t i = 0; i < sending.size(); ++i) { SetModuleIsActive(sending[i], *rtp_streams_[i].rtp_rtcp); diff -Nru chromium-147.0.7727.116/third_party/webrtc/call/rtp_video_sender_unittest.cc chromium-147.0.7727.137/third_party/webrtc/call/rtp_video_sender_unittest.cc --- chromium-147.0.7727.116/third_party/webrtc/call/rtp_video_sender_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/webrtc/call/rtp_video_sender_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -40,6 +40,7 @@ #include "api/video/encoded_image.h" #include "api/video/video_codec_type.h" #include "api/video/video_frame_type.h" +#include "api/video/video_layers_allocation.h" #include "api/video_codecs/video_encoder.h" #include "call/rtp_config.h" #include "call/rtp_transport_config.h" @@ -1607,4 +1608,73 @@ EXPECT_EQ(retransmitted_rtp_sequence_numbers, base_rtp_sequence_numbers); } +TEST(RtpVideoSenderTest, PostTaskRaceDoesNotLeadToDanglingPointer) { + NiceMock transport; + NiceMock encoder_feedback; + + GlobalSimulatedTimeController time_controller(Timestamp::Millis(1000000)); + Environment env = CreateEnvironment(time_controller.GetClock(), + time_controller.CreateTaskQueueFactory()); + + VideoSendStream::Config config(&transport); + config.rtp.ssrcs = {kSsrc1}; + + SendStatisticsProxy stats_proxy( + time_controller.GetClock(), config, + VideoEncoderConfig::ContentType::kRealtimeVideo, env.field_trials()); + + BitrateConstraints bitrate_config = GetBitrateConfig(); + RtpTransportConfig transport_config{.env = env, + .bitrate_config = bitrate_config}; + RtpTransportControllerSend transport_controller(transport_config); + transport_controller.EnsureStarted(); + + RateLimiter retransmission_rate_limiter(time_controller.GetClock(), + kRetransmitWindowSizeMs); + + std::map suspended_ssrcs; + std::map suspended_payload_states; + + auto router = std::make_unique( + env, time_controller.GetMainThread(), suspended_ssrcs, + suspended_payload_states, config.rtp, config.rtcp_report_interval_ms, + &transport, + CreateObservers(&encoder_feedback, &stats_proxy, &stats_proxy, + &stats_proxy, nullptr, &stats_proxy), + &transport_controller, &retransmission_rate_limiter, + std::make_unique(env), nullptr, CryptoOptions{}, + nullptr); + + router->SetSending(true); + // Verify it's registered + EXPECT_TRUE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); + + // Trigger race: + // 1. OnVideoLayersAllocationUpdated posts a task to re-register modules + // because it sees active_ is true. + VideoLayersAllocation allocation; + allocation.active_spatial_layers.push_back({.rtp_stream_index = 0}); + router->OnVideoLayersAllocationUpdated(allocation); + + // 2. Immediately set active_ to false. This happens BEFORE the posted task + // runs. + router->SetSending(false); + + // 3. Let the posted task run. With the fix, it should return early and not + // re-register the module because active_ is now false. + time_controller.AdvanceTime(TimeDelta::Zero()); + + // 4. Verify that PacketRouter does NOT have a sender. + EXPECT_FALSE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); + + // 5. Destroy the router. + router.reset(); + + // 6. Verify that PacketRouter still does not have a sender. + EXPECT_FALSE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); +} + } // namespace webrtc diff -Nru chromium-147.0.7727.116/third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc chromium-147.0.7727.137/third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc --- chromium-147.0.7727.116/third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/third_party/webrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc 2026-04-27 20:03:22.000000000 +0000 @@ -128,8 +128,9 @@ Mutex latest_frame_lock_ RTC_ACQUIRED_AFTER(queue_lock_); SharedDesktopFrame* latest_available_frame_ RTC_GUARDED_BY(&latest_frame_lock_) = nullptr; - std::unique_ptr mouse_cursor_; - DesktopVector mouse_cursor_position_ = DesktopVector(-1, -1); + std::unique_ptr mouse_cursor_ RTC_GUARDED_BY(&latest_frame_lock_); + DesktopVector mouse_cursor_position_ RTC_GUARDED_BY(&latest_frame_lock_) = + DesktopVector(-1, -1); int64_t modifier_; std::unique_ptr egl_dmabuf_; @@ -695,6 +696,7 @@ } std::unique_ptr SharedScreenCastStreamPrivate::CaptureCursor() { + MutexLock latest_frame_lock(&latest_frame_lock_); if (!mouse_cursor_) { return nullptr; } @@ -703,6 +705,7 @@ } DesktopVector SharedScreenCastStreamPrivate::CaptureCursorPosition() { + MutexLock latest_frame_lock(&latest_frame_lock_); return mouse_cursor_position_; } @@ -774,20 +777,28 @@ mouse_frame->CopyPixelsFrom( bitmap_data, bitmap->stride, DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height)); - mouse_cursor_ = std::make_unique( - mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y)); + { + MutexLock latest_frame_lock(&latest_frame_lock_); + mouse_cursor_ = std::make_unique( + mouse_frame, + DesktopVector(cursor->hotspot.x, cursor->hotspot.y)); + } if (observer_) { observer_->OnCursorShapeChanged(); } } - mouse_cursor_position_.set(cursor->position.x, cursor->position.y); + { + MutexLock latest_frame_lock(&latest_frame_lock_); + mouse_cursor_position_.set(cursor->position.x, cursor->position.y); + } if (observer_) { observer_->OnCursorPositionChanged(); } } else { // Indicate an invalid cursor + MutexLock latest_frame_lock(&latest_frame_lock_); mouse_cursor_position_.set(-1, -1); } } diff -Nru chromium-147.0.7727.116/tools/metrics/histograms/metadata/contextual_cueing/enums.xml chromium-147.0.7727.137/tools/metrics/histograms/metadata/contextual_cueing/enums.xml --- chromium-147.0.7727.116/tools/metrics/histograms/metadata/contextual_cueing/enums.xml 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/tools/metrics/histograms/metadata/contextual_cueing/enums.xml 2026-04-27 20:03:22.000000000 +0000 @@ -154,6 +154,18 @@ + + + + + + + + + + + + diff -Nru chromium-147.0.7727.116/tools/metrics/histograms/metadata/contextual_cueing/histograms.xml chromium-147.0.7727.137/tools/metrics/histograms/metadata/contextual_cueing/histograms.xml --- chromium-147.0.7727.116/tools/metrics/histograms/metadata/contextual_cueing/histograms.xml 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/tools/metrics/histograms/metadata/contextual_cueing/histograms.xml 2026-04-27 20:03:22.000000000 +0000 @@ -44,6 +44,16 @@ + + sanaakbani@google.com + sophiechang@chromium.org + + Recorded when the glic side panel is triggered to auto-open. Records the + outcome (success, or specific failure reasons). + + + diff -Nru chromium-147.0.7727.116/tools/metrics/histograms/metadata/glic/histograms.xml chromium-147.0.7727.137/tools/metrics/histograms/metadata/glic/histograms.xml --- chromium-147.0.7727.116/tools/metrics/histograms/metadata/glic/histograms.xml 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/tools/metrics/histograms/metadata/glic/histograms.xml 2026-04-27 20:03:22.000000000 +0000 @@ -2650,6 +2650,7 @@ the user opened the context menu on a {GlicContextType}. + children()[0]; + + auto* alert_node = static_cast( + AXPlatformNodeFromNode(alert_ax_node)); + ASSERT_TRUE(alert_node); + + const size_t initial_count = + AXPlatformNodeWin::GetAlertTargetCountForTesting(); + + // Put the node in the IsDestroyed() state without actually destroying it, + // so the wrapper can still tear down cleanly at the end of the test. + AXPlatformNodeDelegate* original_delegate = + alert_node->SetDelegateForTesting(nullptr); + ASSERT_TRUE(alert_node->IsDestroyed()); + + alert_node->AddAlertTargetForTesting(); + EXPECT_EQ(initial_count, + AXPlatformNodeWin::GetAlertTargetCountForTesting()); + + alert_node->SetDelegateForTesting(original_delegate); +} + } // namespace ui diff -Nru chromium-147.0.7727.116/ui/base/interaction/interaction_sequence_test_util.h chromium-147.0.7727.137/ui/base/interaction/interaction_sequence_test_util.h --- chromium-147.0.7727.116/ui/base/interaction/interaction_sequence_test_util.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/base/interaction/interaction_sequence_test_util.h 2026-04-27 20:03:22.000000000 +0000 @@ -5,6 +5,8 @@ #ifndef UI_BASE_INTERACTION_INTERACTION_SEQUENCE_TEST_UTIL_H_ #define UI_BASE_INTERACTION_INTERACTION_SEQUENCE_TEST_UTIL_H_ +#include "base/run_loop.h" +#include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff -Nru chromium-147.0.7727.116/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc chromium-147.0.7727.137/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc --- chromium-147.0.7727.116/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc 2026-04-27 20:03:22.000000000 +0000 @@ -654,7 +654,10 @@ FROM_HERE, base::BindOnce(std::move(reply_callback), layout_name, std::move(keymap_str))); } else { - LOG(FATAL) << "Keymap file failed to load: " << layout_name; + LOG(ERROR) << "Keymap file failed to load: " << layout_name; + reply_runner->PostTask( + FROM_HERE, base::BindOnce(std::move(reply_callback), layout_name, + std::unique_ptr())); } } #endif @@ -732,6 +735,11 @@ xkb_keymap* keymap = xkb_keymap_new_from_string( xkb_context_.get(), keymap_str.get(), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + LOG(ERROR) << "Failed to compile keymap from string: " << layout_name; + std::move(callback).Run(/*success=*/false); + return; + } XkbKeymapEntry entry = {layout_name, keymap}; xkb_keymaps_.push_back(entry); if (layout_name == current_layout_name_) { @@ -741,7 +749,8 @@ std::move(callback).Run(/*success=*/false); } } else { - LOG(FATAL) << "Keymap file failed to load: " << layout_name; + LOG(ERROR) << "Keymap file failed to load: " << layout_name; + std::move(callback).Run(/*success=*/false); } } @@ -1146,6 +1155,19 @@ *layout_id = layout_name.substr(0, dash_index); *layout_variant = layout_name.substr(dash_index + 1); } + + // Sanitize the layout ID and variant to only allow safe characters. + const char kAllowedChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "_()-:"; + if (!base::ContainsOnlyChars(*layout_id, kAllowedChars)) { + layout_id->clear(); + } + if (!base::ContainsOnlyChars(*layout_variant, kAllowedChars)) { + layout_variant->clear(); + } } } // namespace ui diff -Nru chromium-147.0.7727.116/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc chromium-147.0.7727.137/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc --- chromium-147.0.7727.116/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc 2026-04-27 20:03:22.000000000 +0000 @@ -928,6 +928,14 @@ /* 50 */ {"ge", "ge", ""}, /* 51 */ {"mn", "mn", ""}, /* 52 */ {"ie", "ie", ""}, + // Path traversal cases. + /* 53 */ {"../../evil", "", ""}, + /* 54 */ {"us(../../evil)", "us", ""}, + /* 55 */ {"us-../../evil", "us", ""}, + // Invalid character cases. + /* 56 */ {"us$", "", ""}, + /* 57 */ {"us(dvo*ak)", "us", ""}, + /* 58 */ {"us-colem@k", "us", ""}, }); for (size_t i = 0; i < std::size(kVkeyTestCase); ++i) { SCOPED_TRACE(i); diff -Nru chromium-147.0.7727.116/ui/strings/translations/ax_strings_it.xtb chromium-147.0.7727.137/ui/strings/translations/ax_strings_it.xtb --- chromium-147.0.7727.116/ui/strings/translations/ax_strings_it.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/strings/translations/ax_strings_it.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -212,7 +212,7 @@ dedica errata corrige Minuti -controllo contenuti multimediali +controllo multimediale simbolo grafico elenco di definizioni intestazione diff -Nru chromium-147.0.7727.116/ui/strings/translations/ui_strings_ta.xtb chromium-147.0.7727.137/ui/strings/translations/ui_strings_ta.xtb --- chromium-147.0.7727.116/ui/strings/translations/ui_strings_ta.xtb 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/strings/translations/ui_strings_ta.xtb 2026-04-27 20:03:22.000000000 +0000 @@ -86,7 +86,7 @@ இணைப்பு நகலெடுக்கப்பட்டது {DAYS,plural, =1{1 நாள்}other{# நாட்கள்}} இந்த ஆப்ஸ் மூலம் அழை: -உள்ளமைந்த டிஸ்பிளே +சாதன டிஸ்ப்ளே பேச்சு விவரங்கள் என்பதைக் கிளிக் செய்யவும் உரையின் அளவு மிகப் பெரியதாக உள்ளது diff -Nru chromium-147.0.7727.116/ui/views/controls/button/button.h chromium-147.0.7727.137/ui/views/controls/button/button.h --- chromium-147.0.7727.116/ui/views/controls/button/button.h 2026-04-21 17:50:58.000000000 +0000 +++ chromium-147.0.7727.137/ui/views/controls/button/button.h 2026-04-27 20:03:22.000000000 +0000 @@ -285,6 +285,8 @@ View* ink_drop_view() const { return ink_drop_view_; } void SetInkDropView(View* view); + base::WeakPtr