| // 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 "ui/accessibility/platform/ax_platform.h" | |
| #include "base/check_op.h" | |
| #include "ui/accessibility/accessibility_features.h" | |
| #include "ui/accessibility/platform/ax_mode_observer.h" | |
| #if BUILDFLAG(IS_WIN) | |
| #include <oleacc.h> | |
| #include <uiautomation.h> | |
| #endif // BUILDFLAG(IS_WIN) | |
| namespace ui { | |
| namespace { | |
| AXPlatform* g_instance = nullptr; | |
| } // namespace | |
| // static | |
| AXPlatform& AXPlatform::GetInstance() { | |
| CHECK_NE(g_instance, nullptr) | |
| << "AXPlatform::GetInstance() called before AXPlatform was initialized " | |
| "or destroyed. If you are in a browser test, you may need cleanup in " | |
| "TearDownOnMainThread()."; | |
| DCHECK_CALLED_ON_VALID_THREAD(g_instance->thread_checker_); | |
| return *g_instance; | |
| } | |
| AXPlatform::AXPlatform(Delegate& delegate) : delegate_(delegate) { | |
| DCHECK_EQ(g_instance, nullptr); | |
| g_instance = this; | |
| } | |
| AXPlatform::~AXPlatform() { | |
| DCHECK_EQ(g_instance, this); | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| g_instance = nullptr; | |
| } | |
| AXMode AXPlatform::GetMode() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| return delegate_->GetAccessibilityMode(); | |
| } | |
| void AXPlatform::AddModeObserver(AXModeObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| observers_.AddObserver(observer); | |
| } | |
| void AXPlatform::RemoveModeObserver(AXModeObserver* observer) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| observers_.RemoveObserver(observer); | |
| } | |
| void AXPlatform::NotifyModeAdded(AXMode mode) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| observers_.Notify(&AXModeObserver::OnAXModeAdded, mode); | |
| } | |
| void AXPlatform::NotifyAssistiveTechChanged(AssistiveTech assistive_tech) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| if (active_assistive_tech_ == assistive_tech) { | |
| return; | |
| } | |
| active_assistive_tech_ = assistive_tech; | |
| observers_.Notify(&AXModeObserver::OnAssistiveTechChanged, assistive_tech); | |
| } | |
| bool AXPlatform::IsScreenReaderActive() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| return IsScreenReader(active_assistive_tech_); | |
| } | |
| bool AXPlatform::IsCaretBrowsingEnabled() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| return caret_browsing_enabled_; | |
| } | |
| void AXPlatform::SetCaretBrowsingState(bool enabled) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| caret_browsing_enabled_ = enabled; | |
| } | |
| #if BUILDFLAG(IS_WIN) | |
| const std::string& AXPlatform::GetProductName() const { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| RetrieveProductStringsIfNeeded(); | |
| return product_strings_->product_name; | |
| } | |
| const std::string& AXPlatform::GetProductVersion() const { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| RetrieveProductStringsIfNeeded(); | |
| return product_strings_->product_version; | |
| } | |
| const std::string& AXPlatform::GetToolkitVersion() const { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| RetrieveProductStringsIfNeeded(); | |
| return product_strings_->toolkit_version; | |
| } | |
| void AXPlatform::DisableActiveUiaProvider() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| if (!uia_provider_enabled_) { | |
| return; // Already disabled. | |
| } | |
| uia_provider_enabled_ = false; | |
| // We must call this *after* we disabled the UIA provider to ensure that we | |
| // don't respond with the same provider to a re-entrant WM_GETOBJECT call. See | |
| // https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcoreapi/nf-uiautomationcoreapi-uiadisconnectallproviders | |
| // for more info. | |
| HRESULT hr = ::UiaDisconnectAllProviders(); | |
| DCHECK(SUCCEEDED(hr)); | |
| } | |
| bool AXPlatform::IsUiaProviderEnabled() const { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| return uia_provider_enabled_ && | |
| base::FeatureList::IsEnabled(features::kUiaProvider); | |
| } | |
| void AXPlatform::SetUiaClientServiced(bool uia_client_serviced) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| has_serviced_uia_clients_ = uia_client_serviced; | |
| } | |
| bool AXPlatform::HasServicedUiaClients() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| return has_serviced_uia_clients_; | |
| } | |
| #endif // BUILDFLAG(IS_WIN) | |
| #if BUILDFLAG(IS_WIN) | |
| void AXPlatform::OnScreenReaderHoneyPotQueried() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| // We used to trust this as a signal that a screen reader is running, but it's | |
| // been abused. Now only enable accessibility if we detect that the name is | |
| // also used. Do not do the same for location and role, as the Windows Text | |
| // Services Framework (MSTSF) has been known to check the role of each new | |
| // window; see https://crbug.com/416429182. | |
| if (screen_reader_honeypot_queried_) { | |
| return; | |
| } | |
| screen_reader_honeypot_queried_ = true; | |
| if (is_name_used_) { | |
| OnPropertiesUsedInWebContent(); | |
| } | |
| } | |
| #endif // BUILDFLAG(IS_WIN) | |
| void AXPlatform::OnMinimalPropertiesUsed(bool is_name_used) { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnMinimalPropertiesUsed(); | |
| #if BUILDFLAG(IS_WIN) | |
| // See OnScreenReaderHoneyPotQueried, above. | |
| if (!is_name_used || is_name_used_) { | |
| return; | |
| } | |
| is_name_used_ = true; | |
| if (screen_reader_honeypot_queried_) { | |
| OnPropertiesUsedInWebContent(); | |
| return; | |
| } | |
| #endif | |
| } | |
| void AXPlatform::OnPropertiesUsedInBrowserUI() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnPropertiesUsedInBrowserUI(); | |
| } | |
| void AXPlatform::OnPropertiesUsedInWebContent() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnPropertiesUsedInWebContent(); | |
| } | |
| void AXPlatform::OnInlineTextBoxesUsedInWebContent() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnInlineTextBoxesUsedInWebContent(); | |
| } | |
| void AXPlatform::OnExtendedPropertiesUsedInWebContent() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnExtendedPropertiesUsedInWebContent(); | |
| } | |
| void AXPlatform::OnHTMLAttributesUsed() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnHTMLAttributesUsed(); | |
| } | |
| void AXPlatform::OnActionFromAssistiveTech() { | |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | |
| delegate_->OnActionFromAssistiveTech(); | |
| } | |
| void AXPlatform::DetachFromThreadForTesting() { | |
| DETACH_FROM_THREAD(thread_checker_); | |
| } | |
| #if BUILDFLAG(IS_WIN) | |
| void AXPlatform::RetrieveProductStringsIfNeeded() const { | |
| if (!product_strings_) { | |
| product_strings_ = delegate_->GetProductStrings(); | |
| } | |
| } | |
| #endif // BUILDFLAG(IS_WIN) | |
| } // namespace ui |