diff --git a/chrome/browser/ash/accessibility/DEPS b/chrome/browser/ash/accessibility/DEPS index c1a71ee07673b..08f75ae44a8d1 100644 --- a/chrome/browser/ash/accessibility/DEPS +++ b/chrome/browser/ash/accessibility/DEPS @@ -1,12 +1,18 @@ specific_include_rules = { # Testing - "accessibility_manager_browsertest.cc": [ + ".*_browsertest\.cc": [ + "+mojo/public", + "+testing/gmock", + "+testing/gtest", + "+ui/events", + "+ui/gfx", + "+ui/views", "+ui/message_center/message_center.h" ], "speech_monitor\.(cc|h)": [ "+content/public/browser/tts_controller.h", ], - "accessibility_manager.cc": [ + "accessibility_manager\.cc": [ "+services/accessibility/buildflags.h", ], } diff --git a/chrome/browser/ash/accessibility/live_caption_ui_remote_driver_browsertest.cc b/chrome/browser/ash/accessibility/live_caption_ui_remote_driver_browsertest.cc new file mode 100644 index 0000000000000..3fc7f27d44319 --- /dev/null +++ b/chrome/browser/ash/accessibility/live_caption_ui_remote_driver_browsertest.cc @@ -0,0 +1,390 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test lives in ::ash (v.s. the live_caption component) because it +// exercises as much of the UI stack as possible, which includes e.g. enabling +// Ash-specific features. + +#include "components/live_caption/live_caption_ui_remote_driver.h" + +#include "ash/constants/ash_features.h" +#include "base/memory/raw_ptr.h" +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/accessibility/live_caption_controller_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/live_caption/caption_bubble_controller.h" +#include "components/live_caption/live_caption_controller.h" +#include "components/live_caption/pref_names.h" +#include "components/live_caption/views/caption_bubble.h" +#include "components/live_caption/views/caption_bubble_controller_views.h" +#include "components/soda/soda_installer.h" +#include "content/public/test/browser_test.h" +#include "media/mojo/mojom/speech_recognition_result.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/bindings/self_owned_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/types/event_type.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace { + +using media::mojom::SpeechRecognitionRecognizerClient; +using media::mojom::SpeechRecognitionSurface; +using media::mojom::SpeechRecognitionSurfaceClient; +using testing::_; + +class MockSurface : public SpeechRecognitionSurface { + public: + MockSurface() = default; + ~MockSurface() override = default; + + MockSurface(const MockSurface&) = delete; + MockSurface& operator=(const MockSurface&) = delete; + + // Establish ourselves as the implementation of the surface, and grab a handle + // to the remote surface client. For good measure, hold the given recognizer + // client remote, since its self-owned implementation will destroy itself + // unless we do. + void Bind(mojo::PendingReceiver receiver, + mojo::PendingRemote surface_client, + mojo::PendingRemote + recognizer_client) { + receiver_.Bind(std::move(receiver)); + surface_client_.Bind(std::move(surface_client)); + recognizer_client_.Bind(std::move(recognizer_client)); + } + + mojo::Remote& remote_recognizer_client() { + return recognizer_client_; + } + + // media::mojom::SpeechRecognitionSurface: + MOCK_METHOD(void, Activate, (), (override)); + MOCK_METHOD(void, + GetBounds, + (SpeechRecognitionSurface::GetBoundsCallback), + (override)); + + private: + mojo::Receiver receiver_{this}; + mojo::Remote surface_client_; + + // Must hold this to keep the UI driver alive. + mojo::Remote recognizer_client_; +}; + +class LiveCaptionUiRemoteDriverTest : public InProcessBrowserTest { + public: + LiveCaptionUiRemoteDriverTest() + : scoped_feature_list_(features::kOnDeviceSpeechRecognition) {} + ~LiveCaptionUiRemoteDriverTest() override = default; + + LiveCaptionUiRemoteDriverTest(const LiveCaptionUiRemoteDriverTest&) = delete; + LiveCaptionUiRemoteDriverTest& operator=( + const LiveCaptionUiRemoteDriverTest&) = delete; + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + + browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, + true); + + // Don't actually try to download SODA. + speech::SodaInstaller::GetInstance()->NeverDownloadSodaForTesting(); + + // Create bubble UI and grab a handle to it. + controller_ = captions::LiveCaptionControllerFactory::GetForProfile( + browser()->profile()); + base::RunLoop().RunUntilIdle(); + } + + // Test-only getters. + + captions::CaptionBubbleControllerViews* bubble_controller() const { + return static_cast( + controller_->caption_bubble_controller_for_testing()); + } + + captions::CaptionBubble* bubble() { + return bubble_controller()->GetCaptionBubbleForTesting(); + } + + bool IsWidgetVisible() const { + return bubble_controller() && + bubble_controller()->IsWidgetVisibleForTesting(); + } + + std::string GetBubbleText() const { + return bubble_controller() + ? bubble_controller()->GetBubbleLabelTextForTesting() + : ""; + } + + // Create a new UI driver that communicates with the provided mock surface. + // The driver will destroy itself its connection to the surface drops. + captions::LiveCaptionUiRemoteDriver* NewUiDriverForSurface( + MockSurface* surface, + bool stub_bounds) { + // Bind the fake lacros ends of the Mojo pipes. + mojo::PendingReceiver + client_receiver; + mojo::PendingReceiver + surface_client_receiver; + mojo::PendingRemote surface_remote; + surface->Bind(surface_remote.InitWithNewPipeAndPassReceiver(), + surface_client_receiver.InitWithNewPipeAndPassRemote(), + client_receiver.InitWithNewPipeAndPassRemote()); + + // Sending an initial transcription will trigger a bounds query that must be + // replied to. The expectation is optionally added here since it is used in + // many tests. + if (stub_bounds) { + EXPECT_CALL(*surface, GetBounds(_)) + .WillOnce([&](auto cb) { std::move(cb).Run(gfx::Rect(1, 2, 3, 4)); }) + .RetiresOnSaturation(); + } + + // Create a driver and bind the Ash ends of the Mojo pipes. + auto driver = std::make_unique( + controller_, std::move(surface_client_receiver), + std::move(surface_remote), "session-id"); + captions::LiveCaptionUiRemoteDriver* raw_driver = driver.get(); + + mojo::MakeSelfOwnedReceiver(std::move(driver), std::move(client_receiver)); + return raw_driver; + } + + // Emulate new transcriptions being generated for the given surface and wait + // until the recognizer client has responded. + bool EmitTranscribedText(MockSurface* surface, const std::string& text) { + bool result = false; + surface->remote_recognizer_client()->OnSpeechRecognitionRecognitionEvent( + media::SpeechRecognitionResult(text, /*is_final=*/false), + base::BindLambdaForTesting([&](bool r) { result = r; })); + base::RunLoop().RunUntilIdle(); + return result; + } + + // Emulate clicking the given button with the mouse. + void ClickButton(views::Button* button) { + button->OnMousePressed( + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0), gfx::Point(0, 0), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + button->OnMouseReleased(ui::MouseEvent( + ui::ET_MOUSE_RELEASED, gfx::Point(0, 0), gfx::Point(0, 0), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + } + + protected: + raw_ptr controller_; + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +// Test that captions sent from lacros are shown in the bubble. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, DisplaysText) { + MockSurface surface; + NewUiDriverForSurface(&surface, /*stub_bounds=*/true); + + // Emitting text should cause the bubble to appear. + EXPECT_TRUE(EmitTranscribedText(&surface, "Test text")); + EXPECT_TRUE(IsWidgetVisible()); + EXPECT_EQ("Test text", GetBubbleText()); + + // Emitting empty text should cause the bubble to disappear. + EXPECT_TRUE(EmitTranscribedText(&surface, "")); + EXPECT_FALSE(IsWidgetVisible()); + + // Bubble should be shown again when non-empty text is emitted. + EXPECT_TRUE(EmitTranscribedText(&surface, "Test text 2")); + EXPECT_TRUE(IsWidgetVisible()); + EXPECT_EQ("Test text 2", GetBubbleText()); +} + +// Test that two media sources are both handled by the bubble. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, MultipleSources) { + MockSurface surface_1; + NewUiDriverForSurface(&surface_1, /*stub_bounds=*/true); + + MockSurface surface_2; + NewUiDriverForSurface(&surface_2, /*stub_bounds=*/false); + + // Text from surface 1 should be shown. + EXPECT_TRUE(EmitTranscribedText(&surface_1, "Surface 1 text")); + EXPECT_TRUE(IsWidgetVisible()); + EXPECT_EQ("Surface 1 text", GetBubbleText()); + + // Text from surface 2 should replace surface 1's text because it is newer. + EXPECT_TRUE(EmitTranscribedText(&surface_2, "Surface 2 text")); + EXPECT_TRUE(IsWidgetVisible()); + EXPECT_EQ("Surface 2 text", GetBubbleText()); + + // Back to surface 1. + EXPECT_TRUE(EmitTranscribedText(&surface_1, "More surface 1 text")); + EXPECT_TRUE(IsWidgetVisible()); + EXPECT_EQ("More surface 1 text", GetBubbleText()); +} + +// Test that bubble is placed in correct position. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, BubblePosition) { + MockSurface surface; + NewUiDriverForSurface(&surface, /*stub_bounds=*/false); + + // Set browser window to a known size and have the surface report the size. + const gfx::Rect window_bounds(10, 10, 800, 600); + browser()->window()->SetBounds(window_bounds); + EXPECT_CALL(surface, GetBounds(_)) + .WillOnce([&](auto cb) { std::move(cb).Run(window_bounds); }) + .RetiresOnSaturation(); + base::RunLoop().RunUntilIdle(); + const gfx::Rect context_rect = views::Widget::GetWidgetForNativeWindow( + browser()->window()->GetNativeWindow()) + ->GetClientAreaBoundsInScreen(); + + // Trigger positioning via first emission of text. + EXPECT_TRUE(EmitTranscribedText(&surface, "Test text")); + EXPECT_TRUE(IsWidgetVisible()); + + // Reuses the positioning logic from + // `CaptionBubbleControllerViewsTest::BubblePositioning`. + const int bubble_width = 536; + const int bubble_y_offset = 20; + const gfx::Insets bubble_margins(6); + const gfx::Rect bubble_bounds = bubble_controller() + ->GetCaptionWidgetForTesting() + ->GetWindowBoundsInScreen(); + + // There may be some rounding errors as we do floating point math with ints. + // Check that points are almost the same. + EXPECT_LT( + abs(bubble_bounds.CenterPoint().x() - context_rect.CenterPoint().x()), 2); + EXPECT_EQ(bubble_bounds.bottom(), context_rect.bottom() - bubble_y_offset); + EXPECT_EQ(bubble()->GetBoundsInScreen().width(), bubble_width); + EXPECT_EQ(bubble()->margins(), bubble_margins); +} + +// Test that an error is shown when reported by the surface. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, DisplaysError) { + MockSurface surface; + NewUiDriverForSurface(&surface, /*stub_bounds=*/true); + + // Bubble should be shown initially. + EXPECT_TRUE(EmitTranscribedText(&surface, "Test text")); + EXPECT_TRUE(IsWidgetVisible()); + ASSERT_NE(nullptr, bubble_controller()); + EXPECT_FALSE(bubble_controller()->IsGenericErrorMessageVisibleForTesting()); + + // Error should trigger error message. + surface.remote_recognizer_client()->OnSpeechRecognitionError(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(IsWidgetVisible()); + ASSERT_NE(nullptr, bubble_controller()); + EXPECT_TRUE(bubble_controller()->IsGenericErrorMessageVisibleForTesting()); + + // Receiving new text during an error should cause the error to disappear. + EXPECT_TRUE(EmitTranscribedText(&surface, "More test text")); + EXPECT_TRUE(IsWidgetVisible()); + ASSERT_NE(nullptr, bubble_controller()); + EXPECT_FALSE(bubble_controller()->IsGenericErrorMessageVisibleForTesting()); +} + +// Test that the bubble is hidden when a stream end is reported. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, StreamEnd) { + MockSurface surface; + NewUiDriverForSurface(&surface, /*stub_bounds=*/true); + + // Emitting text should cause the bubble to appear. + EXPECT_TRUE(EmitTranscribedText(&surface, "Test text")); + EXPECT_TRUE(IsWidgetVisible()); + + // Send stream end signal. + surface.remote_recognizer_client()->OnSpeechRecognitionStopped(); + base::RunLoop().RunUntilIdle(); + + // Bubble should have disappeared. + ASSERT_NE(nullptr, bubble_controller()); + EXPECT_FALSE(IsWidgetVisible()); +} + +// Test that closing the bubble ends transcription until a navigation. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, CloseBubble) { + MockSurface surface_1, surface_2, surface_3; + auto* driver_1 = NewUiDriverForSurface(&surface_1, /*stub_bounds=*/true); + NewUiDriverForSurface(&surface_2, /*stub_bounds=*/false); + NewUiDriverForSurface(&surface_3, /*stub_bounds=*/false); + + // Emitting text should cause the bubble to appear. + EXPECT_TRUE(EmitTranscribedText(&surface_1, "Test text")); + EXPECT_TRUE(IsWidgetVisible()); + + // Close the bubble. + ASSERT_NE(nullptr, bubble()); + ClickButton(bubble()->GetCloseButtonForTesting()); + EXPECT_FALSE(IsWidgetVisible()); + + // Emitting further text should fail. + EXPECT_FALSE(EmitTranscribedText(&surface_1, "More test text")); + EXPECT_FALSE(IsWidgetVisible()); + + // Emitting text from a different stream in the same closed session should + // also fail. + EXPECT_FALSE(EmitTranscribedText(&surface_2, "Surface 2 text")); + EXPECT_FALSE(IsWidgetVisible()); + + // Emulate a navigation (i.e. session end). + driver_1->OnSessionEnded(); + + // Text from a new page should cause the bubble to reappear. + EXPECT_TRUE(EmitTranscribedText(&surface_3, "New page text")); + EXPECT_TRUE(IsWidgetVisible()); +} + +// Test that the back to tab message is delivered. +IN_PROC_BROWSER_TEST_F(LiveCaptionUiRemoteDriverTest, BackToTab) { + MockSurface surface_1; + MockSurface surface_2; + NewUiDriverForSurface(&surface_1, /*stub_bounds=*/true); + NewUiDriverForSurface(&surface_2, /*stub_bounds=*/false); + + // We expect these activate calls when we toggle back and forth. + EXPECT_CALL(surface_1, Activate()).Times(2).RetiresOnSaturation(); + EXPECT_CALL(surface_2, Activate()).RetiresOnSaturation(); + + // Emit text from surface 1 to activate its model. Then use the "back to tab" + // UI. + EXPECT_TRUE(EmitTranscribedText(&surface_1, "Surface 1 text")); + ClickButton(bubble()->GetBackToTabButtonForTesting()); + + // Emit text from surface 2 to activate its model. + EXPECT_TRUE(EmitTranscribedText(&surface_2, "Surface 2 text")); + ClickButton(bubble()->GetBackToTabButtonForTesting()); + + // Emit text from surface 1 again to reactivate its model. + EXPECT_TRUE(EmitTranscribedText(&surface_1, "More surface 1 text")); + ClickButton(bubble()->GetBackToTabButtonForTesting()); + + // Surface 1 should be activated twice, and surface 2 once. + base::RunLoop().RunUntilIdle(); +} + +} // namespace +} // namespace ash diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc index 3b6a38f9066fd..b2c1ff46ae04b 100644 --- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc +++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc @@ -69,7 +69,7 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest { } CaptionBubble* GetBubble() { - return controller_ ? controller_->caption_bubble_.get() : nullptr; + return controller_ ? controller_->GetCaptionBubbleForTesting() : nullptr; } views::Label* GetLabel() { @@ -143,7 +143,7 @@ class CaptionBubbleControllerViewsTest : public InProcessBrowserTest { } views::Widget* GetCaptionWidget() { - return controller_ ? controller_->caption_widget_.get() : nullptr; + return controller_ ? controller_->GetCaptionWidgetForTesting() : nullptr; } bool IsWidgetVisible() { diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 7f5ac063f371a..18ca933e7a07e 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -9818,6 +9818,7 @@ if (!is_android) { "//ui/display/manager", ] sources += [ + "../browser/ash/accessibility/live_caption_ui_remote_driver_browsertest.cc", "../browser/ash/app_list/app_list_client_interactive_uitest.cc", "../browser/ash/login/login_manager_test.cc", "../browser/ash/login/login_manager_test.h", diff --git a/components/live_caption/BUILD.gn b/components/live_caption/BUILD.gn index 54e99ceedfb48..0b4c8e1c90b01 100644 --- a/components/live_caption/BUILD.gn +++ b/components/live_caption/BUILD.gn @@ -53,6 +53,13 @@ if (!is_android) { "//ui/views", ] } # toolkit_views + + if (is_chromeos_ash) { + sources += [ + "live_caption_ui_remote_driver.cc", + "live_caption_ui_remote_driver.h", + ] + } # is_chromeos_ash } static_library("live_translate") { diff --git a/components/live_caption/live_caption_ui_remote_driver.cc b/components/live_caption/live_caption_ui_remote_driver.cc new file mode 100644 index 0000000000000..1ad57dfff2b21 --- /dev/null +++ b/components/live_caption/live_caption_ui_remote_driver.cc @@ -0,0 +1,61 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/live_caption/live_caption_ui_remote_driver.h" + +#include +#include + +#include "base/functional/callback_forward.h" +#include "components/live_caption/caption_bubble_context_remote.h" +#include "components/live_caption/live_caption_controller.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace captions { + +LiveCaptionUiRemoteDriver::LiveCaptionUiRemoteDriver( + LiveCaptionController* controller, + mojo::PendingReceiver + client_receiver, + mojo::PendingRemote surface_remote, + const std::string& session_id) + : controller_(controller), + client_receiver_(this, std::move(client_receiver)), + context_(std::move(surface_remote), session_id) {} + +LiveCaptionUiRemoteDriver::~LiveCaptionUiRemoteDriver() { + controller_->OnAudioStreamEnd(&context_); +} + +void LiveCaptionUiRemoteDriver::OnSpeechRecognitionRecognitionEvent( + const media::SpeechRecognitionResult& result, + OnSpeechRecognitionRecognitionEventCallback reply) { + std::move(reply).Run(controller_->DispatchTranscription(&context_, result)); +} + +void LiveCaptionUiRemoteDriver::OnLanguageIdentificationEvent( + media::mojom::LanguageIdentificationEventPtr event) { + controller_->OnLanguageIdentificationEvent(std::move(event)); +} + +void LiveCaptionUiRemoteDriver::OnSpeechRecognitionError() { + controller_->OnError(&context_, CaptionBubbleErrorType::kGeneric, + base::RepeatingClosure(), + base::BindRepeating([](CaptionBubbleErrorType error_type, + bool checked) {})); +} + +void LiveCaptionUiRemoteDriver::OnSpeechRecognitionStopped() { + controller_->OnAudioStreamEnd(&context_); +} + +void LiveCaptionUiRemoteDriver::OnSessionEnded() { + context_.OnSessionEnded(); +} + +void LiveCaptionUiRemoteDriver::OnFullscreenToggled() { + controller_->OnToggleFullscreen(&context_); +} + +} // namespace captions diff --git a/components/live_caption/live_caption_ui_remote_driver.h b/components/live_caption/live_caption_ui_remote_driver.h new file mode 100644 index 0000000000000..fd5322f209342 --- /dev/null +++ b/components/live_caption/live_caption_ui_remote_driver.h @@ -0,0 +1,66 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_LIVE_CAPTION_LIVE_CAPTION_UI_REMOTE_DRIVER_H_ +#define COMPONENTS_LIVE_CAPTION_LIVE_CAPTION_UI_REMOTE_DRIVER_H_ + +#include + +#include "base/memory/raw_ptr.h" +#include "components/live_caption/caption_bubble_context_remote.h" +#include "media/mojo/mojom/speech_recognition.mojom.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace captions { + +class CaptionBubbleContextRemote; +class LiveCaptionController; + +// Receives both speech recognition events and speech surface events (e.g. +// fullscreen-ing) from remote lacros processes, passing them to the live +// caption UI. +// +// One driver exists in the Ash browser process for each caption-producing +// stream of media in a lacros renderer. +class LiveCaptionUiRemoteDriver + : public media::mojom::SpeechRecognitionSurfaceClient, + public media::mojom::SpeechRecognitionRecognizerClient { + public: + // The speech surface client implementation is bound at construction time, but + // the speech recognition client implementation is bound in the "client + // browser interface"'s receiver set. + LiveCaptionUiRemoteDriver( + LiveCaptionController* controller, + mojo::PendingReceiver + client_receiver, + mojo::PendingRemote surface, + const std::string& session_id); + ~LiveCaptionUiRemoteDriver() override; + + // media::mojom::SpeechOriginRecognizerClient: + void OnSpeechRecognitionRecognitionEvent( + const media::SpeechRecognitionResult& result, + OnSpeechRecognitionRecognitionEventCallback reply) override; + void OnLanguageIdentificationEvent( + media::mojom::LanguageIdentificationEventPtr event) override; + void OnSpeechRecognitionError() override; + void OnSpeechRecognitionStopped() override; + + // media::mojom::SpeechRecognitionSurfaceClient: + void OnSessionEnded() override; + void OnFullscreenToggled() override; + + private: + // We are owned by the "client browser interface", which is a service that + // `DependsOn` the controller. Hence the controller is guaranteed to exist. + const raw_ptr controller_; + + mojo::Receiver client_receiver_; + + CaptionBubbleContextRemote context_; +}; + +} // namespace captions + +#endif // COMPONENTS_LIVE_CAPTION_LIVE_CAPTION_UI_REMOTE_DRIVER_H_ diff --git a/components/live_caption/views/caption_bubble.cc b/components/live_caption/views/caption_bubble.cc index 8323ee6c255f4..6feecf2430962 100644 --- a/components/live_caption/views/caption_bubble.cc +++ b/components/live_caption/views/caption_bubble.cc @@ -1178,6 +1178,14 @@ base::RetainingOneShotTimer* CaptionBubble::GetInactivityTimerForTesting() { return inactivity_timer_.get(); } +views::Button* CaptionBubble::GetCloseButtonForTesting() { + return close_button_.get(); +} + +views::Button* CaptionBubble::GetBackToTabButtonForTesting() { + return back_to_tab_button_.get(); +} + BEGIN_METADATA(CaptionBubble, views::BubbleDialogDelegateView) END_METADATA diff --git a/components/live_caption/views/caption_bubble.h b/components/live_caption/views/caption_bubble.h index 18bcc30ef2463..847f033bc44c2 100644 --- a/components/live_caption/views/caption_bubble.h +++ b/components/live_caption/views/caption_bubble.h @@ -98,6 +98,8 @@ class CaptionBubble : public views::BubbleDialogDelegateView { void set_tick_clock_for_testing(const base::TickClock* tick_clock) { tick_clock_ = tick_clock; } + views::Button* GetCloseButtonForTesting(); + views::Button* GetBackToTabButtonForTesting(); void SetCaptionBubbleStyle(); diff --git a/components/live_caption/views/caption_bubble_controller_views.cc b/components/live_caption/views/caption_bubble_controller_views.cc index 4b4c6a9f632cd..a1b3f16e387f7 100644 --- a/components/live_caption/views/caption_bubble_controller_views.cc +++ b/components/live_caption/views/caption_bubble_controller_views.cc @@ -186,4 +186,12 @@ void CaptionBubbleControllerViews::CloseActiveModelForTesting() { active_model_->Close(); } +views::Widget* CaptionBubbleControllerViews::GetCaptionWidgetForTesting() { + return caption_widget_; +} + +CaptionBubble* CaptionBubbleControllerViews::GetCaptionBubbleForTesting() { + return caption_bubble_; +} + } // namespace captions diff --git a/components/live_caption/views/caption_bubble_controller_views.h b/components/live_caption/views/caption_bubble_controller_views.h index ad24fcb4d4996..c724937fe38bb 100644 --- a/components/live_caption/views/caption_bubble_controller_views.h +++ b/components/live_caption/views/caption_bubble_controller_views.h @@ -64,6 +64,8 @@ class CaptionBubbleControllerViews : public CaptionBubbleController { bool IsGenericErrorMessageVisibleForTesting() override; std::string GetBubbleLabelTextForTesting() override; void CloseActiveModelForTesting() override; + views::Widget* GetCaptionWidgetForTesting(); + CaptionBubble* GetCaptionBubbleForTesting(); private: friend class CaptionBubbleControllerViewsTest;