Bug 1654956 - add UI for displaying tabbing order overlay in the accessibility panel. r=jdescottes,devtools-backward-compat-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D94926
This commit is contained in:
Yura Zenevich 2020-11-03 15:33:16 +00:00
parent ba825c5746
commit c7aaa8e041
16 changed files with 322 additions and 19 deletions

View File

@ -77,6 +77,7 @@ class AccessibilityProxy {
this
);
this.unhighlightBeforeCalling = this.unhighlightBeforeCalling.bind(this);
this.toggleDisplayTabbingOrder = this.toggleDisplayTabbingOrder.bind(this);
}
get enabled() {
@ -140,6 +141,24 @@ class AccessibilityProxy {
return combinedAudit;
}
async toggleDisplayTabbingOrder(displayTabbingOrder) {
if (displayTabbingOrder) {
const { walker: domWalkerFront } = await this.currentTarget.getFront(
"inspector"
);
await this.accessibilityFront.accessibleWalkerFront.showTabbingOrder(
await domWalkerFront.getRootNode(),
0
);
} else {
await this.withAllAccessibilityWalkerFronts(
async accessibleWalkerFront => {
await accessibleWalkerFront.hideTabbingOrder();
}
);
}
}
startListeningForTargetUpdated(onTargetUpdated) {
this._updateTargetListeners.on("target-updated", onTargetUpdated);
}
@ -196,13 +215,9 @@ class AccessibilityProxy {
* Function to execute with each accessiblity walker front.
*/
withAllAccessibilityWalkerFronts(taskFn) {
return this.withAllAccessibilityFronts(async accessibilityFront => {
if (!accessibilityFront.accessibleWalkerFront) {
await accessibilityFront.bootstrap();
}
return taskFn(accessibilityFront.accessibleWalkerFront);
});
return this.withAllAccessibilityFronts(async accessibilityFront =>
taskFn(accessibilityFront.accessibleWalkerFront)
);
}
/**
@ -509,13 +524,10 @@ class AccessibilityProxy {
this.accessibilityFront = await this.currentTarget.getFront(
"accessibility"
);
// To add a check for backward compatibility add something similar to the
// example below:
//
// [this.supports.simulation] = await Promise.all([
// // Please specify the version of Firefox when the feature was added.
// this.currentTarget.actorHasMethod("accessibility", "getSimulator"),
// ]);
// Check for backward compatibility. New API's must be described in the
// "getTraits" method of the AccessibilityActor.
this.supports = { ...this.accessibilityFront.traits };
this.simulatorFront = this.accessibilityFront.simulatorFront;
if (this.simulatorFront) {
this.simulate = types => this.simulatorFront.simulate({ types });

View File

@ -25,7 +25,12 @@ const createStore = require("devtools/client/shared/redux/create-store");
// Reducers
const { reducers } = require("devtools/client/accessibility/reducers/index");
const store = createStore(reducers);
const thunkOptions = { options: {} };
const store = createStore(reducers, {
// Thunk options will be updated, when we [re]initialize the accessibility
// view.
thunkOptions,
});
// Actions
const { reset } = require("devtools/client/accessibility/actions/ui");
@ -79,6 +84,10 @@ AccessibilityView.prototype = {
* Apply simulation of a given type
* (by setting color matrices in
* docShell).
* - toggleDisplayTabbingOrder {Function}
* Toggle the highlight of focusable
* elements along with their tabbing
* index.
* - enableAccessibility {Function}
* Enable accessibility services.
* - resetAccessiblity {Function}
@ -112,6 +121,7 @@ AccessibilityView.prototype = {
stopListeningForAccessibilityEvents,
audit,
simulate,
toggleDisplayTabbingOrder,
enableAccessibility,
resetAccessiblity,
startListeningForLifecycleEvents,
@ -141,6 +151,7 @@ AccessibilityView.prototype = {
highlightAccessible,
unhighlightAccessible,
});
thunkOptions.options.toggleDisplayTabbingOrder = toggleDisplayTabbingOrder;
// Render top level component
const provider = createElement(Provider, { store: this.store }, mainFrame);
window.once(EVENTS.PROPERTIES_UPDATED).then(() => {

View File

@ -236,12 +236,12 @@ body {
width: 100%;
}
/* @remove after release 68 (See Bug 1551574) */
.devtools-toolbar .beta {
color: var(--theme-highlight-blue);
font-size: 80%;
font-weight: 500;
margin-inline-end: 3px;
margin-inline-start: 4px;
}
#audit-progress-container {

View File

@ -12,6 +12,7 @@ const {
UPDATE_CAN_BE_ENABLED,
UPDATE_PREF,
PREF_KEYS,
UPDATE_DISPLAY_TABBING_ORDER,
} = require("devtools/client/accessibility/constants");
/**
@ -54,3 +55,15 @@ exports.enable = enableAccessibility => async ({ dispatch }) => {
dispatch({ error, type: ENABLE });
}
};
exports.updateDisplayTabbingOrder = tabbingOrderDisplayed => async ({
dispatch,
options: { toggleDisplayTabbingOrder },
}) => {
try {
await toggleDisplayTabbingOrder(tabbingOrderDisplayed);
dispatch({ tabbingOrderDisplayed, type: UPDATE_DISPLAY_TABBING_ORDER });
} catch (error) {
dispatch({ error, type: UPDATE_DISPLAY_TABBING_ORDER });
}
};

View File

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// React
const { PureComponent } = require("devtools/client/shared/vendor/react");
const {
label,
input,
} = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { L10N } = require("devtools/client/accessibility/utils/l10n");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
updateDisplayTabbingOrder,
} = require("devtools/client/accessibility/actions/ui");
class DisplayTabbingOrder extends PureComponent {
static get propTypes() {
return {
describedby: PropTypes.string,
dispatch: PropTypes.func.isRequired,
tabbingOrderDisplayed: PropTypes.bool.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
disabled: false,
};
this.onChange = this.onChange.bind(this);
}
async onChange() {
const { dispatch, tabbingOrderDisplayed } = this.props;
this.setState({ disabled: true });
await dispatch(updateDisplayTabbingOrder(!tabbingOrderDisplayed));
this.setState({ disabled: false });
}
render() {
const { describedby, tabbingOrderDisplayed } = this.props;
return label(
{
className: "accessibility-tabbing-order devtools-checkbox-label",
htmlFor: "devtools-display-tabbing-order-checkbox",
title: L10N.getStr("accessibility.toolbar.displayTabbingOrder.tooltip"),
},
input({
id: "devtools-display-tabbing-order-checkbox",
className: "devtools-checkbox",
type: "checkbox",
checked: tabbingOrderDisplayed,
disabled: this.state.disabled,
onChange: this.onChange,
"aria-describedby": describedby,
}),
L10N.getStr("accessibility.toolbar.displayTabbingOrder.label")
);
}
}
const mapStateToProps = ({ ui: { tabbingOrderDisplayed } }) => ({
tabbingOrderDisplayed,
});
module.exports = connect(mapStateToProps)(DisplayTabbingOrder);

View File

@ -5,7 +5,11 @@
// React
const { createFactory } = require("devtools/client/shared/vendor/react");
const { div } = require("devtools/client/shared/vendor/react-dom-factories");
const {
div,
span,
} = require("devtools/client/shared/vendor/react-dom-factories");
const { L10N } = require("devtools/client/accessibility/utils/l10n");
const AccessibilityTreeFilter = createFactory(
require("devtools/client/accessibility/components/AccessibilityTreeFilter")
);
@ -17,8 +21,14 @@ loader.lazyGetter(this, "SimulationMenuButton", function() {
require("devtools/client/accessibility/components/SimulationMenuButton")
);
});
const DisplayTabbingOrder = createFactory(
require("devtools/client/accessibility/components/DisplayTabbingOrder")
);
function Toolbar({ toolboxDoc, audit, simulate }) {
const { connect } = require("devtools/client/shared/vendor/react-redux");
function Toolbar({ audit, simulate, supportsTabbingOrder, toolboxDoc }) {
const betaID = "beta";
const optionalSimulationSection = simulate
? [
div({
@ -28,6 +38,23 @@ function Toolbar({ toolboxDoc, audit, simulate }) {
SimulationMenuButton({ simulate, toolboxDoc }),
]
: [];
const optionalDisplayTabbingOrderSection = supportsTabbingOrder
? [
div({
role: "separator",
className: "devtools-separator",
}),
span(
{
className: "beta",
role: "presentation",
id: betaID,
},
L10N.getStr("accessibility.beta")
),
DisplayTabbingOrder({ describedby: betaID }),
]
: [];
return div(
{
@ -37,9 +64,18 @@ function Toolbar({ toolboxDoc, audit, simulate }) {
AccessibilityTreeFilter({ audit, toolboxDoc }),
// Simulation section is shown if webrender is enabled
...optionalSimulationSection,
...optionalDisplayTabbingOrderSection,
AccessibilityPrefs({ toolboxDoc })
);
}
const mapStateToProps = ({
ui: {
supports: { tabbingOrder },
},
}) => ({
supportsTabbingOrder: tabbingOrder,
});
// Exports from this module
exports.Toolbar = Toolbar;
exports.Toolbar = connect(mapStateToProps)(Toolbar);

View File

@ -20,6 +20,7 @@ DevToolsModules(
"ColorContrastAccessibility.js",
"ContrastBadge.js",
"Description.js",
"DisplayTabbingOrder.js",
"KeyboardBadge.js",
"KeyboardCheck.js",
"LearnMoreLink.js",

View File

@ -70,6 +70,7 @@ exports.AUDIT = "AUDIT";
exports.AUDITING = "AUDITING";
exports.AUDIT_PROGRESS = "AUDIT_PROGRESS";
exports.SIMULATE = "SIMULATE";
exports.UPDATE_DISPLAY_TABBING_ORDER = "UPDATE_DISPLAY_TABBING_ORDER";
// List of filters for accessibility checks.
exports.FILTERS = {

View File

@ -212,6 +212,7 @@ AccessibilityPanel.prototype = {
stopListeningForAccessibilityEvents,
audit,
simulate,
toggleDisplayTabbingOrder,
enableAccessibility,
resetAccessiblity,
startListeningForLifecycleEvents,
@ -230,6 +231,7 @@ AccessibilityPanel.prototype = {
stopListeningForAccessibilityEvents,
audit,
simulate,
toggleDisplayTabbingOrder,
enableAccessibility,
resetAccessiblity,
startListeningForLifecycleEvents,

View File

@ -19,6 +19,7 @@ const {
UPDATE_DETAILS,
PREF_KEYS,
PREFS,
UPDATE_DISPLAY_TABBING_ORDER,
} = require("devtools/client/accessibility/constants");
const TreeView = require("devtools/client/shared/components/tree/TreeView");
@ -38,6 +39,7 @@ function getInitialState() {
PREF_KEYS[PREFS.SCROLL_INTO_VIEW],
false
),
tabbingOrderDisplayed: false,
supports: {},
};
}
@ -67,6 +69,8 @@ function ui(state = getInitialState(), action) {
return onSelect(state, action);
case RESET:
return onReset(state, action);
case UPDATE_DISPLAY_TABBING_ORDER:
return onUpdateDisplayTabbingOrder(state, action);
default:
return state;
}
@ -203,4 +207,13 @@ function onToggle(state, { error }, enabled) {
return Object.assign({}, state, { enabled });
}
function onUpdateDisplayTabbingOrder(state, { error, tabbingOrderDisplayed }) {
if (error) {
console.warn("Error updating displaying tabbing order: ", error);
return state;
}
return Object.assign({}, state, { tabbingOrderDisplayed });
}
exports.ui = ui;

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DisplayTabbingOrder component: default render 1`] = `"<label class=\\"accessibility-tabbing-order devtools-checkbox-label\\" for=\\"devtools-display-tabbing-order-checkbox\\" title=\\"Show tabbing order of elements and their tabbing index.\\"><input id=\\"devtools-display-tabbing-order-checkbox\\" class=\\"devtools-checkbox\\" type=\\"checkbox\\" aria-describedby=\\"test\\">Show Tabbing Order</label>"`;
exports[`DisplayTabbingOrder component: displaying tabbing order render/update 1`] = `"<label class=\\"accessibility-tabbing-order devtools-checkbox-label\\" for=\\"devtools-display-tabbing-order-checkbox\\" title=\\"Show tabbing order of elements and their tabbing index.\\"><input id=\\"devtools-display-tabbing-order-checkbox\\" class=\\"devtools-checkbox\\" type=\\"checkbox\\">Show Tabbing Order</label>"`;
exports[`DisplayTabbingOrder component: displaying tabbing order render/update 2`] = `"<label class=\\"accessibility-tabbing-order devtools-checkbox-label\\" for=\\"devtools-display-tabbing-order-checkbox\\" title=\\"Show tabbing order of elements and their tabbing index.\\"><input id=\\"devtools-display-tabbing-order-checkbox\\" class=\\"devtools-checkbox\\" type=\\"checkbox\\">Show Tabbing Order</label>"`;
exports[`DisplayTabbingOrder component: toggle tabbing order overlay 1`] = `"<label class=\\"accessibility-tabbing-order devtools-checkbox-label\\" for=\\"devtools-display-tabbing-order-checkbox\\" title=\\"Show tabbing order of elements and their tabbing index.\\"><input id=\\"devtools-display-tabbing-order-checkbox\\" class=\\"devtools-checkbox\\" type=\\"checkbox\\">Show Tabbing Order</label>"`;

View File

@ -0,0 +1,95 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { mount } = require("enzyme");
const { createFactory } = require("devtools/client/shared/vendor/react");
const Provider = createFactory(
require("devtools/client/shared/vendor/react-redux").Provider
);
const {
setupStore,
} = require("devtools/client/accessibility/test/node/helpers");
const {
UPDATE_DISPLAY_TABBING_ORDER,
} = require("devtools/client/accessibility/constants");
const ConnectedDisplayTabbingOrderClass = require("devtools/client/accessibility/components/DisplayTabbingOrder");
const DisplayTabbingOrderClass =
ConnectedDisplayTabbingOrderClass.WrappedComponent;
const DisplayTabbingOrder = createFactory(ConnectedDisplayTabbingOrderClass);
function testCheckbox(wrapper, expected) {
expect(wrapper.html()).toMatchSnapshot();
const displayTabbingOrder = wrapper.find(DisplayTabbingOrderClass);
expect(displayTabbingOrder.children().length).toBe(1);
// Label checks
const label = displayTabbingOrder.childAt(0);
expect(label.hasClass("accessibility-tabbing-order")).toBe(true);
expect(label.hasClass("devtools-checkbox-label")).toBe(true);
expect(label.prop("title")).toBe(
"Show tabbing order of elements and their tabbing index."
);
expect(label.text()).toBe("Show Tabbing Order");
expect(label.children().length).toBe(1);
// Checkbox checks
const checkbox = label.childAt(0);
expect(checkbox.prop("checked")).toBe(expected.checked);
expect(checkbox.prop("disabled")).toBe(!!expected.disabled);
if (expected.describedby) {
expect(checkbox.prop("aria-describedby")).toBe(expected.describedby);
}
}
describe("DisplayTabbingOrder component:", () => {
it("default render", () => {
const DESCRIBEDBY = "test";
const store = setupStore();
const wrapper = mount(
Provider({ store }, DisplayTabbingOrder({ describedby: DESCRIBEDBY }))
);
testCheckbox(wrapper, { checked: false, describedby: DESCRIBEDBY });
});
it("toggle tabbing order overlay", () => {
const store = setupStore();
const wrapper = mount(Provider({ store }, DisplayTabbingOrder()));
expect(wrapper.html()).toMatchSnapshot();
const displayTabbingOrderInstance = wrapper
.find(DisplayTabbingOrderClass)
.instance();
displayTabbingOrderInstance.onChange = jest.fn();
displayTabbingOrderInstance.forceUpdate();
const checkbox = wrapper.find("input");
checkbox.simulate("change");
expect(displayTabbingOrderInstance.onChange.mock.calls.length).toBe(1);
});
it("displaying tabbing order render/update", () => {
const store = setupStore({
preloadedState: {
ui: {
tabbingOrderDisplayed: true,
},
},
});
const wrapper = mount(Provider({ store }, DisplayTabbingOrder()));
testCheckbox(wrapper, { checked: true });
store.dispatch({
type: UPDATE_DISPLAY_TABBING_ORDER,
tabbingOrderDisplayed: false,
});
wrapper.update();
testCheckbox(wrapper, { checked: false });
});
});

View File

@ -527,6 +527,18 @@ class AccessibilityFront extends FrontClassWithSpec(accessibilitySpec) {
this.simulatorFront = await super.getSimulator();
const { enabled } = await super.bootstrap();
this.enabled = enabled;
try {
this._traits = await this.getTraits();
} catch (e) {
// Backward compatibility: can be removed when FF84 is on the release
// channel.
this._traits = {};
}
}
get traits() {
return this._traits;
}
init() {

View File

@ -286,3 +286,13 @@ accessibility.simulation.contrastLoss=Contrast loss
# LOCALIZATION NOTE (accessibility.simulation.achromatopsia): This label is shown
# in the "Simulate" menu in the accessibility panel and represent the achromatopsia simulation option.
accessibility.simulation.achromatopsia=Achromatopsia (no color)
# LOCALIZATION NOTE (accessibility.toolbar.displayTabbingOrder.label): A title text for a checkbox label
# in the accessibility panel toolbar that turns on/off the overlay of focusable elements in their
# tabbing order.
accessibility.toolbar.displayTabbingOrder.label=Show Tabbing Order
# LOCALIZATION NOTE (accessibility.toolbar.displayTabbingOrder.tooltip): A title text for a checkbox
# tooltip in the accessibility panel toolbar that turns on/off the overlay of focusable elements in
# their tabbing order.
accessibility.toolbar.displayTabbingOrder.tooltip=Show tabbing order of elements and their tabbing index.

View File

@ -43,6 +43,16 @@ const AccessibilityActor = ActorClassWithSpec(accessibilitySpec, {
this.targetActor = targetActor;
},
getTraits: function() {
// The traits are used to know if accessibility actors support particular
// API on the server side.
return {
// Backward compatibility: can be removed when FF84 is on release.
// Fixed on the server by Bug 1654956.
tabbingOrder: true,
};
},
bootstrap() {
return {
enabled: this.enabled,

View File

@ -235,6 +235,10 @@ const accessibilitySpec = generateActorSpec({
},
methods: {
getTraits: {
request: {},
response: { traits: RetVal("json") },
},
bootstrap: {
request: {},
response: {