blob: 6bea4ac8e5609071126aca314f37d13054b3847d [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// 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/core/accessibility/blink_ax_event_intent.h"
#include <limits>
#include "third_party/blink/renderer/core/events/input_event.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "ui/accessibility/ax_enums.mojom-blink.h"
namespace blink {
// static
BlinkAXEventIntent BlinkAXEventIntent::FromEditCommand(
const EditCommand& edit_command) {
ax::mojom::blink::Command command;
ax::mojom::blink::InputEventType input_event_type;
switch (edit_command.GetInputType()) {
case InputEvent::InputType::kNone:
return BlinkAXEventIntent(); // An empty intent.
// Insertion.
case InputEvent::InputType::kInsertText:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertText;
break;
case InputEvent::InputType::kInsertLineBreak:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertLineBreak;
break;
case InputEvent::InputType::kInsertParagraph:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertParagraph;
break;
case InputEvent::InputType::kInsertOrderedList:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertOrderedList;
break;
case InputEvent::InputType::kInsertUnorderedList:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertUnorderedList;
break;
case InputEvent::InputType::kInsertHorizontalRule:
command = ax::mojom::blink::Command::kInsert;
input_event_type =
ax::mojom::blink::InputEventType::kInsertHorizontalRule;
break;
case InputEvent::InputType::kInsertFromPaste:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertFromPaste;
break;
case InputEvent::InputType::kInsertFromDrop:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertFromDrop;
break;
case InputEvent::InputType::kInsertFromYank:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertFromYank;
break;
case InputEvent::InputType::kInsertTranspose:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertTranspose;
break;
case InputEvent::InputType::kInsertReplacementText:
command = ax::mojom::blink::Command::kInsert;
input_event_type =
ax::mojom::blink::InputEventType::kInsertReplacementText;
break;
case InputEvent::InputType::kInsertCompositionText:
command = ax::mojom::blink::Command::kInsert;
input_event_type =
ax::mojom::blink::InputEventType::kInsertCompositionText;
break;
case InputEvent::InputType::kInsertLink:
command = ax::mojom::blink::Command::kInsert;
input_event_type = ax::mojom::blink::InputEventType::kInsertLink;
break;
// Deletion.
case InputEvent::InputType::kDeleteWordBackward:
command = ax::mojom::blink::Command::kDelete;
input_event_type = ax::mojom::blink::InputEventType::kDeleteWordBackward;
break;
case InputEvent::InputType::kDeleteWordForward:
command = ax::mojom::blink::Command::kDelete;
input_event_type = ax::mojom::blink::InputEventType::kDeleteWordForward;
break;
case InputEvent::InputType::kDeleteSoftLineBackward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteSoftLineBackward;
break;
case InputEvent::InputType::kDeleteSoftLineForward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteSoftLineForward;
break;
case InputEvent::InputType::kDeleteHardLineBackward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteHardLineBackward;
break;
case InputEvent::InputType::kDeleteHardLineForward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteHardLineForward;
break;
case InputEvent::InputType::kDeleteContentBackward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteContentBackward;
break;
case InputEvent::InputType::kDeleteContentForward:
command = ax::mojom::blink::Command::kDelete;
input_event_type =
ax::mojom::blink::InputEventType::kDeleteContentForward;
break;
case InputEvent::InputType::kDeleteByCut:
command = ax::mojom::blink::Command::kDelete;
input_event_type = ax::mojom::blink::InputEventType::kDeleteByCut;
break;
case InputEvent::InputType::kDeleteByDrag:
command = ax::mojom::blink::Command::kDelete;
input_event_type = ax::mojom::blink::InputEventType::kDeleteByDrag;
break;
// History.
case InputEvent::InputType::kHistoryUndo:
command = ax::mojom::blink::Command::kHistory;
input_event_type = ax::mojom::blink::InputEventType::kHistoryUndo;
break;
case InputEvent::InputType::kHistoryRedo:
command = ax::mojom::blink::Command::kHistory;
input_event_type = ax::mojom::blink::InputEventType::kHistoryRedo;
break;
// Formatting.
case InputEvent::InputType::kFormatBold:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatBold;
break;
case InputEvent::InputType::kFormatItalic:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatItalic;
break;
case InputEvent::InputType::kFormatUnderline:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatUnderline;
break;
case InputEvent::InputType::kFormatStrikeThrough:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatStrikeThrough;
break;
case InputEvent::InputType::kFormatSuperscript:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatSuperscript;
break;
case InputEvent::InputType::kFormatSubscript:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatSubscript;
break;
case InputEvent::InputType::kFormatJustifyCenter:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatJustifyCenter;
break;
case InputEvent::InputType::kFormatJustifyFull:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatJustifyFull;
break;
case InputEvent::InputType::kFormatJustifyRight:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatJustifyRight;
break;
case InputEvent::InputType::kFormatJustifyLeft:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatJustifyLeft;
break;
case InputEvent::InputType::kFormatIndent:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatIndent;
break;
case InputEvent::InputType::kFormatOutdent:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatOutdent;
break;
case InputEvent::InputType::kFormatRemove:
command = ax::mojom::blink::Command::kFormat;
input_event_type = ax::mojom::blink::InputEventType::kFormatRemove;
break;
case InputEvent::InputType::kFormatSetBlockTextDirection:
command = ax::mojom::blink::Command::kFormat;
input_event_type =
ax::mojom::blink::InputEventType::kFormatSetBlockTextDirection;
break;
case InputEvent::InputType::kNumberOfInputTypes:
NOTREACHED()
<< "Should never be assigned as an input type to |edit_command|.";
return BlinkAXEventIntent();
}
return BlinkAXEventIntent(command, input_event_type);
}
// static
BlinkAXEventIntent BlinkAXEventIntent::FromClearedSelection(
const SetSelectionBy set_selection_by) {
// text boundary and move direction are not needed in this case.
return BlinkAXEventIntent(ax::mojom::blink::Command::kClearSelection);
}
// static
BlinkAXEventIntent BlinkAXEventIntent::FromModifiedSelection(
const SelectionModifyAlteration alter,
const SelectionModifyDirection direction,
const TextGranularity granularity,
const SetSelectionBy set_selection_by,
const TextDirection direction_of_selection,
const PlatformWordBehavior platform_word_behavior) {
ax::mojom::blink::Command command;
switch (alter) {
case SelectionModifyAlteration::kExtend:
// Includes the case when the existing selection has been shrunk.
command = ax::mojom::blink::Command::kExtendSelection;
break;
case SelectionModifyAlteration::kMove:
// The existing selection has been move by a specific |granularity|, e.g.
// the caret has been moved to the beginning of the next word.
command = ax::mojom::blink::Command::kMoveSelection;
break;
}
ax::mojom::blink::MoveDirection move_direction;
switch (direction) {
case SelectionModifyDirection::kBackward:
move_direction = ax::mojom::blink::MoveDirection::kBackward;
break;
case SelectionModifyDirection::kForward:
move_direction = ax::mojom::blink::MoveDirection::kForward;
break;
case SelectionModifyDirection::kLeft:
move_direction = IsLtr(direction_of_selection)
? ax::mojom::blink::MoveDirection::kBackward
: ax::mojom::blink::MoveDirection::kForward;
break;
case SelectionModifyDirection::kRight:
move_direction = IsLtr(direction_of_selection)
? ax::mojom::blink::MoveDirection::kForward
: ax::mojom::blink::MoveDirection::kBackward;
break;
}
ax::mojom::blink::TextBoundary text_boundary;
switch (granularity) {
case TextGranularity::kCharacter:
text_boundary = ax::mojom::blink::TextBoundary::kCharacter;
break;
case TextGranularity::kWord:
switch (move_direction) {
case ax::mojom::blink::MoveDirection::kNone:
NOTREACHED();
return BlinkAXEventIntent();
case ax::mojom::blink::MoveDirection::kBackward:
// All platforms behave the same when moving backward by word.
text_boundary = ax::mojom::blink::TextBoundary::kWordStart;
break;
case ax::mojom::blink::MoveDirection::kForward:
switch (platform_word_behavior) {
case PlatformWordBehavior::kWordSkipSpaces:
// Windows behavior is to always move to the beginning of the next
// word.
text_boundary = ax::mojom::blink::TextBoundary::kWordStart;
break;
case PlatformWordBehavior::kWordDontSkipSpaces:
// Mac, Linux and ChromeOS behavior is to move to the end of the
// current word.
text_boundary = ax::mojom::blink::TextBoundary::kWordEnd;
break;
}
break;
}
break;
case TextGranularity::kSentence:
// This granularity always moves to the start of the next or previous
// sentence.
text_boundary = ax::mojom::blink::TextBoundary::kSentenceStart;
break;
case TextGranularity::kLine:
// This granularity always moves to the start of the next or previous
// line.
text_boundary = ax::mojom::blink::TextBoundary::kLineStart;
break;
case TextGranularity::kParagraph:
// This granularity always moves to the start of the next or previous
// paragraph.
text_boundary = ax::mojom::blink::TextBoundary::kParagraphStart;
break;
case TextGranularity::kSentenceBoundary:
// This granularity moves either to the start or the end of the current
// sentence, depending on the direction.
switch (move_direction) {
case ax::mojom::blink::MoveDirection::kNone:
NOTREACHED();
return BlinkAXEventIntent();
case ax::mojom::blink::MoveDirection::kBackward:
text_boundary = ax::mojom::blink::TextBoundary::kSentenceStart;
break;
case ax::mojom::blink::MoveDirection::kForward:
text_boundary = ax::mojom::blink::TextBoundary::kSentenceEnd;
break;
}
break;
case TextGranularity::kLineBoundary:
// This granularity moves either to the start or the end of the current
// line, depending on the direction.
switch (move_direction) {
case ax::mojom::blink::MoveDirection::kNone:
NOTREACHED();
return BlinkAXEventIntent();
case ax::mojom::blink::MoveDirection::kBackward:
text_boundary = ax::mojom::blink::TextBoundary::kLineStart;
break;
case ax::mojom::blink::MoveDirection::kForward:
text_boundary = ax::mojom::blink::TextBoundary::kLineEnd;
break;
}
break;
case TextGranularity::kParagraphBoundary:
// This granularity moves either to the start or the end of the current
// paragraph, depending on the direction.
switch (move_direction) {
case ax::mojom::blink::MoveDirection::kNone:
NOTREACHED();
return BlinkAXEventIntent();
case ax::mojom::blink::MoveDirection::kBackward:
text_boundary = ax::mojom::blink::TextBoundary::kParagraphStart;
break;
case ax::mojom::blink::MoveDirection::kForward:
text_boundary = ax::mojom::blink::TextBoundary::kParagraphEnd;
break;
}
break;
case TextGranularity::kDocumentBoundary:
text_boundary = ax::mojom::blink::TextBoundary::kWebPage;
break;
}
return BlinkAXEventIntent(command, text_boundary, move_direction);
}
// static
BlinkAXEventIntent BlinkAXEventIntent::FromNewSelection(
const TextGranularity granularity,
bool is_base_first,
const SetSelectionBy set_selection_by) {
// Unfortunately, when setting a completely new selection, |text_boundary| is
// not always known, or is hard to compute. For example, if a new selection
// has been made using the mouse, it would be expensive to compute any
// meaningful granularity information.
ax::mojom::blink::TextBoundary text_boundary;
switch (granularity) {
case TextGranularity::kCharacter:
text_boundary = ax::mojom::blink::TextBoundary::kCharacter;
break;
case TextGranularity::kWord:
text_boundary = ax::mojom::blink::TextBoundary::kWordStartOrEnd;
break;
case TextGranularity::kSentence:
case TextGranularity::kSentenceBoundary:
text_boundary = ax::mojom::blink::TextBoundary::kSentenceStartOrEnd;
break;
case TextGranularity::kLine:
case TextGranularity::kLineBoundary:
text_boundary = ax::mojom::blink::TextBoundary::kLineStartOrEnd;
break;
case TextGranularity::kParagraph:
case TextGranularity::kParagraphBoundary:
text_boundary = ax::mojom::blink::TextBoundary::kParagraphStartOrEnd;
break;
case TextGranularity::kDocumentBoundary:
text_boundary = ax::mojom::blink::TextBoundary::kWebPage;
break;
}
return BlinkAXEventIntent(
ax::mojom::blink::Command::kSetSelection, text_boundary,
is_base_first ? ax::mojom::blink::MoveDirection::kForward
: ax::mojom::blink::MoveDirection::kBackward);
}
BlinkAXEventIntent::BlinkAXEventIntent() = default;
BlinkAXEventIntent::BlinkAXEventIntent(ax::mojom::blink::Command command)
: intent_(command), is_initialized_(true) {}
BlinkAXEventIntent::BlinkAXEventIntent(
ax::mojom::blink::Command command,
ax::mojom::blink::InputEventType input_event_type)
: intent_(command, input_event_type), is_initialized_(true) {}
BlinkAXEventIntent::BlinkAXEventIntent(
ax::mojom::blink::Command command,
ax::mojom::blink::TextBoundary text_boundary,
ax::mojom::blink::MoveDirection move_direction)
: intent_(command, text_boundary, move_direction), is_initialized_(true) {}
BlinkAXEventIntent::BlinkAXEventIntent(WTF::HashTableDeletedValueType type)
: is_initialized_(true), is_deleted_(true) {}
BlinkAXEventIntent::~BlinkAXEventIntent() = default;
BlinkAXEventIntent::BlinkAXEventIntent(const BlinkAXEventIntent& intent) =
default;
BlinkAXEventIntent& BlinkAXEventIntent::operator=(
const BlinkAXEventIntent& intent) = default;
bool operator==(const BlinkAXEventIntent& a, const BlinkAXEventIntent& b) {
return BlinkAXEventIntentHashTraits::GetHash(a) ==
BlinkAXEventIntentHashTraits::GetHash(b);
}
bool operator!=(const BlinkAXEventIntent& a, const BlinkAXEventIntent& b) {
return !(a == b);
}
bool BlinkAXEventIntent::IsHashTableDeletedValue() const {
return is_deleted_;
}
std::string BlinkAXEventIntent::ToString() const {
if (!is_initialized())
return "AXEventIntent(uninitialized)";
if (IsHashTableDeletedValue())
return "AXEventIntent(is_deleted)";
return intent().ToString();
}
// static
unsigned int BlinkAXEventIntentHashTraits::GetHash(
const BlinkAXEventIntent& key) {
// If the intent is uninitialized, it is not safe to rely on the memory being
// initialized to zero, because any uninitialized field that might be
// accidentally added in the future will produce a potentially non-zero memory
// value especially in the hard to control "intent_" member.
if (!key.is_initialized())
return 0u;
if (key.IsHashTableDeletedValue())
return std::numeric_limits<unsigned>::max();
unsigned hash = 1u;
WTF::AddIntToHash(hash, static_cast<const unsigned>(key.intent().command));
WTF::AddIntToHash(hash,
static_cast<const unsigned>(key.intent().input_event_type));
WTF::AddIntToHash(hash,
static_cast<const unsigned>(key.intent().text_boundary));
WTF::AddIntToHash(hash,
static_cast<const unsigned>(key.intent().move_direction));
return hash;
}
} // namespace blink