blob: b474d941df106f0df3549e923ce7775d1fb746c4 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/win/settings_app_monitor.h"
#include <wrl/client.h>
#include <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/pattern.h"
#include "base/strings/string16.h"
#include "base/synchronization/lock.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/win/scoped_variant.h"
#include "chrome/browser/win/automation_controller.h"
#include "chrome/browser/win/ui_automation_util.h"
namespace {
// Each item represent one UI element in the Settings App.
enum class ElementType {
// The "Web browser" element in the "Default apps" pane.
DEFAULT_BROWSER,
// The element representing a browser in the "Choose an app" popup.
BROWSER_BUTTON,
// The button labeled "Check it out" that leaves Edge as the default browser.
CHECK_IT_OUT,
// The button labeled "Switch Anyway" that dismisses the Edge promo.
SWITCH_ANYWAY,
// Any other element.
UNKNOWN,
};
// Configures a cache request so that it includes all properties needed by
// DetectElementType() to detect the elements of interest.
void ConfigureCacheRequest(IUIAutomationCacheRequest* cache_request) {
DCHECK(cache_request);
cache_request->AddProperty(UIA_AutomationIdPropertyId);
cache_request->AddProperty(UIA_NamePropertyId);
cache_request->AddProperty(UIA_ClassNamePropertyId);
}
// Helper function to get the parent element with class name "Flyout". Used to
// determine the |element|'s type.
base::string16 GetFlyoutParentAutomationId(IUIAutomation* automation,
IUIAutomationElement* element) {
// Create a condition that will include only elements with the right class
// name in the tree view.
base::win::ScopedVariant class_name(L"Flyout");
Microsoft::WRL::ComPtr<IUIAutomationCondition> condition;
HRESULT result = automation->CreatePropertyCondition(
UIA_ClassNamePropertyId, class_name, condition.GetAddressOf());
if (FAILED(result))
return base::string16();
Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
result =
automation->CreateTreeWalker(condition.Get(), tree_walker.GetAddressOf());
if (FAILED(result))
return base::string16();
Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
result = automation->CreateCacheRequest(cache_request.GetAddressOf());
if (FAILED(result))
return base::string16();
ConfigureCacheRequest(cache_request.Get());
// From MSDN, NormalizeElementBuildCache() "Retrieves the ancestor element
// nearest to the specified Microsoft UI Automation element in the tree view".
IUIAutomationElement* flyout_element = nullptr;
result = tree_walker->NormalizeElementBuildCache(element, cache_request.Get(),
&flyout_element);
if (FAILED(result) || !flyout_element)
return base::string16();
return GetCachedBstrValue(flyout_element, UIA_AutomationIdPropertyId);
}
ElementType DetectElementType(IUIAutomation* automation,
IUIAutomationElement* sender) {
DCHECK(automation);
DCHECK(sender);
base::string16 aid(GetCachedBstrValue(sender, UIA_AutomationIdPropertyId));
if (aid == L"SystemSettings_DefaultApps_Browser_Button")
return ElementType::DEFAULT_BROWSER;
if (aid == L"SystemSettings_DefaultApps_Browser_App0_HyperlinkButton")
return ElementType::SWITCH_ANYWAY;
if (base::MatchPattern(aid, L"SystemSettings_DefaultApps_Browser_*_Button")) {
// This element type depends on the automation id of one of its ancestors.
base::string16 automation_id =
GetFlyoutParentAutomationId(automation, sender);
if (automation_id == L"settingsFlyout")
return ElementType::CHECK_IT_OUT;
else if (automation_id == L"DefaultAppsFlyoutPresenter")
return ElementType::BROWSER_BUTTON;
}
return ElementType::UNKNOWN;
}
} // namespace
class SettingsAppMonitor::AutomationControllerDelegate
: public AutomationController::Delegate {
public:
AutomationControllerDelegate(
scoped_refptr<base::SequencedTaskRunner> monitor_runner,
base::WeakPtr<SettingsAppMonitor> monitor);
~AutomationControllerDelegate() override;
// AutomationController::Delegate:
void OnInitialized(HRESULT result) const override;
void ConfigureCacheRequest(
IUIAutomationCacheRequest* cache_request) const override;
void OnAutomationEvent(IUIAutomation* automation,
IUIAutomationElement* sender,
EVENTID event_id) const override;
void OnFocusChangedEvent(IUIAutomation* automation,
IUIAutomationElement* sender) const override;
private:
// The task runner on which the SettingsAppMonitor lives.
const scoped_refptr<base::SequencedTaskRunner> monitor_runner_;
// Only used to post callbacks to |monitor_runner_|;
const base::WeakPtr<SettingsAppMonitor> monitor_;
// Protect against concurrent accesses to |last_focused_element_|.
mutable base::Lock last_focused_element_lock_;
// State to suppress duplicate "focus changed" events.
mutable ElementType last_focused_element_;
DISALLOW_COPY_AND_ASSIGN(AutomationControllerDelegate);
};
SettingsAppMonitor::AutomationControllerDelegate::AutomationControllerDelegate(
scoped_refptr<base::SequencedTaskRunner> monitor_runner,
base::WeakPtr<SettingsAppMonitor> monitor)
: monitor_runner_(monitor_runner),
monitor_(std::move(monitor)),
last_focused_element_(ElementType::UNKNOWN) {}
SettingsAppMonitor::AutomationControllerDelegate::
~AutomationControllerDelegate() = default;
void SettingsAppMonitor::AutomationControllerDelegate::OnInitialized(
HRESULT result) const {
monitor_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SettingsAppMonitor::OnInitialized, monitor_, result));
}
void SettingsAppMonitor::AutomationControllerDelegate::ConfigureCacheRequest(
IUIAutomationCacheRequest* cache_request) const {
::ConfigureCacheRequest(cache_request);
}
void SettingsAppMonitor::AutomationControllerDelegate::OnAutomationEvent(
IUIAutomation* automation,
IUIAutomationElement* sender,
EVENTID event_id) const {
switch (DetectElementType(automation, sender)) {
case ElementType::DEFAULT_BROWSER:
monitor_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SettingsAppMonitor::OnChooserInvoked, monitor_));
break;
case ElementType::BROWSER_BUTTON: {
base::string16 browser_name(
GetCachedBstrValue(sender, UIA_NamePropertyId));
if (!browser_name.empty()) {
monitor_runner_->PostTask(
FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnBrowserChosen,
monitor_, browser_name));
}
break;
}
case ElementType::SWITCH_ANYWAY:
monitor_runner_->PostTask(
FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnPromoChoiceMade,
monitor_, false));
break;
case ElementType::CHECK_IT_OUT:
monitor_runner_->PostTask(
FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnPromoChoiceMade,
monitor_, true));
break;
case ElementType::UNKNOWN:
break;
}
}
void SettingsAppMonitor::AutomationControllerDelegate::OnFocusChangedEvent(
IUIAutomation* automation,
IUIAutomationElement* sender) const {
ElementType element_type = DetectElementType(automation, sender);
{
// Duplicate focus changed events are suppressed.
base::AutoLock auto_lock(last_focused_element_lock_);
if (last_focused_element_ == element_type)
return;
last_focused_element_ = element_type;
}
if (element_type == ElementType::DEFAULT_BROWSER) {
monitor_runner_->PostTask(
FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnAppFocused, monitor_));
} else if (element_type == ElementType::CHECK_IT_OUT) {
monitor_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SettingsAppMonitor::OnPromoFocused, monitor_));
}
}
SettingsAppMonitor::SettingsAppMonitor(Delegate* delegate)
: delegate_(delegate), weak_ptr_factory_(this) {
// A fully initialized WeakPtrFactory is needed to create the
// AutomationControllerDelegate.
auto automation_controller_delegate =
std::make_unique<SettingsAppMonitor::AutomationControllerDelegate>(
base::SequencedTaskRunnerHandle::Get(),
weak_ptr_factory_.GetWeakPtr());
automation_controller_ = std::make_unique<AutomationController>(
std::move(automation_controller_delegate));
}
SettingsAppMonitor::~SettingsAppMonitor() = default;
void SettingsAppMonitor::OnInitialized(HRESULT result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnInitialized(result);
}
void SettingsAppMonitor::OnAppFocused() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnAppFocused();
}
void SettingsAppMonitor::OnChooserInvoked() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnChooserInvoked();
}
void SettingsAppMonitor::OnBrowserChosen(const base::string16& browser_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnBrowserChosen(browser_name);
}
void SettingsAppMonitor::OnPromoFocused() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnPromoFocused();
}
void SettingsAppMonitor::OnPromoChoiceMade(bool accept_promo) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delegate_->OnPromoChoiceMade(accept_promo);
}