AX: When navigating the elements of a scrollable element with VoiceOver, the scrollTo...
authorcfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Aug 2015 07:41:46 +0000 (07:41 +0000)
committercfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Aug 2015 07:41:46 +0000 (07:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=125720

Reviewed by Daniel Bates.

Source/WebCore:

The scrollToVisible code did not account for scrollable elements that are larger than their viewports.
First, we need to pass the sub-focus up the scroll chain (otherwise we'll scroll some parent to y=0).
Second, we should try to center the focus within the viewport, rather than positioning at the bottom for a
better experience.

This change was adapted from Blink r183926:
https://src.chromium.org/viewvc/blink?view=rev&revision=183926

Tests: accessibility/scroll-to-global-point-iframe-nested.html
       accessibility/scroll-to-global-point-iframe.html
       accessibility/scroll-to-global-point-main-window.html
       accessibility/scroll-to-global-point-nested.html
       accessibility/scroll-to-make-visible-div-overflow.html
       accessibility/scroll-to-make-visible-iframe.html
       accessibility/scroll-to-make-visible-nested-2.html
       accessibility/scroll-to-make-visible-nested.html
       accessibility/scroll-to-make-visible-with-subfocus.html

* accessibility/AccessibilityObject.cpp:
(WebCore::computeBestScrollOffset):
(WebCore::AccessibilityObject::isOnscreen):
(WebCore::AccessibilityObject::scrollToMakeVisibleWithSubFocus):
(WebCore::AccessibilityObject::scrollToGlobalPoint):
* accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
(-[WebAccessibilityObjectWrapper accessibilityScrollToVisible]):
(-[WebAccessibilityObjectWrapper _accessibilityScrollToMakeVisibleWithSubFocus:]):
(-[WebAccessibilityObjectWrapper _accessibilityScrollToGlobalPoint:]):
(-[WebAccessibilityObjectWrapper accessibilityPerformAction:]):

Tools:

Add support for scrollToMakeVisibleWithSubFocus and scrollToGlobalPoint.

* DumpRenderTree/AccessibilityUIElement.cpp:
(pressCallback):
(scrollToMakeVisibleWithSubFocusCallback):
(scrollToGlobalPointCallback):
(scrollToMakeVisibleCallback):
(AccessibilityUIElement::getJSClass):
* DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
(AccessibilityUIElement::scrollToMakeVisible):
(AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
(AccessibilityUIElement::scrollToGlobalPoint):
(AccessibilityUIElement::selectedTextRange):
* DumpRenderTree/mac/AccessibilityUIElementMac.mm:
(AccessibilityUIElement::AccessibilityUIElement):
(AccessibilityUIElement::mathPrescriptsDescription):
(AccessibilityUIElement::scrollToMakeVisible):
(AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
(AccessibilityUIElement::scrollToGlobalPoint):
* WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp:
(WTR::AccessibilityUIElement::isTextMarkerValid):
(WTR::AccessibilityUIElement::textMarkerForIndex):
(WTR::AccessibilityUIElement::scrollToMakeVisible):
(WTR::AccessibilityUIElement::scrollToGlobalPoint):
(WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
(WTR::AccessibilityUIElement::supportedActions):
(WTR::AccessibilityUIElement::mathPostscriptsDescription):
(WTR::AccessibilityUIElement::mathPrescriptsDescription):
* WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
* WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
* WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
(WTR::AccessibilityUIElement::scrollToMakeVisible):
(WTR::AccessibilityUIElement::scrollToGlobalPoint):
(WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
(WTR::AccessibilityUIElement::selectedTextRange):
* WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
(WTR::AccessibilityUIElement::scrollToMakeVisible):
(WTR::AccessibilityUIElement::scrollToGlobalPoint):
(WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
(WTR::AccessibilityUIElement::selectedTextRange):

LayoutTests:

* accessibility/scroll-to-global-point-iframe-expected.txt: Added.
* accessibility/scroll-to-global-point-iframe-nested-expected.txt: Added.
* accessibility/scroll-to-global-point-iframe-nested.html: Added.
* accessibility/scroll-to-global-point-iframe.html: Added.
* accessibility/scroll-to-global-point-main-window-expected.txt: Added.
* accessibility/scroll-to-global-point-main-window.html: Added.
* accessibility/scroll-to-global-point-nested-expected.txt: Added.
* accessibility/scroll-to-global-point-nested.html: Added.
* accessibility/scroll-to-make-visible-div-overflow-expected.txt: Added.
* accessibility/scroll-to-make-visible-div-overflow.html: Added.
* accessibility/scroll-to-make-visible-iframe-expected.txt: Added.
* accessibility/scroll-to-make-visible-iframe.html: Added.
* accessibility/scroll-to-make-visible-nested-2-expected.txt: Added.
* accessibility/scroll-to-make-visible-nested-2.html: Added.
* accessibility/scroll-to-make-visible-nested-expected.txt: Added.
* accessibility/scroll-to-make-visible-nested.html: Added.
* accessibility/scroll-to-make-visible-with-subfocus-expected.txt: Added.
* accessibility/scroll-to-make-visible-with-subfocus.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@189149 268f45cc-cd09-0410-ab3c-d52691b4dbfc

32 files changed:
LayoutTests/ChangeLog
LayoutTests/accessibility/scroll-to-global-point-iframe-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-iframe-nested-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-iframe-nested.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-iframe.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-main-window-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-main-window.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-nested-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-global-point-nested.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-div-overflow-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-div-overflow.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-iframe-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-iframe.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-nested-2-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-nested-2.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-nested-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-nested.html [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-with-subfocus-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/scroll-to-make-visible-with-subfocus.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AccessibilityObject.cpp
Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
Tools/ChangeLog
Tools/DumpRenderTree/AccessibilityUIElement.cpp
Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm
Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm
Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp
Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h
Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl
Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp
Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm
Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm

index 7b8ff32..c0fe295 100644 (file)
@@ -1,3 +1,29 @@
+2015-08-29  Chris Fleizach  <cfleizach@apple.com>
+
+        AX: When navigating the elements of a scrollable element with VoiceOver, the scrollTop() position of the element does not permanently change
+        https://bugs.webkit.org/show_bug.cgi?id=125720
+
+        Reviewed by Daniel Bates.
+
+        * accessibility/scroll-to-global-point-iframe-expected.txt: Added.
+        * accessibility/scroll-to-global-point-iframe-nested-expected.txt: Added.
+        * accessibility/scroll-to-global-point-iframe-nested.html: Added.
+        * accessibility/scroll-to-global-point-iframe.html: Added.
+        * accessibility/scroll-to-global-point-main-window-expected.txt: Added.
+        * accessibility/scroll-to-global-point-main-window.html: Added.
+        * accessibility/scroll-to-global-point-nested-expected.txt: Added.
+        * accessibility/scroll-to-global-point-nested.html: Added.
+        * accessibility/scroll-to-make-visible-div-overflow-expected.txt: Added.
+        * accessibility/scroll-to-make-visible-div-overflow.html: Added.
+        * accessibility/scroll-to-make-visible-iframe-expected.txt: Added.
+        * accessibility/scroll-to-make-visible-iframe.html: Added.
+        * accessibility/scroll-to-make-visible-nested-2-expected.txt: Added.
+        * accessibility/scroll-to-make-visible-nested-2.html: Added.
+        * accessibility/scroll-to-make-visible-nested-expected.txt: Added.
+        * accessibility/scroll-to-make-visible-nested.html: Added.
+        * accessibility/scroll-to-make-visible-with-subfocus-expected.txt: Added.
+        * accessibility/scroll-to-make-visible-with-subfocus.html: Added.
+
 2015-08-28  Alexey Proskuryakov  <ap@apple.com>
 
         Mac TestExpectations gardening.
diff --git a/LayoutTests/accessibility/scroll-to-global-point-iframe-expected.txt b/LayoutTests/accessibility/scroll-to-global-point-iframe-expected.txt
new file mode 100644 (file)
index 0000000..ab7072d
--- /dev/null
@@ -0,0 +1,17 @@
+Tests that scrolling to move an element to a specific point successfully scrolls an iframe.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+
+5000-pixel box
+PASS window.pageYOffset is 0
+PASS frameWindow.pageYOffset is 0
+PASS target.getBoundingClientRect().top is 0
+PASS target.getBoundingClientRect().top is 300
+PASS target.getBoundingClientRect().top is 3000
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-global-point-iframe-nested-expected.txt b/LayoutTests/accessibility/scroll-to-global-point-iframe-nested-expected.txt
new file mode 100644 (file)
index 0000000..11435a3
--- /dev/null
@@ -0,0 +1,18 @@
+Tests that scrolling to move an element to a specific point successfully scrolls both an iframe and a div with overflow.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+
+5000-pixel box
+PASS window.pageYOffset is 0
+PASS frameWindow.pageYOffset is 0
+PASS container.scrollTop is 0
+PASS target.getBoundingClientRect().top is 0
+PASS target.getBoundingClientRect().top is 300
+PASS target.getBoundingClientRect().top is 3000
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-global-point-iframe-nested.html b/LayoutTests/accessibility/scroll-to-global-point-iframe-nested.html
new file mode 100644 (file)
index 0000000..0d2ce45
--- /dev/null
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<iframe id="frame" srcdoc="<body> 
+    <style>button { border: 0; }</style> 
+    <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div> 
+    <div id='container' style='height: 100px; overflow: scroll'> 
+        <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div> 
+        <button id='target'>Target</button> 
+        <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div> 
+    </div> 
+    <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div> 
+</body>"></iframe>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to move an element to a specific point successfully scrolls both an iframe and a div with overflow.");
+
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+window.jsTestIsAsync = true;
+
+function runTest() {
+    window.frame = document.getElementById("frame");
+    window.frameWindow = frame.contentWindow;
+    window.frameDoc = frameWindow.document;
+    window.container = frameDoc.getElementById("container");
+    window.target = frameDoc.getElementById("target");
+
+    var targetAccessibleObject;
+    if (window.accessibilityController) {
+        target.focus();
+        targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    frameWindow.scrollTo(0, 0);
+    container.scrollTop = 0;
+    shouldBeZero("window.pageYOffset");
+    shouldBeZero("frameWindow.pageYOffset");
+    shouldBeZero("container.scrollTop");
+
+    // Scroll to various locations and check.
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 0);
+    shouldBeZero("target.getBoundingClientRect().top");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 300);
+    shouldBe("target.getBoundingClientRect().top", "300");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 3000);
+    shouldBe("target.getBoundingClientRect().top", "3000");
+
+    finishJSTest();
+}
+
+window.addEventListener("load", function() {
+    setTimeout(runTest, 0);
+}, false);
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-global-point-iframe.html b/LayoutTests/accessibility/scroll-to-global-point-iframe.html
new file mode 100644 (file)
index 0000000..ad498f4
--- /dev/null
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<iframe id="frame" src="data:text/html,
+    <body>
+        <style>button { border: 0; }</style>
+        <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div>
+        <button id='target'>Target</button>
+        <div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div>
+    </body>"></iframe>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to move an element to a specific point successfully scrolls an iframe.");
+
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+window.jsTestIsAsync = true;
+
+function runTest() {
+    window.frame = document.getElementById("frame");
+    window.frameWindow = frame.contentWindow;
+    window.frameDoc = frameWindow.document;
+    window.target = frameDoc.getElementById("target");
+
+    var targetAccessibleObject;
+    if (window.accessibilityController) {
+        target.focus();
+        targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    frameWindow.scrollTo(0, 0);
+    shouldBeZero("window.pageYOffset");
+    shouldBeZero("frameWindow.pageYOffset");
+
+    // Scroll to various locations and check.
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 0);
+    shouldBeZero("target.getBoundingClientRect().top");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 300);
+    shouldBe("target.getBoundingClientRect().top", "300");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 3000);
+    shouldBe("target.getBoundingClientRect().top", "3000");
+
+    finishJSTest();
+}
+
+window.addEventListener("load", function() {
+    setTimeout(runTest, 0);
+}, false);
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-global-point-main-window-expected.txt b/LayoutTests/accessibility/scroll-to-global-point-main-window-expected.txt
new file mode 100644 (file)
index 0000000..d228740
--- /dev/null
@@ -0,0 +1,16 @@
+Tests that scrolling an element to a specific point successfully scrolls the main window.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+Target
+5000-pixel box
+PASS window.pageYOffset is 0
+PASS target.getBoundingClientRect().top is 0
+PASS target.getBoundingClientRect().top is 300
+PASS target.getBoundingClientRect().top is 3000
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-global-point-main-window.html b/LayoutTests/accessibility/scroll-to-global-point-main-window.html
new file mode 100644 (file)
index 0000000..4e8f491
--- /dev/null
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+<style>
+button {
+  border: 0;
+}
+</style>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+<button id="target">Target</button>
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling an element to a specific point successfully scrolls the main window.");
+
+function runTest() {
+    var target = document.getElementById("target");
+
+    if (window.accessibilityController) {
+        target.focus();
+        var targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    shouldBe("window.pageYOffset", "0");
+
+    // Scroll to various locations and check.
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 0);
+    shouldBe("target.getBoundingClientRect().top", "0");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 300);
+    shouldBe("target.getBoundingClientRect().top", "300");
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 3000);
+    shouldBe("target.getBoundingClientRect().top", "3000");
+
+    finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-global-point-nested-expected.txt b/LayoutTests/accessibility/scroll-to-global-point-nested-expected.txt
new file mode 100644 (file)
index 0000000..a81a1fd
--- /dev/null
@@ -0,0 +1,23 @@
+Tests that scrolling an element to a specific point can successfully scroll multiple nested scrolling views'.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+5000-pixel box
+5000-pixel box
+Target
+5000-pixel box
+5000-pixel box
+5000-pixel box
+PASS window.pageYOffset is 0
+PASS outerContainer.scrollTop is 0
+PASS innerContainer.scrollTop is 0
+PASS target.getBoundingClientRect().top is >= 15000
+PASS target.getBoundingClientRect().top is 0
+PASS target.getBoundingClientRect().top is 300
+PASS target.getBoundingClientRect().top is 3000
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-global-point-nested.html b/LayoutTests/accessibility/scroll-to-global-point-nested.html
new file mode 100644 (file)
index 0000000..9fc30b7
--- /dev/null
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+<style>
+button {
+  border: 0;
+}
+</style>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="outer_container" style="height: 100px; overflow: scroll">
+  <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+  <div id="inner_container" style="height: 100px; overflow: scroll">
+    <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+    <button id="target">Target</button>
+    <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+  </div>
+  <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+</div>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling an element to a specific point can successfully scroll multiple nested scrolling views'.");
+
+function runTest() {
+    window.outerContainer = document.getElementById("outer_container");
+    window.innerContainer = document.getElementById("inner_container");
+    window.target = document.getElementById("target");
+
+    var targetAccessibleObject;
+    if (window.accessibilityController) {
+        target.focus();
+        targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll positions (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    outerContainer.scrollTop = 0;
+    innerContainer.scrollTop = 0;
+    shouldBeZero("window.pageYOffset");
+    shouldBeZero("outerContainer.scrollTop");
+    shouldBeZero("innerContainer.scrollTop");
+    shouldBeGreaterThanOrEqual("target.getBoundingClientRect().top", "15000");
+
+    // Scroll to various locations and check.
+
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 0);
+    shouldBeZero("target.getBoundingClientRect().top");
+
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 300);
+    shouldBe("target.getBoundingClientRect().top", "300");
+
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToGlobalPoint(0, 3000);
+    shouldBe("target.getBoundingClientRect().top", "3000");
+
+  finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-div-overflow-expected.txt b/LayoutTests/accessibility/scroll-to-make-visible-div-overflow-expected.txt
new file mode 100644 (file)
index 0000000..b7fafb8
--- /dev/null
@@ -0,0 +1,19 @@
+Tests that scrolling to make an element visible successfully scrolls an arbitrary HTML element that has CSS overflow set to 'scroll'.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Upper Target
+5000-pixel box
+Lower Target
+PASS container.scrollTop is 0
+PASS container.scrollTop >= minYOffset is true
+PASS container.scrollTop <= maxYOffset is true
+PASS container.scrollTop >= minYOffset is true
+PASS container.scrollTop <= maxYOffset is true
+PASS container.scrollTop >= minYOffset is true
+PASS container.scrollTop <= maxYOffset is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-div-overflow.html b/LayoutTests/accessibility/scroll-to-make-visible-div-overflow.html
new file mode 100644 (file)
index 0000000..a827a1e
--- /dev/null
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div id="container" style="height: 100px; overflow: scroll">
+  <button id="upper_target">Upper Target</button>
+  <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+  <button id="lower_target">Lower Target</button>
+</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to make an element visible successfully scrolls an arbitrary HTML element that has CSS overflow set to 'scroll'.");
+
+function runTest() {
+    window.container = document.getElementById("container");
+    var upperTarget = document.getElementById("upper_target");
+    var lowerTarget = document.getElementById("lower_target");
+
+    var lowerTargetAccessibleObject;
+    var upperTargetAccessibleObject;
+    if (window.accessibilityController) {
+        lowerTarget.focus();
+        lowerTargetAccessibleObject = accessibilityController.focusedElement;
+        upperTarget.focus();
+        upperTargetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    container.scrollTop = 0;
+    shouldBeZero("container.scrollTop");
+
+    // Scroll to make lower target visible and check.
+    if (window.accessibilityController)
+        lowerTargetAccessibleObject.scrollToMakeVisible();
+    var top = lowerTarget.offsetTop - container.offsetTop;
+    window.minYOffset = top + lowerTarget.offsetHeight - container.offsetHeight;
+    window.maxYOffset = top;
+    shouldBeTrue("container.scrollTop >= minYOffset");
+    shouldBeTrue("container.scrollTop <= maxYOffset");
+
+    // Do it again. It shouldn't scroll.
+    if (window.accessibilityController)
+        lowerTargetAccessibleObject.scrollToMakeVisible();
+    var top = lowerTarget.offsetTop - container.offsetTop;
+    window.minYOffset = top + lowerTarget.offsetHeight - container.offsetHeight;
+    window.maxYOffset = top;
+    shouldBeTrue("container.scrollTop >= minYOffset");
+    shouldBeTrue("container.scrollTop <= maxYOffset");
+
+    // Scroll to make upper target visible and check.
+    if (window.accessibilityController)
+        upperTargetAccessibleObject.scrollToMakeVisible();
+    top = upperTarget.offsetTop - container.offsetTop;
+    window.minYOffset = top + upperTarget.offsetHeight - container.offsetHeight;
+    window.maxYOffset = top;
+    shouldBe("container.scrollTop >= minYOffset", "true");
+    shouldBe("container.scrollTop <= maxYOffset", "true");
+
+    finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-iframe-expected.txt b/LayoutTests/accessibility/scroll-to-make-visible-iframe-expected.txt
new file mode 100644 (file)
index 0000000..ca325d5
--- /dev/null
@@ -0,0 +1,15 @@
+Tests that scrolling to make an element visible successfully scrolls an iframe.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+PASS frameWindow.pageYOffset is 0
+PASS frameWindow.pageYOffset >= minYOffset is true
+PASS frameWindow.pageYOffset <= maxYOffset is true
+PASS frameWindow.pageYOffset >= minYOffset is true
+PASS frameWindow.pageYOffset <= maxYOffset is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-iframe.html b/LayoutTests/accessibility/scroll-to-make-visible-iframe.html
new file mode 100644 (file)
index 0000000..8f6f2ea
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<iframe id="frame" src="data:text/html,<body><button id='upper_target'>Upper Target</button><div style='border: 1px solid #000; height: 5000px;'>5000-pixel box</div><button id='lower_target'>Lower Target</button></body>"></iframe>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to make an element visible successfully scrolls an iframe.");
+
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+window.jsTestIsAsync = true;
+
+function runTest() {
+    window.frame = document.getElementById("frame");
+    window.frameWindow = frame.contentWindow;
+    window.frameDoc = frameWindow.document;
+
+    var upperTarget = frameDoc.getElementById("upper_target");
+    var lowerTarget = frameDoc.getElementById("lower_target");
+
+    var lowerTargetAccessibleObject;
+    var upperTargetAccessibleObject;
+    if (window.accessibilityController) {
+        lowerTarget.focus();
+        lowerTargetAccessibleObject = accessibilityController.focusedElement;
+        upperTarget.focus();
+        upperTargetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    frameWindow.scrollTo(0, 0);
+    shouldBeZero("frameWindow.pageYOffset");
+
+    // Scroll to make lower target visible and check.
+    if (window.accessibilityController)
+        lowerTargetAccessibleObject.scrollToMakeVisible();
+    window.minYOffset = lowerTarget.offsetTop + lowerTarget.offsetHeight - frameWindow.innerHeight;
+    window.maxYOffset = lowerTarget.offsetTop;
+    shouldBeTrue("frameWindow.pageYOffset >= minYOffset");
+    shouldBeTrue("frameWindow.pageYOffset <= maxYOffset");
+
+    // Scroll to make upper target visible and check.
+    if (window.accessibilityController)
+        upperTargetAccessibleObject.scrollToMakeVisible();
+    window.minYOffset = upperTarget.offsetTop + upperTarget.offsetHeight - frameWindow.innerHeight;
+    window.maxYOffset = upperTarget.offsetTop;
+    shouldBeTrue("frameWindow.pageYOffset >= minYOffset");
+    shouldBeTrue("frameWindow.pageYOffset <= maxYOffset");
+
+    finishJSTest();
+}
+
+window.addEventListener("load", function() {
+    setTimeout(runTest, 0);
+}, false);
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-nested-2-expected.txt b/LayoutTests/accessibility/scroll-to-make-visible-nested-2-expected.txt
new file mode 100644 (file)
index 0000000..d78902e
--- /dev/null
@@ -0,0 +1,18 @@
+Tests that scrolling to make an element visible works when the inner scroll container doesn't need to be scrolled, but the other one does.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+Target Target Target
+5000-pixel box
+PASS window.pageYOffset is 0
+PASS container.scrollTop is 0
+PASS target1.getBoundingClientRect().top is >= 5000
+PASS window.innerHeight is >= target2.getBoundingClientRect().bottom
+PASS target2top is target2.getBoundingClientRect().top
+PASS target2top is target2.getBoundingClientRect().top
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-nested-2.html b/LayoutTests/accessibility/scroll-to-make-visible-nested-2.html
new file mode 100644 (file)
index 0000000..44f2779
--- /dev/null
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div id="container" style="width: 100px; overflow: scroll">
+  <div style="border: 1px solid #000; width: 1000px; height: 5000px;">5000-pixel box</div>
+  <button id="target1">Target</button>
+  <button id="target2">Target</button>
+  <button id="target3">Target</button>
+  <div style="border: 1px solid #000; width: 1000px; height: 5000px;">5000-pixel box</div>
+</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to make an element visible works when the inner scroll container doesn't need to be scrolled, but the other one does.");
+
+function runTest() {
+    window.container = document.getElementById("container");
+    window.target1 = document.getElementById("target1");
+    window.target2 = document.getElementById("target2");
+    window.target3 = document.getElementById("target3");
+
+    var target1AccessibleObject;
+    var target2AccessibleObject;
+    var target3AccessibleObject;
+    if (window.accessibilityController) {
+        target1.focus();
+        target1AccessibleObject = accessibilityController.focusedElement;
+        target2.focus();
+        target2AccessibleObject = accessibilityController.focusedElement;
+        target3.focus();
+        target3AccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll positions (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    container.scrollTop = 0;
+    shouldBeZero("window.pageYOffset");
+    shouldBeZero("container.scrollTop");
+    shouldBeGreaterThanOrEqual("target1.getBoundingClientRect().top", "5000");
+
+    // Scroll to make the middle target visible.
+    if (window.accessibilityController)
+        target2AccessibleObject.scrollToMakeVisible();
+
+    // Instead of testing the exact scroll offsets of the two containers, just test that
+    // the new absolute position of the target is on-screen.
+    shouldBeGreaterThanOrEqual("window.innerHeight", "target2.getBoundingClientRect().bottom");
+
+    // Make sure that calling scrollToMakeVisible on target1 and target3 don't result in any
+    // scrolling, because they should already be within the viewport.
+    window.target2top = target2.getBoundingClientRect().top;
+    if (window.accessibilityController)
+        target1AccessibleObject.scrollToMakeVisible();
+    shouldBe("target2top", "target2.getBoundingClientRect().top");
+    if (window.accessibilityController)
+        target3AccessibleObject.scrollToMakeVisible();
+    shouldBe("target2top", "target2.getBoundingClientRect().top");
+
+    finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-nested-expected.txt b/LayoutTests/accessibility/scroll-to-make-visible-nested-expected.txt
new file mode 100644 (file)
index 0000000..d3df09f
--- /dev/null
@@ -0,0 +1,18 @@
+Tests that scrolling to make an element visible successfully scrolls multiple nested scrolling views'.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+5000-pixel box
+5000-pixel box
+Target
+PASS window.pageYOffset is 0
+PASS outerContainer.scrollTop is 0
+PASS innerContainer.scrollTop is 0
+PASS target.getBoundingClientRect().top is >= 15000
+PASS window.innerHeight is >= target.getBoundingClientRect().bottom
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-nested.html b/LayoutTests/accessibility/scroll-to-make-visible-nested.html
new file mode 100644 (file)
index 0000000..d138e9c
--- /dev/null
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+
+<div id="outer_container" style="height: 100px; overflow: scroll">
+  <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+  <div id="inner_container" style="height: 100px; overflow: scroll">
+    <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+    <button id="target">Target</button>
+  </div>
+</div>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to make an element visible successfully scrolls multiple nested scrolling views'.");
+
+function runTest() {
+    window.outerContainer = document.getElementById("outer_container");
+    window.innerContainer = document.getElementById("inner_container");
+    window.target = document.getElementById("target");
+
+    var targetAccessibleObject;
+    if (window.accessibilityController) {
+        target.focus();
+        targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll positions (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    outerContainer.scrollTop = 0;
+    innerContainer.scrollTop = 0;
+    shouldBeZero("window.pageYOffset");
+    shouldBeZero("outerContainer.scrollTop");
+    shouldBeZero("innerContainer.scrollTop");
+    shouldBeGreaterThanOrEqual("target.getBoundingClientRect().top", "15000");
+
+    // Scroll to make target visible.
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToMakeVisible();
+
+    // Instead of testing the exact scroll offsets of the two containers, just test that
+    // the new absolute position of the target is on-screen.
+    shouldBeGreaterThanOrEqual("window.innerHeight", "target.getBoundingClientRect().bottom");
+
+    finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-with-subfocus-expected.txt b/LayoutTests/accessibility/scroll-to-make-visible-with-subfocus-expected.txt
new file mode 100644 (file)
index 0000000..2fd7816
--- /dev/null
@@ -0,0 +1,14 @@
+Tests that scrolling to make a certain region within an element visible successfully scrolls the main window.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+5000-pixel box
+Target
+PASS window.pageYOffset is 0
+PASS window.pageYOffset >= minYOffset is true
+PASS window.pageYOffset <= maxYOffset is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/scroll-to-make-visible-with-subfocus.html b/LayoutTests/accessibility/scroll-to-make-visible-with-subfocus.html
new file mode 100644 (file)
index 0000000..ccd0745
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+
+<p id="description"></p>
+
+<div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
+<button id="target" style="height: 5000px;">Target</button>
+
+<div id="console"></div>
+
+<script>
+description("Tests that scrolling to make a certain region within an element visible successfully scrolls the main window.");
+
+function runTest() {
+    var target = document.getElementById("target");
+
+    var targetAccessibleObject;
+    if (window.accessibilityController) {
+        target.focus();
+        targetAccessibleObject = accessibilityController.focusedElement;
+    }
+
+    // Reset the initial scroll position (since calling focus() can scroll the page too).
+    window.scrollTo(0, 0);
+    shouldBeZero("window.pageYOffset");
+
+    // Scroll to make the midpoint of the target visible and check.
+    if (window.accessibilityController)
+        targetAccessibleObject.scrollToMakeVisibleWithSubFocus(0, 2500, 100, 2600);
+    window.minYOffset = target.offsetTop + 2500 - window.innerHeight;
+    window.maxYOffset = target.offsetTop + 2500;
+    shouldBeTrue("window.pageYOffset >= minYOffset");
+    shouldBeTrue("window.pageYOffset <= maxYOffset");
+
+    finishJSTest();
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
index 4f06c35..74a4bc3 100644 (file)
@@ -1,3 +1,39 @@
+2015-08-29  Chris Fleizach  <cfleizach@apple.com>
+
+        AX: When navigating the elements of a scrollable element with VoiceOver, the scrollTop() position of the element does not permanently change
+        https://bugs.webkit.org/show_bug.cgi?id=125720
+
+        Reviewed by Daniel Bates.
+
+        The scrollToVisible code did not account for scrollable elements that are larger than their viewports.
+        First, we need to pass the sub-focus up the scroll chain (otherwise we'll scroll some parent to y=0).
+        Second, we should try to center the focus within the viewport, rather than positioning at the bottom for a 
+        better experience.
+
+        This change was adapted from Blink r183926:
+        https://src.chromium.org/viewvc/blink?view=rev&revision=183926
+
+        Tests: accessibility/scroll-to-global-point-iframe-nested.html
+               accessibility/scroll-to-global-point-iframe.html
+               accessibility/scroll-to-global-point-main-window.html
+               accessibility/scroll-to-global-point-nested.html
+               accessibility/scroll-to-make-visible-div-overflow.html
+               accessibility/scroll-to-make-visible-iframe.html
+               accessibility/scroll-to-make-visible-nested-2.html
+               accessibility/scroll-to-make-visible-nested.html
+               accessibility/scroll-to-make-visible-with-subfocus.html
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::computeBestScrollOffset):
+        (WebCore::AccessibilityObject::isOnscreen):
+        (WebCore::AccessibilityObject::scrollToMakeVisibleWithSubFocus):
+        (WebCore::AccessibilityObject::scrollToGlobalPoint):
+        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+        (-[WebAccessibilityObjectWrapper accessibilityScrollToVisible]):
+        (-[WebAccessibilityObjectWrapper _accessibilityScrollToMakeVisibleWithSubFocus:]):
+        (-[WebAccessibilityObjectWrapper _accessibilityScrollToGlobalPoint:]):
+        (-[WebAccessibilityObjectWrapper accessibilityPerformAction:]):
+
 2015-08-28  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Migrate GraphicsContexts from pointers to references
index 6b8b9b5..5167278 100644 (file)
@@ -2331,65 +2331,89 @@ AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
 // logic is the same. The goal is to compute the best scroll offset
 // in order to make an object visible within a viewport.
 //
+// If the object is already fully visible, returns the same scroll
+// offset.
+//
 // In case the whole object cannot fit, you can specify a
 // subfocus - a smaller region within the object that should
 // be prioritized. If the whole object can fit, the subfocus is
 // ignored.
 //
-// Example: the viewport is scrolled to the right just enough
-// that the object is in view.
+// If possible, the object and subfocus are centered within the
+// viewport.
+//
+// Example 1: the object is already visible, so nothing happens.
+//   +----------Viewport---------+
+//                 +---Object---+
+//                 +--SubFocus--+
+//
+// Example 2: the object is not fully visible, so it's centered
+// within the viewport.
 //   Before:
 //   +----------Viewport---------+
 //                         +---Object---+
 //                         +--SubFocus--+
 //
 //   After:
-//          +----------Viewport---------+
+//                 +----------Viewport---------+
 //                         +---Object---+
 //                         +--SubFocus--+
 //
+// Example 3: the object is larger than the viewport, so the
+// viewport moves to show as much of the object as possible,
+// while also trying to center the subfocus.
+//   Before:
+//   +----------Viewport---------+
+//     +---------------Object--------------+
+//                         +-SubFocus-+
+//
+//   After:
+//             +----------Viewport---------+
+//     +---------------Object--------------+
+//                         +-SubFocus-+
+//
 // When constraints cannot be fully satisfied, the min
 // (left/top) position takes precedence over the max (right/bottom).
 //
 // Note that the return value represents the ideal new scroll offset.
 // This may be out of range - the calling function should clip this
 // to the available range.
-static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int objectMin, int objectMax, int viewportMin, int viewportMax)
+static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax)
 {
     int viewportSize = viewportMax - viewportMin;
-
-    // If the focus size is larger than the viewport size, shrink it in the
-    // direction of subfocus.
+    
+    // If the object size is larger than the viewport size, consider
+    // only a portion that's as large as the viewport, centering on
+    // the subfocus as much as possible.
     if (objectMax - objectMin > viewportSize) {
-        // Subfocus must be within focus:
+        // Since it's impossible to fit the whole object in the
+        // viewport, exit now if the subfocus is already within the viewport.
+        if (subfocusMin - currentScrollOffset >= viewportMin && subfocusMax - currentScrollOffset <= viewportMax)
+            return currentScrollOffset;
+        
+        // Subfocus must be within focus.
         subfocusMin = std::max(subfocusMin, objectMin);
-
+        subfocusMax = std::min(subfocusMax, objectMax);
+        
         // Subfocus must be no larger than the viewport size; favor top/left.
-        if (subfocusMin + viewportSize > objectMax)
-            objectMin = objectMax - viewportSize;
-        else {
-            objectMin = subfocusMin;
-            objectMax = subfocusMin + viewportSize;
-        }
+        if (subfocusMax - subfocusMin > viewportSize)
+            subfocusMax = subfocusMin + viewportSize;
+        
+        // Compute the size of an object centered on the subfocus, the size of the viewport.
+        int centeredObjectMin = (subfocusMin + subfocusMax - viewportSize) / 2;
+        int centeredObjectMax = centeredObjectMin + viewportSize;
+
+        objectMin = std::max(objectMin, centeredObjectMin);
+        objectMax = std::min(objectMax, centeredObjectMax);
     }
 
     // Exit now if the focus is already within the viewport.
     if (objectMin - currentScrollOffset >= viewportMin
         && objectMax - currentScrollOffset <= viewportMax)
         return currentScrollOffset;
-
-    // Scroll left if we're too far to the right.
-    if (objectMax - currentScrollOffset > viewportMax)
-        return objectMax - viewportMax;
-
-    // Scroll right if we're too far to the left.
-    if (objectMin - currentScrollOffset < viewportMin)
-        return objectMin - viewportMin;
-
-    ASSERT_NOT_REACHED();
-
-    // This shouldn't happen.
-    return currentScrollOffset;
+    
+    // Center the object in the viewport.
+    return (objectMin + objectMax - viewportMin - viewportMax) / 2;
 }
 
 bool AccessibilityObject::isOnscreen() const
@@ -2449,22 +2473,34 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu
     // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
     IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
 
+    if (!scrollParent->isScrollView()) {
+        objectRect.moveBy(scrollPosition);
+        objectRect.moveBy(-snappedIntRect(scrollParent->elementRect()).location());
+    }
+    
     int desiredX = computeBestScrollOffset(
         scrollPosition.x(),
-        objectRect.x() + subfocus.x(),
+        objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(),
         objectRect.x(), objectRect.maxX(),
         0, scrollVisibleRect.width());
     int desiredY = computeBestScrollOffset(
         scrollPosition.y(),
-        objectRect.y() + subfocus.y(),
+        objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(),
         objectRect.y(), objectRect.maxY(),
         0, scrollVisibleRect.height());
 
     scrollParent->scrollTo(IntPoint(desiredX, desiredY));
 
+    // Convert the subfocus into the coordinates of the scroll parent.
+    IntRect newSubfocus = subfocus;
+    IntRect newElementRect = snappedIntRect(elementRect());
+    IntRect scrollParentRect = snappedIntRect(scrollParent->elementRect());
+    newSubfocus.move(newElementRect.x(), newElementRect.y());
+    newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y());
+    
     // Recursively make sure the scroll parent itself is visible.
     if (scrollParent->parentObject())
-        scrollParent->scrollToMakeVisible();
+        scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus);
 }
 
 void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
@@ -2503,12 +2539,12 @@ void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
 
         int desiredX = computeBestScrollOffset(
             0,
-            objectRect.x(),
+            objectRect.x(), objectRect.maxX(),
             objectRect.x(), objectRect.maxX(),
             point.x(), point.x());
         int desiredY = computeBestScrollOffset(
             0,
-            objectRect.y(),
+            objectRect.y(), objectRect.maxY(),
             objectRect.y(), objectRect.maxY(),
             point.y(), point.y());
         outer->scrollTo(IntPoint(desiredX, desiredY));
index daa81b2..881c99f 100644 (file)
@@ -3361,6 +3361,16 @@ static NSString* roleValueToNSString(AccessibilityRole value)
     m_object->scrollToMakeVisible();
 }
 
+- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect
+{
+    m_object->scrollToMakeVisibleWithSubFocus(IntRect(rect));
+}
+
+- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point
+{
+    m_object->scrollToGlobalPoint(IntPoint(point));
+}
+
 - (void)accessibilityPerformAction:(NSString*)action
 {
     if (![self updateObjectBackingStore])
index 22205cb..a1cea05 100644 (file)
@@ -1,3 +1,51 @@
+2015-08-29  Chris Fleizach  <cfleizach@apple.com>
+
+        AX: When navigating the elements of a scrollable element with VoiceOver, the scrollTop() position of the element does not permanently change
+        https://bugs.webkit.org/show_bug.cgi?id=125720
+
+        Reviewed by Daniel Bates.
+
+        Add support for scrollToMakeVisibleWithSubFocus and scrollToGlobalPoint.
+
+        * DumpRenderTree/AccessibilityUIElement.cpp:
+        (pressCallback):
+        (scrollToMakeVisibleWithSubFocusCallback):
+        (scrollToGlobalPointCallback):
+        (scrollToMakeVisibleCallback):
+        (AccessibilityUIElement::getJSClass):
+        * DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
+        (AccessibilityUIElement::scrollToMakeVisible):
+        (AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
+        (AccessibilityUIElement::scrollToGlobalPoint):
+        (AccessibilityUIElement::selectedTextRange):
+        * DumpRenderTree/mac/AccessibilityUIElementMac.mm:
+        (AccessibilityUIElement::AccessibilityUIElement):
+        (AccessibilityUIElement::mathPrescriptsDescription):
+        (AccessibilityUIElement::scrollToMakeVisible):
+        (AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
+        (AccessibilityUIElement::scrollToGlobalPoint):
+        * WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp:
+        (WTR::AccessibilityUIElement::isTextMarkerValid):
+        (WTR::AccessibilityUIElement::textMarkerForIndex):
+        (WTR::AccessibilityUIElement::scrollToMakeVisible):
+        (WTR::AccessibilityUIElement::scrollToGlobalPoint):
+        (WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
+        (WTR::AccessibilityUIElement::supportedActions):
+        (WTR::AccessibilityUIElement::mathPostscriptsDescription):
+        (WTR::AccessibilityUIElement::mathPrescriptsDescription):
+        * WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
+        * WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
+        * WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
+        (WTR::AccessibilityUIElement::scrollToMakeVisible):
+        (WTR::AccessibilityUIElement::scrollToGlobalPoint):
+        (WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
+        (WTR::AccessibilityUIElement::selectedTextRange):
+        * WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
+        (WTR::AccessibilityUIElement::scrollToMakeVisible):
+        (WTR::AccessibilityUIElement::scrollToGlobalPoint):
+        (WTR::AccessibilityUIElement::scrollToMakeVisibleWithSubFocus):
+        (WTR::AccessibilityUIElement::selectedTextRange):
+
 2015-08-28  Timothy Horton  <timothy_horton@apple.com>
 
         Add a mysteriously nonproblematic missing comma.
index 15766ac..318f73f 100644 (file)
@@ -708,6 +708,36 @@ static JSValueRef pressCallback(JSContextRef context, JSObjectRef function, JSOb
     return JSValueMakeUndefined(context);
 }
 
+static JSValueRef scrollToMakeVisibleWithSubFocusCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    unsigned x = 0;
+    unsigned y = 0;
+    unsigned width = 0;
+    unsigned height = 0;
+    if (argumentCount == 4) {
+        x = JSValueToNumber(context, arguments[0], exception);
+        y = JSValueToNumber(context, arguments[1], exception);
+        width = JSValueToNumber(context, arguments[2], exception);
+        height = JSValueToNumber(context, arguments[3], exception);
+    }
+
+    toAXElement(thisObject)->scrollToMakeVisibleWithSubFocus(x, y, width, height);
+    return JSValueMakeUndefined(context);
+}
+
+static JSValueRef scrollToGlobalPointCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    unsigned x = 0;
+    unsigned y = 0;
+    if (argumentCount == 2) {
+        x = JSValueToNumber(context, arguments[0], exception);
+        y = JSValueToNumber(context, arguments[1], exception);
+    }
+
+    toAXElement(thisObject)->scrollToGlobalPoint(x, y);
+    return JSValueMakeUndefined(context);
+}
+
 static JSValueRef scrollToMakeVisibleCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     toAXElement(thisObject)->scrollToMakeVisible();
@@ -1700,6 +1730,8 @@ JSClassRef AccessibilityUIElement::getJSClass()
         { "setSelectedVisibleTextRange", setSelectedVisibleTextRangeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "selectedChildAtIndex", selectedChildAtIndexCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "scrollToMakeVisible", scrollToMakeVisibleCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "scrollToGlobalPoint", scrollToGlobalPointCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "scrollToMakeVisibleWithSubFocus", scrollToMakeVisibleWithSubFocusCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
 #if PLATFORM(GTK) || PLATFORM(EFL)
         { "characterAtOffset", characterAtOffsetCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "wordAtOffset", wordAtOffsetCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
index 77eec2e..2a4c64a 100644 (file)
@@ -680,6 +680,16 @@ void AccessibilityUIElement::scrollToMakeVisible()
     // FIXME: implement
 }
 
+void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
+{
+    // FIXME: implement
+}
+
+void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
+{
+    // FIXME: implement
+}
+
 JSStringRef AccessibilityUIElement::selectedTextRange()
 {
     return JSStringCreateWithCharacters(0, 0);
index 4779d08..79471e5 100644 (file)
@@ -76,6 +76,8 @@ typedef void (*AXPostedNotificationCallback)(id element, NSString* notification,
 - (NSUInteger)accessibilityIndexOfChild:(id)child;
 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute;
 - (void)_accessibilitySetTestValue:(id)value forAttribute:(NSString*)attributeName;
+- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect;
+- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point;
 - (void)_accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName;
 @end
 
@@ -1881,7 +1883,6 @@ JSStringRef AccessibilityUIElement::mathPrescriptsDescription() const
     return nullptr;
 }
 
-
 void AccessibilityUIElement::scrollToMakeVisible()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
@@ -1891,10 +1892,14 @@ void AccessibilityUIElement::scrollToMakeVisible()
 
 void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
 {
-    // FIXME: implement
+    BEGIN_AX_OBJC_EXCEPTIONS
+    [m_element _accessibilityScrollToMakeVisibleWithSubFocus:NSMakeRect(x, y, width, height)];
+    END_AX_OBJC_EXCEPTIONS
 }
 
 void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
 {
-    // FIXME: implement
+    BEGIN_AX_OBJC_EXCEPTIONS
+    [m_element _accessibilityScrollToGlobalPoint:NSMakePoint(x, y)];
+    END_AX_OBJC_EXCEPTIONS
 }
index 4f77c47..a20d8b2 100644 (file)
@@ -224,6 +224,8 @@ int AccessibilityUIElement::indexForTextMarker(AccessibilityTextMarker*) { retur
 bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker*) { return false; }
 PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int) { return 0; }
 void AccessibilityUIElement::scrollToMakeVisible() { }
+void AccessibilityUIElement::scrollToGlobalPoint(int, int) { }
+void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int, int, int, int) { }
 JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const { return 0; }
 JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const { return 0; }
 JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const { return 0; }
index 029cb7f..eeb6abc 100644 (file)
@@ -225,6 +225,8 @@ public:
     PassRefPtr<AccessibilityUIElement> verticalScrollbar() const;
 
     void scrollToMakeVisible();
+    void scrollToGlobalPoint(int x, int y);
+    void scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height);
     
     // Text markers.
     PassRefPtr<AccessibilityTextMarkerRange> lineTextMarkerRangeForTextMarker(AccessibilityTextMarker*);
index d6d6363..59a8da2 100644 (file)
@@ -169,6 +169,9 @@ interface AccessibilityUIElement {
     readonly attribute AccessibilityUIElement verticalScrollbar;
 
     void scrollToMakeVisible();
+    void scrollToGlobalPoint(int x, int y);
+    void scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height);
+
     void takeFocus();
     boolean scrollPageDown();
     boolean scrollPageUp();
index 29cd3a8..e7db2c9 100644 (file)
@@ -1935,6 +1935,16 @@ void AccessibilityUIElement::scrollToMakeVisible()
 {
     // FIXME: implement
 }
+    
+void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
+{
+    // FIXME: implement
+}
+    
+void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
+{
+    // FIXME: implement
+}
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const
 {
index 9165570..a0cbf34 100644 (file)
@@ -699,6 +699,14 @@ PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() c
 void AccessibilityUIElement::scrollToMakeVisible()
 {
 }
+    
+void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
+{
+}
+    
+void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
+{
+}
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
 {
index 75cc2f0..0351d1e 100644 (file)
@@ -80,6 +80,8 @@ typedef void (*AXPostedNotificationCallback)(id element, NSString* notification,
 - (NSUInteger)accessibilityIndexOfChild:(id)child;
 - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute;
 - (void)_accessibilitySetTestValue:(id)value forAttribute:(NSString*)attributeName;
+- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect;
+- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point;
 - (void)_accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName;
 @end
 
@@ -1373,6 +1375,20 @@ void AccessibilityUIElement::scrollToMakeVisible()
     END_AX_OBJC_EXCEPTIONS
 }
     
+void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    [m_element _accessibilityScrollToGlobalPoint:NSMakePoint(x, y)];
+    END_AX_OBJC_EXCEPTIONS
+}
+
+void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    [m_element _accessibilityScrollToMakeVisibleWithSubFocus:NSMakeRect(x, y, width, height)];
+    END_AX_OBJC_EXCEPTIONS
+}
+
 JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
 {
     NSRange range = NSMakeRange(NSNotFound, 0);