1 // Copyright 2017 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/taskbar_icon_finder.h"
6 
7 #include <windows.h>
8 #include <wrl/client.h>
9 
10 #include <objbase.h>
11 #include <oleauto.h>
12 #include <uiautomation.h>
13 
14 #include <utility>
15 #include <vector>
16 
17 #include "base/bind.h"
18 #include "base/location.h"
19 #include "base/logging.h"
20 #include "base/macros.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/sequenced_task_runner.h"
23 #include "base/task/thread_pool.h"
24 #include "base/threading/sequenced_task_runner_handle.h"
25 #include "base/win/com_init_util.h"
26 #include "base/win/scoped_variant.h"
27 #include "chrome/installer/util/install_util.h"
28 #include "chrome/installer/util/shell_util.h"
29 #include "ui/display/win/screen_win.h"
30 #include "ui/gfx/geometry/rect.h"
31 
32 namespace {
33 
34 // TaskbarIconFinder -----------------------------------------------------------
35 
36 // A class that uses UIAutomation in a COM multi-threaded apartment thread to
37 // find the bounding rectangle of Chrome's taskbar icon on the system's primary
38 // monitor.
39 class TaskbarIconFinder {
40  public:
41   // Constructs a new finder and immediately starts running it on a dedicated
42   // automation task in a multi-threaded COM apartment.
43   explicit TaskbarIconFinder(TaskbarIconFinderResultCallback result_callback);
44 
45  private:
46   // Receives the result computed on the automation task, passes the results to
47   // the caller, then self-destructs.
48   void OnComplete(const gfx::Rect& rect);
49 
50   // Main function for the finder's automation task. Bounces the results of
51   // the operation back to OnComplete on the caller's sequenced task runner
52   // (|finder_runner|).
53   static void RunOnComTask(
54       scoped_refptr<base::SequencedTaskRunner> finder_runner,
55       TaskbarIconFinder* finder);
56 
57   // Returns the values of the |property_id| property (of type VT_R8 | VT_ARRAY)
58   // cached in |element|. May only be used on the automation task.
59   static std::vector<double> GetCachedDoubleArrayValue(
60       IUIAutomationElement* element,
61       PROPERTYID property_id);
62 
63   // Populates |rect| with the bounding rectangle of any item in |icons| that is
64   // on the primary monitor. |rect| is unmodified if no such item/rect is found.
65   // May only be used on the automation task.
66   static void FindRectOnPrimaryMonitor(IUIAutomation* automation,
67                                        IUIAutomationElementArray* icons,
68                                        gfx::Rect* rect);
69 
70   // Finds an item with an automation id matching Chrome's app user model id.
71   // Returns the first failure HRESULT, or the final success HRESULT. On
72   // success, |rect| is populated with the bouning rectangle of the icon if
73   // found.
74   static HRESULT DoOnComTask(gfx::Rect* rect);
75 
76   // The caller's callback.
77   TaskbarIconFinderResultCallback result_callback_;
78 
79   DISALLOW_COPY_AND_ASSIGN(TaskbarIconFinder);
80 };
81 
TaskbarIconFinder(TaskbarIconFinderResultCallback result_callback)82 TaskbarIconFinder::TaskbarIconFinder(
83     TaskbarIconFinderResultCallback result_callback)
84     : result_callback_(std::move(result_callback)) {
85   DCHECK(result_callback_);
86 
87   // Since all threads servicing the base::ThreadPool initialize COM into the
88   // MTA and only one task is needed for this job, it is sufficient to post a
89   // simple task here. Should automation event handlers be needed or more than
90   // one task, care must be taken to follow proper threading rules as required
91   // for automation clients.
92   base::ThreadPool::PostTask(
93       FROM_HERE, base::BindOnce(&TaskbarIconFinder::RunOnComTask,
94                                 base::SequencedTaskRunnerHandle::Get(),
95                                 base::Unretained(this)));
96 }
97 
OnComplete(const gfx::Rect & rect)98 void TaskbarIconFinder::OnComplete(const gfx::Rect& rect) {
99   std::move(result_callback_).Run(rect);
100   delete this;
101 }
102 
103 // static
RunOnComTask(scoped_refptr<base::SequencedTaskRunner> finder_runner,TaskbarIconFinder * finder)104 void TaskbarIconFinder::RunOnComTask(
105     scoped_refptr<base::SequencedTaskRunner> finder_runner,
106     TaskbarIconFinder* finder) {
107   // This and all methods below must be called on the automation task.
108   DCHECK(!finder_runner->RunsTasksInCurrentSequence());
109 
110   gfx::Rect rect;
111   DoOnComTask(&rect);
112   finder_runner->PostTask(FROM_HERE,
113                           base::BindOnce(&TaskbarIconFinder::OnComplete,
114                                          base::Unretained(finder), rect));
115 }
116 
117 // static
GetCachedDoubleArrayValue(IUIAutomationElement * element,PROPERTYID property_id)118 std::vector<double> TaskbarIconFinder::GetCachedDoubleArrayValue(
119     IUIAutomationElement* element,
120     PROPERTYID property_id) {
121   base::win::AssertComApartmentType(base::win::ComApartmentType::MTA);
122 
123   std::vector<double> values;
124   base::win::ScopedVariant var;
125 
126   if (FAILED(element->GetCachedPropertyValueEx(property_id, TRUE,
127                                                var.Receive()))) {
128     return values;
129   }
130 
131   if (V_VT(var.ptr()) != (VT_R8 | VT_ARRAY)) {
132     LOG_IF(ERROR, V_VT(var.ptr()) != VT_UNKNOWN)
133         << __func__ << " property is not an R8 array: " << V_VT(var.ptr());
134     return values;
135   }
136 
137   SAFEARRAY* array = V_ARRAY(var.ptr());
138   if (SafeArrayGetDim(array) != 1)
139     return values;
140   long lower_bound = 0;
141   long upper_bound = 0;
142   SafeArrayGetLBound(array, 1, &lower_bound);
143   SafeArrayGetUBound(array, 1, &upper_bound);
144   if (lower_bound || upper_bound <= lower_bound)
145     return values;
146   double* data = nullptr;
147   SafeArrayAccessData(array, reinterpret_cast<void**>(&data));
148   values.assign(data, data + upper_bound + 1);
149   SafeArrayUnaccessData(array);
150 
151   return values;
152 }
153 
154 // static
FindRectOnPrimaryMonitor(IUIAutomation * automation,IUIAutomationElementArray * icons,gfx::Rect * rect)155 void TaskbarIconFinder::FindRectOnPrimaryMonitor(
156     IUIAutomation* automation,
157     IUIAutomationElementArray* icons,
158     gfx::Rect* rect) {
159   base::win::AssertComApartmentType(base::win::ComApartmentType::MTA);
160 
161   int length = 0;
162   icons->get_Length(&length);
163 
164   // Find each icon's nearest ancestor with an HWND.
165   Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
166   HRESULT result = automation->get_RawViewWalker(&tree_walker);
167   if (FAILED(result) || !tree_walker)
168     return;
169   Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
170   result = automation->CreateCacheRequest(&cache_request);
171   if (FAILED(result) || !cache_request)
172     return;
173   cache_request->AddProperty(UIA_NativeWindowHandlePropertyId);
174 
175   Microsoft::WRL::ComPtr<IUIAutomationElement> icon;
176   HWND hwnd = 0;
177   for (int i = 0; i < length; ++i) {
178     icons->GetElement(i, &icon);
179 
180     // Walk up the tree to find the icon's first parent with an HWND.
181     Microsoft::WRL::ComPtr<IUIAutomationElement> search = icon;
182     while (true) {
183       Microsoft::WRL::ComPtr<IUIAutomationElement> parent;
184       result = tree_walker->GetParentElementBuildCache(
185           search.Get(), cache_request.Get(), &parent);
186       if (FAILED(result) || !parent)
187         break;
188       base::win::ScopedVariant var;
189       result = parent->GetCachedPropertyValueEx(
190           UIA_NativeWindowHandlePropertyId, TRUE, var.Receive());
191       if (FAILED(result))
192         break;
193       hwnd = reinterpret_cast<HWND>(V_I4(var.ptr()));
194       if (hwnd)
195         break;  // Found.
196       search.Reset();
197       std::swap(parent, search);
198     }
199 
200     // No parent hwnd found for this icon.
201     if (!hwnd)
202       continue;
203 
204     // Is this icon's window on the primary monitor?
205     HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
206     MONITORINFO monitor_info = {};
207     monitor_info.cbSize = sizeof(monitor_info);
208     if (monitor && ::GetMonitorInfo(monitor, &monitor_info) &&
209         (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0) {
210       break;  // All done.
211     }
212     icon.Reset();
213   }
214 
215   if (!icon)
216     return;  // No taskbar icon found on the primary monitor.
217 
218   std::vector<double> bounding_rect =
219       GetCachedDoubleArrayValue(icon.Get(), UIA_BoundingRectanglePropertyId);
220   if (!bounding_rect.empty()) {
221     gfx::Rect screen_rect(bounding_rect[0], bounding_rect[1], bounding_rect[2],
222                           bounding_rect[3]);
223     *rect = display::win::ScreenWin::ScreenToDIPRect(hwnd, screen_rect);
224   }
225 }
226 
227 // static
DoOnComTask(gfx::Rect * rect)228 HRESULT TaskbarIconFinder::DoOnComTask(gfx::Rect* rect) {
229   base::win::AssertComApartmentType(base::win::ComApartmentType::MTA);
230 
231   Microsoft::WRL::ComPtr<IUIAutomation> automation;
232   HRESULT result =
233       ::CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER,
234                          IID_PPV_ARGS(&automation));
235   if (FAILED(result) || !automation)
236     return result;
237 
238   // Create a condition: automation_id=ap_user_model_id && type=button_type.
239   base::win::ScopedVariant app_user_model_id(
240       ShellUtil::GetBrowserModelId(InstallUtil::IsPerUserInstall()).c_str());
241   Microsoft::WRL::ComPtr<IUIAutomationCondition> id_condition;
242   result = automation->CreatePropertyCondition(
243       UIA_AutomationIdPropertyId, app_user_model_id, &id_condition);
244   if (FAILED(result) || !id_condition)
245     return result;
246 
247   base::win::ScopedVariant button_type(UIA_ButtonControlTypeId);
248   Microsoft::WRL::ComPtr<IUIAutomationCondition> type_condition;
249   result = automation->CreatePropertyCondition(UIA_ControlTypePropertyId,
250                                                button_type, &type_condition);
251   if (FAILED(result) || !type_condition)
252     return result;
253 
254   Microsoft::WRL::ComPtr<IUIAutomationCondition> condition;
255   result = automation->CreateAndCondition(id_condition.Get(),
256                                           type_condition.Get(), &condition);
257 
258   // Cache the bounding rectangle of all found items.
259   Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
260   result = automation->CreateCacheRequest(&cache_request);
261   if (FAILED(result) || !cache_request)
262     return result;
263   cache_request->AddProperty(UIA_BoundingRectanglePropertyId);
264 
265   // Search the desktop to find all buttons with the correct automation id.
266   Microsoft::WRL::ComPtr<IUIAutomationElement> desktop;
267   result = automation->GetRootElement(&desktop);
268   if (FAILED(result) || !desktop)
269     return result;
270 
271   Microsoft::WRL::ComPtr<IUIAutomationElementArray> icons;
272   result = desktop->FindAllBuildCache(TreeScope_Subtree, condition.Get(),
273                                       cache_request.Get(), &icons);
274   if (FAILED(result) || !icons)
275     return result;
276 
277   // Pick the icon on the primary monitor.
278   FindRectOnPrimaryMonitor(automation.Get(), icons.Get(), rect);
279   return S_OK;
280 }
281 
282 }  // namespace
283 
FindTaskbarIcon(TaskbarIconFinderResultCallback result_callback)284 void FindTaskbarIcon(TaskbarIconFinderResultCallback result_callback) {
285   DCHECK(result_callback);
286   // The instance self-destructs in OnComplete.
287   new TaskbarIconFinder(std::move(result_callback));
288 }
289