Skip to content
This repository has been archived by the owner on Dec 7, 2022. It is now read-only.

Commit

Permalink
Check for scrollbars on parent elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Alice Boxhall committed Nov 15, 2013
1 parent 1254847 commit 21176ae
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/audits/UnfocusableElementsWithOnClick.js
Expand Up @@ -42,7 +42,8 @@ axs.AuditRule.specs.unfocusableElementsWithOnClick = {
},
test: function(element) {
return !element.hasAttribute('tabindex') &&
!axs.utils.isElementImplicitlyFocusable(element);
!axs.utils.isElementImplicitlyFocusable(element) &&
!element.disabled;
},
code: 'AX_FOCUS_02'
};
45 changes: 36 additions & 9 deletions src/js/AccessibilityUtils.js
Expand Up @@ -130,17 +130,44 @@ axs.utils.elementHasZeroArea = function(element) {
*/
axs.utils.elementIsOutsideScrollArea = function(element) {
var rect = element.getBoundingClientRect();
var scroll_height = document.body.scrollHeight;
var scroll_width = document.body.scrollWidth;
var scroll_top = document.body.scrollTop;
var scroll_left = document.body.scrollLeft;
var scrollHeight = document.body.scrollHeight;
var scrollWidth = document.body.scrollWidth;
var scrollTop = document.body.scrollTop;
var scrollLeft = document.body.scrollLeft;

var scrollArea = { top: -scrollTop, bottom: scrollHeight - scrollTop,
left: -scrollLeft, right: scrollWidth - scrollLeft }
if (rect.top < scrollArea.bottom && rect.bottom > scrollArea.top &&
rect.left < scrollArea.right && rect.right > scrollArea.left) {
return false;
}

if (rect.top >= scroll_height || rect.bottom <= -scroll_top ||
rect.left >= scroll_width || rect.right <= -scroll_left)
return true;
return false;
};
var parent = element.parentElement;
var defaultView = element.ownerDocument.defaultView;
while (parent != null) {
var style = defaultView.getComputedStyle(parent);
if (style.overflow == 'auto' && parent.scrollHeight > scrollHeight) {
if (axs.utils.elementIsOutsideScrollArea(parent)) {
parent = parent.parentElement;
continue;
}
var parentRect = parent.getBoundingClientRect();
var parentTop = parentRect.top;
var parentLeft = parentRect.left;
var parentScrollArea = { top: parentTop - parent.scrollTop,
bottom: parentTop - parent.scrollTop + parent.scrollHeight,
left: parentLeft - parent.scrollLeft,
right: parentLeft - parent.scrollLeft + parent.scrollWidth };
if (rect.top < parentScrollArea.bottom && rect.bottom > parentScrollArea.top &&
rect.left < parentScrollArea.right && rect.right > parentScrollArea.left) {
return false;
}
}
parent = parent.parentElement;
}

return true;
};

