blob: 0c5960012087df57d8a25a5c44301111c28cf996 [file] [log] [blame]
// 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/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]];
}
} // namespace
namespace ui {
base::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)) {
return base::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 base::Optional<ViewTreeProblemDetails>(
{ViewTreeProblemDetails::VIEWS_OVERLAP, view, other});
}
}
return base::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