blob: 2c8c5574c89c773631088fe3ef0e734df879981a [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/native_theme/native_theme_win.h"
#include <Windows.Media.ClosedCaptioning.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <wrl/implements.h>
#include "base/scoped_observation.h"
#include "base/test/task_environment.h"
#include "base/win/scoped_com_initializer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"
namespace ui {
namespace {
// Fake implementation of IClosedCaptionPropertiesStatics and
// IClosedCaptionPropertiesStatics2 that stores the event handler passed to
// add_PropertiesChanged so tests can invoke it.
class FakeClosedCaptionPropertiesStatics
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<
Microsoft::WRL::WinRt | Microsoft::WRL::InhibitRoOriginateError>,
ABI::Windows::Media::ClosedCaptioning::
IClosedCaptionPropertiesStatics,
ABI::Windows::Media::ClosedCaptioning::
IClosedCaptionPropertiesStatics2> {
public:
FakeClosedCaptionPropertiesStatics() = default;
// IClosedCaptionPropertiesStatics — all getters return E_NOTIMPL since
// they are not called during listener registration.
IFACEMETHODIMP get_FontColor(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionColor*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_ComputedFontColor(ABI::Windows::UI::Color*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_FontOpacity(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionOpacity*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_FontSize(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionSize*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_FontStyle(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionStyle*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_FontEffect(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionEdgeEffect*)
override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_BackgroundColor(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionColor*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_ComputedBackgroundColor(
ABI::Windows::UI::Color*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_BackgroundOpacity(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionOpacity*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_RegionColor(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionColor*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_ComputedRegionColor(ABI::Windows::UI::Color*) override {
return E_NOTIMPL;
}
IFACEMETHODIMP get_RegionOpacity(
ABI::Windows::Media::ClosedCaptioning::ClosedCaptionOpacity*) override {
return E_NOTIMPL;
}
// IClosedCaptionPropertiesStatics2:
IFACEMETHODIMP add_PropertiesChanged(
ABI::Windows::Foundation::IEventHandler<IInspectable*>* handler,
EventRegistrationToken* token) override {
handler_ = handler;
*token = EventRegistrationToken{1};
return S_OK;
}
IFACEMETHODIMP remove_PropertiesChanged(
EventRegistrationToken token) override {
handler_.Reset();
return S_OK;
}
// Fire the stored event handler to simulate a caption style change.
HRESULT SimulatePropertiesChanged() {
if (!handler_) {
return E_FAIL;
}
return handler_->Invoke(nullptr, nullptr);
}
bool has_handler() const { return handler_.Get() != nullptr; }
private:
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IEventHandler<IInspectable*>>
handler_;
};
// Subclass to access the protected NativeThemeWin constructor.
class TestNativeThemeWin : public NativeThemeWin {
public:
TestNativeThemeWin() = default;
~TestNativeThemeWin() override = default;
};
class MockCaptionObserver : public NativeThemeObserver {
public:
MOCK_METHOD(void, OnCaptionStyleUpdated, (), (override));
};
class NativeThemeWinCaptionTest : public ::testing::Test {
protected:
void SetUp() override {
fake_statics_ = Microsoft::WRL::Make<FakeClosedCaptionPropertiesStatics>();
// Inject the fake statics so the next NativeThemeWin constructor will
// register with our fake.
NativeThemeWin::SetClosedCaptionPropertiesStaticsForTesting(fake_statics_);
}
void TearDown() override {
// Clear the test override.
NativeThemeWin::SetClosedCaptionPropertiesStaticsForTesting(nullptr);
}
FakeClosedCaptionPropertiesStatics& fake_statics() {
return *fake_statics_.Get();
}
private:
base::test::TaskEnvironment task_environment_;
base::win::ScopedCOMInitializer com_initializer_;
Microsoft::WRL::ComPtr<FakeClosedCaptionPropertiesStatics> fake_statics_;
};
// Verifies that constructing a NativeThemeWin registers an event handler
// via the IClosedCaptionPropertiesStatics2 interface, and destroying it
// unregisters the handler.
TEST_F(NativeThemeWinCaptionTest, ListenerLifecycle) {
EXPECT_FALSE(fake_statics().has_handler());
{
TestNativeThemeWin theme;
ASSERT_TRUE(fake_statics().has_handler());
}
EXPECT_FALSE(fake_statics().has_handler());
}
// Verifies that when the WinRT PropertiesChanged event fires,
// OnCaptionStyleUpdated is called on observers of the web NativeTheme.
TEST_F(NativeThemeWinCaptionTest, EventFiresObserverCallback) {
TestNativeThemeWin theme;
ASSERT_TRUE(fake_statics().has_handler());
MockCaptionObserver observer;
base::ScopedObservation<NativeTheme, NativeThemeObserver> observation(
&observer);
observation.Observe(NativeTheme::GetInstanceForWeb());
EXPECT_CALL(observer, OnCaptionStyleUpdated()).Times(1);
// Simulate a caption style change from the OS.
EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
}
// Verifies that multiple PropertiesChanged events each trigger
// OnCaptionStyleUpdated.
TEST_F(NativeThemeWinCaptionTest, MultipleEventsFireMultipleCallbacks) {
TestNativeThemeWin theme;
ASSERT_TRUE(fake_statics().has_handler());
MockCaptionObserver observer;
base::ScopedObservation<NativeTheme, NativeThemeObserver> observation(
&observer);
observation.Observe(NativeTheme::GetInstanceForWeb());
EXPECT_CALL(observer, OnCaptionStyleUpdated()).Times(3);
EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
}
} // namespace
} // namespace ui