diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc b/chromium/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc new file mode 100644 index 00000000000..9a91e8f4d82 --- /dev/null +++ b/chromium/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc @@ -0,0 +1,264 @@ +// Copyright 2018 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 "third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.h" + +#include "third_party/blink/renderer/core/dom/node.h" +#include "third_party/blink/renderer/core/html/html_element.h" +#include "third_party/blink/renderer/modules/accessibility/ax_object.h" +#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h" +#include "third_party/blink/renderer/modules/accessibility/ax_position.h" +#include "third_party/blink/renderer/modules/accessibility/ax_selection.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +namespace { + +// Serialize accessibility subtree to selection text. +// Adds a '^' at the selection anchor offset and a '|' at the focus offset. +class AXSelectionSerializer final { + STACK_ALLOCATED(); + + public: + explicit AXSelectionSerializer(const AXSelection& selection) + : selection_(selection) {} + + std::string Serialize(const AXObject& subtree) { + if (!selection_.IsValid()) + return {}; + SerializeSubtree(subtree); + return builder_.ToString().Utf8().data(); + } + + private: + void HandleTextObject(const AXObject& text_object) { + builder_.Append('<'); + builder_.Append(AXObject::InternalRoleName(text_object.RoleValue())); + builder_.Append(": "); + const String name = text_object.ComputedName() + '>'; + const AXObject& base_container = *selection_.Base().ContainerObject(); + const AXObject& extent_container = *selection_.Extent().ContainerObject(); + + if (base_container == text_object && extent_container == text_object) { + DCHECK(selection_.Base().IsTextPosition() && + selection_.Extent().IsTextPosition()); + const int base_offset = selection_.Base().TextOffset(); + const int extent_offset = selection_.Extent().TextOffset(); + + if (base_offset == extent_offset) { + builder_.Append(name.Left(base_offset)); + builder_.Append('|'); + builder_.Append(name.Substring(base_offset)); + return; + } + + if (base_offset < extent_offset) { + builder_.Append(name.Left(base_offset)); + builder_.Append('^'); + builder_.Append( + name.Substring(base_offset, extent_offset - base_offset)); + builder_.Append('|'); + builder_.Append(name.Substring(extent_offset)); + return; + } + + builder_.Append(name.Left(extent_offset)); + builder_.Append('|'); + builder_.Append( + name.Substring(extent_offset, base_offset - extent_offset)); + builder_.Append('^'); + builder_.Append(name.Substring(base_offset)); + return; + } + + if (base_container == text_object) { + DCHECK(selection_.Base().IsTextPosition()); + const int base_offset = selection_.Base().TextOffset(); + + builder_.Append(name.Left(base_offset)); + builder_.Append('^'); + builder_.Append(name.Substring(base_offset)); + return; + } + + if (extent_container == text_object) { + DCHECK(selection_.Extent().IsTextPosition()); + const int extent_offset = selection_.Extent().TextOffset(); + + builder_.Append(name.Left(extent_offset)); + builder_.Append('|'); + builder_.Append(name.Substring(extent_offset)); + return; + } + + builder_.Append(name); + } + + void HandleObject(const AXObject& object) { + builder_.Append('<'); + builder_.Append(AXObject::InternalRoleName(object.RoleValue())); + builder_.Append(": "); + builder_.Append(object.ComputedName()); + builder_.Append('>'); + SerializeSubtree(object); + } + + void HandleSelection(const AXPosition& position) { + if (!position.IsValid()) + return; + + if (selection_.Extent() == position) { + builder_.Append('|'); + return; + } + + if (selection_.Base() != position) + return; + + builder_.Append('^'); + } + + void SerializeSubtree(const AXObject& subtree) { + for (const Member<AXObject>& child : subtree.Children()) { + if (!child) + continue; + const auto position = AXPosition::CreatePositionBeforeObject(*child); + HandleSelection(position); + if (position.IsTextPosition()) { + HandleTextObject(*child); + } else { + HandleObject(*child); + } + } + HandleSelection(AXPosition::CreateLastPositionInObject(subtree)); + } + + StringBuilder builder_; + AXSelection selection_; +}; + +// Deserializes an HTML snippet with or without selection markers to an +// accessibility tree. A '^' could be present at the selection anchor offset and +// a '|' at the focus offset. If multiple markers are present, the deserializer +// will DCHECK. If there are no markers, no selection will be made. +class AXSelectionDeserializer final { + STACK_ALLOCATED(); + + public: + AXSelectionDeserializer(AXObjectCacheImpl& cache) + : ax_object_cache_(&cache), base_(), extent_() {} + ~AXSelectionDeserializer() = default; + + // Creates an accessibility tree rooted at the given HTML element from the + // provided HTML snippet, selects the part of the tree indicated by the + // selection markers in the snippet if present, and returns the tree's root. + AXObject* Deserialize(const std::string& html_snippet, HTMLElement& element) { + element.SetInnerHTMLFromString(String::FromUTF8(html_snippet.c_str())); + AXObject* root = ax_object_cache_->GetOrCreate(&element); + if (!root) + return nullptr; + + FindSelectionMarkers(*root); + if (base_ && extent_) { + AXSelection::Builder builder; + AXSelection ax_selection = + builder.SetBase(base_).SetExtent(extent_).Build(); + ax_selection.Select(); + } + + return root; + } + + private: + void HandleCharacterData(const AXObject& text_object) { + int base_offset = -1; + int extent_offset = -1; + String name = text_object.ComputedName(); + for (unsigned i = 0; i < name.length(); ++i) { + const UChar character = name[i]; + if (character == '^') { + DCHECK_EQ(base_offset, -1) << text_object; + base_offset = static_cast<int>(i); + continue; + } + if (character == '|') { + DCHECK_EQ(extent_offset, -1) << text_object; + extent_offset = static_cast<int>(i); + continue; + } + } + + if (base_offset == -1 && extent_offset == -1) + return; + + if (base_offset >= 0) + base_ = AXPosition::CreatePositionInTextObject(text_object, base_offset); + if (extent_offset >= 0) { + extent_ = + AXPosition::CreatePositionInTextObject(text_object, extent_offset); + } + } + + void HandleObject(const AXObject& object) { + for (const Member<AXObject>& child : object.Children()) { + if (!child) + continue; + FindSelectionMarkers(*child); + } + } + + void FindSelectionMarkers(const AXObject& root) { + const Node* node = root.GetNode(); + if (node && node->IsCharacterDataNode()) { + HandleCharacterData(root); + return; + } + HandleObject(root); + } + + Member<AXObjectCacheImpl> const ax_object_cache_; + AXPosition base_; + AXPosition extent_; +}; + +} // namespace + +AccessibilitySelectionTest::AccessibilitySelectionTest( + LocalFrameClient* local_frame_client) + : AccessibilityTest(local_frame_client) {} + +std::string AccessibilitySelectionTest::GetSelectionText( + const AXSelection& selection) const { + const AXObject* root = GetAXRootObject(); + if (!root) + return {}; + return AXSelectionSerializer(selection).Serialize(*root); +} + +std::string AccessibilitySelectionTest::GetSelectionText( + const AXSelection& selection, + const AXObject& subtree) const { + return AXSelectionSerializer(selection).Serialize(subtree); +} + +AXObject* AccessibilitySelectionTest::SetSelectionText( + const std::string& selection_text) const { + HTMLElement* body = GetDocument().body(); + if (!body) + return nullptr; + return AXSelectionDeserializer(GetAXObjectCache()) + .Deserialize(selection_text, *body); +} + +AXObject* AccessibilitySelectionTest::SetSelectionText( + const std::string& selection_text, + HTMLElement& element) const { + return AXSelectionDeserializer(GetAXObjectCache()) + .Deserialize(selection_text, element); +} + +} // namespace blink |