在做一些最近的可访问性工作时,我遇到了一个场景,即在iframe中将焦点设置为标头(设置了tabindex)对用户来说是不一致的结果,但是在Chrome隐身模式下却始终失败。
经过大量测试,我缩小了范围,提出了以下测试方案。这些需要在本地HTML文件中运行,因为测试用例在iframe中运行(例如,在Codepen上)的工作方式有所不同:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>"Fun" with focus!</title>
</head>
<body translate="no">
<h1>"Fun" with focus!</h1>
<ol>
<li>Test the following *6* cases in Chrome incognito</li>
<li>Enable VoiceOver on Mac by hitting Cmd + fingerprint sensor 3 times, or Cmd + F5</li>
<li>Keyboard navigate through the sections below using tab, and activate them using space or enter.</li>
<li>Look inside the console to make sure all 6 focus calls were made ("focusing on iframe-# header")</li>
</ol>
<h2>On-page IFrame</h2>
<p>Clicking this
<button id="btn-1">button</button>
should work result in the IFrame header being focused on and read out
</p>
<div id="iframe-1-container">
<iframe id="iframe-1"></iframe>
</div>
<h2>Sandboxed On-page IFrame</h2>
<p>Clicking this
<button id="btn-2">button</button>
should work result in the IFrame header being focused on and read out
</p>
<div id="iframe-2-container">
<iframe id="iframe-2" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts"></iframe>
</div>
<script>
// Render iframe-1
document.querySelector('#btn-1').addEventListener('click', function () {
const doc = document.querySelector('#iframe-1').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I should be focused and read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-1 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
// Render iframe-2
document.querySelector('#btn-2').addEventListener('click', function () {
const doc = document.querySelector('#iframe-2').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I should be focused and read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-2 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
</script>
<h2>Dynamically Inserted IFrame Outside an Event Handler</h2>
<p>Clicking this
<button id="btn-3">button</button>
should also result in the IFrame header being focused on and read out
</p>
<div id="iframe-3-container"></div>
<h2>Dynamically Inserted Sandboxed IFrame Outside an Event Handler</h2>
<p>Clicking this
<button id="btn-4">button</button>
should also result in the IFrame header being focused on and read out
</p>
<div id="iframe-4-container"></div>
<script>
// Render iframe-3
let div = document.createElement('div');
div.innerHTML = '<iframe id="iframe-3"></iframe>';
document.querySelector('#iframe-3-container').appendChild(div);
document.querySelector('#btn-3').addEventListener('click', function () {
const doc = document.querySelector('#iframe-3').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I should also be focused and read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-3 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
// Render iframe-4
div = document.createElement('div');
div.innerHTML = '<iframe id="iframe-4" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts"></iframe>';
document.querySelector('#iframe-4-container').appendChild(div);
document.querySelector('#btn-4').addEventListener('click', function () {
const doc = document.querySelector('#iframe-4').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I should also be focused and read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-4 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
</script>
<h2>Dynamically Inserted IFrame Inside an Event Handler</h2>
<p>Clicking this
<button id="btn-5">button</button>
should also result in the IFrame header being focused on and read out
</p>
<div id="iframe-5-container"></div>
<h2>Dynamically Inserted Sandbox IFrame Inside an Event Handler</h2>
<p>Clicking this
<button id="btn-6">button</button>
probably won't result in the IFrame header being focused or read out (focus will likely stay here)
</p>
<div id="iframe-6-container"></div>
<script>
// Render iframe-5
document.querySelector('#btn-5').addEventListener('click', function () {
div = document.createElement('div');
div.innerHTML = '<iframe id="iframe-5"></iframe>';
document.querySelector('#iframe-5-container').appendChild(div);
const doc = document.querySelector('#iframe-5').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I should also be focused and read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-5 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
// Render iframe-6
document.querySelector('#btn-6').addEventListener('click', function () {
div = document.createElement('div');
div.innerHTML = '<iframe id="iframe-6" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts"></iframe>';
document.querySelector('#iframe-6-container').appendChild(div);
const doc = document.querySelector('#iframe-6').contentWindow.document;
doc.write(
'<h1 id="header" tabindex="-1">I will neither be focused nor read out</h1>' +
'<button>Ignore me I am an useless button</button>' +
'<scr' + "ipt>console.log('focusing on iframe-6 header'); document.querySelector('#header').focus();</scr" + 'ipt>');
doc.close();
});
</script>
</body>
</html>
请注意,前五种情况均会导致焦点设置正确,而第六种情况则不会。具体来说,将iframe动态插入事件处理程序中并对其设置沙箱权限时,将无法正确设置内部焦点。为什么会这样?
Safari可以处理所有这些情况,Firefox也可以处理所有情况(尽管VoiceOver无法读取),但是Chrome始终无法解决这最后一种情况。