Add perform action for Eche accessibility.
- Adds conversion from ui:AXActionData to proto data. Skips mojom types.
- Todos will be resolved when this is integrated with the accessibility
system in chrome.
See Design at:
go/exo-accessibility-support
Bug: b/267373987
Change-Id: Id7ce464d0134773e22596ac1f9a755c2709b096c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4518144
Reviewed-by: Matthew Denton <mpdenton@chromium.org>
Reviewed-by: Abbas Nayebi <nayebi@google.com>
Commit-Queue: Jacob Francis <francisjp@google.com>
Cr-Commit-Position: refs/heads/main@{#1145673}
diff --git a/ash/webui/eche_app_ui/accessibility_provider.cc b/ash/webui/eche_app_ui/accessibility_provider.cc
index ee3b37c..b95251e2 100644
--- a/ash/webui/eche_app_ui/accessibility_provider.cc
+++ b/ash/webui/eche_app_ui/accessibility_provider.cc
@@ -4,8 +4,8 @@
// found in the LICENSE file.
#include "ash/webui/eche_app_ui/accessibility_provider.h"
+
#include <cstdint>
-#include "ash/webui/eche_app_ui/accessibility_tree_converter.h"
#include "base/notreached.h"
namespace ash::eche_app {
@@ -21,6 +21,25 @@
NOTIMPLEMENTED();
}
+void AccessibilityProvider::SetAccessibilityObserver(
+ mojo::PendingRemote<mojom::AccessibilityObserver> observer) {
+ observer_remote_.reset();
+ observer_remote_.Bind(std::move(observer));
+}
+
+void AccessibilityProvider::PerformAction(const ui::AXActionData& action) {
+ AccessibilityTreeConverter converter;
+ auto proto_action = converter.ConvertActionDataToProto(action);
+ if (proto_action.has_value()) {
+ size_t nbytes = proto_action->ByteSizeLong();
+ std::vector<uint8_t> serialized_proto(nbytes);
+ proto_action->SerializeToArray(serialized_proto.data(), nbytes);
+ observer_remote_->PerformAction(serialized_proto);
+ } else {
+ LOG(ERROR) << "Failed to serialize AXActionData to protobuf.";
+ }
+}
+
void AccessibilityProvider::Bind(
mojo::PendingReceiver<mojom::AccessibilityProvider> receiver) {
receiver_.reset();
diff --git a/ash/webui/eche_app_ui/accessibility_provider.h b/ash/webui/eche_app_ui/accessibility_provider.h
index 73dfe815..486bd08 100644
--- a/ash/webui/eche_app_ui/accessibility_provider.h
+++ b/ash/webui/eche_app_ui/accessibility_provider.h
@@ -5,11 +5,13 @@
#ifndef ASH_WEBUI_ECHE_APP_UI_ACCESSIBILITY_PROVIDER_H_
#define ASH_WEBUI_ECHE_APP_UI_ACCESSIBILITY_PROVIDER_H_
+#include "ash/webui/eche_app_ui/accessibility_tree_converter.h"
#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
+#include "ui/accessibility/ax_action_data.h"
namespace ash::eche_app {
class AccessibilityProvider : public mojom::AccessibilityProvider {
@@ -19,10 +21,15 @@
// Proto from ash/webui/eche_app_ui/proto/accessibility_mojom.proto.
void HandleAccessibilityEventReceived(
const std::vector<uint8_t>& serialized_proto) override;
+ void SetAccessibilityObserver(
+ ::mojo::PendingRemote<mojom::AccessibilityObserver> observer) override;
void Bind(mojo::PendingReceiver<mojom::AccessibilityProvider> receiver);
+ void PerformAction(const ui::AXActionData& action);
+
private:
mojo::Receiver<mojom::AccessibilityProvider> receiver_{this};
+ mojo::Remote<mojom::AccessibilityObserver> observer_remote_;
};
} // namespace ash::eche_app
#endif // ASH_WEBUI_ECHE_APP_UI_ACCESSIBILITY_PROVIDER_H_
diff --git a/ash/webui/eche_app_ui/accessibility_tree_converter.cc b/ash/webui/eche_app_ui/accessibility_tree_converter.cc
index 2a99fc34..b7032f7 100644
--- a/ash/webui/eche_app_ui/accessibility_tree_converter.cc
+++ b/ash/webui/eche_app_ui/accessibility_tree_converter.cc
@@ -18,6 +18,7 @@
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
namespace ash::eche_app {
AccessibilityTreeConverter::AccessibilityTreeConverter() = default;
@@ -164,6 +165,138 @@
return out_data;
}
+absl::optional<proto::AccessibilityActionType> ConvertType(
+ ax::mojom::Action action_type) {
+ switch (action_type) {
+ case ax::mojom::Action::kDoDefault:
+ return proto::AccessibilityActionType::ACTION_CLICK;
+ case ax::mojom::Action::kFocus:
+ return proto::AccessibilityActionType::ACTION_ACCESSIBILITY_ACTION_FOCUS;
+ case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint:
+ return proto::AccessibilityActionType::ACTION_ACCESSIBILITY_FOCUS;
+ case ax::mojom::Action::kScrollToMakeVisible:
+ return proto::AccessibilityActionType::ACTION_SHOW_ON_SCREEN;
+ case ax::mojom::Action::kScrollBackward:
+ return proto::AccessibilityActionType::ACTION_SCROLL_BACKWARD;
+ case ax::mojom::Action::kScrollForward:
+ return proto::AccessibilityActionType::ACTION_SCROLL_FORWARD;
+ case ax::mojom::Action::kScrollUp:
+ return proto::AccessibilityActionType::ACTION_SCROLL_UP;
+ case ax::mojom::Action::kScrollDown:
+ return proto::AccessibilityActionType::ACTION_SCROLL_DOWN;
+ case ax::mojom::Action::kScrollLeft:
+ return proto::AccessibilityActionType::ACTION_SCROLL_LEFT;
+ case ax::mojom::Action::kScrollRight:
+ return proto::AccessibilityActionType::ACTION_SCROLL_RIGHT;
+ case ax::mojom::Action::kScrollToPositionAtRowColumn:
+ return proto::AccessibilityActionType::ACTION_SCROLL_TO_POSITION;
+ case ax::mojom::Action::kCustomAction:
+ return proto::AccessibilityActionType::ACTION_CUSTOM_ACTION;
+ case ax::mojom::Action::kSetAccessibilityFocus:
+ return proto::AccessibilityActionType::ACTION_ACCESSIBILITY_FOCUS;
+ case ax::mojom::Action::kClearAccessibilityFocus:
+ return proto::AccessibilityActionType::ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+ case ax::mojom::Action::kGetTextLocation:
+ return proto::AccessibilityActionType::ACTION_GET_TEXT_LOCATION;
+ case ax::mojom::Action::kShowTooltip:
+ return proto::AccessibilityActionType::ACTION_SHOW_TOOLTIP;
+ case ax::mojom::Action::kHideTooltip:
+ return proto::AccessibilityActionType::ACTION_HIDE_TOOLTIP;
+ case ax::mojom::Action::kCollapse:
+ return proto::AccessibilityActionType::ACTION_COLLAPSE;
+ case ax::mojom::Action::kExpand:
+ return proto::AccessibilityActionType::ACTION_EXPAND;
+ case ax::mojom::Action::kLongClick:
+ return proto::AccessibilityActionType::ACTION_LONG_CLICK;
+ default:
+ return absl::nullopt;
+ }
+}
+
+void PopulateActionParameters(const ui::AXActionData& chrome_data,
+ proto::AccessibilityActionData& action_data) {
+ switch (action_data.action_type()) {
+ case proto::AccessibilityActionType::ACTION_SCROLL_TO_POSITION: {
+ const auto [row, column] = chrome_data.row_column;
+ auto* row_kvp = action_data.add_int_parameters();
+ row_kvp->set_key(proto::ActionIntArgumentType::TYPE_ROW_INT);
+ row_kvp->set_value(row);
+ auto* col_kvp = action_data.add_int_parameters();
+ col_kvp->set_key(proto::ActionIntArgumentType::TYPE_COLUMN_INT);
+ col_kvp->set_value(column);
+ break;
+ }
+ case proto::AccessibilityActionType::ACTION_CUSTOM_ACTION:
+ action_data.set_custom_action_id(chrome_data.custom_action_id);
+ break;
+ case proto::AccessibilityActionType::ACTION_NEXT_HTML_ELEMENT:
+ case proto::AccessibilityActionType::ACTION_PREVIOUS_HTML_ELEMENT:
+ case proto::AccessibilityActionType::ACTION_ACCESSIBILITY_ACTION_FOCUS:
+ case proto::AccessibilityActionType::ACTION_CLEAR_FOCUS:
+ case proto::AccessibilityActionType::ACTION_SELECT:
+ case proto::AccessibilityActionType::ACTION_CLEAR_SELECTION:
+ case proto::AccessibilityActionType::ACTION_CLICK:
+ case proto::AccessibilityActionType::ACTION_LONG_CLICK:
+ case proto::AccessibilityActionType::ACTION_ACCESSIBILITY_FOCUS:
+ case proto::AccessibilityActionType::ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ case proto::AccessibilityActionType::ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ case proto::AccessibilityActionType::
+ ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ case proto::AccessibilityActionType::ACTION_SCROLL_FORWARD:
+ case proto::AccessibilityActionType::ACTION_SCROLL_BACKWARD:
+ case proto::AccessibilityActionType::ACTION_COPY:
+ case proto::AccessibilityActionType::ACTION_PASTE:
+ case proto::AccessibilityActionType::ACTION_CUT:
+ case proto::AccessibilityActionType::ACTION_SET_SELECTION:
+ case proto::AccessibilityActionType::ACTION_EXPAND:
+ case proto::AccessibilityActionType::ACTION_COLLAPSE:
+ case proto::AccessibilityActionType::ACTION_DISMISS:
+ case proto::AccessibilityActionType::ACTION_SET_TEXT:
+ case proto::AccessibilityActionType::ACTION_CONTEXT_CLICK:
+ case proto::AccessibilityActionType::ACTION_SCROLL_DOWN:
+ case proto::AccessibilityActionType::ACTION_SCROLL_LEFT:
+ case proto::AccessibilityActionType::ACTION_SCROLL_RIGHT:
+ case proto::AccessibilityActionType::ACTION_SCROLL_UP:
+ case proto::AccessibilityActionType::ACTION_SET_PROGRESS:
+ case proto::AccessibilityActionType::ACTION_SHOW_ON_SCREEN:
+ case proto::AccessibilityActionType::ACTION_GET_TEXT_LOCATION:
+ case proto::AccessibilityActionType::ACTION_SHOW_TOOLTIP:
+ case proto::AccessibilityActionType::ACTION_HIDE_TOOLTIP:
+ case proto::AccessibilityActionType::
+ AccessibilityActionType_INT_MIN_SENTINEL_DO_NOT_USE_:
+ case proto::AccessibilityActionType::
+ AccessibilityActionType_INT_MAX_SENTINEL_DO_NOT_USE_:
+ break;
+ }
+}
+
+absl::optional<proto::AccessibilityActionData>
+AccessibilityTreeConverter::ConvertActionDataToProto(
+ const ui::AXActionData& data) {
+ proto::AccessibilityActionData action_data;
+ // TODO(francisjp/282044350) window_id - We may not actually end up having a
+ // window id since there is only one window at a time.
+ auto action_type = ConvertType(data.action);
+ if (!action_type.has_value()) {
+ return absl::nullopt;
+ }
+ action_data.set_action_type(action_type.value());
+ action_data.set_node_id(data.target_node_id);
+ PopulateActionParameters(data, action_data);
+
+ if (action_type == proto::AccessibilityActionType::ACTION_GET_TEXT_LOCATION) {
+ action_data.set_start_index(data.start_index);
+ action_data.set_end_index(data.end_index);
+ // TODO(francisjp/282044350) Refresh with Extra Data here
+ bool refresh_success = true;
+ if (!refresh_success) {
+ return absl::nullopt;
+ }
+ }
+
+ return action_data;
+}
+
// Object converters
mojo::StructPtr<AXWindowData> AccessibilityTreeConverter::ToMojomWindowData(
const proto::AccessibilityWindowInfoData& proto_in) {
diff --git a/ash/webui/eche_app_ui/accessibility_tree_converter.h b/ash/webui/eche_app_ui/accessibility_tree_converter.h
index 4c402bc..1cfd493 100644
--- a/ash/webui/eche_app_ui/accessibility_tree_converter.h
+++ b/ash/webui/eche_app_ui/accessibility_tree_converter.h
@@ -9,6 +9,7 @@
#include "ash/components/arc/mojom/accessibility_helper.mojom.h"
#include "ash/webui/eche_app_ui/proto/accessibility_mojom.pb.h"
+#include "ui/accessibility/ax_action_data.h"
namespace {
@@ -54,6 +55,9 @@
mojo::StructPtr<AXEventData> ConvertEventDataProtoToMojom(
const std::vector<uint8_t>& serialized_proto);
+ absl::optional<proto::AccessibilityActionData> ConvertActionDataToProto(
+ const ui::AXActionData& data);
+
private:
// Utility Functions
template <class ProtoType, class MojomType>
diff --git a/ash/webui/eche_app_ui/mojom/eche_app.mojom b/ash/webui/eche_app_ui/mojom/eche_app.mojom
index 9038fa9..db58b1fb 100644
--- a/ash/webui/eche_app_ui/mojom/eche_app.mojom
+++ b/ash/webui/eche_app_ui/mojom/eche_app.mojom
@@ -74,6 +74,18 @@
// the browser.
// Proto from: ash/webui/eche_app_ui/proto/accessibility_mojom.proto
HandleAccessibilityEventReceived(array<uint8> serialized_proto);
+
+ // Sets the observer for when accessibility actions need to be send to the
+ // SWA from the browser.
+ SetAccessibilityObserver(pending_remote<AccessibilityObserver> observer);
+};
+
+// Observer for accessibility provider. Should be created using
+// SetAccessibilityObserver in AccessibilityProvider.
+interface AccessibilityObserver {
+ // Sends the accessibility action to the SWA for processing.
+ // Proto from: ash/webui/eche_app_ui/proto/accessibility_mojom.proto
+ PerformAction(array<uint8> serialized_proto);
};
// Interface for generating uid. The uid is unique and persistent.
diff --git a/ash/webui/eche_app_ui/resources/browser_proxy.js b/ash/webui/eche_app_ui/resources/browser_proxy.js
index 9674721..103fbe3 100644
--- a/ash/webui/eche_app_ui/resources/browser_proxy.js
+++ b/ash/webui/eche_app_ui/resources/browser_proxy.js
@@ -43,6 +43,13 @@
systemInfoObserverRouter.$.bindNewPipeAndPassRemote());
// Returns the remote for AccessibilityProvider.
const accessibility = ash.echeApp.mojom.AccessibilityProvider.getRemote();
+// An object that receives requests for the AccessibilityObserver mojom
+// interface and dispatches them as callbacks. Setup the message
+const accessibilityObserverRouter =
+ new ash.echeApp.mojom.AccessibilityObserverCallbackRouter();
+// Set up a message pipe to the browser process to accessibility actions.
+accessibility.setAccessibilityObserver(
+ accessibilityObserverRouter.$.bindNewPipeAndPassRemote());
const notificationGenerator =
ash.echeApp.mojom.NotificationGenerator.getRemote();
@@ -148,6 +155,10 @@
});
});
+accessibilityObserverRouter.performAction.addListener(action => {
+ guestMessagePipe.sendMessage(Message.ACCESSIBILITY_PERFORM_ACTION, action);
+});
+
// Add stream action listener and send result via pipes.
streamActionObserverRouter.onStreamAction.addListener((action) => {
console.log(`echeapi browser_proxy.js OnStreamAction ${action}`);
diff --git a/ash/webui/eche_app_ui/resources/message_types.js b/ash/webui/eche_app_ui/resources/message_types.js
index dcc3bf5..8251827c 100644
--- a/ash/webui/eche_app_ui/resources/message_types.js
+++ b/ash/webui/eche_app_ui/resources/message_types.js
@@ -116,4 +116,6 @@
CONNECTION_STATUS_CHANGED: 'connection_status_changed',
// Message for sending accessibility event data.
ACCESSIBILITY_EVENT_DATA: 'accessibility_event_data',
+ // Message for sending actions and their parameters.
+ ACCESSIBILITY_PERFORM_ACTION: 'accessibility_perform_action',
};
diff --git a/ash/webui/eche_app_ui/resources/receiver.js b/ash/webui/eche_app_ui/resources/receiver.js
index 92cfbf7..374665d 100644
--- a/ash/webui/eche_app_ui/resources/receiver.js
+++ b/ash/webui/eche_app_ui/resources/receiver.js
@@ -69,6 +69,12 @@
/** @type {boolean} */ (message.androidDeviceOnCellular));
});
+// Handle accessibility perform action.
+parentMessagePipe.registerHandler(
+ Message.ACCESSIBILITY_PERFORM_ACTION, async (action) => {
+ console.log('Performed accessibility action: ' + action);
+ });
+
// The implementation of echeapi.d.ts
const EcheApiBindingImpl = new (class {
closeWindow() {