| // Copyright (c) 2012 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 "content/browser/accessibility/browser_accessibility_mac.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/accessibility/browser_accessibility_cocoa.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| #include "content/browser/accessibility/browser_accessibility_manager_mac.h" |
| #include "content/public/browser/ax_event_notification_details.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #include "ui/accessibility/ax_tree_update.h" |
| #import "ui/base/test/cocoa_helper.h" |
| |
| namespace content { |
| |
| class BrowserAccessibilityTest : public ui::CocoaTest { |
| public: |
| void SetUp() override { |
| CocoaTest::SetUp(); |
| RebuildAccessibilityTree(); |
| } |
| |
| protected: |
| void RebuildAccessibilityTree() { |
| // Clean out the existing root data in case this method is called multiple |
| // times in a test. |
| root_ = ui::AXNodeData(); |
| root_.id = 1000; |
| root_.location.set_width(500); |
| root_.location.set_height(100); |
| root_.role = ui::AX_ROLE_ROOT_WEB_AREA; |
| root_.AddStringAttribute(ui::AX_ATTR_DESCRIPTION, "HelpText"); |
| root_.child_ids.push_back(1001); |
| root_.child_ids.push_back(1002); |
| |
| ui::AXNodeData child1; |
| child1.id = 1001; |
| child1.SetName("Child1"); |
| child1.location.set_width(250); |
| child1.location.set_height(100); |
| child1.role = ui::AX_ROLE_BUTTON; |
| |
| ui::AXNodeData child2; |
| child2.id = 1002; |
| child2.location.set_x(250); |
| child2.location.set_width(250); |
| child2.location.set_height(100); |
| child2.role = ui::AX_ROLE_HEADING; |
| |
| manager_.reset(new BrowserAccessibilityManagerMac( |
| MakeAXTreeUpdate(root_, child1, child2), nullptr)); |
| accessibility_.reset([ToBrowserAccessibilityCocoa(manager_->GetRoot()) |
| retain]); |
| } |
| |
| void SetRootValue(std::string value) { |
| if (!manager_) |
| return; |
| root_.SetValue(value); |
| AXEventNotificationDetails param; |
| param.update.nodes.push_back(root_); |
| param.event_type = ui::AX_EVENT_VALUE_CHANGED; |
| param.id = root_.id; |
| std::vector<AXEventNotificationDetails> events{param}; |
| manager_->OnAccessibilityEvents(events); |
| } |
| |
| ui::AXNodeData root_; |
| base::scoped_nsobject<BrowserAccessibilityCocoa> accessibility_; |
| std::unique_ptr<BrowserAccessibilityManager> manager_; |
| }; |
| |
| // Standard hit test. |
| TEST_F(BrowserAccessibilityTest, HitTestTest) { |
| BrowserAccessibilityCocoa* firstChild = |
| [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; |
| EXPECT_NSEQ(@"Child1", |
| [firstChild |
| accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); |
| } |
| |
| // Test doing a hit test on the edge of a child. |
| TEST_F(BrowserAccessibilityTest, EdgeHitTest) { |
| BrowserAccessibilityCocoa* firstChild = |
| [accessibility_ accessibilityHitTest:NSZeroPoint]; |
| EXPECT_NSEQ(@"Child1", |
| [firstChild |
| accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); |
| } |
| |
| // This will test a hit test with invalid coordinates. It is assumed that |
| // the hit test has been narrowed down to this object or one of its children |
| // so it should return itself since it has no better hit result. |
| TEST_F(BrowserAccessibilityTest, InvalidHitTestCoordsTest) { |
| BrowserAccessibilityCocoa* hitTestResult = |
| [accessibility_ accessibilityHitTest:NSMakePoint(-50, 50)]; |
| EXPECT_NSEQ(accessibility_, hitTestResult); |
| } |
| |
| // Test to ensure querying standard attributes works. |
| TEST_F(BrowserAccessibilityTest, BasicAttributeTest) { |
| NSString* helpText = [accessibility_ |
| accessibilityAttributeValue:NSAccessibilityHelpAttribute]; |
| EXPECT_NSEQ(@"HelpText", helpText); |
| } |
| |
| // Test querying for an invalid attribute to ensure it doesn't crash. |
| TEST_F(BrowserAccessibilityTest, InvalidAttributeTest) { |
| NSString* shouldBeNil = [accessibility_ |
| accessibilityAttributeValue:@"NSAnInvalidAttribute"]; |
| EXPECT_TRUE(shouldBeNil == nil); |
| } |
| |
| TEST_F(BrowserAccessibilityTest, RetainedDetachedObjectsReturnNil) { |
| // Get the first child. |
| BrowserAccessibilityCocoa* retainedFirstChild = |
| [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; |
| EXPECT_NSEQ(@"Child1", [retainedFirstChild |
| accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); |
| |
| // Retain it. This simulates what the system might do with an |
| // accessibility object. |
| [retainedFirstChild retain]; |
| |
| // Rebuild the accessibility tree, which should detach |retainedFirstChild|. |
| RebuildAccessibilityTree(); |
| |
| // Now any attributes we query should return nil. |
| EXPECT_EQ(nil, [retainedFirstChild |
| accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); |
| |
| // Don't leak memory in the test. |
| [retainedFirstChild release]; |
| } |
| |
| TEST_F(BrowserAccessibilityTest, TestComputeTextEdit) { |
| BrowserAccessibility* wrapper = [accessibility_ browserAccessibility]; |
| ASSERT_NE(nullptr, wrapper); |
| |
| // Insertion but no deletion. |
| |
| SetRootValue("text"); |
| AXTextEdit text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("text"), text_edit.inserted_text); |
| EXPECT_TRUE(text_edit.deleted_text.empty()); |
| |
| SetRootValue("new text"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("new "), text_edit.inserted_text); |
| EXPECT_TRUE(text_edit.deleted_text.empty()); |
| |
| SetRootValue("new text hello"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16(" hello"), text_edit.inserted_text); |
| EXPECT_TRUE(text_edit.deleted_text.empty()); |
| |
| SetRootValue("newer text hello"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("er"), text_edit.inserted_text); |
| EXPECT_TRUE(text_edit.deleted_text.empty()); |
| |
| // Deletion but no insertion. |
| |
| SetRootValue("new text hello"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("er"), text_edit.deleted_text); |
| EXPECT_TRUE(text_edit.inserted_text.empty()); |
| |
| SetRootValue("new text"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16(" hello"), text_edit.deleted_text); |
| EXPECT_TRUE(text_edit.inserted_text.empty()); |
| |
| SetRootValue("text"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("new "), text_edit.deleted_text); |
| EXPECT_TRUE(text_edit.inserted_text.empty()); |
| |
| SetRootValue(""); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("text"), text_edit.deleted_text); |
| EXPECT_TRUE(text_edit.inserted_text.empty()); |
| |
| // Both insertion and deletion. |
| |
| SetRootValue("new text hello"); |
| text_edit = [accessibility_ computeTextEdit]; |
| SetRootValue("new word hello"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("text"), text_edit.deleted_text); |
| EXPECT_EQ(base::UTF8ToUTF16("word"), text_edit.inserted_text); |
| |
| SetRootValue("new word there"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("hello"), text_edit.deleted_text); |
| EXPECT_EQ(base::UTF8ToUTF16("there"), text_edit.inserted_text); |
| |
| SetRootValue("old word there"); |
| text_edit = [accessibility_ computeTextEdit]; |
| EXPECT_EQ(base::UTF8ToUTF16("new"), text_edit.deleted_text); |
| EXPECT_EQ(base::UTF8ToUTF16("old"), text_edit.inserted_text); |
| } |
| |
| } // namespace content |