Version in base suite: 147.0.7727.137-1~deb13u1 Version in overlay suite: 149.0.7827.102-1~deb13u1 Base version: chromium_149.0.7827.102-1~deb13u1 Target version: chromium_149.0.7827.114-1~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/chromium/chromium_149.0.7827.102-1~deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/chromium/chromium_149.0.7827.114-1~deb13u1.dsc build/util/LASTCHANGE | 2 build/util/LASTCHANGE.committime | 2 chrome/VERSION | 2 chrome/browser/devtools/devtools_ui_bindings.cc | 21 chrome/browser/devtools/devtools_ui_bindings_unittest.cc | 125 ++--- chrome/browser/digital_credentials/digital_identity_provider_desktop.cc | 13 chrome/browser/digital_credentials/digital_identity_provider_desktop.h | 12 chrome/browser/extensions/extension_security_exploit_browsertest.cc | 101 ++++ chrome/browser/media/cast_mirroring_service_host.cc | 51 +- chrome/browser/password_manager/chrome_password_manager_client.cc | 19 chrome/browser/password_manager/chrome_password_manager_client.h | 2 chrome/browser/password_manager/chrome_password_manager_client_unittest.cc | 2 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc | 8 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc | 11 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h | 2 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc | 15 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h | 5 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.cc | 9 chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h | 2 chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc | 104 ++++ chrome/chrome_branch_deps.json | 39 - components/autofill/core/browser/payments/payments_request_details.h | 5 components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.cc | 9 components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl_unittest.cc | 57 ++ components/named_mojo_ipc_server/named_mojo_message_pipe_server.cc | 3 components/password_manager/core/browser/mock_password_credential_filler.h | 2 components/password_manager/core/browser/password_credential_filler.h | 4 components/password_manager/core/browser/password_credential_filler_impl.cc | 5 components/password_manager/core/browser/password_credential_filler_impl.h | 2 components/password_manager/core/browser/password_credential_filler_impl_unittest.cc | 21 components/password_manager/core/browser/password_manager_client.cc | 3 components/password_manager/core/browser/password_manager_client.h | 2 content/browser/devtools/protocol/input_handler.cc | 2 content/browser/renderer_host/direct_manipulation_event_handler_win.cc | 4 content/browser/renderer_host/direct_manipulation_helper_win.cc | 44 +- content/browser/renderer_host/direct_manipulation_helper_win.h | 17 content/browser/renderer_host/direct_manipulation_test_helper_win.cc | 4 content/browser/renderer_host/direct_manipulation_test_helper_win.h | 9 content/browser/renderer_host/direct_manipulation_win_unittest.cc | 177 +++++++- content/browser/renderer_host/legacy_render_widget_host_win.cc | 9 content/browser/renderer_host/render_widget_host_impl.cc | 27 + content/browser/renderer_host/render_widget_host_view_aura.cc | 16 debian/changelog | 48 ++ debian/patches/loongarch64/0024-fix-libyuv-lsx.patch | 94 ---- debian/patches/series | 1 extensions/browser/extension_function_dispatcher.cc | 14 gpu/command_buffer/service/framebuffer_manager.cc | 7 gpu/command_buffer/service/framebuffer_manager.h | 1 gpu/command_buffer/service/gles2_cmd_decoder.cc | 31 + gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc | 9 gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc | 5 gpu/command_buffer/service/shared_image/gl_texture_holder.cc | 36 + gpu/command_buffer/tests/gl_unittest.cc | 218 +++++++++- gpu/config/gpu_lists_version.h | 2 headless/lib/browser/protocol/target_handler.cc | 12 headless/test/headless_devtooled_browsertest.cc | 50 ++ media/base/decoder_buffer.cc | 12 media/base/win/mf_helpers.cc | 3 media/gpu/chromeos/decoder_buffer_transcryptor.cc | 11 media/gpu/v4l2/v4l2_image_processor_backend.cc | 29 - media/gpu/vaapi/vaapi_video_encode_accelerator.cc | 6 media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc | 8 media/gpu/windows/d3d12_video_encode_accelerator.cc | 21 media/midi/midi_manager_winrt.cc | 80 ++- media/midi/midi_manager_winrt.h | 3 media/midi/task_service.cc | 56 +- media/midi/task_service.h | 29 - media/midi/task_service_unittest.cc | 133 ++++++ media/mojo/services/mojo_audio_decoder_service.cc | 7 media/mojo/services/mojo_decryptor_service.cc | 47 +- media/mojo/services/mojo_decryptor_service.h | 6 media/mojo/services/mojo_video_encode_accelerator_service.cc | 12 media/video/video_encode_accelerator_adapter.cc | 4 media/video/video_encode_accelerator_adapter.h | 3 media/video/video_encode_accelerator_adapter_test.cc | 118 +++++ mojo/public/cpp/system/invitation.cc | 8 mojo/public/cpp/system/invitation.h | 8 mojo/public/cpp/system/isolated_connection.cc | 11 mojo/public/cpp/system/isolated_connection.h | 10 net/spdy/spdy_buffer.cc | 12 net/spdy/spdy_read_queue_unittest.cc | 53 ++ services/network/cors/cors_url_loader_unittest.cc | 52 ++ services/network/public/cpp/cors/cors.cc | 2 services/network/public/cpp/cors/cors.h | 2 services/network/public/cpp/header_util.cc | 13 services/network/public/cpp/header_util.h | 5 services/network/public/cpp/header_util_unittest.cc | 14 third_party/blink/renderer/platform/video_capture/video_capture_impl.cc | 15 ui/accessibility/platform/ax_platform_node_cocoa.mm | 2 ui/gtk/gtk_ui.cc | 37 + ui/gtk/gtk_ui.h | 4 ui/qt/qt_shim.cc | 14 ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc | 4 ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc | 8 ui/views/widget/desktop_aura/desktop_window_tree_host_win.h | 3 ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc | 12 ui/views/win/hwnd_message_handler.cc | 9 ui/views/win/hwnd_message_handler.h | 3 ui/views/win/user_resize_move_detector.cc | 10 ui/views/win/user_resize_move_detector.h | 3 100 files changed, 1984 insertions(+), 415 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpt5o1wysz/chromium_149.0.7827.102-1~deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpt5o1wysz/chromium_149.0.7827.114-1~deb13u1.dsc: no acceptable signature found diff -Nru chromium-149.0.7827.102/build/util/LASTCHANGE chromium-149.0.7827.114/build/util/LASTCHANGE --- chromium-149.0.7827.102/build/util/LASTCHANGE 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/build/util/LASTCHANGE 2026-06-10 17:58:03.000000000 +0000 @@ -1,2 +1,2 @@ -LASTCHANGE=112f665d98a2fe84b156c74fbea2aed742f16c15-refs/branch-heads/7827@{#2560} +LASTCHANGE=5be7af702aa73ed64f47858cecc86290e42f2a20-refs/branch-heads/7827_102@{#39} LASTCHANGE_YEAR=2026 diff -Nru chromium-149.0.7827.102/build/util/LASTCHANGE.committime chromium-149.0.7827.114/build/util/LASTCHANGE.committime --- chromium-149.0.7827.102/build/util/LASTCHANGE.committime 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/build/util/LASTCHANGE.committime 2026-06-10 17:58:03.000000000 +0000 @@ -1 +1 @@ -1780693469 \ No newline at end of file +1781114283 \ No newline at end of file diff -Nru chromium-149.0.7827.102/chrome/VERSION chromium-149.0.7827.114/chrome/VERSION --- chromium-149.0.7827.102/chrome/VERSION 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/VERSION 2026-06-10 17:58:03.000000000 +0000 @@ -1,4 +1,4 @@ MAJOR=149 MINOR=0 BUILD=7827 -PATCH=102 +PATCH=114 diff -Nru chromium-149.0.7827.102/chrome/browser/devtools/devtools_ui_bindings.cc chromium-149.0.7827.114/chrome/browser/devtools/devtools_ui_bindings.cc --- chromium-149.0.7827.102/chrome/browser/devtools/devtools_ui_bindings.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/devtools/devtools_ui_bindings.cc 2026-06-10 17:58:03.000000000 +0000 @@ -61,6 +61,7 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" +#include "chrome/common/webui_url_constants.h" #include "chrome/grit/generated_resources.h" #include "components/content_settings/core/common/features.h" #include "components/infobars/content/content_infobar_manager.h" @@ -533,6 +534,21 @@ base::Value(encoded)); } +bool IsLocalDevToolsFrontendURL(const GURL& url) { + if (!url.is_valid() || url.IsAboutBlank() || + !url.SchemeIs(content::kChromeDevToolsScheme) || + url.host() != chrome::kChromeUIDevToolsHost) { + return false; + } + std::string_view path = url.path(); + if (base::StartsWith(path, "/")) { + path = path.substr(1); + } + return base::StartsWith(path, chrome::kChromeUIDevToolsBundledPath) || + base::StartsWith(path, chrome::kChromeUIDevToolsCustomPath) || + base::StartsWith(path, chrome::kChromeUIDevToolsBlankPath); +} + } // namespace class DevToolsUIBindings::NetworkResourceLoader @@ -1272,10 +1288,7 @@ NetworkResourceLoader::URLLoaderFactoryHolder url_loader_factory; if (gurl.SchemeIsFile()) { GURL frontend_url = web_contents_->GetLastCommittedURL(); - bool is_remote_frontend = - frontend_url.is_valid() && !frontend_url.IsAboutBlank() && - IsValidRemoteFrontendURL(frontend_url); - if (is_remote_frontend) { + if (!IsLocalDevToolsFrontendURL(frontend_url)) { if (!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAllowUnsafeDevToolsRemoteFileLoading)) { base::DictValue response_dict; diff -Nru chromium-149.0.7827.102/chrome/browser/devtools/devtools_ui_bindings_unittest.cc chromium-149.0.7827.114/chrome/browser/devtools/devtools_ui_bindings_unittest.cc --- chromium-149.0.7827.102/chrome/browser/devtools/devtools_ui_bindings_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/devtools/devtools_ui_bindings_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -106,41 +106,12 @@ }; TEST_F(DevToolsUIBindingsLoadNetworkResourceTest, - BlocksFileSchemeFromRemoteFrontend) { - // Simulate a remote frontend URL. - GURL remote_url( - "https://chrome-devtools-frontend.appspot.com/serve_rev/@12345/" - "inspector.html"); - content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), - remote_url); - - base::RunLoop run_loop; - base::DictValue result; - - CallLoadNetworkResource( - "file:///etc/passwd", "", 0, - base::BindLambdaForTesting([&](const base::Value* value) { - result = value->GetDict().Clone(); - run_loop.Quit(); - })); - run_loop.Run(); - - EXPECT_EQ(result.FindInt("statusCode"), 403); - ASSERT_NE(result.FindString("messageOverride"), nullptr); - EXPECT_EQ(*result.FindString("messageOverride"), - "Local file loading is restricted for remote DevTools. Use " - "--allow-unsafe-devtools-remote-file-loading to enable it."); -} - -TEST_F(DevToolsUIBindingsLoadNetworkResourceTest, AllowsFileSchemeFromRemoteFrontendWithFlag) { base::test::ScopedCommandLine scoped_command_line; scoped_command_line.GetProcessCommandLine()->AppendSwitch( switches::kAllowUnsafeDevToolsRemoteFileLoading); - GURL remote_url( - "https://chrome-devtools-frontend.appspot.com/serve_rev/@12345/" - "inspector.html"); + GURL remote_url("devtools://devtools/remote/serve_rev/@12345/inspector.html"); content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), remote_url); @@ -161,35 +132,42 @@ } TEST_F(DevToolsUIBindingsLoadNetworkResourceTest, - AllowsFileSchemeFromLocalFrontend) { - GURL local_url("devtools://devtools/bundled/devtools_app.html"); - content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), - local_url); - - base::RunLoop run_loop; - base::DictValue result; - - CallLoadNetworkResource( - "file:///etc/passwd", "", 0, - base::BindLambdaForTesting([&](const base::Value* value) { - result = value->GetDict().Clone(); - run_loop.Quit(); - })); - run_loop.Run(); - - auto* msg = result.FindString("messageOverride"); - EXPECT_EQ(msg, nullptr); - EXPECT_NE(result.FindInt("statusCode"), 403); + AllowsFileSchemeFromLocalFrontends) { + std::vector local_urls = { + GURL("devtools://devtools/bundled/devtools_app.html"), + GURL("devtools://devtools/custom/inspector.html"), + GURL("devtools://devtools/blank")}; + + for (const GURL& local_url : local_urls) { + content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), + local_url); + + base::RunLoop run_loop; + base::DictValue result; + + CallLoadNetworkResource( + "file:///etc/passwd", "", 0, + base::BindLambdaForTesting([&](const base::Value* value) { + result = value->GetDict().Clone(); + run_loop.Quit(); + })); + run_loop.Run(); + + auto* msg = result.FindString("messageOverride"); + EXPECT_EQ(msg, nullptr); + EXPECT_NE(result.FindInt("statusCode"), 403); + } } TEST_F(DevToolsUIBindingsLoadNetworkResourceTest, - BlocksFileSchemeFromRemoteFrontendWithLocalTarget) { - GURL remote_url( - "https://chrome-devtools-frontend.appspot.com/serve_rev/@12345/" - "inspector.html"); - content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), - remote_url); + BlocksFileSchemeFromUntrustedFrontends) { + std::vector untrusted_urls = { + GURL("devtools://devtools/remote/serve_rev/@12345/inspector.html"), + GURL("https://chrome-devtools-frontend.appspot.com/serve_rev/@12345/" + "inspector.html"), + GURL("https://example.com/index.html")}; + // Set up inspected WebContents to be a local file. content::WebContents* inspected_web_contents = web_contents_factory_.CreateWebContents(profile_.get()); content::NavigationSimulator::NavigateAndCommitFromBrowser( @@ -199,22 +177,27 @@ std::make_unique(inspected_web_contents); bindings()->SetDelegate(delegate.release()); - base::RunLoop run_loop; - base::DictValue result; - - CallLoadNetworkResource( - "file:///etc/passwd", "", 0, - base::BindLambdaForTesting([&](const base::Value* value) { - result = value->GetDict().Clone(); - run_loop.Quit(); - })); - run_loop.Run(); - - EXPECT_EQ(result.FindInt("statusCode"), 403); - ASSERT_NE(result.FindString("messageOverride"), nullptr); - EXPECT_EQ(*result.FindString("messageOverride"), - "Local file loading is restricted for remote DevTools. Use " - "--allow-unsafe-devtools-remote-file-loading to enable it."); + for (const GURL& untrusted_url : untrusted_urls) { + content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), + untrusted_url); + + base::RunLoop run_loop; + base::DictValue result; + + CallLoadNetworkResource( + "file:///etc/passwd", "", 0, + base::BindLambdaForTesting([&](const base::Value* value) { + result = value->GetDict().Clone(); + run_loop.Quit(); + })); + run_loop.Run(); + + EXPECT_EQ(result.FindInt("statusCode"), 403); + ASSERT_NE(result.FindString("messageOverride"), nullptr); + EXPECT_EQ(*result.FindString("messageOverride"), + "Local file loading is restricted for remote DevTools. Use " + "--allow-unsafe-devtools-remote-file-loading to enable it."); + } } TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) { diff -Nru chromium-149.0.7827.102/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc chromium-149.0.7827.114/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc --- chromium-149.0.7827.102/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc 2026-06-10 17:58:03.000000000 +0000 @@ -399,8 +399,19 @@ return; } + // `dialog_.reset()` can synchronously close the UI which (via activation + // observers) may destroy the hosting WebContents, resulting in the + // synchronous destruction of the frame-bound Mojo DocumentService + // `DigitalIdentityRequestImpl` and therefore `this`. + // + // To avoid a Use-After-Free, move the callback to a local variable on the + // stack before resetting the dialog or the manual bluetooth controller (both + // of which could trigger synchronous teardown via UI events). + auto local_callback = std::move(callback_); + bluetooth_manual_dialog_controller_.reset(); dialog_.reset(); + // `this` may be deleted at this point. - std::move(callback_).Run(base::unexpected(status)); + std::move(local_callback).Run(base::unexpected(status)); } diff -Nru chromium-149.0.7827.102/chrome/browser/digital_credentials/digital_identity_provider_desktop.h chromium-149.0.7827.114/chrome/browser/digital_credentials/digital_identity_provider_desktop.h --- chromium-149.0.7827.102/chrome/browser/digital_credentials/digital_identity_provider_desktop.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/digital_credentials/digital_identity_provider_desktop.h 2026-06-10 17:58:03.000000000 +0000 @@ -73,6 +73,13 @@ callback_ = std::move(callback); } + // Ensures `dialog_` is initialized and returns it. + DigitalIdentityMultiStepDialog* EnsureDialogCreated(); + + // Called to end the request with an error. + void EndRequestWithError( + content::DigitalIdentityProvider::RequestStatusForMetrics); + private: // Called whenever some significant event occurs during the transaction. void OnEvent(const std::string& qr_url, @@ -89,8 +96,6 @@ base::expected); - // Ensures `dialog_` is initialized and returns it. - DigitalIdentityMultiStepDialog* EnsureDialogCreated(); // Shows dialog which prompts user to manually turn on bluetooth. void ShowBluetoothManualTurnOnDialog(); @@ -114,9 +119,6 @@ // canceling the dialog. void OnCanceled(); - // Called to end the request with an error. - void EndRequestWithError( - content::DigitalIdentityProvider::RequestStatusForMetrics); // The web contents to which the dialog is modal to. base::WeakPtr web_contents_; diff -Nru chromium-149.0.7827.102/chrome/browser/extensions/extension_security_exploit_browsertest.cc chromium-149.0.7827.114/chrome/browser/extensions/extension_security_exploit_browsertest.cc --- chromium-149.0.7827.102/chrome/browser/extensions/extension_security_exploit_browsertest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/extensions/extension_security_exploit_browsertest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -28,6 +28,7 @@ #include "content/public/common/content_client.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" #include "extensions/browser/api/messaging/channel_endpoint.h" #include "extensions/browser/api/messaging/message_service.h" #include "extensions/browser/api/storage/storage_api.h" @@ -56,11 +57,32 @@ #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h" +#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom.h" #include "url/gurl.h" namespace extensions { +namespace { + +// Helper to serve a page with CSP that blocks subframe navigations. +std::unique_ptr HandleCspBlockRequest( + const net::test_server::HttpRequest& request) { + if (request.relative_url != "/csp_block.html") { + return nullptr; + } + auto response = std::make_unique(); + response->set_code(net::HTTP_OK); + response->set_content_type("text/html"); + response->AddCustomHeader("Content-Security-Policy", "frame-src 'none'"); + response->set_content( + "" + "" + ""); + return response; +} + +} // namespace + // ExtensionFrameHostInterceptor is a helper for: // - Intercepting mojom::LocalFrameHost method calls (e.g. methods // that would normally be handled / implemented by ExtensionFrameHost). @@ -280,6 +302,8 @@ host_resolver()->AddRule("*", "127.0.0.1"); content::SetupCrossSiteRedirector(embedded_test_server()); + embedded_test_server()->RegisterRequestHandler( + base::BindRepeating(&HandleCspBlockRequest)); ASSERT_TRUE(embedded_test_server()->Start()); } @@ -1846,4 +1870,79 @@ kill_waiter.Wait()); } +// Verifies that error pages cannot use extension APIs that might be available +// to their committed URLs. Regression test for crbug.com/516797143. +IN_PROC_BROWSER_TEST_F(ExtensionSecurityExploitBrowserTest, + ErrorPagesAreBlockedFromExtensionAPIs) { + const GURL webstore_url("https://chromewebstore.google.com/"); + + // Load a page that will include a subframe with a committed URL to the + // webstore, but will be blocked by CSP. + GURL test_page_url = + embedded_test_server()->GetURL("a.com", "/csp_block.html"); + auto* web_contents = GetActiveWebContents(); + + content::TestNavigationObserver navigation_observer(web_contents); + // The navigation includes a frame being blocked (and thus committing to an + // error page), so we ignore the result of NavigateToURL(). + std::ignore = NavigateToURL(web_contents, test_page_url); + navigation_observer.Wait(); + + content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); + EXPECT_EQ(main_frame->GetLastCommittedURL(), test_page_url); + content::RenderFrameHost* subframe = content::ChildFrameAt(main_frame, 0); + ASSERT_TRUE(subframe); + + EXPECT_TRUE(subframe->IsErrorDocument()); + EXPECT_EQ(subframe->GetLastCommittedURL(), webstore_url); + + auto* extension_frame_host = + ExtensionWebContentsObserver::GetForWebContents(web_contents) + ->extension_frame_host_for_testing(); + + extension_frame_host->receivers_for_testing().SetCurrentTargetFrameForTesting( + subframe); + + // Impersonate a request for an API call that the webstore is allowed to make. + mojom::RequestParamsPtr params = mojom::RequestParams::New(); + params->name = "management.getAll"; + params->arguments = base::ListValue(); + params->extension_id = ""; + params->source_url = webstore_url; + params->context_type = mojom::ContextType::kWebPage; + params->request_id = 1; + params->has_callback = true; + params->user_gesture = false; + params->worker_thread_id = kMainThreadId; + params->service_worker_version_id = + blink::mojom::kInvalidServiceWorkerVersionId; + + base::RunLoop run_loop; + bool api_success = true; + std::string api_error; + + extension_frame_host->Request( + std::move(params), + base::BindOnce( + [](base::OnceClosure quit_closure, bool* success_out, + std::string* error_out, bool success, + base::ListValue response_wrapper, const std::string& error, + mojom::ExtraResponseDataPtr extra_data) { + *success_out = success; + *error_out = error; + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure(), &api_success, &api_error)); + + run_loop.Run(); + + // Clean up the target frame override. + extension_frame_host->receivers_for_testing().SetCurrentTargetFrameForTesting( + nullptr); + + // We expect the request to have been blocked. + EXPECT_FALSE(api_success); + EXPECT_EQ(api_error, "Cannot call extension APIs from error pages."); +} + } // namespace extensions diff -Nru chromium-149.0.7827.102/chrome/browser/media/cast_mirroring_service_host.cc chromium-149.0.7827.114/chrome/browser/media/cast_mirroring_service_host.cc --- chromium-149.0.7827.102/chrome/browser/media/cast_mirroring_service_host.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/media/cast_mirroring_service_host.cc 2026-06-10 17:58:03.000000000 +0000 @@ -97,22 +97,28 @@ std::move(receiver)); } -void PauseVideoCaptureHostOnIO(media::mojom::VideoCaptureHost* host, - base::UnguessableToken device_id, - base::OnceClosure on_paused_callback) { +void PauseVideoCaptureHostOnIO( + mojo::SelfOwnedReceiverRef host, + base::UnguessableToken device_id, + base::OnceClosure on_paused_callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - host->Pause(device_id); - std::move(on_paused_callback).Run(); + if (host) { + host->impl()->Pause(device_id); + std::move(on_paused_callback).Run(); + } } -void ResumeVideoCaptureHostOnIO(media::mojom::VideoCaptureHost* host, - base::UnguessableToken device_id, - base::UnguessableToken session_id, - media::VideoCaptureParams params, - base::OnceClosure on_resumed_callback) { +void ResumeVideoCaptureHostOnIO( + mojo::SelfOwnedReceiverRef host, + base::UnguessableToken device_id, + base::UnguessableToken session_id, + media::VideoCaptureParams params, + base::OnceClosure on_resumed_callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - host->Resume(device_id, session_id, params); - std::move(on_resumed_callback).Run(); + if (host) { + host->impl()->Resume(device_id, session_id, params); + std::move(on_resumed_callback).Run(); + } } blink::mojom::MediaStreamType ConvertVideoStreamType( @@ -611,23 +617,18 @@ void CastMirroringServiceHost::Pause(base::OnceClosure on_paused_callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (video_capture_host_) { - content::GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&PauseVideoCaptureHostOnIO, video_capture_host_->impl(), - ignored_token_, std::move(on_paused_callback))); - } + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&PauseVideoCaptureHostOnIO, video_capture_host_, + ignored_token_, std::move(on_paused_callback))); } void CastMirroringServiceHost::Resume(base::OnceClosure on_resumed_callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (video_capture_host_) { - content::GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&ResumeVideoCaptureHostOnIO, video_capture_host_->impl(), - ignored_token_, ignored_token_, ignored_params_, - std::move(on_resumed_callback))); - } + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&ResumeVideoCaptureHostOnIO, video_capture_host_, + ignored_token_, ignored_token_, ignored_params_, + std::move(on_resumed_callback))); } void CastMirroringServiceHost::GetMirroringStats( diff -Nru chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client.cc chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client.cc --- chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client.cc 2026-06-10 17:58:03.000000000 +0000 @@ -222,12 +222,6 @@ constexpr char kPasswordBreachEntryTrigger[] = "PASSWORD_ENTRY"; #endif -#if BUILDFLAG(IS_ANDROID) -// TODO(crbug.com/41485955): Get rid of DeprecatedGetOriginAsURL(). -url::Origin URLToOrigin(GURL url) { - return url::Origin::Create(url.DeprecatedGetOriginAsURL()); -} -#endif // BUILDFLAG(IS_ANDROID) } // namespace @@ -632,6 +626,9 @@ // without being called. auto split_delay_callback = base::SplitOnceCallback(std::move(delay_callback)); + if (!weak_driver) { + return; + } password_manager::ContentPasswordManagerDriver* driver = static_cast( weak_driver.get()); @@ -681,8 +678,7 @@ should_show_hybrid_option)); base::span password_credentials = - credential_cache_ - .GetCredentialStore(URLToOrigin(driver->GetLastCommittedURL())) + credential_cache_.GetCredentialStore(driver->GetLastCommittedOrigin()) .GetCredentials(); std::vector credentials; credentials.reserve(password_credentials.size() + passkeys.size()); @@ -1404,10 +1400,9 @@ } void ChromePasswordManagerClient::MarkSharedCredentialsAsNotified( - const GURL& url) { - for (const PasswordForm& form : - credential_cache_.GetCredentialStore(URLToOrigin(url)) - .GetUnnotifiedSharedCredentials()) { + const url::Origin& origin) { + for (const PasswordForm& form : credential_cache_.GetCredentialStore(origin) + .GetUnnotifiedSharedCredentials()) { // Make a non-const copy so we can modify it. password_manager::PasswordForm updatedForm = form; updatedForm.sharing_notification_displayed = true; diff -Nru chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client.h chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client.h --- chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client.h 2026-06-10 17:58:03.000000000 +0000 @@ -320,7 +320,7 @@ #if BUILDFLAG(IS_ANDROID) webauthn::WebAuthnCredManDelegate* GetWebAuthnCredManDelegateForDriver( password_manager::PasswordManagerDriver* driver) override; - void MarkSharedCredentialsAsNotified(const GURL& url) override; + void MarkSharedCredentialsAsNotified(const url::Origin& origin) override; #endif // BUILDFLAG(IS_ANDROID) version_info::Channel GetChannel() const override; void RefreshPasswordManagerSettingsIfNeeded() const override; diff -Nru chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc --- chromium-149.0.7827.102/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -1990,7 +1990,7 @@ shared_not_notified_account.sharing_notification_displayed = true; EXPECT_CALL(*profile_store, UpdateLogin(shared_not_notified_profile, _)); EXPECT_CALL(*account_store, UpdateLogin(shared_not_notified_account, _)); - GetClient()->MarkSharedCredentialsAsNotified(kURL); + GetClient()->MarkSharedCredentialsAsNotified(origin); } #endif // BUILDFLAG(IS_ANDROID) diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc 2026-06-10 17:58:03.000000000 +0000 @@ -110,6 +110,7 @@ // If the render frame host has been destroyed already, the url will be empty // in which case Show() should never be called. CHECK(!url.is_empty()); + url::Origin origin = ttf_delegate_->GetFrameOrigin(); switch (GetResponsibleDisplayTarget(credentials_)) { case DisplayTarget::kNone: @@ -127,7 +128,7 @@ no_passkeys_bridge_ = std::make_unique(); } no_passkeys_bridge_->Show( - GetNativeView()->GetWindowAndroid(), url::Origin::Create(url).host(), + GetNativeView()->GetWindowAndroid(), origin.host(), base::BindOnce(&TouchToFillController::OnDismiss, weak_ptr_factory_.GetWeakPtr()), base::BindOnce(&TouchToFillController::OnHybridSignInSelected, @@ -169,8 +170,7 @@ return view_->Show(url, TouchToFillView::IsOriginSecure( - network::IsOriginPotentiallyTrustworthy( - url::Origin::Create(url))), + network::IsOriginPotentiallyTrustworthy(origin)), *sorted_credentials, flags); } } @@ -182,7 +182,7 @@ if (credential.match_type() == password_manager_util::GetLoginMatchType::kGrouped) { std::string current_origin = - GetDisplayOrigin(url::Origin::Create(ttf_delegate_->GetFrameUrl())); + GetDisplayOrigin(ttf_delegate_->GetFrameOrigin()); // Use `cred->display_name()` instead of origin here to correctly display // credentials saved for android apps. grouped_credential_sheet_controller_->ShowAcknowledgeSheet( diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc 2026-06-10 17:58:03.000000000 +0000 @@ -231,6 +231,11 @@ return filler_->GetFrameUrl(); } +url::Origin TouchToFillControllerAutofillDelegate::GetFrameOrigin() { + CHECK(filler_); + return filler_->GetFrameOrigin(); +} + bool TouchToFillControllerAutofillDelegate::ShouldShowTouchToFill() { if (!form_to_fill_) { return false; @@ -336,9 +341,9 @@ // this case it's not possible to mark credentials as notitied. If the user // has properly interact with the touch to fill UI, the client would have been // notified properly. - GURL url = GetFrameUrl(); - if (!url.is_empty()) { - password_client_->MarkSharedCredentialsAsNotified(url); + url::Origin origin = GetFrameOrigin(); + if (!origin.opaque()) { + password_client_->MarkSharedCredentialsAsNotified(origin); } filler_.reset(); base::UmaHistogramEnumeration("PasswordManager.TouchToFill.Outcome", outcome); diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h 2026-06-10 17:58:03.000000000 +0000 @@ -25,6 +25,7 @@ #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "ui/gfx/native_ui_types.h" +#include "url/origin.h" namespace password_manager { class PasskeyCredential; @@ -127,6 +128,7 @@ void OnDismiss(base::OnceClosure action_completed) override; void OnCredManDismissed(base::OnceClosure action_completed) override; GURL GetFrameUrl() override; + url::Origin GetFrameOrigin() override; bool ShouldShowTouchToFill() override; bool ShouldTriggerSubmission() override; bool ShouldShowHybridOption() override; diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -87,7 +87,10 @@ GetWebAuthnCredentialsDelegateForDriver, (password_manager::PasswordManagerDriver*), (override)); - MOCK_METHOD(void, MarkSharedCredentialsAsNotified, (const GURL&), (override)); + MOCK_METHOD(void, + MarkSharedCredentialsAsNotified, + (const url::Origin&), + (override)); MOCK_METHOD(bool, IsReauthBeforeFillingRequired, (device_reauth::DeviceAuthenticator*), @@ -145,6 +148,8 @@ // cache the raw pointer here to interact with the mock after passing. weak_filler_ = filler.get(); ON_CALL(*filler, GetFrameUrl()).WillByDefault(Return(GURL(kExampleCom))); + ON_CALL(*filler, GetFrameOrigin()) + .WillByDefault(Return(url::Origin::Create(GURL(kExampleCom)))); return filler; } @@ -486,6 +491,8 @@ auto filler_to_pass = CreateMockFiller(); EXPECT_CALL(*last_mock_filler(), GetFrameUrl()) .WillOnce(Return(GURL("http://example.com"))); + EXPECT_CALL(*last_mock_filler(), GetFrameOrigin()) + .WillOnce(Return(url::Origin::Create(GURL("http://example.com")))); Credential credentials[] = { MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})}; @@ -615,7 +622,8 @@ TouchToFillControllerAutofillDelegate::ShowHybridOption(false)), /*cred_man_delegate=*/nullptr); - EXPECT_CALL(client(), MarkSharedCredentialsAsNotified(GURL(kExampleCom))); + EXPECT_CALL(client(), MarkSharedCredentialsAsNotified( + url::Origin::Create(GURL(kExampleCom)))); touch_to_fill_controller().OnDismiss(); auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName); @@ -644,7 +652,8 @@ TouchToFillControllerAutofillDelegate::ShowHybridOption(false)), /*cred_man_delegate=*/nullptr); - EXPECT_CALL(client(), MarkSharedCredentialsAsNotified(GURL(kExampleCom))); + EXPECT_CALL(client(), MarkSharedCredentialsAsNotified( + url::Origin::Create(GURL(kExampleCom)))); EXPECT_CALL(client(), NavigateToManagePasswordsPage( password_manager::ManagePasswordsReferrer::kTouchToFill)); diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_delegate.h 2026-06-10 17:58:03.000000000 +0000 @@ -12,6 +12,7 @@ #include "base/functional/callback_forward.h" #include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_view.h" #include "ui/gfx/native_ui_types.h" +#include "url/origin.h" namespace password_manager { class PasskeyCredential; @@ -64,6 +65,10 @@ // created. virtual GURL GetFrameUrl() = 0; + // Gets the last committed origin for the frame that triggered this sheet to + // be created. + virtual url::Origin GetFrameOrigin() = 0; + // Returns whether TTF is eligible for showing for the currently focused field // (e. g. it should not be triggered for the new password field). virtual bool ShouldShowTouchToFill() = 0; diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.cc chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.cc --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.cc 2026-06-10 17:58:03.000000000 +0000 @@ -18,6 +18,7 @@ #include "components/webauthn/android/webauthn_cred_man_delegate.h" #include "content/public/browser/web_contents.h" #include "url/gurl.h" +#include "url/origin.h" using Credential = TouchToFillView::Credential; @@ -84,6 +85,14 @@ return credential_receiver_->web_contents()->GetLastCommittedURL(); } +url::Origin TouchToFillControllerWebAuthnDelegate::GetFrameOrigin() { + return credential_receiver_->web_contents() + ? credential_receiver_->web_contents() + ->GetPrimaryMainFrame() + ->GetLastCommittedOrigin() + : url::Origin(); +} + bool TouchToFillControllerWebAuthnDelegate::ShouldShowTouchToFill() { return true; } diff -Nru chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h --- chromium-149.0.7827.102/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h 2026-06-10 17:58:03.000000000 +0000 @@ -16,6 +16,7 @@ #include "chrome/browser/webauthn/shared_types.h" #include "chrome/browser/webauthn/touch_to_fill_credential_receiver.h" #include "ui/gfx/native_ui_types.h" +#include "url/origin.h" namespace content { class WebContents; @@ -68,6 +69,7 @@ void OnDismiss(base::OnceClosure action_completed) override; void OnCredManDismissed(base::OnceClosure action_completed) override; GURL GetFrameUrl() override; + url::Origin GetFrameOrigin() override; bool ShouldShowTouchToFill() override; bool ShouldTriggerSubmission() override; bool ShouldShowHybridOption() override; diff -Nru chromium-149.0.7827.102/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc chromium-149.0.7827.114/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc --- chromium-149.0.7827.102/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -6,6 +6,7 @@ #include "base/scoped_observation.h" #include "base/test/scoped_feature_list.h" +#include "chrome/browser/digital_credentials/digital_identity_provider_desktop.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/in_process_browser_test.h" @@ -233,3 +234,106 @@ ui::mojom::DialogButton::kOk)); } } + +namespace { + +// Subclass to expose protected methods for testing. +class TestDigitalIdentityProviderDesktop + : public DigitalIdentityProviderDesktop { + public: + using DigitalIdentityProviderDesktop::EndRequestWithError; + using DigitalIdentityProviderDesktop::EnsureDialogCreated; + using DigitalIdentityProviderDesktop::set_callback_for_testing; + using DigitalIdentityProviderDesktop::set_rp_origin_for_testing; + using DigitalIdentityProviderDesktop::set_web_contents_for_testing; + + // Calls the protected ShowQrCodeDialog. + void SetUpAndShowQrDialog(content::WebContents* web_contents, + base::OnceClosure callback) { + set_web_contents_for_testing(web_contents->GetWeakPtr()); + set_rp_origin_for_testing(url::Origin::Create(GURL("https://rp.example"))); + set_callback_for_testing(base::BindOnce( + [](base::OnceClosure callback, + base::expected< + TestDigitalIdentityProviderDesktop::DigitalCredential, + content::DigitalIdentityProvider::RequestStatusForMetrics> + result) { std::move(callback).Run(); }, + std::move(callback))); + ShowQrCodeDialog("FIDO:/0123456789", RequestInfo::RequestType::kGet); + } + + DigitalIdentityMultiStepDialog* GetDialog() { return EnsureDialogCreated(); } +}; + +class ProviderDestroyerOnWidgetClosingObserver : public views::WidgetObserver { + public: + explicit ProviderDestroyerOnWidgetClosingObserver( + base::OnceClosure destruction_callback) + : destruction_callback_(std::move(destruction_callback)) {} + + void OnWidgetClosing(views::Widget* widget) override { + widget->RemoveObserver(this); + if (destruction_callback_) { + std::move(destruction_callback_).Run(); + } + } + + private: + base::OnceClosure destruction_callback_; +}; + +} // namespace + +// Regression test for UAF in +// DigitalIdentityProviderDesktop::EndRequestWithError when the owner is +// synchronously destroyed during dialog close. +IN_PROC_BROWSER_TEST_F(DigitalIdentityMultiStepDialogBrowserTest, + EndRequestWithErrorOwnerDestroyedDuringDialogClose) { + auto provider = std::make_unique(); + + base::RunLoop run_loop; + // Show the dialog via the real ShowQrCodeDialog flow. + provider->SetUpAndShowQrDialog(GetActiveWebContents(), + run_loop.QuitClosure()); + + // Retrieve the widget robustly using TestApi and EnsureDialogCreated. + // Wrap `TestApi` in a nested scope so it is destroyed before the message + // loop runs. Otherwise, when the loop runs and triggers the UAF teardown, + // the dialog is deleted, leaving `TestApi` holding a dangling raw_ptr. + views::Widget* widget = nullptr; + { + DigitalIdentityMultiStepDialog* dialog = provider->GetDialog(); + DigitalIdentityMultiStepDialog::TestApi dialog_test_api(dialog); + widget = dialog_test_api.GetWidget(); + } + ASSERT_TRUE(widget); + base::WeakPtr weak_widget = widget->GetWeakPtr(); + + // Set up the observer to synchronously destroy the provider when the widget + // closes. + ProviderDestroyerOnWidgetClosingObserver observer(base::BindOnce( + [](std::unique_ptr* provider) { + provider->reset(); + }, + base::Unretained(&provider))); + widget->AddObserver(&observer); + + // Trigger cancellation. OnDialogCanceled() will PostTask a call to + // OnCanceled() -> EndRequestWithError(). + static_cast(widget->widget_delegate()) + ->CancelDialog(); + ASSERT_FALSE(widget->IsClosed()); + + // Run the message loop to execute the posted tasks. + // This will run EndRequestWithError(), triggering the UAF if the bug exists. + run_loop.Run(); + + // Verify that the provider was safely destroyed (pointer is null). + EXPECT_FALSE(provider); + + // Clean up if the widget is still alive. + if (weak_widget) { + weak_widget->RemoveObserver(&observer); + views::test::WidgetDestroyedWaiter(weak_widget.get()).Wait(); + } +} diff -Nru chromium-149.0.7827.102/chrome/chrome_branch_deps.json chromium-149.0.7827.114/chrome/chrome_branch_deps.json --- chromium-149.0.7827.102/chrome/chrome_branch_deps.json 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/chrome/chrome_branch_deps.json 2026-06-10 17:58:03.000000000 +0000 @@ -1,22 +1,21 @@ { - "src": "refs/branch-heads/7827", - "src:src/chrome/browser/glic/e2e_test/internal": "refs/heads/chromium/7827", - "src:src/clank": "refs/heads/chromium/7827", - "src:src/components/optimization_guide/internal": "refs/heads/chromium/7827", - "src:src/internal": "refs/heads/chromium/7827", - "src:src/ios_internal": "refs/heads/chromium/7827", - "src:src/third_party/angle": "refs/heads/chromium/7827", - "src:src/third_party/dawn": "refs/heads/chromium/7827", - "src:src/third_party/devtools-frontend/src": "refs/heads/chromium/7827", - "src:src/third_party/instrumented_libs": "refs/heads/chromium/7827", - "src:src/third_party/litert/src": "refs/heads/chromium/7827", - "src:src/third_party/openscreen/src": "refs/heads/chromium/7827", - "src:src/third_party/pdfium": "refs/heads/chromium/7827", - "src:src/third_party/ruy/src": "refs/heads/chromium/7827", - "src:src/third_party/skia": "refs/heads/chrome/m149", - "src:src/third_party/tflite/src": "refs/heads/chromium/7827", - "src:src/third_party/vulkan-deps": "refs/heads/chromium/7827", - "src:src/third_party/webrtc": "refs/branch-heads/7827", - "src:src/third_party/xnnpack/src": "refs/heads/chromium/7827", - "src:src/v8": "refs/heads/chromium/7827" + "src": "refs/branch-heads/7827_102", + "src:src/chrome/browser/glic/e2e_test/internal": "refs/heads/chromium/7827_102", + "src:src/clank": "refs/heads/chromium/7827_102", + "src:src/components/optimization_guide/internal": "refs/heads/chromium/7827_102", + "src:src/internal": "refs/heads/chromium/7827_102", + "src:src/ios_internal": "refs/heads/chromium/7827_102", + "src:src/third_party/angle": "refs/heads/chromium/7827_102", + "src:src/third_party/dawn": "refs/heads/chromium/7827_102", + "src:src/third_party/devtools-frontend/src": "refs/heads/chromium/7827_102", + "src:src/third_party/instrumented_libs": "refs/heads/chromium/7827_102", + "src:src/third_party/litert/src": "refs/heads/chromium/7827_102", + "src:src/third_party/openscreen/src": "refs/heads/chromium/7827_102", + "src:src/third_party/pdfium": "refs/heads/chromium/7827_102", + "src:src/third_party/ruy/src": "refs/heads/chromium/7827_102", + "src:src/third_party/tflite/src": "refs/heads/chromium/7827_102", + "src:src/third_party/vulkan-deps": "refs/heads/chromium/7827_102", + "src:src/third_party/webrtc": "refs/branch-heads/7827_102", + "src:src/third_party/xnnpack/src": "refs/heads/chromium/7827_102", + "src:src/v8": "refs/heads/chromium/7827_102" } diff -Nru chromium-149.0.7827.102/components/autofill/core/browser/payments/payments_request_details.h chromium-149.0.7827.114/components/autofill/core/browser/payments/payments_request_details.h --- chromium-149.0.7827.102/components/autofill/core/browser/payments/payments_request_details.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/autofill/core/browser/payments/payments_request_details.h 2026-06-10 17:58:03.000000000 +0000 @@ -9,6 +9,7 @@ #include #include #include +#include #include "base/values.h" #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" @@ -511,13 +512,13 @@ // The instrument ID is used by the server to identify a specific BNPL issuer. std::string instrument_id; // The fingerprint data for the user and the device. - std::string_view risk_data; + std::string risk_data; // The merchant domain (including the scheme). GURL merchant_domain; // The total purchase amount (in micros) from the merchant checkout page. int64_t total_amount = 0; // Currency of the amount represented by a three-letter currency code. - std::string_view currency; + std::string currency; }; // Information retrieved from a BNPL FetchUrlRequest. diff -Nru chromium-149.0.7827.102/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.cc chromium-149.0.7827.114/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.cc --- chromium-149.0.7827.102/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.cc 2026-06-10 17:58:03.000000000 +0000 @@ -60,6 +60,11 @@ void AutofillProgressDialogControllerImpl::OnDismissed( bool is_canceled_by_user) { + // On macOS without biometrics, the accept/cancel callbacks have the potential + // to destroy this controller (e.g., if the tab is closed during the nested + // run loop of the passcode prompt). We check a weak pointer to avoid a UAF. + auto weak_self = weak_ptr_factory_.GetWeakPtr(); + // Dialog is being dismissed so set the pointer to nullptr. autofill_progress_dialog_view_.reset(); if (is_canceled_by_user) { @@ -70,6 +75,10 @@ } } + if (!weak_self) { + return; + } + AutofillMetrics::LogProgressDialogResultMetric( is_canceled_by_user, autofill_progress_dialog_type_); cancel_callback_.Reset(); diff -Nru chromium-149.0.7827.102/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl_unittest.cc chromium-149.0.7827.114/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl_unittest.cc --- chromium-149.0.7827.102/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -5,9 +5,16 @@ #include "components/autofill/core/browser/ui/payments/autofill_progress_dialog_controller_impl.h" #include +#include +#include "base/functional/bind.h" +#include "base/functional/callback_helpers.h" +#include "base/test/bind.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/mock_callback.h" +#include "build/buildflag.h" #include "components/autofill/core/browser/ui/payments/autofill_progress_dialog_view.h" +#include "components/autofill/core/browser/ui/payments/autofill_progress_ui_type.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -46,9 +53,59 @@ return controller_.get(); } + void DeleteController() { controller_.reset(); } + + void InitializeController(base::OnceClosure cancel_callback) { + controller_ = std::make_unique( + AutofillProgressUiType::kVirtualCardUnmaskProgressUi, + std::move(cancel_callback)); +#if BUILDFLAG(IS_IOS) + controller_->ShowDialog(base::BindOnce( + &AutofillProgressDialogControllerImplTest::CreateDialogView, + base::Unretained(this))); +#else + controller_->ShowDialog( + base::BindOnce([]() -> std::unique_ptr { + return std::make_unique(); + })); +#endif + } + private: std::unique_ptr view_; std::unique_ptr controller_; }; +// Tests that a Use-After-Free (UAF) is prevented when the controller is +// destroyed synchronously during the success callback. A UAF can occur if +// `OnDismissed()` accesses members after the callback deletes the controller. +TEST_F(AutofillProgressDialogControllerImplTest, + OnDismissed_Success_SafeSelfDestruction) { + base::MockCallback cancel_callback; + InitializeController(cancel_callback.Get()); + + base::OnceClosure no_interactive_auth_callback = + base::BindLambdaForTesting([&]() { DeleteController(); }); + + controller()->DismissDialog(/*show_confirmation_before_closing=*/false, + std::move(no_interactive_auth_callback)); + + controller()->OnDismissed(/*is_canceled_by_user=*/false); + + EXPECT_EQ(controller(), nullptr); +} + +// Tests the cancellation path of the UAF fix. Ensures that if the controller +// is destroyed synchronously during the cancel callback, the `WeakPtr` safely +// prevents `OnDismissed()` from accessing freed memory. +TEST_F(AutofillProgressDialogControllerImplTest, + OnDismissed_Canceled_SafeSelfDestruction) { + InitializeController( + base::BindLambdaForTesting([&]() { DeleteController(); })); + + controller()->OnDismissed(/*is_canceled_by_user=*/true); + + EXPECT_EQ(controller(), nullptr); +} + } // namespace autofill diff -Nru chromium-149.0.7827.102/components/named_mojo_ipc_server/named_mojo_message_pipe_server.cc chromium-149.0.7827.114/components/named_mojo_ipc_server/named_mojo_message_pipe_server.cc --- chromium-149.0.7827.102/components/named_mojo_ipc_server/named_mojo_message_pipe_server.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/named_mojo_ipc_server/named_mojo_message_pipe_server.cc 2026-06-10 17:58:03.000000000 +0000 @@ -156,7 +156,8 @@ // Create isolated connection. auto connection = std::make_unique(); mojo::ScopedMessagePipeHandle message_pipe = - connection->Connect(std::move(endpoint), std::move(peer_process)); + connection->Connect(std::move(endpoint), std::move(peer_process), + options_.extra_send_invitation_flags); on_message_pipe_ready_.Run(std::move(message_pipe), std::move(info), result.context, std::move(connection)); return; diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/mock_password_credential_filler.h chromium-149.0.7827.114/components/password_manager/core/browser/mock_password_credential_filler.h --- chromium-149.0.7827.102/components/password_manager/core/browser/mock_password_credential_filler.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/mock_password_credential_filler.h 2026-06-10 17:58:03.000000000 +0000 @@ -11,6 +11,7 @@ #include "components/password_manager/core/browser/password_credential_filler.h" #include "components/password_manager/core/browser/password_manager_driver.h" #include "testing/gmock/include/gmock/gmock.h" +#include "url/origin.h" namespace password_manager { @@ -36,6 +37,7 @@ (), (const override)); MOCK_METHOD(GURL, GetFrameUrl, (), (const override)); + MOCK_METHOD(url::Origin, GetFrameOrigin, (), (const override)); base::WeakPtr AsWeakPtr() override; diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler.h chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler.h --- chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler.h 2026-06-10 17:58:03.000000000 +0000 @@ -11,6 +11,7 @@ #include "components/password_manager/core/browser/password_manager_driver.h" #include "components/password_manager/core/browser/password_ui_utils.h" #include "url/gurl.h" +#include "url/origin.h" namespace password_manager { @@ -42,6 +43,9 @@ // Returns the frame URL this filler is interacting with. virtual GURL GetFrameUrl() const = 0; + // Returns the frame origin this filler is interacting with. + virtual url::Origin GetFrameOrigin() const = 0; + // Get a WeakPtr to the instance. virtual base::WeakPtr AsWeakPtr() = 0; }; diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl.cc chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl.cc --- chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl.cc 2026-06-10 17:58:03.000000000 +0000 @@ -14,6 +14,7 @@ #include "components/password_manager/core/browser/password_manager_client.h" #include "components/password_manager/core/browser/password_ui_utils.h" #include "components/password_manager/core/common/password_manager_features.h" +#include "url/origin.h" namespace password_manager { @@ -86,6 +87,10 @@ return driver_ ? driver_->GetLastCommittedURL() : GURL(); } +url::Origin PasswordCredentialFillerImpl::GetFrameOrigin() const { + return driver_ ? driver_->GetLastCommittedOrigin() : url::Origin(); +} + base::WeakPtr PasswordCredentialFillerImpl::AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl.h chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl.h --- chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl.h 2026-06-10 17:58:03.000000000 +0000 @@ -11,6 +11,7 @@ #include "components/autofill/core/common/password_form_fill_data.h" #include "components/password_manager/core/browser/password_credential_filler.h" #include "components/password_manager/core/browser/password_manager_driver.h" +#include "url/origin.h" namespace password_manager { @@ -33,6 +34,7 @@ bool ShouldTriggerSubmission() const override; SubmissionReadinessState GetSubmissionReadinessState() const override; GURL GetFrameUrl() const override; + url::Origin GetFrameOrigin() const override; base::WeakPtr AsWeakPtr() override; private: diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl_unittest.cc chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl_unittest.cc --- chromium-149.0.7827.102/components/password_manager/core/browser/password_credential_filler_impl_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_credential_filler_impl_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -21,6 +21,7 @@ #include "components/password_manager/core/common/password_manager_features.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "url/origin.h" namespace { @@ -44,6 +45,7 @@ (override)); MOCK_METHOD(void, TriggerFormSubmission, (), (override)); MOCK_METHOD(const GURL&, GetLastCommittedURL, (), (const override)); + MOCK_METHOD(const url::Origin&, GetLastCommittedOrigin, (), (const override)); }; enum class FormFieldFocusabilityType { @@ -149,6 +151,8 @@ void SetUp() override { ON_CALL(driver_, GetLastCommittedURL()) .WillByDefault(ReturnRefOfCopy(GURL(kExampleCom))); + ON_CALL(driver_, GetLastCommittedOrigin()) + .WillByDefault(ReturnRefOfCopy(url::Origin::Create(GURL(kExampleCom)))); } MockPasswordManagerDriver& driver() { return driver_; } @@ -226,6 +230,23 @@ filler.FillUsernameAndPassword(kUsername, kPassword, base::DoNothing()); } +TEST_F(PasswordCredentialFillerBaseTest, GetFrameOrigin) { + PasswordCredentialFillerImpl filler( + driver().AsWeakPtr(), + CreatePasswordSuggestionRequest({}, /*has_captcha=*/false, + /*username_field_index=*/0, + /*password_field_index=*/0)); + EXPECT_EQ(filler.GetFrameOrigin(), url::Origin::Create(GURL(kExampleCom))); +} + +TEST_F(PasswordCredentialFillerBaseTest, GetFrameOriginWithNullDriver) { + PasswordCredentialFillerImpl filler( + nullptr, CreatePasswordSuggestionRequest({}, /*has_captcha=*/false, + /*username_field_index=*/0, + /*password_field_index=*/0)); + EXPECT_TRUE(filler.GetFrameOrigin().opaque()); +} + class PasswordCredentialFillerV2ParameterTest : public PasswordCredentialFillerBaseTest, public testing::WithParamInterface< diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_manager_client.cc chromium-149.0.7827.114/components/password_manager/core/browser/password_manager_client.cc --- chromium-149.0.7827.102/components/password_manager/core/browser/password_manager_client.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_manager_client.cc 2026-06-10 17:58:03.000000000 +0000 @@ -191,7 +191,8 @@ return nullptr; } -void PasswordManagerClient::MarkSharedCredentialsAsNotified(const GURL& url) {} +void PasswordManagerClient::MarkSharedCredentialsAsNotified( + const url::Origin& origin) {} #endif // BUILDFLAG(IS_ANDROID) diff -Nru chromium-149.0.7827.102/components/password_manager/core/browser/password_manager_client.h chromium-149.0.7827.114/components/password_manager/core/browser/password_manager_client.h --- chromium-149.0.7827.102/components/password_manager/core/browser/password_manager_client.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/components/password_manager/core/browser/password_manager_client.h 2026-06-10 17:58:03.000000000 +0000 @@ -539,7 +539,7 @@ // Marks all credentials that have been loaded for this page and have been // received via the password sharing feature as notified. - virtual void MarkSharedCredentialsAsNotified(const GURL& url); + virtual void MarkSharedCredentialsAsNotified(const url::Origin& origin); #endif // BUILDFLAG(IS_ANDROID) // Returns the Chrome channel for the installation. diff -Nru chromium-149.0.7827.102/content/browser/devtools/protocol/input_handler.cc chromium-149.0.7827.114/content/browser/devtools/protocol/input_handler.cc --- chromium-149.0.7827.102/content/browser/devtools/protocol/input_handler.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/devtools/protocol/input_handler.cc 2026-06-10 17:58:03.000000000 +0000 @@ -1479,7 +1479,7 @@ static_cast(data->GetDragOperationsMask()); std::unique_ptr drop_data = std::make_unique(ProtocolDragDataToDropData(std::move(data))); - drop_data->view_id = widget_host->GetRoutingID(); + widget_host->FilterDropData(drop_data.get()); int event_modifiers = GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers), false, false, 0, 0); diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_event_handler_win.cc chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_event_handler_win.cc --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_event_handler_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_event_handler_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -125,6 +125,8 @@ IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, DIRECTMANIPULATION_STATUS previous) { + Microsoft::WRL::ComPtr keep_alive(this); + // MSDN never mention |viewport| are nullable and we never saw it is null when // testing. DCHECK(viewport); @@ -199,6 +201,8 @@ HRESULT DirectManipulationEventHandler::OnContentUpdated( IDirectManipulationViewport* viewport, IDirectManipulationContent* content) { + Microsoft::WRL::ComPtr keep_alive(this); + // MSDN never mention these params are nullable and we never saw they are null // when testing. DCHECK(viewport); diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_helper_win.cc chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_helper_win.cc --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_helper_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_helper_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -164,10 +164,18 @@ RemoveAnimationObserver(); } + auto weak_ptr = weak_factory_.GetWeakPtr(); + if (event_handler_) { event_handler_.Reset(); viewport_->Stop(); + if (!weak_ptr) { + return; + } viewport_->RemoveEventHandler(view_port_handler_cookie_); + if (!weak_ptr) { + return; + } } window_tree_host_ = window_tree_host; @@ -186,6 +194,9 @@ // IDirectManipulationViewportEventHandler. HRESULT hr = viewport_->AddEventHandler(window_, event_handler_.Get(), &view_port_handler_cookie_); + if (!weak_ptr) { + return; + } if (!SUCCEEDED(hr)) { event_handler_.Reset(); return; @@ -206,7 +217,12 @@ event_handler_->SetViewportSizeInPixels(size_in_pixels); } + auto weak_ptr = weak_factory_.GetWeakPtr(); + HRESULT hr = viewport_->Stop(); + if (!weak_ptr) { + return; + } if (!SUCCEEDED(hr)) return; @@ -215,6 +231,18 @@ } void DirectManipulationHelper::OnPointerHitTest(WPARAM w_param) { + UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); + POINTER_INPUT_TYPE pointer_type; + if (!::GetPointerType(pointer_id, &pointer_type)) { + // Use the generic "any pointer type" for unknown. + pointer_type = PT_POINTER; + } + OnPointerHitTest(pointer_id, pointer_type); +} + +void DirectManipulationHelper::OnPointerHitTest( + UINT32 pointer_id, + POINTER_INPUT_TYPE pointer_type) { if (!event_handler_) { return; } @@ -229,11 +257,12 @@ // For WM_POINTER, the pointer type will show the event from mouse. // For WM_POINTERACTIVATE, the pointer id will be different with the following // message. - UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); - POINTER_INPUT_TYPE pointer_type; - if (::GetPointerType(pointer_id, &pointer_type) && - pointer_type == PT_TOUCHPAD) { + if (pointer_type == PT_TOUCHPAD) { + auto weak_ptr = weak_factory_.GetWeakPtr(); viewport_->SetContact(pointer_id); + if (!weak_ptr) { + return; + } } } @@ -257,8 +286,15 @@ } void DirectManipulationHelper::Destroy() { + auto weak_ptr = weak_factory_.GetWeakPtr(); UpdateEventHandler(nullptr, nullptr); + if (!weak_ptr) { + return; + } viewport_->Abandon(); + if (!weak_ptr) { + return; + } manager_->Deactivate(window_); } diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_helper_win.h chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_helper_win.h --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_helper_win.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_helper_win.h 2026-06-10 17:58:03.000000000 +0000 @@ -6,12 +6,14 @@ #define CONTENT_BROWSER_RENDERER_HOST_DIRECT_MANIPULATION_HELPER_WIN_H_ #include + #include #include #include #include +#include "base/gtest_prod_util.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "content/browser/renderer_host/direct_manipulation_event_handler_win.h" @@ -39,6 +41,13 @@ // when DM_POINTERHITTEST. // 3. OnViewportStatusChanged will be called when the gesture phase change. // OnContentUpdated will be called when the gesture update. +// +// IMPORTANT: Almost every function in this class can spin a nested message +// loop, because they call into DirectManipulation COM objects. The nested +// message loop can process WM_DESTROY messages that can delete the calling +// class. So it's vital that the caller of every method take a WeakPtr to any +// object that could be destroyed, and check if it's still valid after the +// method returns. class CONTENT_EXPORT DirectManipulationHelper : public ui::CompositorAnimationObserver { public: @@ -90,9 +99,13 @@ // Unregister this as an AnimationObserver of ui::Compositor. void RemoveAnimationObserver(); + bool HasEventHandlerForTesting() { return event_handler_ != nullptr; } + private: friend class DirectManipulationBrowserTestBase; friend class DirectManipulationUnitTest; + FRIEND_TEST_ALL_PREFIXES(DirectManipulationUnitTest, + DestroyDuringOnPointerHitTest); template using ComPtr = Microsoft::WRL::ComPtr; @@ -114,6 +127,10 @@ void Destroy(); + // Implementation of OnPointerHitTest, with the `pointer_id` and + // `pointer_type` precalculated. + void OnPointerHitTest(UINT32 pointer_id, POINTER_INPUT_TYPE pointer_type); + ComPtr manager_; ComPtr update_manager_; ComPtr viewport_; diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_test_helper_win.cc chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_test_helper_win.cc --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_test_helper_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_test_helper_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -32,6 +32,10 @@ // [1]https://learn.microsoft.com/en-us/windows/win32/api/directmanipulation/nf-directmanipulation-idirectmanipulationcontent-getcontenttransform HRESULT MockDirectManipulationContent::GetContentTransform(float* transforms, DWORD point_count) { + if (get_content_transform_callback_) { + std::move(get_content_transform_callback_).Run(); + } + DCHECK_EQ(point_count, transforms_.size()); for (size_t i = 0; i < transforms_.size(); ++i) diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_test_helper_win.h chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_test_helper_win.h --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_test_helper_win.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_test_helper_win.h 2026-06-10 17:58:03.000000000 +0000 @@ -11,6 +11,9 @@ #include #include +#include + +#include "base/functional/callback.h" namespace content { class PrecisionTouchpadBrowserTest; @@ -44,6 +47,10 @@ void SetContentTransform(float scale, float scroll_x, float scroll_y); + void set_get_content_transform_callback(base::OnceClosure callback) { + get_content_transform_callback_ = std::move(callback); + } + // IDirectManipulationContent: HRESULT STDMETHODCALLTYPE GetContentTransform(float* transforms, DWORD point_count) override; @@ -80,6 +87,8 @@ // (3,1) - x offset // (3,2) - y offset. std::array transforms_; + + base::OnceClosure get_content_transform_callback_; }; } // namespace content diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_win_unittest.cc chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_win_unittest.cc --- chromium-149.0.7827.102/content/browser/renderer_host/direct_manipulation_win_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/direct_manipulation_win_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -6,6 +6,9 @@ #include +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "base/time/time.h" #include "content/browser/renderer_host/direct_manipulation_helper_win.h" #include "content/browser/renderer_host/direct_manipulation_test_helper_win.h" #include "testing/gtest/include/gtest/gtest.h" @@ -41,6 +44,25 @@ ~MockDirectManipulationViewport() override = default; + void set_stop_callback(base::OnceClosure callback) { + stop_callback_ = std::move(callback); + } + void set_add_event_handler_callback(base::OnceClosure callback) { + add_event_handler_callback_ = std::move(callback); + } + void set_remove_event_handler_callback(base::OnceClosure callback) { + remove_event_handler_callback_ = std::move(callback); + } + void set_set_contact_callback(base::OnceClosure callback) { + set_contact_callback_ = std::move(callback); + } + void set_abandon_callback(base::OnceClosure callback) { + abandon_callback_ = std::move(callback); + } + void set_zoom_to_rect_callback(base::OnceClosure callback) { + zoom_to_rect_callback_ = std::move(callback); + } + bool WasZoomToRectCalled() { bool called = zoom_to_rect_called_; zoom_to_rect_called_ = false; @@ -52,6 +74,9 @@ HRESULT STDMETHODCALLTYPE Disable() override { return S_OK; } HRESULT STDMETHODCALLTYPE SetContact(_In_ UINT32 pointerId) override { + if (set_contact_callback_) { + std::move(set_contact_callback_).Run(); + } return S_OK; } @@ -92,6 +117,9 @@ _In_ const float bottom, _In_ BOOL animate) override { zoom_to_rect_called_ = true; + if (zoom_to_rect_callback_) { + std::move(zoom_to_rect_callback_).Run(); + } return S_OK; } @@ -156,10 +184,16 @@ AddEventHandler(_In_opt_ HWND window, _In_ IDirectManipulationViewportEventHandler* eventHandler, _Out_ DWORD* cookie) override { + if (add_event_handler_callback_) { + std::move(add_event_handler_callback_).Run(); + } return S_OK; } HRESULT STDMETHODCALLTYPE RemoveEventHandler(_In_ DWORD cookie) override { + if (remove_event_handler_callback_) { + std::move(remove_event_handler_callback_).Run(); + } return S_OK; } @@ -173,12 +207,28 @@ return S_OK; } - HRESULT STDMETHODCALLTYPE Stop() override { return S_OK; } + HRESULT STDMETHODCALLTYPE Stop() override { + if (stop_callback_) { + std::move(stop_callback_).Run(); + } + return S_OK; + } - HRESULT STDMETHODCALLTYPE Abandon() override { return S_OK; } + HRESULT STDMETHODCALLTYPE Abandon() override { + if (abandon_callback_) { + std::move(abandon_callback_).Run(); + } + return S_OK; + } private: bool zoom_to_rect_called_ = false; + base::OnceClosure stop_callback_; + base::OnceClosure add_event_handler_callback_; + base::OnceClosure remove_event_handler_callback_; + base::OnceClosure set_contact_callback_; + base::OnceClosure abandon_callback_; + base::OnceClosure zoom_to_rect_callback_; }; class MockDirectManipulationUpdateManager @@ -193,6 +243,10 @@ ~MockDirectManipulationUpdateManager() override = default; + void set_update_callback(base::OnceClosure callback) { + update_callback_ = std::move(callback); + } + HRESULT STDMETHODCALLTYPE RegisterWaitHandleCallback(HANDLE, IDirectManipulationUpdateHandler*, @@ -207,8 +261,14 @@ HRESULT STDMETHODCALLTYPE Update(IDirectManipulationFrameInfoProvider*) override { + if (update_callback_) { + std::move(update_callback_).Run(); + } return S_OK; } + + private: + base::OnceClosure update_callback_; }; class MockDirectManipulationManager @@ -257,6 +317,10 @@ return S_OK; } + ComPtr mock_update_manager() { + return update_manager_; + } + private: ComPtr viewport_; ComPtr update_manager_ = @@ -408,17 +472,36 @@ void SetUp() override { testing::Test::SetUp(); + viewport_ = Microsoft::WRL::Make(); + ASSERT_TRUE(viewport_); + manager_ = Microsoft::WRL::Make(viewport_); + ASSERT_TRUE(manager_); direct_manipulation_helper_ = - DirectManipulationHelper::CreateInstanceForTesting( - Microsoft::WRL::Make(viewport_)); + DirectManipulationHelper::CreateInstanceForTesting(manager_); ASSERT_TRUE(direct_manipulation_helper_); direct_manipulation_helper_->UpdateEventHandler(nullptr, &event_target_); + content_ = Microsoft::WRL::Make(); + ASSERT_TRUE(content_); } DirectManipulationHelper* GetDirectManipulationHelper() { return direct_manipulation_helper_.get(); } + base::OnceClosure ResetDirectManipulationHelperCallback() { + return base::BindOnce( + &DirectManipulationUnitTest::ResetDirectManipulationHelper, + base::Unretained(this)); + } + + void RecreateDirectManipulationHelper() { + ASSERT_FALSE(direct_manipulation_helper_); + ASSERT_TRUE(manager_); + direct_manipulation_helper_ = + DirectManipulationHelper::CreateInstanceForTesting(manager_); + ASSERT_TRUE(direct_manipulation_helper_); + } + std::vector GetEvents() { return event_target_.GetEvents(); } void ViewportStatusChanged(DIRECTMANIPULATION_STATUS current, @@ -439,13 +522,16 @@ direct_manipulation_helper_->SetDeviceScaleFactorForTesting(factor); } + protected: + ComPtr viewport_; + ComPtr manager_; + ComPtr content_; + MockWindowEventTarget event_target_; + private: + void ResetDirectManipulationHelper() { direct_manipulation_helper_.reset(); } + std::unique_ptr direct_manipulation_helper_; - ComPtr viewport_ = - Microsoft::WRL::Make(); - ComPtr content_ = - Microsoft::WRL::Make(); - MockWindowEventTarget event_target_; }; TEST_F(DirectManipulationUnitTest, ReceiveSimplePanTransform) { @@ -771,4 +857,77 @@ EXPECT_EQ(5, events[0].scroll_x_); } +// DirectManipulation COM calls on the UI thread can enter a nested message loop +// while they block on an internal delegate thread. The nested message loop can +// process WM_DESTROY messages that can invalidate pointers on the stack. To +// simulate this, these tests delete the DirectManipulationHelper from a mock +// COM call in each method that makes COM calls. If the methods don't guard +// their stack objects this will cause ASAN errors. Not all of these functions +// will enter a nested message loop in practice, but better safe than sorry. + +TEST_F(DirectManipulationUnitTest, DestroyDuringOnAnimationStep) { + manager_->mock_update_manager()->set_update_callback( + ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->OnAnimationStep(base::TimeTicks::Now()); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringUpdateEventHandler) { + ASSERT_TRUE(GetDirectManipulationHelper()->HasEventHandlerForTesting()); + viewport_->set_remove_event_handler_callback( + ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->UpdateEventHandler(nullptr, nullptr); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); + + RecreateDirectManipulationHelper(); + ASSERT_FALSE(GetDirectManipulationHelper()->HasEventHandlerForTesting()); + viewport_->set_add_event_handler_callback( + ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->UpdateEventHandler(nullptr, &event_target_); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringSetSizeInPixels) { + viewport_->set_stop_callback(ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->SetSizeInPixels(gfx::Size(2000, 2000)); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringOnPointerHitTest) { + ASSERT_TRUE(GetDirectManipulationHelper()->HasEventHandlerForTesting()); + viewport_->set_set_contact_callback(ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->OnPointerHitTest(0, PT_TOUCHPAD); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringDestroy) { + // OnCompositingShuttingDown can call Destroy(), which makes COM calls that + // could enter a nested event loop. Those could process WM_DESTROY messages + // that delete the DirectManipulationHelper, causing the destructor to + // re-enter Destroy(). + viewport_->set_abandon_callback(ResetDirectManipulationHelperCallback()); + GetDirectManipulationHelper()->OnCompositingShuttingDown( + GetDirectManipulationHelper()->compositor()); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringZoomToRect) { + // ZoomToRect is triggered from + // DirectManipulationEventHandler::OnViewportStatusChanged when there's a + // content transform. + ContentUpdated(1.1f, 0, 0); + viewport_->set_zoom_to_rect_callback(ResetDirectManipulationHelperCallback()); + ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + +TEST_F(DirectManipulationUnitTest, DestroyDuringGetContentTransform) { + // GetContentTransform is triggered from + // DirectManipulationEventHandler::OnContentUpdated. + content_->set_get_content_transform_callback( + ResetDirectManipulationHelperCallback()); + ContentUpdated(1.1f, 0, 0); + EXPECT_EQ(GetDirectManipulationHelper(), nullptr); +} + } // namespace content diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/legacy_render_widget_host_win.cc chromium-149.0.7827.114/content/browser/renderer_host/legacy_render_widget_host_win.cc --- chromium-149.0.7827.102/content/browser/renderer_host/legacy_render_widget_host_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/legacy_render_widget_host_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -75,8 +75,15 @@ } void LegacyRenderWidgetHostHWND::Destroy() { - // Delete DirectManipulationHelper before the window is destroyed. + // Delete DirectManipulationHelper before the window is destroyed. The + // helper's destructor makes COM calls that can spin a nested message loop + // that could destroy the window through another path before returning. + base::WeakPtr ref( + msg_handler_weak_factory_.GetWeakPtr()); direct_manipulation_helper_.reset(); + if (!ref) { + return; + } window_tree_host_prop_.reset(); host_ = nullptr; if (::IsWindow(hwnd())) { diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/render_widget_host_impl.cc chromium-149.0.7827.114/content/browser/renderer_host/render_widget_host_impl.cc --- chromium-149.0.7827.102/content/browser/renderer_host/render_widget_host_impl.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/render_widget_host_impl.cc 2026-06-10 17:58:03.000000000 +0000 @@ -2952,6 +2952,33 @@ } } + // Propagate the FilterURL results back into `drag_data` so that the DevTools + // intercept path (Input.dragIntercepted) sees the same filtered values as the + // OS-drag path. Without this, a CDP client following the documented + // dragIntercepted -> dispatchDragEvent round-trip would replay the + // pre-filter renderer-supplied URLs into DragTargetDrop. + for (auto& item : drag_data->items) { + if (!item->is_string()) { + continue; + } + auto& s = item->get_string(); + if (s->string_type == ui::kMimeTypeUriList) { + std::u16string rebuilt; + for (const auto& url_info : filtered_data.url_infos) { + if (!rebuilt.empty()) { + rebuilt.append(u"\r\n"); + } + rebuilt.append(base::UTF8ToUTF16(url_info.url.spec())); + } + s->string_data = std::move(rebuilt); + } else if (s->string_type == ui::kMimeTypeHtml) { + s->base_url = filtered_data.html_base_url; + } else if (s->string_type == ui::kMimeTypeDownloadUrl && + !filtered_data.download_metadata) { + s->string_data.clear(); + } + } + // Filter out any paths that the renderer didn't have access to. This prevents // the following attack on a malicious renderer: // 1. StartDragging IPC sent with renderer-specified filesystem paths that it diff -Nru chromium-149.0.7827.102/content/browser/renderer_host/render_widget_host_view_aura.cc chromium-149.0.7827.114/content/browser/renderer_host/render_widget_host_view_aura.cc --- chromium-149.0.7827.102/content/browser/renderer_host/render_widget_host_view_aura.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/content/browser/renderer_host/render_widget_host_view_aura.cc 2026-06-10 17:58:03.000000000 +0000 @@ -768,7 +768,13 @@ if (host && legacy_render_widget_host_HWND_) { // We reparent the legacy Chrome_RenderWidgetHostHWND window to the // global hidden window on the same lines as Windowed plugin windows. + // This can spin a nested event loop that could potentially delete this. + base::WeakPtr weak_this( + weak_ptr_factory_.GetWeakPtr()); legacy_render_widget_host_HWND_->UpdateParent(ui::GetHiddenWindow()); + if (!weak_this) { + return; + } } #endif } @@ -3261,10 +3267,12 @@ delegated_frame_host_->DetachFromCompositor(); #if BUILDFLAG(IS_WIN) - // Update the legacy window's parent temporarily to the hidden window. It - // will eventually get reparented to the right root. - if (legacy_render_widget_host_HWND_) - legacy_render_widget_host_HWND_->UpdateParent(ui::GetHiddenWindow()); + // Update the legacy window's parent temporarily to the hidden window. It + // will eventually get reparented to the right root. This can spin a nested + // event loop that can delete `this`, so do it last. + if (legacy_render_widget_host_HWND_) { + legacy_render_widget_host_HWND_->UpdateParent(ui::GetHiddenWindow()); + } #endif } diff -Nru chromium-149.0.7827.102/debian/changelog chromium-149.0.7827.114/debian/changelog --- chromium-149.0.7827.102/debian/changelog 2026-06-09 08:00:45.000000000 +0000 +++ chromium-149.0.7827.114/debian/changelog 2026-06-12 21:27:35.000000000 +0000 @@ -1,3 +1,51 @@ +chromium (149.0.7827.114-1~deb13u1) trixie-security; urgency=high + + [ Andres Salomon ] + * New upstream security release. + - CVE-2026-12007: Use after free  Core. Reported by Google. + - CVE-2026-12008: Use after free  DigitalCredentials. Reported by Google. + - CVE-2026-12009: Insufficient validation of untrusted input + Accessibility. Reported by Google. + - CVE-2026-12010: Heap buffer overflow  GPU. Reported by Google. + - CVE-2026-12011: Use after free  WebMIDI. Reported by Google. + - CVE-2026-12012: Use after free  Network. Reported by Google. + - CVE-2026-12013: Use after free  Media. + Reported by Henock Habte, Independent Security Researcher. + - CVE-2026-12014: Use after free  Cast. Reported by Google. + - CVE-2026-12015: Use after free  Autofill. Reported by Google. + - CVE-2026-12016: Insufficient validation of untrusted input  DevTools. + Reported by Google. + - CVE-2026-12017: Insufficient validation of untrusted input + Extensions. Reported by Google. + - CVE-2026-12018: Inappropriate implementation  Mojo. Reported by Google. + - CVE-2026-12019: Out of bounds write  Codecs. Reported by Google. + - CVE-2026-12020: Use after free  Autofill. Reported by Google. + - CVE-2026-12022: Race  Safe Browsing. Reported by Google. + - CVE-2026-12023: Use after free  GPU. Reported by Google. + - CVE-2026-12024: Insufficient policy enforcement  DevTools. + Reported by Google. + - CVE-2026-12025: Insufficient validation of untrusted input  Network. + Reported by Google. + - CVE-2026-12026: Out of bounds read  Video. Reported by Google. + - CVE-2026-12027: Insufficient policy enforcement  Headless. + Reported by Google. + - CVE-2026-12028: Use after free  GPU. Reported by Google. + - CVE-2026-12029: Use after free  Video. Reported by Google. + - CVE-2026-12030: Heap buffer overflow  GPU. Reported by Google. + - CVE-2026-12031: Inappropriate implementation  Views. Reported by Google + - CVE-2026-12032: Inappropriate implementation  Passwords. + Reported by Google. + - CVE-2026-12033: Out of bounds read  VideoCapture. Reported by Google. + - CVE-2026-12034: Insufficient validation of untrusted input  Linux + Toolkit Theming. Reported by Google. + - CVE-2026-12035: Use after free  Views. Reported by Google. + + [ Jianfeng Liu ] + * d/patches/loongarch64/0024-fix-libyuv-lsx.patch: drop due to upstream + reverting to version of libyuv that doesn't have lsx issue. + + -- Andres Salomon Fri, 12 Jun 2026 17:27:35 -0400 + chromium (149.0.7827.102-1~deb13u1) trixie-security; urgency=high [ Andres Salomon ] diff -Nru chromium-149.0.7827.102/debian/patches/loongarch64/0024-fix-libyuv-lsx.patch chromium-149.0.7827.114/debian/patches/loongarch64/0024-fix-libyuv-lsx.patch --- chromium-149.0.7827.102/debian/patches/loongarch64/0024-fix-libyuv-lsx.patch 2026-06-09 08:00:45.000000000 +0000 +++ chromium-149.0.7827.114/debian/patches/loongarch64/0024-fix-libyuv-lsx.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ ---- a/third_party/libyuv/source/row_lasx.cc -+++ b/third_party/libyuv/source/row_lasx.cc -@@ -2013,24 +2013,14 @@ void NV21ToARGBRow_LASX(const uint8_t* s - } - } - --#ifndef RgbConstants --struct RgbConstants { -- uint8_t kRGBToY[4]; -- uint16_t kAddY; -- uint16_t pad; --}; --#define RgbConstants RgbConstants -- - // RGB to JPeg coefficients - // B * 0.1140 coefficient = 29 - // G * 0.5870 coefficient = 150 - // R * 0.2990 coefficient = 77 - // Add 0.5 = 0x80 --static const struct RgbConstants kRgb24JPEGConstants = {{29, 150, 77, 0}, -- 128, -- 0}; -+static const struct RgbConstants kRgb24JPEGConstants = {{29, 150, 77, 0}, {0}, {0}, {128}, {0}}; - --static const struct RgbConstants kRawJPEGConstants = {{77, 150, 29, 0}, 128, 0}; -+static const struct RgbConstants kRawJPEGConstants = {{77, 150, 29, 0}, {0}, {0}, {128}, {0}}; - - // RGB to BT.601 coefficients - // B * 0.1016 coefficient = 25 -@@ -2038,14 +2028,9 @@ static const struct RgbConstants kRawJPE - // R * 0.2578 coefficient = 66 - // Add 16.5 = 0x1080 - --static const struct RgbConstants kRgb24I601Constants = {{25, 129, 66, 0}, -- 0x1080, -- 0}; -- --static const struct RgbConstants kRawI601Constants = {{66, 129, 25, 0}, -- 0x1080, -- 0}; --#endif // RgbConstants -+static const struct ArgbConstants kRgb24I601Constants = {{25, 129, 66, 0}, {0}, {0}, {0x1080}, {0}}; -+ -+static const struct ArgbConstants kRawI601Constants = {{66, 129, 25, 0}, {0}, {0}, {0x1080}, {0}}; - - // ARGB expects first 3 values to contain RGB and 4th value is ignored. - static void ARGBToYMatrixRow_LASX(const uint8_t* src_argb, ---- a/third_party/libyuv/source/row_lsx.cc -+++ b/third_party/libyuv/source/row_lsx.cc -@@ -2798,24 +2798,14 @@ void HalfFloatRow_LSX(const uint16_t* sr - } - } - --#ifndef RgbConstants --struct RgbConstants { -- uint8_t kRGBToY[4]; -- uint16_t kAddY; -- uint16_t pad; --}; --#define RgbConstants RgbConstants -- - // RGB to JPeg coefficients - // B * 0.1140 coefficient = 29 - // G * 0.5870 coefficient = 150 - // R * 0.2990 coefficient = 77 - // Add 0.5 = 0x80 --static const struct RgbConstants kRgb24JPEGConstants = {{29, 150, 77, 0}, -- 128, -- 0}; -+static const struct RgbConstants kRgb24JPEGConstants = {{29, 150, 77, 0}, {0}, {0}, {128}, {0}}; - --static const struct RgbConstants kRawJPEGConstants = {{77, 150, 29, 0}, 128, 0}; -+static const struct RgbConstants kRawJPEGConstants = {{77, 150, 29, 0}, {0}, {0}, {128}, {0}}; - - // RGB to BT.601 coefficients - // B * 0.1016 coefficient = 25 -@@ -2823,14 +2813,9 @@ static const struct RgbConstants kRawJPE - // R * 0.2578 coefficient = 66 - // Add 16.5 = 0x1080 - --static const struct RgbConstants kRgb24I601Constants = {{25, 129, 66, 0}, -- 0x1080, -- 0}; -- --static const struct RgbConstants kRawI601Constants = {{66, 129, 25, 0}, -- 0x1080, -- 0}; --#endif // RgbConstants -+static const struct ArgbConstants kRgb24I601Constants = {{25, 129, 66, 0}, {0}, {0}, {0x1080}, {0}}; -+ -+static const struct ArgbConstants kRawI601Constants = {{66, 129, 25, 0}, {0}, {0}, {0x1080}, {0}}; - - // ARGB expects first 3 values to contain RGB and 4th value is ignored. - static void ARGBToYMatrixRow_LSX(const uint8_t* src_argb, diff -Nru chromium-149.0.7827.102/debian/patches/series chromium-149.0.7827.114/debian/patches/series --- chromium-149.0.7827.102/debian/patches/series 2026-06-09 08:00:45.000000000 +0000 +++ chromium-149.0.7827.114/debian/patches/series 2026-06-12 21:27:35.000000000 +0000 @@ -228,4 +228,3 @@ loongarch64/0021-components-metrics-add-loongarch64-support.patch loongarch64/0022-blink-fix-lsx-code-of-webgl_image_conversion_lsx.patch loongarch64/0023-loongarch64-enable-lsx-by-default.patch -loongarch64/0024-fix-libyuv-lsx.patch diff -Nru chromium-149.0.7827.102/extensions/browser/extension_function_dispatcher.cc chromium-149.0.7827.114/extensions/browser/extension_function_dispatcher.cc --- chromium-149.0.7827.102/extensions/browser/extension_function_dispatcher.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/extensions/browser/extension_function_dispatcher.cc 2026-06-10 17:58:03.000000000 +0000 @@ -278,6 +278,20 @@ const GURL* render_frame_host_url = nullptr; if (render_frame_host) { + // Error pages commit with the target URL and in their parents' process, but + // aren't actually on the page indicated by the committed URL. + // Bail out in this case. Error pages don't use extension APIs. + // If we had perfect timing and no race conditions, this could be a sign of + // a bad message; however, it's possible a page commits to an error page + // after a legitimate message is sent. + if (render_frame_host->IsErrorDocument()) { + constexpr char kCannotUseExtensionAPIsInErrorPages[] = + "Cannot call extension APIs from error pages."; + ResponseCallbackOnError(std::move(callback), + ExtensionFunction::ResponseType::kFailed, + kCannotUseExtensionAPIsInErrorPages); + return; + } render_frame_host_url = &render_frame_host->GetLastCommittedURL(); DCHECK_EQ(render_process_id, render_frame_host->GetProcess()->GetDeprecatedID()); diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/framebuffer_manager.cc chromium-149.0.7827.114/gpu/command_buffer/service/framebuffer_manager.cc --- chromium-149.0.7827.102/gpu/command_buffer/service/framebuffer_manager.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/framebuffer_manager.cc 2026-06-10 17:58:03.000000000 +0000 @@ -697,6 +697,13 @@ : false; } +bool Framebuffer::GetReadBufferIsMultisampledRenderbuffer() const { + const Attachment* attachment = GetReadBufferAttachment(); + return attachment ? attachment->IsRenderbufferAttachment() && + attachment->samples() > 1 + : false; +} + GLsizei Framebuffer::GetSamples() const { // Assume the framebuffer is complete, so return any attachment's samples. auto iter = attachments_.begin(); diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/framebuffer_manager.h chromium-149.0.7827.114/gpu/command_buffer/service/framebuffer_manager.h --- chromium-149.0.7827.102/gpu/command_buffer/service/framebuffer_manager.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/framebuffer_manager.h 2026-06-10 17:58:03.000000000 +0000 @@ -174,6 +174,7 @@ // returns 0. GLenum GetReadBufferTextureType() const; bool GetReadBufferIsMultisampledTexture() const; + bool GetReadBufferIsMultisampledRenderbuffer() const; // Verify all the rules in OpenGL ES 2.0.25 4.4.5 are followed. // Returns GL_FRAMEBUFFER_COMPLETE if there are no reasons we know we can't diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/gles2_cmd_decoder.cc chromium-149.0.7827.114/gpu/command_buffer/service/gles2_cmd_decoder.cc --- chromium-149.0.7827.102/gpu/command_buffer/service/gles2_cmd_decoder.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/gles2_cmd_decoder.cc 2026-06-10 17:58:03.000000000 +0000 @@ -933,6 +933,7 @@ // If the color image is a renderbuffer, returns 0 for type. GLenum GetBoundReadFramebufferTextureType(); GLenum GetBoundReadFramebufferInternalFormat(); + bool IsBoundReadFramebufferMultisampledRenderbuffer(); // Get the i-th draw buffer's internal format/type from the bound framebuffer. // If no framebuffer is bound, or no image is attached, or the DrawBuffers @@ -4235,6 +4236,18 @@ } } +bool GLES2DecoderImpl::IsBoundReadFramebufferMultisampledRenderbuffer() { + Framebuffer* read_framebuffer = GetBoundReadFramebuffer(); + // We check if the read framebuffer is a multisampled renderbuffer. + // We explicitly do NOT block multisampled texture attachments (with target + // GL_TEXTURE_2D, etc.) because they are implicit resolve textures (allocated + // via FramebufferTexture2DMultisampleEXT) and are safe to copy from (they are + // resolved on the fly). True multisampled textures + // (GL_TEXTURE_2D_MULTISAMPLE) are not supported by the validating decoder. + return read_framebuffer && + read_framebuffer->GetReadBufferIsMultisampledRenderbuffer(); +} + GLenum GLES2DecoderImpl::GetBoundColorDrawBufferType(GLint drawbuffer_i) { DCHECK(drawbuffer_i >= 0 && drawbuffer_i < static_cast(group_->max_draw_buffers())); @@ -13475,6 +13488,12 @@ return; } + if (IsBoundReadFramebufferMultisampledRenderbuffer()) { + LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, + "cannot copy from a multisampled framebuffer"); + return; + } + GLenum read_format = GetBoundReadFramebufferInternalFormat(); GLenum read_type = GetBoundReadFramebufferTextureType(); if (!ValidateCopyTexFormat(func_name, internal_format, @@ -13727,6 +13746,12 @@ return; } + if (IsBoundReadFramebufferMultisampledRenderbuffer()) { + LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, + "cannot copy from a multisampled framebuffer"); + return; + } + GLenum read_format = GetBoundReadFramebufferInternalFormat(); GLenum read_type = GetBoundReadFramebufferTextureType(); if (!ValidateCopyTexFormat(func_name, internal_format, @@ -13858,6 +13883,12 @@ return; } + if (IsBoundReadFramebufferMultisampledRenderbuffer()) { + LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, + "cannot copy from a multisampled framebuffer"); + return; + } + GLenum read_format = GetBoundReadFramebufferInternalFormat(); GLenum read_type = GetBoundReadFramebufferTextureType(); if (!ValidateCopyTexFormat(func_name, internal_format, diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc chromium-149.0.7827.114/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc --- chromium-149.0.7827.102/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc 2026-06-10 17:58:03.000000000 +0000 @@ -1644,10 +1644,15 @@ return; } - // SECURITY: crbug.com/500187083. Unconditionally clear the debug callback if - // current context IsCurrent before it gets lost to prevent UAF. + // SECURITY: crbug.com/500187083 and crbug.com/517018374. + // Unconditionally clear per-context callbacks that hold a raw + // `this` pointer if the context IsCurrent before it gets lost, to + // prevent UAF when Destroy(have_context=false) skips them. if (context_ && context_->IsCurrent(nullptr) && api()) { api()->glDebugMessageCallbackKHRFn(nullptr, nullptr); + if (feature_info_ && feature_info_->feature_flags().angle_blob_cache) { + api()->glBlobCacheCallbacksANGLEFn(nullptr, nullptr, nullptr); + } } // Don't make GL calls in here, the context might not be current. diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc chromium-149.0.7827.114/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc --- chromium-149.0.7827.102/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc 2026-06-10 17:58:03.000000000 +0000 @@ -21,6 +21,7 @@ #include "base/debug/dump_without_crashing.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_refptr.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" @@ -67,7 +68,7 @@ namespace gpu { namespace { -class OverlayImage final : public base::RefCounted { +class OverlayImage final : public base::RefCountedThreadSafe { public: explicit OverlayImage(AHardwareBuffer* buffer) : handle_(base::android::ScopedHardwareBufferHandle::Create(buffer)) {} @@ -86,7 +87,7 @@ } private: - friend class base::RefCounted; + friend class base::RefCountedThreadSafe; class ScopedHardwareBufferFenceSyncImpl : public base::android::ScopedHardwareBufferFenceSync { diff -Nru chromium-149.0.7827.102/gpu/command_buffer/service/shared_image/gl_texture_holder.cc chromium-149.0.7827.114/gpu/command_buffer/service/shared_image/gl_texture_holder.cc --- chromium-149.0.7827.102/gpu/command_buffer/service/shared_image/gl_texture_holder.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/service/shared_image/gl_texture_holder.cc 2026-06-10 17:58:03.000000000 +0000 @@ -160,22 +160,19 @@ if (is_passthrough_) { passthrough_texture_->SetEstimatedSize(format_.EstimatedSizeInBytes(size_)); - } else { - // TODO(piman): We pretend the texture was created in an ES2 context, so - // that it can be used in other ES2 contexts, and so we have to pass - // gl_format as the internal format in the LevelInfo. - // https://crbug.com/628064 - texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format, - size_.width(), size_.height(), /*depth=*/1, 0, - format_desc_.data_format, format_desc_.data_type, - /*cleared_rect=*/gfx::Rect()); - texture_->SetImmutable(true, format_info.supports_storage); } gl::GLApi* api = gl::g_current_gl_context; gl::ScopedRestoreTexture scoped_restore(api, format_desc_.target, GetServiceId()); + // Drain any pre-existing GL errors so the post-allocation check + // below is attributable to the storage call. Silently squelching + // these errors is unfortunate, but is done in order to mirror other + // allocation checks done in the command decoder. + while (api->glGetErrorFn() != GL_NO_ERROR) { + } + // Initialize the texture storage/image parameters and upload initial pixels // if available. if (format_info.supports_storage) { @@ -224,6 +221,25 @@ } if (!is_passthrough_) { + // Only commit decoder-side LevelInfo / immutable state once the native + // allocation has succeeded. If the driver rejected the allocation (e.g. + // GL_OUT_OF_MEMORY), leaving LevelInfo at {0,0,0} ensures + // Texture::ValidForTexture rejects subsequent TexSubImage calls instead of + // forwarding oversized writes to a zero-storage native texture. This + // mirrors the fix in GLES2DecoderImpl::TexStorageImpl. + if (api->glGetErrorFn() == GL_NO_ERROR) { + // TODO(piman): We pretend the texture was created in an ES2 context, so + // that it can be used in other ES2 contexts, and so we have to pass + // gl_format as the internal format in the LevelInfo. + // https://crbug.com/628064 + texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format, + size_.width(), size_.height(), /*depth=*/1, 0, + format_desc_.data_format, format_desc_.data_type, + /*cleared_rect=*/gfx::Rect()); + texture_->SetImmutable(true, format_info.supports_storage); + } else { + LOG(ERROR) << "GLTextureHolder: native storage allocation failed"; + } // Must be set after initial pixel upload. texture_->SetCompatibilitySwizzle(format_info.swizzle); } diff -Nru chromium-149.0.7827.102/gpu/command_buffer/tests/gl_unittest.cc chromium-149.0.7827.114/gpu/command_buffer/tests/gl_unittest.cc --- chromium-149.0.7827.102/gpu/command_buffer/tests/gl_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/command_buffer/tests/gl_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -131,11 +132,7 @@ reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION))); } -// TODO(crbug.com/513543143): Goldfish GLES emulator driver on 32-bit x86 -// Android bots has a known driver bug where it incorrectly rejects -// glCopyTexImage2D on cubemaps with GL_INVALID_ENUM. -#if BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER) && \ - !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)) +#if BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER) class GL3Test : public GLTest { protected: void SetUp() override { @@ -155,7 +152,12 @@ // instead of the specific face target (e.g., GL_TEXTURE_CUBE_MAP_POSITIVE_X) // which would cause driver-side GL_INVALID_ENUM errors and result in a state // desynchronization between the decoder and the GPU driver. -TEST_F(GL3Test, CopyTexImage2DWorkaroundStateDesync) { +// +// TODO(crbug.com/513543143): Goldfish GLES emulator driver on 32-bit x86 +// Android bots has a known driver bug where it incorrectly rejects +// glCopyTexImage2D on cubemaps with GL_INVALID_ENUM. +#if !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)) +TEST_F(GL3Test, CopyTexImage2DCubeMapStateDesync) { GLuint tex = 0; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_CUBE_MAP, tex); @@ -202,6 +204,208 @@ glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &tex); } -#endif +#endif // !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)) + +class GL3MultisampleCopyTexImageTest : public GL3Test { + protected: + void SetUp() override { GL3Test::SetUp(); } + + void TearDown() override { + if (sample_fbo_) { + glDeleteFramebuffers(1, &sample_fbo_); + } + if (sample_tex_) { + glDeleteTextures(1, &sample_tex_); + } + if (sample_rb_) { + glDeleteRenderbuffers(1, &sample_rb_); + } + if (dest_tex_) { + glDeleteTextures(1, &dest_tex_); + } + GL3Test::TearDown(); + } + + void SetUpImplicitResolveTextureFBO() { + glGenTextures(1, &sample_tex_); + glBindTexture(GL_TEXTURE_2D, sample_tex_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + glGenFramebuffers(1, &sample_fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_); + glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, sample_tex_, 0, 4); + EXPECT_EQ(static_cast(GL_FRAMEBUFFER_COMPLETE), + glCheckFramebufferStatus(GL_FRAMEBUFFER)); + GLTestHelper::CheckGLError("SetUpImplicitResolveTextureFBO", __LINE__); + } + + void SetUpMultisampleRenderbufferFBO() { + glGenRenderbuffers(1, &sample_rb_); + glBindRenderbuffer(GL_RENDERBUFFER, sample_rb_); + glRenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, 4, GL_RGBA8, 16, + 16); + glGenFramebuffers(1, &sample_fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, sample_rb_); + EXPECT_EQ(static_cast(GL_FRAMEBUFFER_COMPLETE), + glCheckFramebufferStatus(GL_FRAMEBUFFER)); + GLTestHelper::CheckGLError("SetUpMultisampleRenderbufferFBO", __LINE__); + } + + void SetUpImplicitResolveRenderbufferFBO() { + glGenRenderbuffers(1, &sample_rb_); + glBindRenderbuffer(GL_RENDERBUFFER, sample_rb_); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_RGBA8, 16, 16); + glGenFramebuffers(1, &sample_fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, sample_rb_); + EXPECT_EQ(static_cast(GL_FRAMEBUFFER_COMPLETE), + glCheckFramebufferStatus(GL_FRAMEBUFFER)); + GLTestHelper::CheckGLError("SetUpImplicitResolveRenderbufferFBO", __LINE__); + } + + void SetUpDestTexture2D() { + glGenTextures(1, &dest_tex_); + glBindTexture(GL_TEXTURE_2D, dest_tex_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + GLTestHelper::CheckGLError("SetUpDestTexture2D", __LINE__); + } + + void SetUpDestTexture3D() { + glGenTextures(1, &dest_tex_); + glBindTexture(GL_TEXTURE_3D, dest_tex_); + glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 16, 16, 16, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + GLTestHelper::CheckGLError("SetUpDestTexture3D", __LINE__); + } + + void VerifyCopySucceeded(GLenum dest_target, + int expected_width, + int expected_height) { + EXPECT_EQ(static_cast(GL_NO_ERROR), glGetError()); + VerifyTextureSize(dest_target, expected_width, expected_height); + } + + void VerifyCopyBlocked(GLenum dest_target) { + EXPECT_EQ(static_cast(GL_INVALID_OPERATION), glGetError()); + VerifyTextureSize(dest_target, 16, 16); + } + + void VerifyTextureSize(GLenum target, + int expected_width, + int expected_height) { + int tracked_width = 0; + int tracked_height = 0; + bool defined = InspectTextureLevelSize(&gl_, dest_tex_, target, 0, + &tracked_width, &tracked_height); + EXPECT_TRUE(defined); + EXPECT_EQ(expected_width, tracked_width); + EXPECT_EQ(expected_height, tracked_height); + } + + GLuint sample_fbo_ = 0; + GLuint sample_tex_ = 0; + GLuint sample_rb_ = 0; + GLuint dest_tex_ = 0; +}; + +// Implicit Resolve Texture +TEST_F(GL3MultisampleCopyTexImageTest, CopyTexImage2DImplicitResolveTexture) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveTextureFBO(); + SetUpDestTexture2D(); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0); + VerifyCopySucceeded(GL_TEXTURE_2D, 2, 2); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage2DImplicitResolveTexture) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveTextureFBO(); + SetUpDestTexture2D(); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2); + VerifyCopySucceeded(GL_TEXTURE_2D, 16, 16); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage3DImplicitResolveTexture) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveTextureFBO(); + SetUpDestTexture3D(); + glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2); + VerifyCopySucceeded(GL_TEXTURE_3D, 16, 16); +} + +// Multisample Renderbuffer +TEST_F(GL3MultisampleCopyTexImageTest, CopyTexImage2DMultisampleRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) { + GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported"; + } + SetUpMultisampleRenderbufferFBO(); + SetUpDestTexture2D(); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0); + VerifyCopyBlocked(GL_TEXTURE_2D); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage2DMultisampleRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) { + GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported"; + } + SetUpMultisampleRenderbufferFBO(); + SetUpDestTexture2D(); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2); + VerifyCopyBlocked(GL_TEXTURE_2D); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage3DMultisampleRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) { + GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported"; + } + SetUpMultisampleRenderbufferFBO(); + SetUpDestTexture3D(); + glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2); + VerifyCopyBlocked(GL_TEXTURE_3D); +} + +// Implicit Resolve Renderbuffer +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexImage2DImplicitResolveRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveRenderbufferFBO(); + SetUpDestTexture2D(); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0); + VerifyCopyBlocked(GL_TEXTURE_2D); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage2DImplicitResolveRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveRenderbufferFBO(); + SetUpDestTexture2D(); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2); + VerifyCopyBlocked(GL_TEXTURE_2D); +} +TEST_F(GL3MultisampleCopyTexImageTest, + CopyTexSubImage3DImplicitResolveRenderbuffer) { + if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) { + GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported"; + } + SetUpImplicitResolveRenderbufferFBO(); + SetUpDestTexture3D(); + glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2); + VerifyCopyBlocked(GL_TEXTURE_3D); +} +#endif // BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER) } // namespace gpu diff -Nru chromium-149.0.7827.102/gpu/config/gpu_lists_version.h chromium-149.0.7827.114/gpu/config/gpu_lists_version.h --- chromium-149.0.7827.102/gpu/config/gpu_lists_version.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/gpu/config/gpu_lists_version.h 2026-06-10 17:58:03.000000000 +0000 @@ -3,6 +3,6 @@ #ifndef GPU_CONFIG_GPU_LISTS_VERSION_H_ #define GPU_CONFIG_GPU_LISTS_VERSION_H_ -#define GPU_LISTS_VERSION "112f665d98a2fe84b156c74fbea2aed742f16c15" +#define GPU_LISTS_VERSION "5be7af702aa73ed64f47858cecc86290e42f2a20" #endif // GPU_CONFIG_GPU_LISTS_VERSION_H_ diff -Nru chromium-149.0.7827.102/headless/lib/browser/protocol/target_handler.cc chromium-149.0.7827.114/headless/lib/browser/protocol/target_handler.cc --- chromium-149.0.7827.102/headless/lib/browser/protocol/target_handler.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/headless/lib/browser/protocol/target_handler.cc 2026-06-10 17:58:03.000000000 +0000 @@ -8,6 +8,9 @@ #include #include "build/build_config.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" #include "headless/lib/browser/headless_browser_context_impl.h" #include "headless/lib/browser/headless_browser_impl.h" #include "headless/lib/browser/headless_web_contents_impl.h" @@ -110,6 +113,15 @@ HeadlessWebContentsImpl* web_contents_impl = HeadlessWebContentsImpl::From( context->CreateWebContentsBuilder().SetInitialURL(gurl).Build()); + // Mark the process used so IsSuitableHost() rejects it for sites that + // require a dedicated process. (Mirrors content::HiddenTargetManager, which + // the headless embedder layer bypasses by handling Target.createTarget + // itself.) + web_contents_impl->web_contents() + ->GetPrimaryMainFrame() + ->GetProcess() + ->SetIsUsed(); + *out_target_id = content::DevToolsAgentHost::GetOrCreateFor( web_contents_impl->web_contents()) ->GetId(); diff -Nru chromium-149.0.7827.102/headless/test/headless_devtooled_browsertest.cc chromium-149.0.7827.114/headless/test/headless_devtooled_browsertest.cc --- chromium-149.0.7827.102/headless/test/headless_devtooled_browsertest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/headless/test/headless_devtooled_browsertest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -11,6 +11,8 @@ #include "base/strings/stringprintf.h" #include "base/values.h" #include "build/build_config.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" @@ -170,4 +172,52 @@ #endif // #if !BUILDFLAG(IS_FUCHSIA) +class HeadlessCreatedTargetIsUsedProcessTest + : public HeadlessDevTooledBrowserTest, + public testing::WithParamInterface { + public: + HeadlessCreatedTargetIsUsedProcessTest() = default; + + bool IsHiddenTarget() const { return GetParam(); } + + private: + void RunDevTooledTest() override { + base::DictValue params; + params.Set("url", ""); + params.Set("hidden", IsHiddenTarget()); + browser_devtools_client_.SendCommand( + "Target.createTarget", std::move(params), + base::BindOnce(&HeadlessCreatedTargetIsUsedProcessTest::OnTargetCreated, + base::Unretained(this))); + } + + void OnTargetCreated(base::DictValue result) { + std::string target_id = DictString(result, "result.targetId"); + ASSERT_FALSE(target_id.empty()); + + scoped_refptr agent_host = + content::DevToolsAgentHost::GetForId(target_id); + ASSERT_TRUE(agent_host); + + content::WebContents* web_contents = agent_host->GetWebContents(); + ASSERT_TRUE(web_contents); + + bool is_used = + !web_contents->GetPrimaryMainFrame()->GetProcess()->IsUnused(); + EXPECT_EQ(is_used, IsHiddenTarget()); + + FinishAsynchronousTest(); + } +}; + +INSTANTIATE_TEST_SUITE_P( + /* no prefix */, + HeadlessCreatedTargetIsUsedProcessTest, + testing::Bool(), + [](const testing::TestParamInfo& info) { + return info.param ? "hidden" : "normal"; + }); + +HEADLESS_DEVTOOLED_TEST_P(HeadlessCreatedTargetIsUsedProcessTest); + } // namespace headless diff -Nru chromium-149.0.7827.102/media/base/decoder_buffer.cc chromium-149.0.7827.114/media/base/decoder_buffer.cc --- chromium-149.0.7827.102/media/base/decoder_buffer.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/base/decoder_buffer.cc 2026-06-10 17:58:03.000000000 +0000 @@ -172,7 +172,7 @@ } void DecoderBuffer::set_discard_padding(const DiscardPadding& discard_padding) { - DCHECK(!end_of_stream()); + CHECK(!end_of_stream()); if (!side_data_ && discard_padding == DiscardPadding()) { return; } @@ -180,7 +180,7 @@ } DecoderBufferSideData& DecoderBuffer::WritableSideData() { - DCHECK(!end_of_stream()); + CHECK(!end_of_stream()); if (!side_data()) { side_data_ = std::make_unique(); } @@ -189,7 +189,7 @@ void DecoderBuffer::set_side_data( std::unique_ptr side_data) { - DCHECK(!end_of_stream()); + CHECK(!end_of_stream()); side_data_ = std::move(side_data); } @@ -199,7 +199,7 @@ return false; } - // Note: We use `side_data_` directly to avoid DCHECKs for EOS buffers. + // Note: We use `side_data_` directly to avoid CHECKs for EOS buffers. if (side_data_ && !side_data_->Matches(*buffer.side_data_)) { return false; } @@ -229,7 +229,7 @@ if (end_of_stream()) return true; - DCHECK(!buffer.end_of_stream()); + CHECK(!buffer.end_of_stream()); return base::span(*this) == base::span(buffer); } @@ -275,7 +275,7 @@ } void DecoderBuffer::set_timestamp(base::TimeDelta timestamp) { - DCHECK(!end_of_stream()); + CHECK(!end_of_stream()); timestamp_ = timestamp; } diff -Nru chromium-149.0.7827.102/media/base/win/mf_helpers.cc chromium-149.0.7827.114/media/base/win/mf_helpers.cc --- chromium-149.0.7827.102/media/base/win/mf_helpers.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/base/win/mf_helpers.cc 2026-06-10 17:58:03.000000000 +0000 @@ -1083,8 +1083,7 @@ } auto shared_context_state = shared_image_stub->shared_context_state(); - const bool needs_gl = !shared_context_state->IsGraphiteDawn(); - if (!shared_context_state->MakeCurrent(nullptr, needs_gl)) { + if (!shared_context_state->MakeCurrent(nullptr, /*needs_gl=*/true)) { RETURN_ON_FAILURE_WITH_CALLBACK(E_FAIL, "Failed to make context current"); } diff -Nru chromium-149.0.7827.102/media/gpu/chromeos/decoder_buffer_transcryptor.cc chromium-149.0.7827.114/media/gpu/chromeos/decoder_buffer_transcryptor.cc --- chromium-149.0.7827.102/media/gpu/chromeos/decoder_buffer_transcryptor.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/gpu/chromeos/decoder_buffer_transcryptor.cc 2026-06-10 17:58:03.000000000 +0000 @@ -113,6 +113,17 @@ if (needs_vp9_superframe_splitting_ && Vp9Parser::IsSuperframe(curr_buffer_span, curr_buffer->decrypt_config())) { + const bool secure_handle_already_attached = + curr_buffer->side_data() && curr_buffer->side_data()->secure_handle; + if (secure_handle_already_attached) { + // This should not happen. If a secure handle is already attached, this + // buffer must be a previously split sub-frame. If it still parses as a + // superframe, it is invalid or a maliciously crafted nested superframe. + LOG(ERROR) + << "Invalid Vp9 superframe detected (already has secure handle)"; + OnBufferTranscrypted(Decryptor::kError, nullptr); + return; + } base::circular_deque frames = Vp9Parser::ExtractFrames(curr_buffer_span, curr_buffer->decrypt_config()); diff -Nru chromium-149.0.7827.102/media/gpu/v4l2/v4l2_image_processor_backend.cc chromium-149.0.7827.114/media/gpu/v4l2/v4l2_image_processor_backend.cc --- chromium-149.0.7827.102/media/gpu/v4l2/v4l2_image_processor_backend.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/gpu/v4l2/v4l2_image_processor_backend.cc 2026-06-10 17:58:03.000000000 +0000 @@ -140,6 +140,7 @@ base::SingleThreadTaskRunnerThreadMode::DEDICATED)) { DVLOGF(2); DETACH_FROM_SEQUENCE(poll_sequence_checker_); + CHECK_EQ(input_memory_type_, V4L2_MEMORY_DMABUF); DCHECK_NE(output_memory_type_, V4L2_MEMORY_USERPTR); VLOGF(2) << "V4L2ImageProcessorBackend constructed with input: " @@ -216,14 +217,12 @@ v4l2_memory InputStorageTypeToV4L2Memory(VideoFrame::StorageType storage_type) { switch (storage_type) { - case VideoFrame::STORAGE_OWNED_MEMORY: - case VideoFrame::STORAGE_UNOWNED_MEMORY: - case VideoFrame::STORAGE_SHMEM: - return V4L2_MEMORY_USERPTR; case VideoFrame::STORAGE_DMABUFS: case VideoFrame::STORAGE_MAPPABLE_SHARED_IMAGE: return V4L2_MEMORY_DMABUF; default: + VLOGF(2) << "Unsupported storage type:" + << VideoFrame::StorageTypeToString(storage_type); return static_cast(0); } } @@ -261,8 +260,7 @@ const v4l2_memory input_memory_type = InputStorageTypeToV4L2Memory(input_config.storage_type); - if (input_memory_type != V4L2_MEMORY_USERPTR && - input_memory_type != V4L2_MEMORY_DMABUF) { + if (input_memory_type != V4L2_MEMORY_DMABUF) { VLOGF(2) << "Unsupported input storage type"; return nullptr; } @@ -875,25 +873,6 @@ DCHECK(input_queue_); switch (input_memory_type_) { - case V4L2_MEMORY_USERPTR: { - const size_t num_planes = - GetNumPlanesOfV4L2PixFmt(input_config_.fourcc.ToV4L2PixFmt()); - std::vector user_ptrs(num_planes); - for (size_t i = 0; i < num_planes; ++i) { - int bytes_used = - VideoFrame::PlaneSize(job_record->input_frame->format(), i, - input_config_.size) - .GetArea(); - buffer.SetPlaneBytesUsed(i, bytes_used); - user_ptrs[i] = const_cast(job_record->input_frame->data(i)); - } - if (!std::move(buffer).QueueUserPtr(user_ptrs)) { - VPLOGF(1) << "Failed to queue a DMABUF buffer to input queue"; - NotifyError(); - return false; - } - break; - } case V4L2_MEMORY_DMABUF: { auto input_handle = CreateHandle(job_record->input_frame.get()); if (!input_handle) { diff -Nru chromium-149.0.7827.102/media/gpu/vaapi/vaapi_video_encode_accelerator.cc chromium-149.0.7827.114/media/gpu/vaapi/vaapi_video_encode_accelerator.cc --- chromium-149.0.7827.102/media/gpu/vaapi/vaapi_video_encode_accelerator.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/gpu/vaapi/vaapi_video_encode_accelerator.cc 2026-06-10 17:58:03.000000000 +0000 @@ -657,6 +657,12 @@ spatial_layer_resolutions.size()); auto source_rect = frame.visible_rect(); for (const gfx::Size& encode_size : spatial_layer_resolutions) { + if (encode_size.width() > source_rect.width() || + encode_size.height() > source_rect.height()) { + NotifyError({EncoderStatus::Codes::kInvalidInputFrame, + "Only down scaling is supported in spatial layer encoding"}); + return false; + } const bool engage_vpp = source_rect != gfx::Rect(encode_size); // Crop and scale |source_surface| to a surface whose size is |encode_size|. // The size of a reconstructed surface is also |encode_size|. diff -Nru chromium-149.0.7827.102/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc chromium-149.0.7827.114/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc --- chromium-149.0.7827.102/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc 2026-06-10 17:58:03.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include "base/bits.h" @@ -261,6 +262,13 @@ return false; } for (const auto& spatial_layer : config.spatial_layers) { + // Only down scaling is supported in spatial layer encoding. + if (spatial_layer.width > visible_size_.width() || + spatial_layer.height > visible_size_.height()) { + VLOGF(1) << "Spatial layer resolution is larger than visible size"; + return false; + } + spatial_layer_resolutions.emplace_back( gfx::Size(spatial_layer.width, spatial_layer.height)); } diff -Nru chromium-149.0.7827.102/media/gpu/windows/d3d12_video_encode_accelerator.cc chromium-149.0.7827.114/media/gpu/windows/d3d12_video_encode_accelerator.cc --- chromium-149.0.7827.102/media/gpu/windows/d3d12_video_encode_accelerator.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/gpu/windows/d3d12_video_encode_accelerator.cc 2026-06-10 17:58:03.000000000 +0000 @@ -135,19 +135,16 @@ scoped_refptr command_buffer_helper, FrameAvailableCB frame_available_cb) { // ProduceVideo may go through GLTextureImageBacking which uses GL calls - // (e.g. glGenTextures), so a GL context must be current. When Graphite/Dawn - // is in use, the D3D shared image is consumed via the Dawn path instead, so - // forcing a GL MakeCurrent is unnecessary (and risks losing the shared - // context if the GL context is unavailable). + // (e.g. glGenTextures), so a GL context must be current to avoid operating + // on a stale foreign context left current by a prior scheduler task. auto* shared_image_stub = command_buffer_helper->GetSharedImageStub(); if (!shared_image_stub || !shared_image_stub->shared_context_state()) { RETURN_ON_FAILURE_WITH_CALLBACK(E_FAIL, "Failed to get shared context state"); } - const bool needs_gl = - !shared_image_stub->shared_context_state()->IsGraphiteDawn(); - if (!shared_image_stub->shared_context_state()->MakeCurrent(nullptr, - needs_gl)) { + if (!shared_image_stub->shared_context_state()->MakeCurrent( + nullptr, + /*needs_gl=*/true)) { RETURN_ON_FAILURE_WITH_CALLBACK(E_FAIL, "Failed to make context current"); } @@ -416,6 +413,14 @@ VLOGF(2); DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_); + // Wait for any in-flight copy to complete before the members are destroyed, + // so that the resources backing the copy (e.g. `upload_buffer_`, + // `input_texture_`) are not released while the GPU may still be reading from + // or writing to them. + if (copy_command_queue_) { + copy_command_queue_->WaitSync(); + } + if (!error_occurred_ && encoded_at_least_one_frame_) { base::UmaHistogramEnumeration( GetEncoderStatusHistogramName(config_.output_profile), diff -Nru chromium-149.0.7827.102/media/midi/midi_manager_winrt.cc chromium-149.0.7827.114/media/midi/midi_manager_winrt.cc --- chromium-149.0.7827.102/media/midi/midi_manager_winrt.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/midi/midi_manager_winrt.cc 2026-06-10 17:58:03.000000000 +0000 @@ -25,6 +25,7 @@ #include #include +#include #include "base/check.h" #include "base/containers/heap_array.h" @@ -219,11 +220,17 @@ class MidiManagerWinrt::MidiPortManager { public: // MidiPortManager instances should be constructed on the kComTaskRunner. - MidiPortManager(MidiManagerWinrt* midi_manager) - : midi_service_(midi_manager->service()), midi_manager_(midi_manager) {} + MidiPortManager(MidiManagerWinrt* midi_manager, + TaskService::InstanceId instance_id) + : midi_service_(midi_manager->service()), + midi_manager_(midi_manager), + instance_id_(instance_id) {} virtual ~MidiPortManager() { DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner)); + for (auto* async_op : async_ops_) { + async_op->Release(); + } } bool StartWatcher() { @@ -267,12 +274,13 @@ // we can handle raw pointers safely in the following blocks. MidiPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; hr = watcher_->add_Added( WRL::Callback>( - [port_manager, task_service](IDeviceWatcher* watcher, - IDeviceInformation* info) { + [port_manager, task_service, instance_id]( + IDeviceWatcher* watcher, IDeviceInformation* info) { if (!info) { VLOG(1) << "DeviceWatcher.Added callback provides null " "pointer, ignoring"; @@ -284,13 +292,14 @@ if (IsMicrosoftSynthesizer(info)) return S_OK; - std::string dev_id = GetIdString(info), - dev_name = GetNameString(info); + const std::string dev_id = GetIdString(info); + const std::string dev_name = GetNameString(info); task_service->PostBoundTask( - kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded, - base::Unretained(port_manager), - dev_id, dev_name)); + instance_id, kComTaskRunner, + base::BindOnce(&MidiPortManager::OnAdded, + base::Unretained(port_manager), dev_id, + dev_name)); return S_OK; }) @@ -303,10 +312,10 @@ hr = watcher_->add_EnumerationCompleted( WRL::Callback>( - [port_manager, task_service](IDeviceWatcher* watcher, - IInspectable* insp) { + [port_manager, task_service, instance_id](IDeviceWatcher* watcher, + IInspectable* insp) { task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce(&MidiPortManager::OnEnumerationCompleted, base::Unretained(port_manager))); @@ -322,8 +331,8 @@ hr = watcher_->add_Removed( WRL::Callback< ITypedEventHandler>( - [port_manager, task_service](IDeviceWatcher* watcher, - IDeviceInformationUpdate* update) { + [port_manager, task_service, instance_id]( + IDeviceWatcher* watcher, IDeviceInformationUpdate* update) { if (!update) { VLOG(1) << "DeviceWatcher.Removed callback provides null " "pointer, ignoring"; @@ -333,7 +342,7 @@ std::string dev_id = GetIdString(update); task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce(&MidiPortManager::OnRemoved, base::Unretained(port_manager), dev_id)); @@ -451,6 +460,8 @@ // from tasks that are invoked by TaskService. raw_ptr midi_manager_; + const TaskService::InstanceId instance_id_; + private: // DeviceWatcher callbacks: void OnAdded(std::string dev_id, std::string dev_name) { @@ -474,16 +485,17 @@ MidiPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; hr = async_op->put_Completed( WRL::Callback< Win::Foundation::IAsyncOperationCompletedHandler>( - [port_manager, task_service]( + [port_manager, task_service, instance_id]( IAsyncOperation* async_op, AsyncStatus status) { // A reference to |async_op| is kept in |async_ops_|, safe to pass // outside. task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce( &MidiPortManager::OnCompletedGetPortFromIdAsync, base::Unretained(port_manager), @@ -640,8 +652,9 @@ Win::Devices::Midi::IMidiInPortStatics, RuntimeClass_Windows_Devices_Midi_MidiInPort> { public: - MidiInPortManager(MidiManagerWinrt* midi_manager) - : MidiPortManager(midi_manager) {} + MidiInPortManager(MidiManagerWinrt* midi_manager, + TaskService::InstanceId instance_id) + : MidiPortManager(midi_manager, instance_id) {} MidiInPortManager(const MidiInPortManager&) = delete; MidiInPortManager& operator=(const MidiInPortManager&) = delete; @@ -654,17 +667,18 @@ MidiInPortManager* port_manager = this; TaskService* task_service = midi_service_->task_service(); + const TaskService::InstanceId instance_id = instance_id_; HRESULT hr = handle->add_MessageReceived( WRL::Callback>( - [port_manager, task_service]( + [port_manager, task_service, instance_id]( Win::Devices::Midi::IMidiInPort* handle, Win::Devices::Midi::IMidiMessageReceivedEventArgs* args) { const base::TimeTicks now = base::TimeTicks::Now(); - std::string dev_id = GetDeviceIdString(handle); + const std::string dev_id = GetDeviceIdString(handle); WRL::ComPtr message; HRESULT hr = args->get_Message(&message); @@ -688,7 +702,7 @@ std::vector data(buffer_span.begin(), buffer_span.end()); task_service->PostBoundTask( - kComTaskRunner, + instance_id, kComTaskRunner, base::BindOnce(&MidiInPortManager::OnMessageReceived, base::Unretained(port_manager), dev_id, data, now)); @@ -744,8 +758,9 @@ Win::Devices::Midi::IMidiOutPortStatics, RuntimeClass_Windows_Devices_Midi_MidiOutPort> { public: - MidiOutPortManager(MidiManagerWinrt* midi_manager) - : MidiPortManager(midi_manager) {} + MidiOutPortManager(MidiManagerWinrt* midi_manager, + TaskService::InstanceId instance_id) + : MidiPortManager(midi_manager, instance_id) {} MidiOutPortManager(const MidiOutPortManager&) = delete; MidiOutPortManager& operator=(const MidiOutPortManager&) = delete; @@ -794,12 +809,16 @@ } void MidiManagerWinrt::StartInitialization() { - if (!service()->task_service()->BindInstance()) + std::optional instance_id = + service()->task_service()->BindInstance(); + if (!instance_id) { return CompleteInitialization(Result::INITIALIZATION_ERROR); + } service()->task_service()->PostBoundTask( - kComTaskRunner, base::BindOnce(&MidiManagerWinrt::InitializeOnComRunner, - base::Unretained(this))); + *instance_id, kComTaskRunner, + base::BindOnce(&MidiManagerWinrt::InitializeOnComRunner, + base::Unretained(this), *instance_id)); } void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client, @@ -819,13 +838,14 @@ delay); } -void MidiManagerWinrt::InitializeOnComRunner() { +void MidiManagerWinrt::InitializeOnComRunner( + TaskService::InstanceId instance_id) { base::AutoLock auto_lock(lazy_init_member_lock_); DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner)); - port_manager_in_ = std::make_unique(this); - port_manager_out_ = std::make_unique(this); + port_manager_in_ = std::make_unique(this, instance_id); + port_manager_out_ = std::make_unique(this, instance_id); if (!(port_manager_in_->StartWatcher() && port_manager_out_->StartWatcher())) { diff -Nru chromium-149.0.7827.102/media/midi/midi_manager_winrt.h chromium-149.0.7827.114/media/midi/midi_manager_winrt.h --- chromium-149.0.7827.102/media/midi/midi_manager_winrt.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/midi/midi_manager_winrt.h 2026-06-10 17:58:03.000000000 +0000 @@ -9,6 +9,7 @@ #include "base/thread_annotations.h" #include "media/midi/midi_manager.h" +#include "media/midi/task_service.h" namespace midi { @@ -42,7 +43,7 @@ class MidiPortManager; // Callbacks on kComTaskRunner. - void InitializeOnComRunner(); + void InitializeOnComRunner(TaskService::InstanceId instance_id); void SendOnComRunner(uint32_t port_index, const std::vector& data); // Callback from MidiPortManager::OnEnumerationComplete on kComTaskRunner. diff -Nru chromium-149.0.7827.102/media/midi/task_service.cc chromium-149.0.7827.114/media/midi/task_service.cc --- chromium-149.0.7827.102/media/midi/task_service.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/midi/task_service.cc 2026-06-10 17:58:03.000000000 +0000 @@ -34,23 +34,23 @@ threads.clear(); } -bool TaskService::BindInstance() { +std::optional TaskService::BindInstance() { DCHECK_CALLED_ON_VALID_SEQUENCE(instance_binding_sequence_checker_); base::AutoLock lock(lock_); if (bound_instance_id_ != kInvalidInstanceId) - return false; + return std::nullopt; // If the InstanceId reaches to the limit, just fail rather than doing // something nicer for such impractical case. if (std::numeric_limits::max() == next_instance_id_) - return false; + return std::nullopt; bound_instance_id_ = ++next_instance_id_; DCHECK(!default_task_runner_); default_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault(); - return true; + return bound_instance_id_; } bool TaskService::UnbindInstance() { @@ -80,11 +80,12 @@ bool TaskService::IsOnTaskRunner(RunnerId runner_id) { base::AutoLock lock(lock_); - if (bound_instance_id_ == kInvalidInstanceId) - return false; - - if (runner_id == kDefaultRunnerId) + if (runner_id == kDefaultRunnerId) { + if (bound_instance_id_ == kInvalidInstanceId) { + return false; + } return default_task_runner_->BelongsToCurrentThread(); + } size_t thread = runner_id - 1; if (threads_.size() <= thread || !threads_[thread]) @@ -98,28 +99,40 @@ GetTaskRunner(runner_id)->PostTask(FROM_HERE, std::move(task)); } -void TaskService::PostBoundTask(RunnerId runner_id, base::OnceClosure task) { - InstanceId instance_id; +void TaskService::PostBoundTask(InstanceId instance_id, + RunnerId runner_id, + base::OnceClosure task) { + if (instance_id == kInvalidInstanceId) + return; { base::AutoLock lock(lock_); - if (bound_instance_id_ == kInvalidInstanceId) + if (instance_id != bound_instance_id_) return; - instance_id = bound_instance_id_; } GetTaskRunner(runner_id)->PostTask( FROM_HERE, base::BindOnce(&TaskService::RunTask, base::Unretained(this), instance_id, runner_id, std::move(task))); } -void TaskService::PostBoundDelayedTask(RunnerId runner_id, +void TaskService::PostBoundTask(RunnerId runner_id, base::OnceClosure task) { + InstanceId instance_id; + { + base::AutoLock lock(lock_); + instance_id = bound_instance_id_; + } + PostBoundTask(instance_id, runner_id, std::move(task)); +} + +void TaskService::PostBoundDelayedTask(InstanceId instance_id, + RunnerId runner_id, base::OnceClosure task, base::TimeDelta delay) { - InstanceId instance_id; + if (instance_id == kInvalidInstanceId) + return; { base::AutoLock lock(lock_); - if (bound_instance_id_ == kInvalidInstanceId) + if (instance_id != bound_instance_id_) return; - instance_id = bound_instance_id_; } GetTaskRunner(runner_id)->PostDelayedTask( FROM_HERE, @@ -128,6 +141,17 @@ delay); } +void TaskService::PostBoundDelayedTask(RunnerId runner_id, + base::OnceClosure task, + base::TimeDelta delay) { + InstanceId instance_id; + { + base::AutoLock lock(lock_); + instance_id = bound_instance_id_; + } + PostBoundDelayedTask(instance_id, runner_id, std::move(task), delay); +} + void TaskService::OverflowInstanceIdForTesting() { next_instance_id_ = std::numeric_limits::max(); } diff -Nru chromium-149.0.7827.102/media/midi/task_service.h chromium-149.0.7827.114/media/midi/task_service.h --- chromium-149.0.7827.102/media/midi/task_service.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/midi/task_service.h 2026-06-10 17:58:03.000000000 +0000 @@ -6,6 +6,7 @@ #define MEDIA_MIDI_TASK_SERVICE_H_ #include +#include #include #include "base/functional/callback_forward.h" @@ -29,6 +30,7 @@ using InstanceId = int64_t; static constexpr RunnerId kDefaultRunnerId = 0; + static constexpr InstanceId kInvalidInstanceId = -1; TaskService(); @@ -41,11 +43,11 @@ // PostDelayedBoundTask() with the InstanceId. Once UnbindInstance() is // called, tasks posted via these methods with unbind InstanceId won't be // invoked any more. - // Returns true if call is bound or unbound correctly. Otherwise returns - // false, that happens when the BindInstance() is called twice without - // unbinding the previous instance, or the UnbindInstance() is called without - // any successful BindInstance() call. - [[nodiscard]] bool BindInstance(); + // BindInstance() returns the bound InstanceId, or nullopt if + // it fails (e.g., if another instance is already bound). + // UnbindInstance() returns true if unbound correctly, or false if there + // was no active binding. + [[nodiscard]] std::optional BindInstance(); [[nodiscard]] bool UnbindInstance(); // Checks if the current thread belongs to the specified runner. @@ -61,7 +63,20 @@ // |runner_id| should be |kDefaultRunnerId| or a positive number. If // |kDefaultRunnerId| is specified, the task runs on the thread on which // BindInstance() was called. - void PostBoundTask(RunnerId runner, base::OnceClosure task); + // + // The overloads taking |instance_id| will only run the task if the instance + // is still bound (i.e., UnbindInstance() has not been called for this + // instance) when the task is about to run. + // The overloads without |instance_id| will bind the task to the current + // bound instance at the time of posting. + void PostBoundTask(InstanceId instance_id, + RunnerId runner_id, + base::OnceClosure task); + void PostBoundTask(RunnerId runner_id, base::OnceClosure task); + void PostBoundDelayedTask(InstanceId instance_id, + RunnerId runner_id, + base::OnceClosure task, + base::TimeDelta delay); void PostBoundDelayedTask(RunnerId runner_id, base::OnceClosure task, base::TimeDelta delay); @@ -69,8 +84,6 @@ void OverflowInstanceIdForTesting(); private: - static constexpr TaskService::InstanceId kInvalidInstanceId = -1; - // Returns a SingleThreadTaskRunner reference. Each TaskRunner will be // constructed on demand. scoped_refptr GetTaskRunner(RunnerId runner_id); diff -Nru chromium-149.0.7827.102/media/midi/task_service_unittest.cc chromium-149.0.7827.114/media/midi/task_service_unittest.cc --- chromium-149.0.7827.102/media/midi/task_service_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/midi/task_service_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -5,6 +5,7 @@ #include "media/midi/task_service.h" #include +#include #include "base/functional/bind.h" #include "base/functional/callback.h" @@ -61,9 +62,23 @@ TaskServiceClient(const TaskServiceClient&) = delete; TaskServiceClient& operator=(const TaskServiceClient&) = delete; - bool Bind() { return task_service()->BindInstance(); } + bool Bind() { + std::optional instance_id = + task_service()->BindInstance(); + if (instance_id) { + instance_id_ = *instance_id; + return true; + } + instance_id_ = TaskService::kInvalidInstanceId; + return false; + } + + bool Unbind() { + instance_id_ = TaskService::kInvalidInstanceId; + return task_service()->UnbindInstance(); + } - bool Unbind() { return task_service()->UnbindInstance(); } + TaskService::InstanceId instance_id() const { return instance_id_; } void PostBoundTask(TaskService::RunnerId runner_id) { task_service()->PostBoundTask( @@ -71,6 +86,22 @@ base::Unretained(this))); } + void PostBoundTaskWithExplicitId(TaskService::InstanceId instance_id, + TaskService::RunnerId runner_id) { + task_service()->PostBoundTask( + instance_id, runner_id, + base::BindOnce(&TaskServiceClient::IncrementCount, + base::Unretained(this))); + } + + void PostBoundSignalTaskWithExplicitId(TaskService::InstanceId instance_id, + TaskService::RunnerId runner_id) { + task_service()->PostBoundTask( + instance_id, runner_id, + base::BindOnce(&TaskServiceClient::SignalEvent, + base::Unretained(this))); + } + void PostBoundSignalTask(TaskService::RunnerId runner_id) { task_service()->PostBoundTask( runner_id, base::BindOnce(&TaskServiceClient::SignalEvent, @@ -91,6 +122,27 @@ base::Milliseconds(100)); } + void PostBoundDelayedTaskWithExplicitId(TaskService::InstanceId instance_id, + TaskService::RunnerId runner_id, + base::TimeDelta delay) { + task_service()->PostBoundDelayedTask( + instance_id, runner_id, + base::BindOnce(&TaskServiceClient::IncrementCount, + base::Unretained(this)), + delay); + } + + void PostBoundDelayedSignalTaskWithExplicitId( + TaskService::InstanceId instance_id, + TaskService::RunnerId runner_id, + base::TimeDelta delay) { + task_service()->PostBoundDelayedTask( + instance_id, runner_id, + base::BindOnce(&TaskServiceClient::SignalEvent, + base::Unretained(this)), + delay); + } + void WaitTask() { wait_task_event_->Wait(); } size_t count() { @@ -121,6 +173,7 @@ raw_ptr task_service_; std::unique_ptr wait_task_event_; size_t count_; + TaskService::InstanceId instance_id_ = TaskService::kInvalidInstanceId; }; class MidiTaskServiceTest : public ::testing::Test { @@ -309,6 +362,82 @@ EXPECT_TRUE(client->Unbind()); } + +// Tests if bound tasks with explicit InstanceId are handled correctly. +TEST_F(MidiTaskServiceTest, RunBoundTaskWithExplicitInstanceId) { + std::unique_ptr client = + std::make_unique(task_service()); + + EXPECT_TRUE(client->Bind()); + TaskService::InstanceId id1 = client->instance_id(); + EXPECT_NE(TaskService::kInvalidInstanceId, id1); + + // Task with correct ID should run. + EXPECT_EQ(0u, client->count()); + client->PostBoundTaskWithExplicitId(id1, kFirstRunner); + client->PostBoundSignalTaskWithExplicitId(id1, kFirstRunner); + WaitEvent(); + EXPECT_EQ(2u, client->count()); + + // Unbind. + EXPECT_TRUE(client->Unbind()); + + // Bind again to get a new ID. + ResetEvent(); + EXPECT_TRUE(client->Bind()); + TaskService::InstanceId id2 = client->instance_id(); + EXPECT_NE(id1, id2); + + // Task with old ID should be ignored. + client->PostBoundTaskWithExplicitId(id1, kFirstRunner); + + // Task with new ID should run. + client->PostBoundSignalTaskWithExplicitId(id2, kFirstRunner); + WaitEvent(); + // Count should only be incremented by the new task (2 -> 3). + EXPECT_EQ(3u, client->count()); + + EXPECT_TRUE(client->Unbind()); +} + +// Tests if bound delayed tasks with explicit InstanceId are handled correctly. +TEST_F(MidiTaskServiceTest, RunBoundDelayedTaskWithExplicitInstanceId) { + std::unique_ptr client = + std::make_unique(task_service()); + + EXPECT_TRUE(client->Bind()); + TaskService::InstanceId id1 = client->instance_id(); + EXPECT_NE(TaskService::kInvalidInstanceId, id1); + + // Task with correct ID should run. + EXPECT_EQ(0u, client->count()); + client->PostBoundDelayedSignalTaskWithExplicitId(id1, kFirstRunner, + base::Milliseconds(10)); + WaitEvent(); + EXPECT_EQ(1u, client->count()); + + // Unbind. + EXPECT_TRUE(client->Unbind()); + + // Bind again to get a new ID. + ResetEvent(); + EXPECT_TRUE(client->Bind()); + TaskService::InstanceId id2 = client->instance_id(); + EXPECT_NE(id1, id2); + + // Task with old ID should be ignored. + client->PostBoundDelayedTaskWithExplicitId(id1, kFirstRunner, + base::Milliseconds(10)); + + // Task with new ID should run. + client->PostBoundDelayedSignalTaskWithExplicitId(id2, kFirstRunner, + base::Milliseconds(10)); + WaitEvent(); + // Count should only be incremented by the new task (1 -> 2). + EXPECT_EQ(2u, client->count()); + + EXPECT_TRUE(client->Unbind()); +} } // namespace diff -Nru chromium-149.0.7827.102/media/mojo/services/mojo_audio_decoder_service.cc chromium-149.0.7827.114/media/mojo/services/mojo_audio_decoder_service.cc --- chromium-149.0.7827.102/media/mojo/services/mojo_audio_decoder_service.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/mojo/services/mojo_audio_decoder_service.cc 2026-06-10 17:58:03.000000000 +0000 @@ -216,6 +216,13 @@ return; } + if (!buffer->end_of_stream() && buffer->side_data() && + buffer->side_data()->secure_handle) { + std::move(bad_message_callback) + .Run("Renderer sent non-zero DecoderBufferSideData.secure_handle."); + return; + } + decoder_->Decode(std::move(buffer), base::BindOnce(&MojoAudioDecoderService::OnDecodeStatus, weak_this_, std::move(callback))); diff -Nru chromium-149.0.7827.102/media/mojo/services/mojo_decryptor_service.cc chromium-149.0.7827.114/media/mojo/services/mojo_decryptor_service.cc --- chromium-149.0.7827.102/media/mojo/services/mojo_decryptor_service.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/mojo/services/mojo_decryptor_service.cc 2026-06-10 17:58:03.000000000 +0000 @@ -163,8 +163,9 @@ } audio_buffer_reader_->ReadDecoderBuffer( - std::move(encrypted), base::BindOnce(&MojoDecryptorService::OnAudioRead, - weak_this_, std::move(callback))); + std::move(encrypted), + base::BindOnce(&MojoDecryptorService::OnAudioRead, weak_this_, + mojo::GetBadMessageCallback(), std::move(callback))); } void MojoDecryptorService::DecryptAndDecodeVideo( @@ -179,8 +180,9 @@ } video_buffer_reader_->ReadDecoderBuffer( - std::move(encrypted), base::BindOnce(&MojoDecryptorService::OnVideoRead, - weak_this_, std::move(callback))); + std::move(encrypted), + base::BindOnce(&MojoDecryptorService::OnVideoRead, weak_this_, + mojo::GetBadMessageCallback(), std::move(callback))); } void MojoDecryptorService::ResetDecoder(StreamType stream_type) { @@ -223,6 +225,14 @@ return; } + if (!buffer->end_of_stream() && buffer->side_data() && + buffer->side_data()->secure_handle) { + std::move(callback).Run(Status::kError, nullptr); + std::move(bad_message_callback) + .Run("Renderer sent non-zero DecoderBufferSideData.secure_handle."); + return; + } + if (!GetBufferReader(stream_type)) { std::move(bad_message_callback).Run("Unexpected stream_type"); return; @@ -268,26 +278,47 @@ std::move(callback).Run(success); } -void MojoDecryptorService::OnAudioRead(DecryptAndDecodeAudioCallback callback, - scoped_refptr buffer) { +void MojoDecryptorService::OnAudioRead( + mojo::ReportBadMessageCallback bad_message_callback, + DecryptAndDecodeAudioCallback callback, + scoped_refptr buffer) { if (!buffer) { std::move(callback).Run(Status::kError, std::vector()); return; } + if (!buffer->end_of_stream() && buffer->side_data() && + buffer->side_data()->secure_handle) { + std::move(callback).Run(Status::kError, + std::vector()); + std::move(bad_message_callback) + .Run("Renderer sent non-zero DecoderBufferSideData.secure_handle."); + return; + } + decryptor_->DecryptAndDecodeAudio( std::move(buffer), base::BindOnce(&MojoDecryptorService::OnAudioDecoded, weak_this_, std::move(callback))); } -void MojoDecryptorService::OnVideoRead(DecryptAndDecodeVideoCallback callback, - scoped_refptr buffer) { +void MojoDecryptorService::OnVideoRead( + mojo::ReportBadMessageCallback bad_message_callback, + DecryptAndDecodeVideoCallback callback, + scoped_refptr buffer) { if (!buffer) { std::move(callback).Run(Status::kError, nullptr, mojo::NullRemote()); return; } + if (!buffer->end_of_stream() && buffer->side_data() && + buffer->side_data()->secure_handle) { + std::move(callback).Run(Status::kError, nullptr, mojo::NullRemote()); + std::move(bad_message_callback) + .Run("Renderer sent non-zero DecoderBufferSideData.secure_handle."); + return; + } + decryptor_->DecryptAndDecodeVideo( std::move(buffer), base::BindOnce(&MojoDecryptorService::OnVideoDecoded, weak_this_, std::move(callback))); diff -Nru chromium-149.0.7827.102/media/mojo/services/mojo_decryptor_service.h chromium-149.0.7827.114/media/mojo/services/mojo_decryptor_service.h --- chromium-149.0.7827.102/media/mojo/services/mojo_decryptor_service.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/mojo/services/mojo_decryptor_service.h 2026-06-10 17:58:03.000000000 +0000 @@ -79,9 +79,11 @@ void OnVideoDecoderInitialized(InitializeVideoDecoderCallback callback, bool success); - void OnAudioRead(DecryptAndDecodeAudioCallback callback, + void OnAudioRead(mojo::ReportBadMessageCallback bad_message_callback, + DecryptAndDecodeAudioCallback callback, scoped_refptr buffer); - void OnVideoRead(DecryptAndDecodeVideoCallback callback, + void OnVideoRead(mojo::ReportBadMessageCallback bad_message_callback, + DecryptAndDecodeVideoCallback callback, scoped_refptr buffer); void OnReaderFlushDone(StreamType stream_type); diff -Nru chromium-149.0.7827.102/media/mojo/services/mojo_video_encode_accelerator_service.cc chromium-149.0.7827.114/media/mojo/services/mojo_video_encode_accelerator_service.cc --- chromium-149.0.7827.102/media/mojo/services/mojo_video_encode_accelerator_service.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/mojo/services/mojo_video_encode_accelerator_service.cc 2026-06-10 17:58:03.000000000 +0000 @@ -147,6 +147,18 @@ .Run({EncoderStatus::Codes::kEncoderInitializationError}); return; } + if (spatial_layer.width > config.input_visible_size.width() || + spatial_layer.height > config.input_visible_size.height()) { + MEDIA_LOG(ERROR, media_log_.get()) + << __func__ + << " spatial layer size is larger than input_visible_size: " + << spatial_layer.width << "x" << spatial_layer.height << " vs " + << config.input_visible_size.ToString(); + + std::move(success_callback) + .Run({EncoderStatus::Codes::kEncoderInitializationError}); + return; + } } encoder_.reset(); diff -Nru chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter.cc chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter.cc --- chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter.cc 2026-06-10 17:58:03.000000000 +0000 @@ -756,7 +756,7 @@ if (state_ == State::kFlushing && flush_support_.value()) { accelerator_->Flush( base::BindOnce(&VideoEncodeAcceleratorAdapter::FlushCompleted, - base::Unretained(this))); + weak_factory_.GetWeakPtr())); } } @@ -1033,7 +1033,7 @@ if (flush_support_.value()) { accelerator_->Flush( base::BindOnce(&VideoEncodeAcceleratorAdapter::FlushCompleted, - base::Unretained(this))); + weak_factory_.GetWeakPtr())); } } } diff -Nru chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter.h chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter.h --- chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter.h 2026-06-10 17:58:03.000000000 +0000 @@ -15,6 +15,7 @@ #include "base/memory/read_only_shared_memory_region.h" #include "base/memory/scoped_refptr.h" #include "base/memory/unsafe_shared_memory_pool.h" +#include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "base/synchronization/lock.h" #include "base/time/time.h" @@ -207,6 +208,8 @@ #endif bool supports_frame_size_change_ = false; bool supports_gpu_shared_images_ = false; + + base::WeakPtrFactory weak_factory_{this}; }; } // namespace media diff -Nru chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter_test.cc chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter_test.cc --- chromium-149.0.7827.102/media/video/video_encode_accelerator_adapter_test.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/media/video/video_encode_accelerator_adapter_test.cc 2026-06-10 17:58:03.000000000 +0000 @@ -11,12 +11,14 @@ #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "base/synchronization/waitable_event.h" #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/bind.h" #include "base/test/gmock_callback_support.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" #include "base/time/time.h" #include "build/build_config.h" #include "components/viz/test/test_context_provider.h" @@ -44,6 +46,30 @@ namespace media { +class FlushHoldingVideoEncodeAccelerator : public FakeVideoEncodeAccelerator { + public: + explicit FlushHoldingVideoEncodeAccelerator( + scoped_refptr task_runner, + FlushCallback* out_held_callback) + : FakeVideoEncodeAccelerator(std::move(task_runner)), + out_held_callback_(out_held_callback) {} + + void Flush(FlushCallback flush_callback) override { + flush_callback_ = std::move(flush_callback); + } + + bool IsFlushSupported() override { return true; } + + void Destroy() override { + *out_held_callback_ = std::move(flush_callback_); + FakeVideoEncodeAccelerator::Destroy(); + } + + private: + FlushCallback flush_callback_; + raw_ptr out_held_callback_; +}; + class VideoEncodeAcceleratorAdapterTest : public ::testing::TestWithParam { public: @@ -393,6 +419,98 @@ RunUntilIdle(); } +TEST_F(VideoEncodeAcceleratorAdapterTest, HeldFlushCallbackAfterDestroy) { + // Delete the default vea_ allocated in SetUp since we are replacing it. + delete vea_.ExtractAsDangling(); + + VideoEncodeAccelerator::FlushCallback held_flush_callback; + auto* flush_holding_vea = + new FlushHoldingVideoEncodeAccelerator(vea_runner_, &held_flush_callback); + vea_ = flush_holding_vea; + EXPECT_CALL(*gpu_factories_.get(), DoCreateVideoEncodeAccelerator()) + .WillRepeatedly(Return(flush_holding_vea)); + + VideoEncoder::Options options; + options.frame_size = gfx::Size(640, 480); + + // Wait 1: Initialize + base::RunLoop init_run_loop; + adapter()->Initialize( + profile_, options, /*info_cb=*/base::DoNothing(), + /*output_cb=*/base::DoNothing(), + base::BindLambdaForTesting([&](EncoderStatus status) { + EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence()); + EXPECT_TRUE(status.is_ok()); + init_run_loop.Quit(); + })); + init_run_loop.Run(); + + auto frame = CreateGreenFrame(options.frame_size, PIXEL_FORMAT_I420, + base::Milliseconds(1)); + + // Block vea_runner_ to ensure Encode and Flush are both posted before either + // runs. + base::WaitableEvent event; + vea_runner_->PostTask( + FROM_HERE, base::BindOnce( + [](base::WaitableEvent* e) { + base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait; + e->Wait(); + }, + base::Unretained(&event))); + + // Start Encode but don't wait yet + base::RunLoop encode_run_loop; + adapter()->Encode( + frame, VideoEncoder::EncodeOptions(true), + base::BindLambdaForTesting([&](EncoderStatus status) { + EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence()); + EXPECT_TRUE(status.is_ok()); + encode_run_loop.Quit(); + })); + + bool flush_called = false; + adapter()->Flush(base::BindLambdaForTesting([&](EncoderStatus status) { + flush_called = true; + EXPECT_TRUE(status.is_ok()); + })); + + // Unblock vea_runner_ now that both are posted. + event.Signal(); + + // Wait for vea_runner_ to process Encode and Flush, which will post + // the Encode completion callback to the main thread. + { + base::RunLoop run_loop; + vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(), + run_loop.QuitClosure()); + run_loop.Run(); + } + + // Now run the main thread loop until the Encode callback executes. + encode_run_loop.Run(); + EXPECT_FALSE(flush_called); + + // Wait 4: Deletion + vea_runner_->DeleteSoon(FROM_HERE, std::move(vae_adapter_)); + { + base::RunLoop run_loop; + vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(), + run_loop.QuitClosure()); + run_loop.Run(); + } + + // Wait 5: Running the held callback + vea_runner_->PostTask(FROM_HERE, + base::BindOnce(std::move(held_flush_callback), true)); + { + base::RunLoop run_loop; + vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(), + run_loop.QuitClosure()); + run_loop.Run(); + } +} + TEST_F(VideoEncodeAcceleratorAdapterTest, InitializationError) { VideoEncoder::Options options; options.frame_size = gfx::Size(640, 480); diff -Nru chromium-149.0.7827.102/mojo/public/cpp/system/invitation.cc chromium-149.0.7827.114/mojo/public/cpp/system/invitation.cc --- chromium-149.0.7827.102/mojo/public/cpp/system/invitation.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/mojo/public/cpp/system/invitation.cc 2026-06-10 17:58:03.000000000 +0000 @@ -249,8 +249,10 @@ ScopedMessagePipeHandle OutgoingInvitation::SendIsolated( PlatformChannelEndpoint channel_endpoint, std::string_view connection_name, - base::ProcessHandle target_process) { + base::ProcessHandle target_process, + MojoSendInvitationFlags invitation_flags) { OutgoingInvitation invitation; + invitation.set_extra_flags(invitation_flags); ScopedMessagePipeHandle pipe = invitation.AttachMessagePipe(kIsolatedPipeName); SendInvitation(std::move(invitation.handle_), target_process, @@ -265,8 +267,10 @@ ScopedMessagePipeHandle OutgoingInvitation::SendIsolated( PlatformChannelServerEndpoint server_endpoint, std::string_view connection_name, - base::ProcessHandle target_process) { + base::ProcessHandle target_process, + MojoSendInvitationFlags invitation_flags) { OutgoingInvitation invitation; + invitation.set_extra_flags(invitation_flags); ScopedMessagePipeHandle pipe = invitation.AttachMessagePipe(kIsolatedPipeName); #if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_IOS) diff -Nru chromium-149.0.7827.102/mojo/public/cpp/system/invitation.h chromium-149.0.7827.114/mojo/public/cpp/system/invitation.h --- chromium-149.0.7827.102/mojo/public/cpp/system/invitation.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/mojo/public/cpp/system/invitation.h 2026-06-10 17:58:03.000000000 +0000 @@ -139,7 +139,9 @@ static ScopedMessagePipeHandle SendIsolated( PlatformChannelEndpoint channel_endpoint, std::string_view connection_name = {}, - base::ProcessHandle target_process = base::kNullProcessHandle); + base::ProcessHandle target_process = base::kNullProcessHandle, + MojoSendInvitationFlags invitation_flags = + MOJO_SEND_INVITATION_FLAG_NONE); // Similar to above but sends |invitation| via |server_endpoint|, which should // correspond to a |PlatformChannelServerEndpoint| taken from a @@ -150,7 +152,9 @@ static ScopedMessagePipeHandle SendIsolated( PlatformChannelServerEndpoint server_endpoint, std::string_view connection_name = {}, - base::ProcessHandle target_process = base::kNullProcessHandle); + base::ProcessHandle target_process = base::kNullProcessHandle, + MojoSendInvitationFlags invitation_flags = + MOJO_SEND_INVITATION_FLAG_NONE); private: MojoSendInvitationFlags extra_flags_ = MOJO_SEND_INVITATION_FLAG_NONE; diff -Nru chromium-149.0.7827.102/mojo/public/cpp/system/isolated_connection.cc chromium-149.0.7827.114/mojo/public/cpp/system/isolated_connection.cc --- chromium-149.0.7827.102/mojo/public/cpp/system/isolated_connection.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/mojo/public/cpp/system/isolated_connection.cc 2026-06-10 17:58:03.000000000 +0000 @@ -40,8 +40,17 @@ ScopedMessagePipeHandle IsolatedConnection::Connect( PlatformChannelEndpoint endpoint, base::Process process) { + return Connect(std::move(endpoint), std::move(process), + MOJO_SEND_INVITATION_FLAG_NONE); +} + +ScopedMessagePipeHandle IsolatedConnection::Connect( + PlatformChannelEndpoint endpoint, + base::Process process, + MojoSendInvitationFlags invitation_flags) { return OutgoingInvitation::SendIsolated(std::move(endpoint), - token_.ToString(), process.Handle()); + token_.ToString(), process.Handle(), + invitation_flags); } ScopedMessagePipeHandle IsolatedConnection::Connect( diff -Nru chromium-149.0.7827.102/mojo/public/cpp/system/isolated_connection.h chromium-149.0.7827.114/mojo/public/cpp/system/isolated_connection.h --- chromium-149.0.7827.102/mojo/public/cpp/system/isolated_connection.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/mojo/public/cpp/system/isolated_connection.h 2026-06-10 17:58:03.000000000 +0000 @@ -7,6 +7,7 @@ #include "base/process/process.h" #include "base/unguessable_token.h" +#include "mojo/public/c/system/invitation.h" #include "mojo/public/cpp/platform/platform_channel_endpoint.h" #include "mojo/public/cpp/platform/platform_channel_server_endpoint.h" #include "mojo/public/cpp/system/message_pipe.h" @@ -56,6 +57,15 @@ ScopedMessagePipeHandle Connect(PlatformChannelEndpoint endpoint, base::Process process); + // Connects to a process at the other end of the channel. Returns a primordial + // message pipe that can be used for Mojo IPC. The connection + // will be connected to a corresponding peer pipe in the remote process. + // `process` identifies the remote process. Invitation flag sets additional + // invitation flags. + ScopedMessagePipeHandle Connect(PlatformChannelEndpoint endpoint, + base::Process process, + MojoSendInvitationFlags invitation_flags); + // Same as above but works with a server endpoint. The corresponding client // could use the above signature with NamedPlatformChannel::ConnectToServer. ScopedMessagePipeHandle Connect(PlatformChannelServerEndpoint endpoint); diff -Nru chromium-149.0.7827.102/net/spdy/spdy_buffer.cc chromium-149.0.7827.114/net/spdy/spdy_buffer.cc --- chromium-149.0.7827.102/net/spdy/spdy_buffer.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/net/spdy/spdy_buffer.cc 2026-06-10 17:58:03.000000000 +0000 @@ -104,10 +104,16 @@ DCHECK_GE(consume_size, 1u); DCHECK_LE(consume_size, GetRemainingSize()); offset_ += consume_size; - for (std::vector::const_iterator it = - consume_callbacks_.begin(); it != consume_callbacks_.end(); ++it) { - it->Run(consume_size, consume_source); + // Copy callbacks before iterating: a consume callback may cause `this` to be + // destroyed reentrantly. Iterating a local copy keeps the iterator valid and + // keeps each callback's BindState alive (via RepeatingCallback's + // scoped_refptr) even after `this` is freed. The callbacks themselves are + // WeakPtr-bound and tolerate the receiver being gone. + std::vector callbacks = consume_callbacks_; + for (const auto& callback : callbacks) { + callback.Run(consume_size, consume_source); } + // `this` may have been deleted here. } } // namespace net diff -Nru chromium-149.0.7827.102/net/spdy/spdy_read_queue_unittest.cc chromium-149.0.7827.114/net/spdy/spdy_read_queue_unittest.cc --- chromium-149.0.7827.102/net/spdy/spdy_read_queue_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/net/spdy/spdy_read_queue_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -5,10 +5,12 @@ #include "net/spdy/spdy_read_queue.h" #include +#include #include #include #include #include +#include #include "base/containers/heap_array.h" #include "base/containers/span.h" @@ -138,4 +140,55 @@ EXPECT_TRUE(read_queue.IsEmpty()); } +// Tests that calling Dequeue() reentrantly from within a consume callback +// does not cause a use-after-free when the SpdyBuffer is destroyed during +// the reentrant call. +namespace { + +void ReentrantDequeue(SpdyReadQueue* queue, + bool* fired, + size_t inner_buf_len, + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source) { + if (*fired) { + return; + } + *fired = true; + + std::vector inner_buf(inner_buf_len); + queue->Dequeue(inner_buf); +} + +} // namespace + +TEST_F(SpdyReadQueueTest, ReentrantDequeue) { + constexpr size_t kPayloadSize = 20; + constexpr size_t kUserBufLen = 12; + + std::array payload = {}; + SpdyReadQueue queue; + auto buffer = + std::make_unique(base::span(payload)); + + bool reentry_fired = false; + + buffer->AddConsumeCallback(base::BindRepeating(&ReentrantDequeue, &queue, + &reentry_fired, kUserBufLen)); + // Add a second callback to ensure that the loop in ConsumeHelper continues + // and attempts to access the next callback after the buffer has been deleted. + int second_callback_called = 0; + buffer->AddConsumeCallback(base::BindRepeating( + [](int* counter, size_t, SpdyBuffer::ConsumeSource) { (*counter)++; }, + &second_callback_called)); + + queue.Enqueue(std::move(buffer)); + + std::array user_buf; + size_t copied = queue.Dequeue(base::span(user_buf)); + + EXPECT_EQ(copied, kUserBufLen); + EXPECT_TRUE(reentry_fired); + EXPECT_EQ(second_callback_called, 2); +} + } // namespace net::test diff -Nru chromium-149.0.7827.102/services/network/cors/cors_url_loader_unittest.cc chromium-149.0.7827.114/services/network/cors/cors_url_loader_unittest.cc --- chromium-149.0.7827.102/services/network/cors/cors_url_loader_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/cors/cors_url_loader_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -165,6 +165,58 @@ } } +TEST_F(CorsURLLoaderTest, ForbiddenMethodOverride) { + const struct { + std::string header_name; + std::string header_value; + } kTestCases[] = { + {"X-HTTP-Method-Override", "TRACE"}, + {"X-HTTP-Method-Override", "TRACK"}, + {"X-HTTP-Method-Override", "CONNECT"}, + {"X-HTTP-Method", "TRACE"}, + {"X-Method-Override", "TRACE"}, + }; + for (const auto& test_case : kTestCases) { + SCOPED_TRACE(test_case.header_name); + SCOPED_TRACE(test_case.header_value); + for (const mojom::RequestMode mode : + {mojom::RequestMode::kSameOrigin, mojom::RequestMode::kNoCors, + mojom::RequestMode::kCors, + mojom::RequestMode::kCorsWithForcedPreflight, + mojom::RequestMode::kNavigate}) { + SCOPED_TRACE(mode); + + ResetFactory( + url::Origin::Create(GURL("https://example.com")) /* initiator */, + OriginatingProcessId::browser()); + + ResourceRequest request; + request.mode = mode; + request.credentials_mode = mojom::CredentialsMode::kInclude; + request.url = GURL("https://example.com/"); + request.request_initiator = url::Origin::Create(request.url); + request.method = "POST"; + request.headers.SetHeader(test_case.header_name, test_case.header_value); + + BadMessageTestHelper bad_message_helper; + CreateLoaderAndStart(request); + if (IsNetworkLoaderStarted()) { + RunUntilCreateLoaderAndStartCalled(); + NotifyLoaderClientOnReceiveResponse(); + NotifyLoaderClientOnComplete(net::OK); + } + RunUntilComplete(); + + EXPECT_FALSE(IsNetworkLoaderStarted()); + EXPECT_FALSE(client().has_received_redirect()); + EXPECT_FALSE(client().has_received_response()); + EXPECT_TRUE(client().has_received_completion()); + EXPECT_THAT(client().completion_status().error_code, + net::test::IsError(net::ERR_INVALID_ARGUMENT)); + } + } +} + TEST_F(CorsURLLoaderTest, SameOriginWithoutInitiator) { ResourceRequest request; request.mode = mojom::RequestMode::kSameOrigin; diff -Nru chromium-149.0.7827.102/services/network/public/cpp/cors/cors.cc chromium-149.0.7827.114/services/network/public/cpp/cors/cors.cc --- chromium-149.0.7827.102/services/network/public/cpp/cors/cors.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/public/cpp/cors/cors.cc 2026-06-10 17:58:03.000000000 +0000 @@ -463,7 +463,7 @@ return {"range"}; } -bool IsForbiddenMethod(const std::string& method) { +bool IsForbiddenMethod(std::string_view method) { const std::string upper_method = base::ToUpperASCII(method); return upper_method == net::HttpRequestHeaders::kConnectMethod || upper_method == net::HttpRequestHeaders::kTraceMethod || diff -Nru chromium-149.0.7827.102/services/network/public/cpp/cors/cors.h chromium-149.0.7827.114/services/network/public/cpp/cors/cors.h --- chromium-149.0.7827.102/services/network/public/cpp/cors/cors.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/public/cpp/cors/cors.h 2026-06-10 17:58:03.000000000 +0000 @@ -123,7 +123,7 @@ // Checks forbidden method in the fetch spec. // See https://fetch.spec.whatwg.org/#forbidden-method. -COMPONENT_EXPORT(NETWORK_CPP) bool IsForbiddenMethod(const std::string& name); +COMPONENT_EXPORT(NETWORK_CPP) bool IsForbiddenMethod(std::string_view name); // Returns true if |type| is a response type which makes a response // CORS-same-origin. See https://html.spec.whatwg.org/C/#cors-same-origin. diff -Nru chromium-149.0.7827.102/services/network/public/cpp/header_util.cc chromium-149.0.7827.114/services/network/public/cpp/header_util.cc --- chromium-149.0.7827.102/services/network/public/cpp/header_util.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/public/cpp/header_util.cc 2026-06-10 17:58:03.000000000 +0000 @@ -17,6 +17,8 @@ #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" +#include "net/http/http_util.h" +#include "services/network/public/cpp/cors/cors.h" #include "services/network/public/cpp/features.h" #include "services/network/public/mojom/url_response_head.mojom.h" #include "url/gurl.h" @@ -95,6 +97,17 @@ if (base::StartsWith(key, "Proxy-", base::CompareCase::INSENSITIVE_ASCII)) return false; + if (base::EqualsCaseInsensitiveASCII(key, "X-HTTP-Method") || + base::EqualsCaseInsensitiveASCII(key, "X-HTTP-Method-Override") || + base::EqualsCaseInsensitiveASCII(key, "X-Method-Override")) { + net::HttpUtil::ValuesIterator method_iterator(value, ','); + while (method_iterator.GetNext()) { + if (cors::IsForbiddenMethod(method_iterator.value())) { + return false; + } + } + } + return true; } diff -Nru chromium-149.0.7827.102/services/network/public/cpp/header_util.h chromium-149.0.7827.114/services/network/public/cpp/header_util.h --- chromium-149.0.7827.102/services/network/public/cpp/header_util.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/public/cpp/header_util.h 2026-06-10 17:58:03.000000000 +0000 @@ -22,6 +22,11 @@ } // namespace mojom // Checks if a single request header is safe to send. +// +// Per https://fetch.spec.whatwg.org/#forbidden-request-header, the method- +// override headers are forbidden when their value parses to a forbidden +// method. The logic is almost compatible but exclude some headers that would +// be set by renderer's internal code. COMPONENT_EXPORT(NETWORK_CPP) bool IsRequestHeaderSafe(std::string_view key, std::string_view value); diff -Nru chromium-149.0.7827.102/services/network/public/cpp/header_util_unittest.cc chromium-149.0.7827.114/services/network/public/cpp/header_util_unittest.cc --- chromium-149.0.7827.102/services/network/public/cpp/header_util_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/services/network/public/cpp/header_util_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -39,6 +39,13 @@ {"Proxy-Foo", "bar", false}, {"PrOxY-FoO", "bar", false}, + {"X-HTTP-Method-Override", "TRACE", false}, + {"x-http-method-override", "trAcE", false}, + {"X-HTTP-Method-Override", "GET", true}, + {"X-HTTP-Method-Override", "GET, TRACE", false}, + {"X-HTTP-Method", "TRACK", false}, + {"X-Method-Override", "CONNECT", false}, + {"dnt", "1", true}, }; @@ -80,6 +87,13 @@ {"Proxy-Foo", "bar", false}, {"PrOxY-FoO", "bar", false}, + {"X-HTTP-Method-Override", "TRACE", false}, + {"x-http-method-override", "trAcE", false}, + {"X-HTTP-Method-Override", "GET", true}, + {"X-HTTP-Method-Override", "GET, TRACE", false}, + {"X-HTTP-Method", "TRACK", false}, + {"X-Method-Override", "CONNECT", false}, + {"dnt", "1", true}, }; diff -Nru chromium-149.0.7827.102/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc chromium-149.0.7827.114/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc --- chromium-149.0.7827.102/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc 2026-06-10 17:58:03.000000000 +0000 @@ -289,6 +289,21 @@ (media::VideoFrame::NumPlanes( video_frame_init_data.ready_buffer->info->pixel_format) == 3)) << "Currently, only YUV formats support custom strides."; + const auto pixel_format = + video_frame_init_data.ready_buffer->info->pixel_format; + const auto coded_width = + video_frame_init_data.ready_buffer->info->coded_size.width(); + const auto& strides = + video_frame_init_data.ready_buffer->info->strides->stride_by_plane; + CHECK_GE(static_cast(strides[0]), + media::VideoFrame::RowBytes(media::VideoFrame::Plane::kY, + pixel_format, coded_width)); + CHECK_GE(static_cast(strides[1]), + media::VideoFrame::RowBytes(media::VideoFrame::Plane::kU, + pixel_format, coded_width)); + CHECK_GE(static_cast(strides[2]), + media::VideoFrame::RowBytes(media::VideoFrame::Plane::kV, + pixel_format, coded_width)); const size_t y_size = (media::VideoFrame::Rows( media::VideoFrame::Plane::kY, diff -Nru chromium-149.0.7827.102/ui/accessibility/platform/ax_platform_node_cocoa.mm chromium-149.0.7827.114/ui/accessibility/platform/ax_platform_node_cocoa.mm --- chromium-149.0.7827.102/ui/accessibility/platform/ax_platform_node_cocoa.mm 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/accessibility/platform/ax_platform_node_cocoa.mm 2026-06-10 17:58:03.000000000 +0000 @@ -2221,6 +2221,7 @@ case ax::mojom::InvalidState::kTrue: return @"true"; } + NOTREACHED(); } - (NSNumber*)AXIsMultiSelectable { @@ -2283,6 +2284,7 @@ case ax::mojom::HasPopup::kDialog: return @"dialog"; } + NOTREACHED(); } - (NSNumber*)AXRequired { diff -Nru chromium-149.0.7827.102/ui/gtk/gtk_ui.cc chromium-149.0.7827.114/ui/gtk/gtk_ui.cc --- chromium-149.0.7827.102/ui/gtk/gtk_ui.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/gtk/gtk_ui.cc 2026-06-10 17:58:03.000000000 +0000 @@ -22,6 +22,7 @@ #include "base/containers/flat_map.h" #include "base/debug/leak_annotations.h" #include "base/environment.h" +#include "base/files/file_path.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/nix/mime_util_xdg.h" @@ -288,6 +289,24 @@ return true; } +bool IsValidIconThemeName(const std::string& theme) { + base::FilePath theme_path(theme); + return !theme.empty() && theme != "." && !theme_path.IsAbsolute() && + !theme_path.ReferencesParent() && theme_path.BaseName() == theme_path; +} + +std::string GetIconThemeName() { + gchar* theme = nullptr; + g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &theme, + nullptr); + std::string theme_string; + if (theme) { + theme_string = theme; + g_free(theme); + } + return theme_string; +} + } // namespace GtkUi::GtkUi() : window_frame_actions_() {} @@ -345,6 +364,7 @@ }; GtkSettings* settings = gtk_settings_get_default(); + SanitizeIconThemeName(); connect(settings, "notify::gtk-theme-name", &GtkUi::OnThemeChanged); connect(settings, "notify::gtk-icon-theme-name", &GtkUi::OnThemeChanged); connect(settings, "notify::gtk-application-prefer-dark-theme", @@ -495,6 +515,10 @@ gfx::Image GtkUi::GetIconForContentType(const std::string& content_type, int dip_size, float scale) const { + if (!IsValidIconThemeName(GetIconThemeName())) { + return gfx::Image(); + } + // This call doesn't take a reference. GtkIconTheme* theme = GetDefaultIconTheme(); @@ -772,6 +796,16 @@ return theme_string; } +bool GtkUi::SanitizeIconThemeName() { + std::string theme = GetIconThemeName(); + if (!IsValidIconThemeName(theme)) { + g_object_set(gtk_settings_get_default(), "gtk-icon-theme-name", "hicolor", + nullptr); + return true; + } + return false; +} + int GtkUi::GetCursorThemeSize() { gint size = 0; g_object_get(gtk_settings_get_default(), "gtk-cursor-theme-size", &size, @@ -822,6 +856,9 @@ #endif void GtkUi::OnThemeChanged(GtkSettings* settings, GtkParamSpec* param) { + if (SanitizeIconThemeName()) { + return; // Exit early; modifying the setting re-triggered this function + } colors_.clear(); custom_frame_colors_.clear(); native_frame_colors_.clear(); diff -Nru chromium-149.0.7827.102/ui/gtk/gtk_ui.h chromium-149.0.7827.114/ui/gtk/gtk_ui.h --- chromium-149.0.7827.102/ui/gtk/gtk_ui.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/gtk/gtk_ui.h 2026-06-10 17:58:03.000000000 +0000 @@ -117,6 +117,10 @@ void OnThemeChanged(GtkSettings* settings, GtkParamSpec* param); + // Sanitizes the "gtk-icon-theme-name" setting in GtkSettings if it is unsafe. + // Returns true if the setting was modified. + bool SanitizeIconThemeName(); + void OnCursorThemeNameChanged(GtkSettings* settings, GtkParamSpec* param); void OnCursorThemeSizeChanged(GtkSettings* settings, GtkParamSpec* param); diff -Nru chromium-149.0.7827.102/ui/qt/qt_shim.cc chromium-149.0.7827.114/ui/qt/qt_shim.cc --- chromium-149.0.7827.102/ui/qt/qt_shim.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/qt/qt_shim.cc 2026-06-10 17:58:03.000000000 +0000 @@ -216,6 +216,16 @@ return scale > 0 ? scale : 1.0; } +bool IsValidIconThemeName(const QString& theme) { + if (theme.isEmpty() || theme == "." || theme == "..") { + return false; + } + if (theme.contains('/') || theme.contains('\\') || theme.contains("..")) { + return false; + } + return true; +} + } // namespace QtShim::QtShim(QtInterface::Delegate* delegate, int* argc, char** argv) @@ -275,6 +285,10 @@ Image QtShim::GetIconForContentType(const String& content_type, int size) const { + if (!IsValidIconThemeName(QIcon::themeName())) { + QIcon::setThemeName("hicolor"); + } + QMimeDatabase db; for (const char* mime : {content_type.c_str(), "application/octet-stream"}) { auto mt = db.mimeTypeForName(mime); diff -Nru chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc --- chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -72,6 +72,10 @@ int allowed_operations, ui::mojom::DragEventSource source) { CHECK(!drag_drop_in_progress_); + if (desktop_host_->IsInNativeMoveResizeLoop()) { + return ui::PreferredDragOperation( + ui::DragDropTypes::DropEffectToDragOperation(DROPEFFECT_NONE)); + } gfx::Point touch_screen_point; if (source == ui::mojom::DragEventSource::kTouch) { source_window->GetHost()->ConvertDIPToPixels(&touch_screen_point); diff -Nru chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc --- chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc 2026-06-10 17:58:03.000000000 +0000 @@ -167,6 +167,10 @@ } } +bool DesktopWindowTreeHostWin::IsInNativeMoveResizeLoop() const { + return message_handler_ && message_handler_->IsInNativeMoveResizeLoop(); +} + // DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation: void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) { @@ -1162,7 +1166,11 @@ // Adding/removing a monitor, or changing the primary monitor can cause a // WM_MOVE message before `OnDisplayChanged()`. Without this call, we would // DCHECK due to stale `DisplayInfo`s. See https:://crbug.com/1413940. + auto weak_ptr = GetWeakPtr(); display::win::GetScreenWin()->UpdateDisplayInfosIfNeeded(); + if (!weak_ptr) { + return; + } CheckForMonitorChange(); OnHostMovedInPixels(); } diff -Nru chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h --- chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h 2026-06-10 17:58:03.000000000 +0000 @@ -93,6 +93,9 @@ // false. void FinishTouchDrag(gfx::Point screen_point); + // Returns true if any window is in a move/resize loop. + bool IsInNativeMoveResizeLoop() const; + protected: // Overridden from DesktopWindowTreeHost: void Init(const Widget::InitParams& params) override; diff -Nru chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc --- chromium-149.0.7827.102/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc 2026-06-10 17:58:03.000000000 +0000 @@ -199,5 +199,17 @@ EXPECT_EQ(test_node_->m_dwRef, 1); } +TEST_F(DesktopWindowTreeHostWinTest, IsInNativeMoveResizeLoop) { + Widget widget; + Widget::InitParams params = CreateParams( + Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW); + widget.Init(std::move(params)); + widget.Show(); + + DesktopWindowTreeHostWin* host = static_cast( + widget.GetNativeWindow()->GetHost()); + EXPECT_FALSE(host->IsInNativeMoveResizeLoop()); +} + } // namespace test } // namespace views diff -Nru chromium-149.0.7827.102/ui/views/win/hwnd_message_handler.cc chromium-149.0.7827.114/ui/views/win/hwnd_message_handler.cc --- chromium-149.0.7827.102/ui/views/win/hwnd_message_handler.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/win/hwnd_message_handler.cc 2026-06-10 17:58:03.000000000 +0000 @@ -899,6 +899,11 @@ ::SendMessage(hwnd(), WM_CANCELMODE, 0, 0); } +// static +bool HWNDMessageHandler::IsInNativeMoveResizeLoop() { + return UserResizeMoveDetector::InMoveResizeLoop(); +} + void HWNDMessageHandler::SendFrameChanged() { ::SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | @@ -2389,7 +2394,11 @@ } void HWNDMessageHandler::OnMove(const gfx::Point& point) { + auto ref = msg_handler_weak_factory_.GetWeakPtr(); delegate_->HandleMove(); + if (!ref) { + return; + } SetMsgHandled(FALSE); } diff -Nru chromium-149.0.7827.102/ui/views/win/hwnd_message_handler.h chromium-149.0.7827.114/ui/views/win/hwnd_message_handler.h --- chromium-149.0.7827.102/ui/views/win/hwnd_message_handler.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/win/hwnd_message_handler.h 2026-06-10 17:58:03.000000000 +0000 @@ -167,6 +167,9 @@ bool hide_on_escape); virtual void EndMoveLoop(); + // Returns true if any HWndMessageHandler is in a native move/resize loop. + static bool IsInNativeMoveResizeLoop(); + // Tells the HWND its client area has changed. virtual void SendFrameChanged(); diff -Nru chromium-149.0.7827.102/ui/views/win/user_resize_move_detector.cc chromium-149.0.7827.114/ui/views/win/user_resize_move_detector.cc --- chromium-149.0.7827.102/ui/views/win/user_resize_move_detector.cc 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/win/user_resize_move_detector.cc 2026-06-10 17:58:03.000000000 +0000 @@ -10,6 +10,8 @@ namespace views { +static bool g_in_move_resize_loop = false; + UserResizeMoveDetector::UserResizeMoveDetector( HWNDMessageHandlerDelegate* hwnd_delegate) : hwnd_delegate_(hwnd_delegate) {} @@ -22,6 +24,7 @@ void UserResizeMoveDetector::OnSizing() { if (state_ == State::kInSizeMove) { + g_in_move_resize_loop = true; state_ = State::kInSizing; hwnd_delegate_->HandleBeginUserResize(); } @@ -29,6 +32,7 @@ void UserResizeMoveDetector::OnMoving() { if (state_ == State::kInSizeMove) { + g_in_move_resize_loop = true; state_ = State::kInMoving; hwnd_delegate_->HandleBeginUserDrag(); } @@ -40,7 +44,13 @@ } else if (state_ == State::kInMoving) { hwnd_delegate_->HandleEndUserDrag(); } + g_in_move_resize_loop = false; state_ = State::kNotResizing; } +// static +bool UserResizeMoveDetector::InMoveResizeLoop() { + return g_in_move_resize_loop; +} + } // namespace views diff -Nru chromium-149.0.7827.102/ui/views/win/user_resize_move_detector.h chromium-149.0.7827.114/ui/views/win/user_resize_move_detector.h --- chromium-149.0.7827.102/ui/views/win/user_resize_move_detector.h 2026-06-05 21:04:29.000000000 +0000 +++ chromium-149.0.7827.114/ui/views/win/user_resize_move_detector.h 2026-06-10 17:58:03.000000000 +0000 @@ -29,6 +29,9 @@ // Called on WM_EXITSIZEMOVE. void OnExitSizeMove(); + // Returns true if any window is being resized or moved.. + static bool InMoveResizeLoop(); + private: enum class State { // Start with not resizing.