Created
October 26, 2011 23:17
-
-
Save illicitonion/1318309 to your computer and use it in GitHub Desktop.
Scrolling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Index: javascript/atoms/test/scrolling_test.html | |
| =================================================================== | |
| --- javascript/atoms/test/scrolling_test.html (revision 0) | |
| +++ javascript/atoms/test/scrolling_test.html (revision 0) | |
| @@ -0,0 +1,130 @@ | |
| +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> | |
| +<html> | |
| +<head> | |
| + <title>scrolling_test.html</title> | |
| + <script src="test_bootstrap.js"></script> | |
| + <script type="text/javascript"> | |
| + goog.require('bot'); | |
| + goog.require('bot.action'); | |
| + goog.require('bot.dom'); | |
| + goog.require('goog.events'); | |
| + goog.require('goog.testing.AsyncTestCase'); | |
| + goog.require('goog.testing.jsunit'); | |
| + goog.require('goog.userAgent'); | |
| + </script> | |
| +</head> | |
| +<body> | |
| + <script type="text/javascript"> | |
| + var findElement = bot.locators.findElement; | |
| + var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); | |
| + | |
| + function setUp() { | |
| + goog.events.removeAll(); | |
| + window.scrollTo(0, 0); | |
| + assertEquals(document.body.scrollLeft, 0); | |
| + assertEquals(document.body.scrollTop, 0); | |
| + } | |
| + | |
| + function tearDown() { | |
| + window.scrollTo(0, 0); //Convenience to view results | |
| + } | |
| + | |
| + function getTopLevelParent() { | |
| + var document = bot.getDocument(); | |
| + return goog.userAgent.WEBKIT ? document.body : document.documentElement; | |
| + } | |
| + | |
| + function testScrollsElementInToViewIfRequired() { | |
| + bot.action.click(findElement({id: 'offscreen'})); | |
| + assertNotEquals(0, getTopLevelParent().scrollLeft); | |
| + } | |
| + | |
| + function testDoesNotScrollElementInToViewIfAlreadyVisible() { | |
| + bot.action.click(findElement({id: 'onscreen'})); | |
| + assertEquals(0, getTopLevelParent().scrollLeft); | |
| + } | |
| + | |
| + function testClicksElementIfHiddenByScrollableOverflow() { | |
| + var element = findElement({id: 'scrollable'}); | |
| + assertClickClicks(element); | |
| + } | |
| + | |
| + function testDoesNotScrollContainerIfNotHiddenByOverflow() { | |
| + bot.action.click(findElement({id: 'scrollable-in-view'})); | |
| + assertEquals(0, findElement({id: 'scrollable-in-view-container'}).scrollLeft); | |
| + } | |
| + | |
| + function testScrollsContainerIfHiddenByOverflow() { | |
| + bot.action.click(findElement({id: 'scrollable'})); | |
| + assertNotEquals(0, findElement({id: 'scrollable-container'}).scrollLeft); | |
| + } | |
| + | |
| + function testDoesNotClickElementHiddenByOverflow() { | |
| + var element = findElement({id: 'hidden-by-overflow'}); | |
| + goog.events.listenOnce(element, 'click', function() { | |
| + fail('Expected not to click on element'); | |
| + }); | |
| + | |
| + asyncTestCase.waitForAsync('Waiting for click event not to fire'); | |
| + | |
| + try { | |
| + bot.action.click(element); | |
| + fail('Expected exception'); | |
| + } catch (e) { | |
| + assertEquals(bot.ErrorCode.ELEMENT_NOT_VISIBLE, e.code); | |
| + } | |
| + | |
| + window.setTimeout(function() { asyncTestCase.continueTesting(); }, 100); | |
| + } | |
| + | |
| + function testClicksElementVisibleInHiddenOverflow() { | |
| + var element = findElement({id: 'not-hidden-by-hidden-overflow'}); | |
| + assertClickClicks(element); | |
| + } | |
| + | |
| + function testScrollsNestedScrollContainers() { | |
| + var top = findElement({id: 'nested-top-container'}); | |
| + var middle = findElement({id: 'nested-inner-container'}); | |
| + var element = findElement({id: 'nested'}); | |
| + assertClickClicks(element); | |
| + assertNotEquals(0, top.scrollLeft); | |
| + assertNotEquals(0, middle.scrollLeft); | |
| + } | |
| + | |
| + function testScrollsNestedScrollContainersInIframes() { | |
| + var iframe = findElement({id: 'scrollable-iframe'}); | |
| + var frameDocument = iframe.contentWindow.document; | |
| + var top = findElement({id: 'iframe-scrollable-top-container'}, frameDocument); | |
| + var middle = findElement({id: 'iframe-scrollable-nested-container'}, frameDocument); | |
| + var element = findElement({id: 'iframe-scrollable'}, frameDocument); | |
| + assertClickClicks(element); | |
| + assertNotEquals(0, frameDocument.body.scrollLeft); | |
| + assertNotEquals(0, top.scrollLeft); | |
| + assertNotEquals(0, middle.scrollLeft); | |
| + } | |
| + | |
| + function assertClickClicks(element) { | |
| + var clicked = 0; | |
| + goog.events.listenOnce(element, 'click', function() { | |
| + asyncTestCase.continueTesting(); | |
| + }); | |
| + | |
| + asyncTestCase.waitForAsync('Waiting for click event not to fire'); | |
| + | |
| + bot.action.click(element); | |
| + } | |
| + </script> | |
| + <div style="position: absolute; left: 150px; top: 100px; border: 2px yellow solid;"><a id="onscreen">Onscreen</a></div> | |
| + <div style="position: absolute; left: 3000px; border: 2px orange solid;"><a id="offscreen">Offscreen</a></div> | |
| + <div style="overflow: hidden; width: 50px; height: 30px; border: 2px blue solid;"> | |
| + <a id="hidden-by-overflow">Hidden</a></div> | |
| + </div> | |
| + <div id="scrollable-container" style="overflow: scroll; width: 100px; height: 100px; border: 2px purple solid;"> <a id="scrollable">Scrollable</a></div> | |
| + <div id="scrollable-in-view-container" style="overflow: scroll; width: 150px; height: 50px; border: 2px grey solid;"> <a id="scrollable-in-view">Scrollable in view</a></div> | |
| + <div style="overflow: hidden; width: 100px; height: 30px; border: 2px black solid;" id="not-hidden-by-hidden-overflow-container"> <a id="not-hidden-by-hidden-overflow">Not hidden</a></div> | |
| + <div style="overflow: scroll; width: 100px; height: 100px;" id="nested-top-container"> | |
| + <div style="overflow: scroll; width: 150px; left: 150px; margin-left: 200px;" id="nested-inner-container"> <a id="nested">Nested</a></div> | |
| + </div> | |
| + <iframe src="testdata/scrolling_iframe.html" style="width: 200px; height: 100px;" id="scrollable-iframe"></iframe> | |
| +</body> | |
| +</html> | |
| Index: javascript/atoms/test/testdata/scrolling_iframe.html | |
| =================================================================== | |
| --- javascript/atoms/test/testdata/scrolling_iframe.html (revision 0) | |
| +++ javascript/atoms/test/testdata/scrolling_iframe.html (revision 0) | |
| @@ -0,0 +1,12 @@ | |
| +<html> | |
| +<head> | |
| + <title>Nested scrollable</title> | |
| +</head> | |
| +<body> | |
| + <div id="iframe-scrollable-top-container" style="overflow: scroll; margin-left: 200px; width: 150px; border: 2px orange solid;"> | |
| + <div id="iframe-scrollable-nested-container" style="overflow: scroll; margin-left: 200px; width: 100px;"> | |
| + <a id="iframe-scrollable">iFrameScrollable</a> | |
| + </div> | |
| + </div> | |
| +</body> | |
| +</html> | |
| Index: javascript/atoms/action.js | |
| =================================================================== | |
| --- javascript/atoms/action.js (revision 14329) | |
| +++ javascript/atoms/action.js (working copy) | |
| @@ -36,6 +36,7 @@ | |
| goog.require('goog.dom.TagName'); | |
| goog.require('goog.events.EventType'); | |
| goog.require('goog.math.Coordinate'); | |
| +goog.require('goog.math.Rect'); | |
| goog.require('goog.userAgent'); | |
| @@ -498,29 +499,31 @@ | |
| /** | |
| - * A helper function which prepares the mouse for a click action. It checks if | |
| - * the the element is shown, scrolls the element into few, sets the | |
| - * {@code opt_coords} if they are undefined, and moves the mouse to the right | |
| - * position. | |
| + * Ensures that the click-point of the passed element is displayed in the | |
| + * browser, ready to be clicked. Throws if element cannot be made to be | |
| + * displayed. | |
| * | |
| - * @param {!Element} element The element to click. | |
| + * @param {!Element} element The element being clicked. | |
| * @param {goog.math.Coordinate=} opt_coords Mouse position relative to the | |
| - * target. | |
| - * @return {!bot.Mouse} The mouse object used for the click. | |
| - * @private | |
| + * target element. | |
| + * @return {!goog.math.Coordinate} Coordinate guaranteed to be in view, | |
| + * relative to the target element. | |
| */ | |
| -bot.action.prepareMouseForClick_ = function(element, opt_coords) { | |
| +bot.action.scrollClickPointInView = function(element, opt_coords) { | |
| if (!bot.dom.isShown(element, true)) { | |
| throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_VISIBLE, | |
| 'Element is not currently visible and may not be manipulated'); | |
| } | |
| - // Unlike element.scrollIntoView(), this scrolls the minimal amount | |
| - // necessary, not scrolling at all if the element is already in view. | |
| - var doc = goog.dom.getOwnerDocument(element); | |
| - goog.style.scrollIntoContainerView(element, | |
| - goog.userAgent.WEBKIT ? doc.body : doc.documentElement); | |
| + if (!opt_coords) { | |
| + var size = goog.style.getSize(element); | |
| + opt_coords = new goog.math.Coordinate(size.width / 2, size.height / 2); | |
| + } | |
| + var rectToFocus = new goog.math.Rect(opt_coords.x, opt_coords.y, 1, 1); | |
| + | |
| + bot.dom.scrollElementRegionIntoClientView(element, rectToFocus); | |
| + | |
| // NOTE(user): Ideally, we would check that any provided coordinates fall | |
| // within the bounds of the element, but this has proven difficult, because: | |
| // (1) Browsers sometimes lie about the true size of elements, e.g. when text | |
| @@ -529,11 +532,25 @@ | |
| // (2) Elements with children styled as position:absolute will often not have | |
| // a bounding box that surrounds all of their children, but it is useful for | |
| // the user to be able to interact with this parent element as if it does. | |
| - if (!opt_coords) { | |
| - var size = goog.style.getSize(element); | |
| - opt_coords = new goog.math.Coordinate(size.width / 2, size.height / 2); | |
| - } | |
| + return opt_coords; | |
| +}; | |
| + | |
| + | |
| +/** | |
| + * A helper function which prepares the mouse for a click action. It checks if | |
| + * the the element is shown, scrolls the element into few, sets the | |
| + * {@code opt_coords} if they are undefined, and moves the mouse to the right | |
| + * position. | |
| + * | |
| + * @param {!Element} element The element to click. | |
| + * @param {goog.math.Coordinate=} opt_coords Mouse position relative to the | |
| + * target. | |
| + * @return {!bot.Mouse} The mouse object used for the click. | |
| + * @private | |
| + */ | |
| +bot.action.prepareMouseForClick_ = function(element, opt_coords) { | |
| + opt_coords = bot.action.scrollClickPointInView(element, opt_coords); | |
| var mouse = new bot.Mouse(); | |
| mouse.move(element, opt_coords); | |
| Index: javascript/atoms/dom.js | |
| =================================================================== | |
| --- javascript/atoms/dom.js (revision 14329) | |
| +++ javascript/atoms/dom.js (working copy) | |
| @@ -980,10 +980,20 @@ | |
| * @private | |
| */ | |
| bot.dom.scrollRegionIntoView_ = function(region, scrollable) { | |
| - scrollable.scrollLeft += Math.min( | |
| - region.left, Math.max(region.left - region.width, 0)); | |
| - scrollable.scrollTop += Math.min( | |
| - region.top, Math.max(region.top - region.height, 0)); | |
| + var leftScroll = Math.min(region.left, | |
| + Math.max(region.left - region.width, 0)); | |
| + var topScroll = Math.min(region.top, | |
| + Math.max(region.top - region.height, 0)); | |
| + | |
| + | |
| + var style = goog.style.getComputedStyle(scrollable, 'overflow'); | |
| + if (style == 'hidden' && (leftScroll != 0 || topScroll != 0)) { | |
| + throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_VISIBLE, | |
| + 'Element cannot be scrolled in to view, and may not be manipulated'); | |
| + } | |
| + | |
| + scrollable.scrollLeft += leftScroll; | |
| + scrollable.scrollTop += topScroll; | |
| }; | |
| @@ -1033,12 +1043,12 @@ | |
| * scrolled into view. | |
| * @private | |
| */ | |
| -bot.dom.scrollElementRegionIntoClientView_ = function(elem, elemRegion) { | |
| +bot.dom.scrollElementRegionIntoClientView = function(elem, elemRegion) { | |
| var doc = goog.dom.getOwnerDocument(elem); | |
| // Scroll the containers. | |
| for (var container = bot.dom.getParentElement(elem); | |
| - container && container != doc.body && container != doc.documentElement; | |
| + container; | |
| container = bot.dom.getParentElement(container)) { | |
| bot.dom.scrollElementRegionIntoContainerView_(elem, elemRegion, container); | |
| } | |
| @@ -1081,7 +1091,7 @@ | |
| } else { | |
| elemRegion = new goog.math.Rect(0, 0, elem.offsetWidth, elem.offsetHeight); | |
| } | |
| - bot.dom.scrollElementRegionIntoClientView_(elem, elemRegion); | |
| + bot.dom.scrollElementRegionIntoClientView(elem, elemRegion); | |
| // This is needed for elements that are split across multiple lines. | |
| var rect = elem.getClientRects ? elem.getClientRects()[0] : null; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment