1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/win/settings_app_monitor.h"
6
7 #include <wrl/client.h>
8
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/feature_list.h"
13 #include "base/location.h"
14 #include "base/memory/scoped_refptr.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/strings/pattern.h"
18 #include "base/strings/string16.h"
19 #include "base/synchronization/lock.h"
20 #include "base/threading/sequenced_task_runner_handle.h"
21 #include "base/win/scoped_variant.h"
22 #include "chrome/browser/win/automation_controller.h"
23 #include "chrome/browser/win/ui_automation_util.h"
24 #include "chrome/common/chrome_features.h"
25
26 namespace {
27
28 // Each item represent one UI element in the Settings App.
29 enum class ElementType {
30 // The "Web browser" element in the "Default apps" pane.
31 DEFAULT_BROWSER,
32 // The element representing a browser in the "Choose an app" popup.
33 BROWSER_BUTTON,
34 // The button labeled "Check it out" that leaves Edge as the default browser.
35 CHECK_IT_OUT,
36 // The button labeled "Switch Anyway" that dismisses the Edge promo.
37 SWITCH_ANYWAY,
38 // Any other element.
39 UNKNOWN,
40 };
41
42 // Configures a cache request so that it includes all properties needed by
43 // DetectElementType() to detect the elements of interest.
ConfigureCacheRequest(IUIAutomationCacheRequest * cache_request)44 void ConfigureCacheRequest(IUIAutomationCacheRequest* cache_request) {
45 DCHECK(cache_request);
46 cache_request->AddProperty(UIA_AutomationIdPropertyId);
47 cache_request->AddProperty(UIA_NamePropertyId);
48 cache_request->AddProperty(UIA_ClassNamePropertyId);
49 cache_request->AddPattern(UIA_InvokePatternId);
50 }
51
52 // Helper function to get the parent element with class name "Flyout". Used to
53 // determine the |element|'s type.
GetFlyoutParentAutomationId(IUIAutomation * automation,IUIAutomationElement * element)54 base::string16 GetFlyoutParentAutomationId(IUIAutomation* automation,
55 IUIAutomationElement* element) {
56 // Create a condition that will include only elements with the right class
57 // name in the tree view.
58 base::win::ScopedVariant class_name(L"Flyout");
59 Microsoft::WRL::ComPtr<IUIAutomationCondition> condition;
60 HRESULT result = automation->CreatePropertyCondition(UIA_ClassNamePropertyId,
61 class_name, &condition);
62 if (FAILED(result))
63 return base::string16();
64
65 Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
66 result = automation->CreateTreeWalker(condition.Get(), &tree_walker);
67 if (FAILED(result))
68 return base::string16();
69
70 Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
71 result = automation->CreateCacheRequest(&cache_request);
72 if (FAILED(result))
73 return base::string16();
74 ConfigureCacheRequest(cache_request.Get());
75
76 // From MSDN, NormalizeElementBuildCache() "Retrieves the ancestor element
77 // nearest to the specified Microsoft UI Automation element in the tree view".
78 IUIAutomationElement* flyout_element = nullptr;
79 result = tree_walker->NormalizeElementBuildCache(element, cache_request.Get(),
80 &flyout_element);
81 if (FAILED(result) || !flyout_element)
82 return base::string16();
83
84 return GetCachedBstrValue(flyout_element, UIA_AutomationIdPropertyId);
85 }
86
DetectElementType(IUIAutomation * automation,IUIAutomationElement * sender)87 ElementType DetectElementType(IUIAutomation* automation,
88 IUIAutomationElement* sender) {
89 DCHECK(automation);
90 DCHECK(sender);
91 base::string16 aid(GetCachedBstrValue(sender, UIA_AutomationIdPropertyId));
92 if (aid == L"SystemSettings_DefaultApps_Browser_Button")
93 return ElementType::DEFAULT_BROWSER;
94 if (aid == L"SystemSettings_DefaultApps_Browser_App0_HyperlinkButton")
95 return ElementType::SWITCH_ANYWAY;
96 if (base::MatchPattern(aid, L"SystemSettings_DefaultApps_Browser_*_Button")) {
97 // This element type depends on the automation id of one of its ancestors.
98 base::string16 automation_id =
99 GetFlyoutParentAutomationId(automation, sender);
100 if (automation_id == L"settingsFlyout")
101 return ElementType::CHECK_IT_OUT;
102 else if (automation_id == L"DefaultAppsFlyoutPresenter")
103 return ElementType::BROWSER_BUTTON;
104 }
105 return ElementType::UNKNOWN;
106 }
107
108 } // namespace
109
110 class SettingsAppMonitor::AutomationControllerDelegate
111 : public AutomationController::Delegate {
112 public:
113 AutomationControllerDelegate(
114 scoped_refptr<base::SequencedTaskRunner> monitor_runner,
115 base::WeakPtr<SettingsAppMonitor> monitor);
116 ~AutomationControllerDelegate() override;
117
118 // AutomationController::Delegate:
119 void OnInitialized(HRESULT result) const override;
120 void ConfigureCacheRequest(
121 IUIAutomationCacheRequest* cache_request) const override;
122 void OnAutomationEvent(IUIAutomation* automation,
123 IUIAutomationElement* sender,
124 EVENTID event_id) const override;
125 void OnFocusChangedEvent(IUIAutomation* automation,
126 IUIAutomationElement* sender) const override;
127
128 private:
129 // Invokes the |browser_button| if the Win10AcceleratedDefaultBrowserFlow
130 // feature is enabled.
131 void MaybeInvokeChooser(IUIAutomationElement* browser_button) const;
132
133 // The task runner on which the SettingsAppMonitor lives.
134 const scoped_refptr<base::SequencedTaskRunner> monitor_runner_;
135
136 // Only used to post callbacks to |monitor_runner_|;
137 const base::WeakPtr<SettingsAppMonitor> monitor_;
138
139 // Protect against concurrent accesses to |last_focused_element_|.
140 mutable base::Lock last_focused_element_lock_;
141
142 // State to suppress duplicate "focus changed" events.
143 mutable ElementType last_focused_element_;
144
145 // Protect against concurrent accesses to |browser_chooser_invoked_|.
146 mutable base::Lock browser_chooser_invoked_lock_;
147
148 // The browser chooser must only be invoked once.
149 mutable bool browser_chooser_invoked_;
150
151 DISALLOW_COPY_AND_ASSIGN(AutomationControllerDelegate);
152 };
153
AutomationControllerDelegate(scoped_refptr<base::SequencedTaskRunner> monitor_runner,base::WeakPtr<SettingsAppMonitor> monitor)154 SettingsAppMonitor::AutomationControllerDelegate::AutomationControllerDelegate(
155 scoped_refptr<base::SequencedTaskRunner> monitor_runner,
156 base::WeakPtr<SettingsAppMonitor> monitor)
157 : monitor_runner_(monitor_runner),
158 monitor_(std::move(monitor)),
159 last_focused_element_(ElementType::UNKNOWN),
160 browser_chooser_invoked_(false) {}
161
162 SettingsAppMonitor::AutomationControllerDelegate::
163 ~AutomationControllerDelegate() = default;
164
OnInitialized(HRESULT result) const165 void SettingsAppMonitor::AutomationControllerDelegate::OnInitialized(
166 HRESULT result) const {
167 monitor_runner_->PostTask(
168 FROM_HERE,
169 base::BindOnce(&SettingsAppMonitor::OnInitialized, monitor_, result));
170 }
171
ConfigureCacheRequest(IUIAutomationCacheRequest * cache_request) const172 void SettingsAppMonitor::AutomationControllerDelegate::ConfigureCacheRequest(
173 IUIAutomationCacheRequest* cache_request) const {
174 ::ConfigureCacheRequest(cache_request);
175 }
176
OnAutomationEvent(IUIAutomation * automation,IUIAutomationElement * sender,EVENTID event_id) const177 void SettingsAppMonitor::AutomationControllerDelegate::OnAutomationEvent(
178 IUIAutomation* automation,
179 IUIAutomationElement* sender,
180 EVENTID event_id) const {
181 switch (DetectElementType(automation, sender)) {
182 case ElementType::DEFAULT_BROWSER:
183 monitor_runner_->PostTask(
184 FROM_HERE,
185 base::BindOnce(&SettingsAppMonitor::OnChooserInvoked, monitor_));
186 break;
187 case ElementType::BROWSER_BUTTON: {
188 base::string16 browser_name(
189 GetCachedBstrValue(sender, UIA_NamePropertyId));
190 if (!browser_name.empty()) {
191 monitor_runner_->PostTask(
192 FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnBrowserChosen,
193 monitor_, browser_name));
194 }
195 break;
196 }
197 case ElementType::SWITCH_ANYWAY:
198 monitor_runner_->PostTask(
199 FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnPromoChoiceMade,
200 monitor_, false));
201 break;
202 case ElementType::CHECK_IT_OUT:
203 monitor_runner_->PostTask(
204 FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnPromoChoiceMade,
205 monitor_, true));
206 break;
207 case ElementType::UNKNOWN:
208 break;
209 }
210 }
211
OnFocusChangedEvent(IUIAutomation * automation,IUIAutomationElement * sender) const212 void SettingsAppMonitor::AutomationControllerDelegate::OnFocusChangedEvent(
213 IUIAutomation* automation,
214 IUIAutomationElement* sender) const {
215 ElementType element_type = DetectElementType(automation, sender);
216 {
217 // Duplicate focus changed events are suppressed.
218 base::AutoLock auto_lock(last_focused_element_lock_);
219 if (last_focused_element_ == element_type)
220 return;
221 last_focused_element_ = element_type;
222 }
223
224 if (element_type == ElementType::DEFAULT_BROWSER) {
225 MaybeInvokeChooser(sender);
226 monitor_runner_->PostTask(
227 FROM_HERE, base::BindOnce(&SettingsAppMonitor::OnAppFocused, monitor_));
228 } else if (element_type == ElementType::CHECK_IT_OUT) {
229 monitor_runner_->PostTask(
230 FROM_HERE,
231 base::BindOnce(&SettingsAppMonitor::OnPromoFocused, monitor_));
232 }
233 }
234
MaybeInvokeChooser(IUIAutomationElement * browser_button) const235 void SettingsAppMonitor::AutomationControllerDelegate::MaybeInvokeChooser(
236 IUIAutomationElement* browser_button) const {
237 if (!base::FeatureList::IsEnabled(
238 features::kWin10AcceleratedDefaultBrowserFlow)) {
239 return;
240 }
241
242 {
243 // Only invoke the browser chooser once.
244 base::AutoLock auto_lock(browser_chooser_invoked_lock_);
245 if (browser_chooser_invoked_)
246 return;
247 browser_chooser_invoked_ = true;
248 }
249
250 // Invoke the dialog and record whether it was successful.
251 Microsoft::WRL::ComPtr<IUIAutomationInvokePattern> invoke_pattern;
252 bool succeeded = SUCCEEDED(browser_button->GetCachedPatternAs(
253 UIA_InvokePatternId, IID_PPV_ARGS(&invoke_pattern))) &&
254 invoke_pattern && SUCCEEDED(invoke_pattern->Invoke());
255
256 UMA_HISTOGRAM_BOOLEAN("DefaultBrowser.Win10ChooserInvoked", succeeded);
257 }
258
SettingsAppMonitor(Delegate * delegate)259 SettingsAppMonitor::SettingsAppMonitor(Delegate* delegate)
260 : delegate_(delegate) {
261 // A fully initialized WeakPtrFactory is needed to create the
262 // AutomationControllerDelegate.
263 auto automation_controller_delegate =
264 std::make_unique<SettingsAppMonitor::AutomationControllerDelegate>(
265 base::SequencedTaskRunnerHandle::Get(),
266 weak_ptr_factory_.GetWeakPtr());
267
268 automation_controller_ = std::make_unique<AutomationController>(
269 std::move(automation_controller_delegate));
270 }
271
272 SettingsAppMonitor::~SettingsAppMonitor() = default;
273
OnInitialized(HRESULT result)274 void SettingsAppMonitor::OnInitialized(HRESULT result) {
275 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
276 delegate_->OnInitialized(result);
277 }
278
OnAppFocused()279 void SettingsAppMonitor::OnAppFocused() {
280 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
281 delegate_->OnAppFocused();
282 }
283
OnChooserInvoked()284 void SettingsAppMonitor::OnChooserInvoked() {
285 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
286 delegate_->OnChooserInvoked();
287 }
288
OnBrowserChosen(const base::string16 & browser_name)289 void SettingsAppMonitor::OnBrowserChosen(const base::string16& browser_name) {
290 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
291 delegate_->OnBrowserChosen(browser_name);
292 }
293
OnPromoFocused()294 void SettingsAppMonitor::OnPromoFocused() {
295 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
296 delegate_->OnPromoFocused();
297 }
298
OnPromoChoiceMade(bool accept_promo)299 void SettingsAppMonitor::OnPromoChoiceMade(bool accept_promo) {
300 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301 delegate_->OnPromoChoiceMade(accept_promo);
302 }
303