33#include < uiautomation.h>
44#include < wrl/client.h>
55
6+ #include < algorithm>
7+ #include < initializer_list>
68#include < optional>
79#include < string_view>
810
@@ -64,7 +66,7 @@ UiaThreadState& GetUiaThreadState() {
6466 return state;
6567}
6668
67- struct TabContext {
69+ struct TabContainer {
6870 ComPtr<IUIAutomationElement> container;
6971 std::wstring_view tab_class;
7072};
@@ -145,7 +147,7 @@ std::optional<std::wstring> GetCurrentStringProperty(
145147 return std::wstring (property.Ref ().bstrVal );
146148}
147149
148- ComPtr<IUIAutomationCondition> MakeClassCondition (
150+ ComPtr<IUIAutomationCondition> CreateClassCondition (
149151 const ComPtr<IUIAutomation>& automation,
150152 std::wstring_view class_name) {
151153 if (!automation || class_name.empty ()) {
@@ -170,20 +172,24 @@ ComPtr<IUIAutomationCondition> MakeClassCondition(
170172}
171173
172174bool IsClassName (const ComPtr<IUIAutomationElement>& element,
173- std::wstring_view class_name_target) {
174- if (!element) {
175+ std::wstring_view expected_class_name) {
176+ const auto actual_class_name =
177+ GetCurrentStringProperty (element, UIA_ClassNamePropertyId);
178+ if (!actual_class_name.has_value ()) {
175179 return false ;
176180 }
181+ return actual_class_name.value () == expected_class_name;
182+ }
177183
178- BSTR class_name = nullptr ;
179- if (FAILED (element->get_CurrentClassName (&class_name)) || !class_name) {
184+ bool IsAnyClassName (
185+ const ComPtr<IUIAutomationElement>& element,
186+ std::initializer_list<std::wstring_view> expected_class_names) {
187+ const auto actual_class_name =
188+ GetCurrentStringProperty (element, UIA_ClassNamePropertyId);
189+ if (!actual_class_name.has_value ()) {
180190 return false ;
181191 }
182-
183- const std::wstring_view class_name_view (class_name);
184- const bool is_match = (class_name_view == class_name_target);
185- SysFreeString (class_name);
186- return is_match;
192+ return std::ranges::contains (expected_class_names, actual_class_name.value ());
187193}
188194
189195ComPtr<IUIAutomationElement> FindFirstInSubtree (
@@ -236,7 +242,35 @@ ComPtr<IUIAutomationElement> FindSiblingByClass(
236242 return nullptr ;
237243}
238244
239- std::optional<TabContext> ResolveTabContext (
245+ ComPtr<IUIAutomationElement> FindParentByClass (
246+ const ComPtr<IUIAutomation>& automation,
247+ const ComPtr<IUIAutomationElement>& element,
248+ std::wstring_view class_name) {
249+ if (!automation || !element) {
250+ return nullptr ;
251+ }
252+
253+ ComPtr<IUIAutomationTreeWalker> walker;
254+ if (FAILED (automation->get_ControlViewWalker (&walker)) || !walker) {
255+ return nullptr ;
256+ }
257+
258+ ComPtr<IUIAutomationElement> current = element;
259+ while (true ) {
260+ ComPtr<IUIAutomationElement> parent;
261+ if (FAILED (walker->GetParentElement (current.Get (),
262+ parent.ReleaseAndGetAddressOf ())) ||
263+ !parent) {
264+ return nullptr ;
265+ }
266+ if (IsClassName (parent, class_name)) {
267+ return parent;
268+ }
269+ current = std::move (parent);
270+ }
271+ }
272+
273+ std::optional<TabContainer> FindTabContainer (
240274 const ComPtr<IUIAutomation>& automation,
241275 POINT pt) {
242276 auto pointed = GetElementFromPoint (automation, pt);
@@ -248,11 +282,11 @@ std::optional<TabContext> ResolveTabContext(
248282 if (IsClassName (pointed, L" ScrollView" )) {
249283 auto container = FindFirstInSubtree (
250284 pointed,
251- MakeClassCondition (automation, L" VerticalUnpinnedTabContainerView" ));
285+ CreateClassCondition (automation, L" VerticalUnpinnedTabContainerView" ));
252286 if (!container) {
253287 return std::nullopt ;
254288 }
255- return TabContext {container, L" VerticalTabView" };
289+ return TabContainer {container, L" VerticalTabView" };
256290 }
257291
258292 // horizontal tabs
@@ -262,28 +296,28 @@ std::optional<TabContext> ResolveTabContext(
262296 if (!container) {
263297 return std::nullopt ;
264298 }
265- return TabContext {container, L" Tab" };
299+ return TabContainer {container, L" Tab" };
266300 }
267301
268302 return std::nullopt ;
269303}
270304
271305} // namespace
272306
273- std::optional<TabHitResult> GetTabHitResult (POINT pt, bool need_count) {
307+ std::optional<TabHitResult> FindTabHitResult (POINT pt, bool need_count) {
274308 if (!EnsureAutomation ()) {
275309 return std::nullopt ;
276310 }
277311 const auto & automation = GetUiaThreadState ().automation ;
278312
279- auto ctx = ResolveTabContext (automation, pt);
280- if (!ctx ) {
313+ auto tab = FindTabContainer (automation, pt);
314+ if (!tab ) {
281315 return std::nullopt ;
282316 }
283317
284- auto condition = MakeClassCondition (automation, ctx ->tab_class );
318+ auto condition = CreateClassCondition (automation, tab ->tab_class );
285319 ComPtr<IUIAutomationElementArray> arr;
286- if (FAILED (ctx ->container ->FindAll (TreeScope_Children, condition.Get (),
320+ if (FAILED (tab ->container ->FindAll (TreeScope_Children, condition.Get (),
287321 arr.ReleaseAndGetAddressOf ())) ||
288322 !arr) {
289323 return std::nullopt ;
@@ -312,7 +346,7 @@ std::optional<TabHitResult> GetTabHitResult(POINT pt, bool need_count) {
312346
313347 result.tab = tab;
314348 auto close_btn = FindFirstInSubtree (
315- tab, MakeClassCondition (automation, L" TabCloseButton" ));
349+ tab, CreateClassCondition (automation, L" TabCloseButton" ));
316350 if (close_btn) {
317351 RECT close_rect;
318352 if (SUCCEEDED (close_btn->get_CurrentBoundingRectangle (&close_rect))) {
@@ -325,6 +359,28 @@ std::optional<TabHitResult> GetTabHitResult(POINT pt, bool need_count) {
325359 return result;
326360}
327361
362+ bool IsOnTabBar (POINT pt) {
363+ if (!EnsureAutomation ()) {
364+ return false ;
365+ }
366+ const auto & automation = GetUiaThreadState ().automation ;
367+
368+ const auto pointed = GetElementFromPoint (automation, pt);
369+ if (!pointed) {
370+ return false ;
371+ }
372+
373+ // Fast path to avoid `GetParentElement` calls for common tab bar elements.
374+ if (IsAnyClassName (
375+ pointed,
376+ {L" HorizontalTabStripRegionView" , L" TabStrip::TabDragContextImpl" ,
377+ L" TabStripControlButton" , L" FrameGrabHandle" , L" ScrollView" })) {
378+ return true ;
379+ }
380+ return FindParentByClass (automation, pointed,
381+ L" HorizontalTabStripRegionView" ) != nullptr ;
382+ }
383+
328384bool IsOnBookmark (POINT pt) {
329385 if (!EnsureAutomation ()) {
330386 return false ;
@@ -350,9 +406,7 @@ bool IsOnBookmark(POINT pt) {
350406 if (is_blocked_scheme || !looks_like_url) {
351407 return false ;
352408 }
353-
354- return IsClassName (pointed, L" BookmarkButton" ) ||
355- IsClassName (pointed, L" MenuItemView" );
409+ return IsAnyClassName (pointed, {L" BookmarkButton" , L" MenuItemView" });
356410}
357411
358412bool IsOmniboxFocused () {
@@ -365,6 +419,5 @@ bool IsOmniboxFocused() {
365419 if (!focused) {
366420 return false ;
367421 }
368- return IsClassName (focused, L" OmniboxViewViews" ) ||
369- IsClassName (focused, L" OmniboxResultView" );
422+ return IsAnyClassName (focused, {L" OmniboxViewViews" , L" OmniboxResultView" });
370423}
0 commit comments