123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ui/base/test/view_tree_validator.h"
- #include <Cocoa/Cocoa.h>
- #include "base/mac/mac_util.h"
- #include "base/strings/sys_string_conversions.h"
- namespace {
- NSArray* CollectSubviews(NSView* root) {
- NSMutableArray* subviews = [NSMutableArray arrayWithObject:root];
- for (NSView* child in root.subviews) {
- [subviews addObjectsFromArray:CollectSubviews(child)];
- }
- return subviews;
- }
- bool ViewsOverlap(NSView* a, NSView* b) {
- NSRect a_frame = [a convertRect:a.bounds toView:nil];
- NSRect b_frame = [b convertRect:b.bounds toView:nil];
- return NSIntersectsRect(a_frame, b_frame);
- }
- bool IsLocalizable(NSView* view) {
- return [view isKindOfClass:[NSControl class]] ||
- [view isKindOfClass:[NSText class]];
- }
- // Returns whether to expect children of |view| to perhaps not fit within its
- // bounds.
- bool IgnoreChildBoundsChecks(NSView* view) {
- // On macOS 10.14+, NSButton has a subview of a private helper class whose
- // bounds extend a bit outside the NSButton itself. We don't care about this
- // helper class's bounds being outside the button.
- return base::mac::IsAtLeastOS10_14() && [view isKindOfClass:[NSButton class]];
- }
- } // namespace
- namespace ui {
- absl::optional<ViewTreeProblemDetails> ValidateViewTree(NSView* root) {
- NSArray* allViews = CollectSubviews(root);
- for (NSView* view in allViews) {
- // 1: Check that every subview's frame lies entirely inside this view's
- // bounds.
- for (NSView* child in view.subviews) {
- if (!NSContainsRect(view.bounds, child.frame) &&
- !IgnoreChildBoundsChecks(view)) {
- return absl::optional<ViewTreeProblemDetails>(
- {ViewTreeProblemDetails::VIEW_OUTSIDE_PARENT, child, view});
- }
- }
- // If |view| isn't localizable, skip the rest of the checks.
- if (!IsLocalizable(view))
- continue;
- // 2: Check that every other subview either:
- // a: doesn't overlap this view
- // b: is a descendant of this view
- // c: has this view as a descendant
- // note that a view is its own descendant.
- for (NSView* other in allViews) {
- if (!ViewsOverlap(view, other))
- continue;
- if ([view isDescendantOf:other] || [other isDescendantOf:view])
- continue;
- return absl::optional<ViewTreeProblemDetails>(
- {ViewTreeProblemDetails::VIEWS_OVERLAP, view, other});
- }
- }
- return absl::nullopt;
- }
- std::string ViewTreeProblemDetails::ToString() {
- NSString* s;
- switch (type) {
- case VIEW_OUTSIDE_PARENT:
- s = [NSString stringWithFormat:@"View %@ [%@] outside parent %@ [%@]",
- view_a, NSStringFromRect(view_a.frame),
- view_b, NSStringFromRect(view_b.frame)];
- break;
- case VIEWS_OVERLAP:
- s = [NSString stringWithFormat:@"Views %@ [%@] and %@ [%@] overlap",
- view_a, NSStringFromRect(view_a.frame),
- view_b, NSStringFromRect(view_b.frame)];
- break;
- }
- return base::SysNSStringToUTF8(s);
- }
- } // namespace ui
|