/**
* @param {Node} ancestor A potential ancestor of |node|.
Expand Down
40 changes: 22 additions & 18 deletions test/audits/focusable-element-not-visible-not-aria-hidden-test.js
Expand Up @@ -12,57 +12,61 @@
// See the License for the specific language governing permissions and
// limitations under the License.

module('FocusableElementNotVisibleAndNotAriaHidden');
module('FocusableElementNotVisibleAndNotAriaHidden', {
setup: function() {
var fixture = document.createElement('div');
document.getElementById('qunit-fixture').appendChild(fixture);

this.fixture_ = fixture;
document.getElementById('qunit-fixture').style.top = 0;
document.getElementById('qunit-fixture').style.left = 0;
},
teardown: function() {
document.getElementById('qunit-fixture').style.removeProperty('top');
document.getElementById('qunit-fixture').style.removeProperty('left');
}
});

test('a focusable element that is visible passes the audit', function() {
var fixture = document.getElementById('qunit-fixture');
var input = document.createElement('input');

fixture.style.top = '0';
fixture.style.left = '0';
fixture.appendChild(input);

this.fixture_.appendChild(input);
var rule = axs.AuditRules.getRule('focusableElementNotVisibleAndNotAriaHidden');
deepEqual(
rule.run({scope: fixture}),
rule.run({scope: this.fixture_}),
{ elements: [], result: axs.constants.AuditResult.PASS }
);
});

test('a focusable element that is hidden fails the audit', function() {
var fixture = document.getElementById('qunit-fixture');
var input = document.createElement('input');
input.style.opacity = '0';

fixture.style.top = '0';
fixture.style.left = '0';
fixture.appendChild(input);
this.fixture_.appendChild(input);

var rule = axs.AuditRules.getRule('focusableElementNotVisibleAndNotAriaHidden');
deepEqual(
rule.run({scope: fixture}),
rule.run({scope: this.fixture_}),
{ elements: [input], result: axs.constants.AuditResult.FAIL }
);
});

test('a focusable element that is hidden but shown on focus passes the audit', function() {
var fixture = document.getElementById('qunit-fixture');
var style = document.createElement('style');
var skipLink = document.createElement('a');

skipLink.href = '#main';
skipLink.id = 'skip';
skipLink.textContent = 'Skip to content';

fixture.style.top = '0';
fixture.style.left = '0';

style.appendChild(document.createTextNode("a#skip { position:fixed; top: -1000px; left: -1000px }" +
"a#skip:focus, a#skip:active { top: 10px; left: 10px }"));
fixture.appendChild(style);
fixture.appendChild(skipLink);
this.fixture_.appendChild(style);
this.fixture_.appendChild(skipLink);

var rule = axs.AuditRules.getRule('focusableElementNotVisibleAndNotAriaHidden');
deepEqual(
rule.run({scope: fixture}),
rule.run({scope: this.fixture_}),
{ elements: [], result: axs.constants.AuditResult.PASS });
});
1 change: 1 addition & 0 deletions test/gui-browser.html
Expand Up @@ -19,6 +19,7 @@
<script src="../src/audits/FocusableElementNotVisibleAndNotAriaHidden.js"></script>
<!-- Tests -->
<script src="./audits/focusable-element-not-visible-not-aria-hidden-test.js"></script>
<script src="./js/gui-utils-test.js"></script>
</head>
<body>
<h1 id="qunit-header">Test Accessibility Extension</h1>
Expand Down
98 changes: 98 additions & 0 deletions test/js/gui-utils-test.js
@@ -0,0 +1,98 @@
// Copyright 2013 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module('Scroll area', {
setup: function() {
var fixture = document.createElement('div');

fixture.style.top = '0';
fixture.style.left = '0';

document.getElementById('qunit-fixture').appendChild(fixture);
this.fixture_ = fixture;

document.getElementById('qunit-fixture').style.top = 0;
document.getElementById('qunit-fixture').style.left = 0;
},
teardown: function() {
document.getElementById('qunit-fixture').style.removeProperty('top');
document.getElementById('qunit-fixture').style.removeProperty('left');
}
});
test('Inside scroll area = no problem', function() {
var input = document.createElement('input');
this.fixture_.appendChild(input);

equal(axs.utils.elementIsOutsideScrollArea(input), false);
});
test('Outside scroll area = bad', function() {
var input = document.createElement('input');
this.fixture_.appendChild(input);
input.style.top = '-1000px';
input.style.left = '-1000px';
input.style.position = 'absolute';
equal(axs.utils.elementIsOutsideScrollArea(input), true);
});
test('In scroll area for element with overflow:auto = ok', function() {
var longDiv = document.createElement('div');
this.fixture_.appendChild(longDiv);
longDiv.style.overflow = 'auto';
longDiv.style.position = 'absolute';
longDiv.style.left = '0';
longDiv.style.top = '0';
longDiv.style.height = '1000px';
for (var i = 0; i < 1000; i++) {
var filler = document.createElement('div');
filler.innerText = 'spam';
longDiv.appendChild(filler);
}
var input = document.createElement('input');
longDiv.appendChild(input);
equal(axs.utils.elementIsOutsideScrollArea(input), false);
});
test('In scroll area for element with overflow:auto = ok', function() {
var longDiv = document.createElement('div');
this.fixture_.appendChild(longDiv);
longDiv.style.overflow = 'auto';
longDiv.style.position = 'absolute';
longDiv.style.left = '0';
longDiv.style.top = '0';
longDiv.style.height = '1000px';
for (var i = 0; i < 1000; i++) {
var filler = document.createElement('div');
filler.innerText = 'spam';
longDiv.appendChild(filler);
}
var input = document.createElement('input');
longDiv.appendChild(input);
equal(axs.utils.elementIsOutsideScrollArea(input), false);
});
test('In scroll area for element but that element is not inside scroll area = bad', function() {
var longDiv = document.createElement('div');
this.fixture_.appendChild(longDiv);
longDiv.style.overflow = 'auto';
longDiv.style.position = 'absolute';
longDiv.style.left = '-10000px';
longDiv.style.top = '-10000px';
longDiv.style.height = '1000px';
longDiv.style.width = '1000px';
for (var i = 0; i < 1000; i++) {
var filler = document.createElement('div');
filler.innerText = 'spam';
longDiv.appendChild(filler);
}
var input = document.createElement('input');
longDiv.appendChild(input);
equal(axs.utils.elementIsOutsideScrollArea(input), true);
});
14 changes: 14 additions & 0 deletions test/js/utils-test.js
@@ -1,3 +1,17 @@
// Copyright 2012 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module("Contrast Ratio", {
setup: function () {
var fixture = document.createElement('div');
Expand Down

0 comments on commit 21176ae

Please sign in to comment.