Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Fix events not being spoken in the most complicated way possible
Browse files Browse the repository at this point in the history
There's details in the comment body of this diff.

See element-hq/element-web#9747
  • Loading branch information
turt2live committed May 22, 2019
1 parent c55f083 commit 09d195b
Showing 1 changed file with 43 additions and 3 deletions.
46 changes: 43 additions & 3 deletions src/components/views/rooms/EventTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import withMatrixClient from '../../../wrappers/withMatrixClient';
import dis from '../../../dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
import {EventStatus} from 'matrix-js-sdk';
import MatrixClientPeg from "../../../MatrixClientPeg";

const ObjectUtils = require('../../../ObjectUtils');

Expand Down Expand Up @@ -545,7 +546,46 @@ module.exports = withMatrixClient(React.createClass({
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();

const muteScreenReader = isSending || !this.props.eventSendStatus;
// TLDR: Screen readers are complicated and can watch for new DOM elements, but not
// changes to DOM elements. As such, we hack a bunch of conditions together.
//
// Screen readers do not react well to aria attributes changing dynamically after
// parsing them. Although readers watch the DOM, the cannot react to aria-hidden
// going from true to false. To work around that, we check to see if the eventSendStatus
// is something worthwhile for us to read out. We specifically don't want to read
// out pending/queued messages because they'll be read out again when they are sent.
//
// There's a small annoyance with doing this though: if we can't change the aria attrs,
// we need to track the entry state for when the component mounts. As it stands, the
// EventTile is unmounted/mounted when going pending->sent, and then a simple properties
// change is made to mxEvent for sent->null (the final state). We abuse this cycle to
// mute the pending state and react on the sent state.
//
// However there's then a bug where readers don't read messages from other people (they
// enter the component as eventSendStatus of null) - to counteract this, we look for a
// transaction_id under the unsigned object of the event. According to the spec, we can
// use this to determine if an event was sent by us (as it's bound to the access token
// which sent the event). This allows us to do a few checks on whether to speak:
// * If the event was sent by our user ID and the eventSendStatus is 'sent', then speak.
// We cannot check the transaction_id at this point because it is undefined. We can
// make the assumption that 'sent' means this exact client is handling it though.
// * If the event was sent by our user ID and the eventSendStatus is falsey (null), then
// only speak if the event was not sent by us (no transaction_id).
// * If the event was not sent by our user ID then speak.
//
// Note: although NVDA (a screen reader) does react to aria-hidden changing, it does so
// in a horrible way. Because multiple properties and DOM elements are changing, it reads
// the message twice when we limit the 'should speak' checks to just 'if eventSendStatus
// is null'. This is part of the reason for the complexity above.
//
// Hopefully all of that leads to us not reading out messages in duplicate or triplicate.
const sentByMyUserId = this.props.mxEvent.getSender() === MatrixClientPeg.get().getUserId();
const sentByThisClient = !!this.props.mxEvent.getUnsigned()["transaction_id"];
const screenReaderShouldSpeak = isSending ? false : (
this.props.eventSendStatus
? sentByMyUserId && this.props.eventSendStatus === 'sent'
: !sentByMyUserId || !sentByThisClient
);

const classes = classNames({
mx_EventTile: true,
Expand Down Expand Up @@ -644,7 +684,7 @@ module.exports = withMatrixClient(React.createClass({
? <MessageTimestamp
showTwelveHour={this.props.isTwelveHour}
ts={this.props.mxEvent.getTs()}
ariaHidden={muteScreenReader}
ariaHidden={!screenReaderShouldSpeak}
/> : null;

const keyRequestHelpText =
Expand Down Expand Up @@ -783,7 +823,7 @@ module.exports = withMatrixClient(React.createClass({
'replyThread',
);
return (
<div className={classes} aria-hidden={muteScreenReader}>
<div className={classes} aria-hidden={!screenReaderShouldSpeak}>
<div className="mx_EventTile_msgOption">
{ readAvatars }
</div>
Expand Down

0 comments on commit 09d195b

Please sign in to comment.