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