为什么 NVDA 不一致地读取我的自定义错误警报?

Why isn't NVDA reading my custom error alert consistently?

我已经为客户的 azure b2c 登录页面设置了一个自定义 html 页面模板,并且内联错误正在按预期读回(如果需要,可以根据要求提供有关这些错误的更多详细信息有益),但我有点困惑为什么页面级错误也没有被读回。

以下是在初始页面加载期间呈现的模板中 html 的相关片段:

<!-- B2C-Generated Form Element (all relevant html changes happen within this element) -->
<form id="localAccountForm" action="JavaScript:void(0);" class="localAccount" aria-label="Sign in to Your Account">

<!--B2C-Generated Error Container (prior to error / this html is generated/injected by Azure B2C when the page is loaded) -->
<div class="error pageLevel" aria-hidden="true" role="alert" style="display: none;">
  <p class="remove"></p>
</div>

<!-- Custom Error Container (prior to error / this html gets added to the template by jQuery once the window is ready) -->
<div role="alert" style="" class="errorContainer no-error" aria-hidden="false">
    <p id="pageError" role="" aria-hidden="true" style="" class="remove"></p>
</div>

初始内容加载后(包括来自 Azure B2C 的内容,以及来自 jQuery 的修改),以下逻辑得到 运行确保页面上的所有错误元素都设置正确(或至少这是目的)并消除一些可能导致问题的差异:

initializeAlerts([
    '#pageError',
    'label[for="signInName"] + .error > p',
    '.password-label + .error > p'
]);

// Below functions are loaded & run from a separate .js file:

function initializeAlerts(errorSelectors) {
    errorSelectors.forEach((s) => {
        // Store the current error & whether or not inline styles were attempting to hide it
        var errorMsg = $(s).html();   
        var errorStyle = `${$(s).attr('style')} ${$(s).parent().attr('style')}`; 

        // Ensure the parent element has the role="alert" attribute (rather than the error itself)
        $(s).attr('role', '');
        $(s).parent().attr('role', 'alert');
        
        // Default both the error element & it's parent to be visible
        $(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');        
        $(s).parent().addClass('errorContainer').addClass('no-error').removeClass('remove').attr('aria-hidden', 'false').attr('style','');
        
        // If an error message is NOT present, add a class to the parent for styling purposes
        if (errorMsg) {
            $(s).parent().removeClass('no-error');
        }

        // If the error was supposed to be hidden dynamically via Azure B2C (i.e. it's a standard error that's prepopulated & simply gets shown/hidden), ensure the error itself is hidden (NOT the parent)
        if (errorStyle.indexOf('none') > -1) {
            $(s).addClass('remove').attr('aria-hidden', 'true');
            $(s).parent().addClass('no-error');
        }   

        // If/when the error gets updated, ensure it gets displayed/read
        callbackOnDOMUpdate([s], function() {
            var currentError = $(s).html();
            if (currentError) {
                $(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
                $(s).parent().removeClass('no-error');
            } else {
                $(s).addClass('remove').attr('aria-hidden', 'true').attr('style','');
                $(s).parent().addClass('no-error');
            }
        });
    });    
}

function callbackOnDOMUpdate(selectors, callback) {
    selectors.forEach(selector => {
        $(function() {
            var target = document.querySelector(selector);
            var observer = new MutationObserver(function(mutations, observer) { 
                callback();
            });

            observer.observe(target, { 
                subtree: true, 
                childList : true, 
                characterData : true
            });
        });
    });
}

在所有这些 运行 之后,如果用户输入了不正确的 user/pass 组合,则会向页面返回一个错误(通过“B2C 生成的错误容器”),看起来有点像像这样(取决于返回的具体错误):

<!--B2C-Generated Error Container within form (after receiving error) -->
<div class="error pageLevel" aria-hidden="false" role="alert" style="display: block;">
    <p class="remove">Unable to validate the information provided.</p>
</div>

不过,客户希望进行一些 verbiage/styling 更改,因此与其按原样显示该消息,不如向其添加“删除”class(与 display: none !important css 规则)并且我的自定义错误容器已更新以显示类似于以下消息的内容(同样,实际消息可能会有所不同,具体取决于从 b2c 返回的消息):

<!-- Custom Error Container (after receiving error) -->
<div role="alert" style="display: block">
    <p id="pageError">
        <strong>We're sorry, the information entered does not match what we have on file.</strong> 
        Please try re-entering the information below or you can recover your login info.
    </p>
</div>

不幸的是,虽然这似乎偶尔会按预期阅读,但大多数时候,我听到的唯一消息是 "Sign in to Your Account form continue button"(这似乎是变化嵌套在其中的表单元素的 aria 标签、表单元素名称本身和用户在看到页面更新之前单击的最后一个按钮的名称的组合。

我已尝试确保错误本身作为子元素嵌套在父元素中:

  1. 始终可见(从 css 和 aria 视角)
  2. role='alert'

(简单地 show/hide 通过“删除”class 的 addition/removal 错误本身)

...但我一定是遗漏了什么地方,所以任何人都可以提供任何帮助,我们将不胜感激。

所以...事实证明,以上所有代码都没有问题(除了上面的 #1 并非完全正确)。该问题原来与以下内容有关...

当用户单击“登录”按钮时,我使用了 $('body').removeClass('loaded');,这反过来会导致:

  1. 要显示的加载动画
  2. 包含要设置为display: none;
  3. 的所有页面内容的元素

当检测到错误时,我会类似地执行以下命令(按此顺序):

  1. $('body').addClass('loaded');(从而使所有内容再次可见)
  2. 读取返回的系统错误并相应地填充代码的自定义错误区域的逻辑

...所以,我尝试删除所有与加载动画相关的 jquery 以查看问题是否与此相关&果然,错误如预期的那样读取。

话虽这么说,我认为这里真正的问题是 error/alert 更新在所有与可见性相关的更改从 css 端开始之前完成,因此 NVDA 没有'阅读警报,因为从 NVDA 的角度来看,错误仍然隐藏。

...希望此经验的文档可以帮助其他人!