Add focus option to context menu in the accessibility tree

Summary: Allow user to open the context menu on an element in the ax tree and request accessibility focus to that element. If the element is focusable (and talkback or another accessibility service is running), accessibility focus will change to that element, if not, it will not change anything.

Differential Revision: D9162382

fbshipit-source-id: 5dda9b87a2cc6eba4130e3feee978b5fa38ac9f1
This commit is contained in:
Sara Valderrama
2018-08-07 09:35:13 -07:00
committed by Facebook Github Bot
parent ae0b8f6b53
commit 1fb2c4ee76
5 changed files with 58 additions and 2 deletions

View File

@@ -142,6 +142,7 @@ public class InspectorSonarPlugin implements SonarPlugin {
connection.receive("getSearchResults", mGetSearchResults); connection.receive("getSearchResults", mGetSearchResults);
connection.receive("getAXRoot", mGetAXRoot); connection.receive("getAXRoot", mGetAXRoot);
connection.receive("getAXNodes", mGetAXNodes); connection.receive("getAXNodes", mGetAXNodes);
connection.receive("onRequestAXFocus", mOnRequestAXFocus);
if (mExtensionCommands != null) { if (mExtensionCommands != null) {
for (ExtensionCommand extensionCommand : mExtensionCommands) { for (ExtensionCommand extensionCommand : mExtensionCommands) {
@@ -260,6 +261,22 @@ public class InspectorSonarPlugin implements SonarPlugin {
} }
}; };
final SonarReceiver mOnRequestAXFocus =
new MainThreadSonarReceiver(mConnection) {
@Override
public void onReceiveOnMainThread(final SonarObject params, final SonarResponder responder)
throws Exception {
final String nodeId = params.getString("id");
final Object obj = mObjectTracker.get(nodeId);
if (obj == null || !(obj instanceof View)) {
return;
}
((View) obj).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
};
final SonarReceiver mSetData = final SonarReceiver mSetData =
new MainThreadSonarReceiver(mConnection) { new MainThreadSonarReceiver(mConnection) {
@Override @Override

View File

@@ -6,7 +6,12 @@
*/ */
import {Component} from 'react'; import {Component} from 'react';
import type {Element, ElementID, ElementSearchResultSet} from 'sonar'; import type {
Element,
ElementID,
ElementSearchResultSet,
ContextMenuExtension,
} from 'sonar';
export class AXElementsInspector extends Component<{ export class AXElementsInspector extends Component<{
onElementExpanded: (key: ElementID, deep: boolean) => void, onElementExpanded: (key: ElementID, deep: boolean) => void,
@@ -19,6 +24,7 @@ export class AXElementsInspector extends Component<{
root: ?ElementID, root: ?ElementID,
elements: {[key: ElementID]: Element}, elements: {[key: ElementID]: Element},
useAppSidebar?: boolean, useAppSidebar?: boolean,
contextMenuExtensions: Array<ContextMenuExtension>,
}> { }> {
render() { render() {
return null; return null;

View File

@@ -791,6 +791,17 @@ export default class Layout extends SonarPlugin<InspectorState> {
}); });
}); });
getAXContextMenuExtensions() {
return [
{
label: 'Focus',
click: (id: ElementID) => {
this.client.send('onRequestAXFocus', {id});
},
},
];
}
onDataValueChanged = (path: Array<string>, value: any) => { onDataValueChanged = (path: Array<string>, value: any) => {
const selected = this.state.inAXMode const selected = this.state.inAXMode
? this.state.AXselected ? this.state.AXselected
@@ -935,6 +946,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
searchResults={null} searchResults={null}
root={AXroot} root={AXroot}
elements={AXelements} elements={AXelements}
contextMenuExtensions={this.getAXContextMenuExtensions()}
/> />
) : null} ) : null}
</FlexRow> </FlexRow>

View File

@@ -215,6 +215,7 @@ type ElementsRowProps = {
childrenCount: number, childrenCount: number,
onElementHovered: ?(key: ?ElementID) => void, onElementHovered: ?(key: ?ElementID) => void,
style: ?Object, style: ?Object,
contextMenuExtensions: Array<ContextMenuExtension>,
}; };
type ElementsRowState = {| type ElementsRowState = {|
@@ -232,7 +233,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
getContextMenu = (): Array<Electron$MenuItemOptions> => { getContextMenu = (): Array<Electron$MenuItemOptions> => {
const {props} = this; const {props} = this;
return [ const items = [
{ {
type: 'separator', type: 'separator',
}, },
@@ -249,6 +250,15 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
}, },
}, },
]; ];
for (const extension of props.contextMenuExtensions) {
items.push({
label: extension.label,
click: () => extension.click(this.props.id),
});
}
return items;
}; };
onClick = () => { onClick = () => {
@@ -390,6 +400,7 @@ type ElementsProps = {|
onElementExpanded: (key: ElementID, deep: boolean) => void, onElementExpanded: (key: ElementID, deep: boolean) => void,
onElementHovered: ?(key: ?ElementID) => void, onElementHovered: ?(key: ?ElementID) => void,
alternateRowColor?: boolean, alternateRowColor?: boolean,
contextMenuExtensions?: Array<ContextMenuExtension>,
|}; |};
type ElementsState = {| type ElementsState = {|
@@ -398,6 +409,11 @@ type ElementsState = {|
maxDepth: number, maxDepth: number,
|}; |};
export type ContextMenuExtension = {|
label: string,
click: ElementID => void,
|};
export class Elements extends PureComponent<ElementsProps, ElementsState> { export class Elements extends PureComponent<ElementsProps, ElementsState> {
static defaultProps = { static defaultProps = {
alternateRowColor: true, alternateRowColor: true,
@@ -554,6 +570,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
selected, selected,
focused, focused,
searchResults, searchResults,
contextMenuExtensions,
} = this.props; } = this.props;
const {flatElements} = this.state; const {flatElements} = this.state;
const row = flatElements[index]; const row = flatElements[index];
@@ -593,6 +610,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
elements={elements} elements={elements}
childrenCount={childrenCount} childrenCount={childrenCount}
style={style} style={style}
contextMenuExtensions={contextMenuExtensions || []}
/> />
); );
}; };

View File

@@ -159,6 +159,9 @@ export type {
ElementSearchResultSet, ElementSearchResultSet,
} from './components/elements-inspector/ElementsInspector.js'; } from './components/elements-inspector/ElementsInspector.js';
export {Elements} from './components/elements-inspector/elements.js'; export {Elements} from './components/elements-inspector/elements.js';
export type {
ContextMenuExtension,
} from './components/elements-inspector/elements.js';
export { export {
default as ElementsInspector, default as ElementsInspector,
} from './components/elements-inspector/ElementsInspector.js'; } from './components/elements-inspector/ElementsInspector.js';