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