Wheel events don't latch to inner scrollable elements
authorbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Feb 2014 20:40:17 +0000 (20:40 +0000)
committerbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Feb 2014 20:40:17 +0000 (20:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=128225
<rdar://problem/12183688>

Reviewed by Simon Fraser

* WebCore.exp.in: Add declarations for new scrolledToTop, scrolledToBottom, scrolledToLeft, and scrolledToRight.
* page/EventHandler.cpp:
(WebCore::EventHandler::EventHandler):
(WebCore::EventHandler::clear):
(WebCore::findScrollableContainer): New helper function to locate first node
in enclosing region of document that is capable of handling mouse wheel events.
(WebCore::isAtMaxDominantScrollPosition): Predicate to check if the scrollable
area is at the limit we will hit based on scroll direction.
(WebCore::EventHandler::handleWheelEvent): Identify the case where we have hit
the end of a scroll, and treat that as a valid 'handled' case. If the scroll event
is just starting, treat end-of-scroll as unhandled so the parent element can
handle things.
* page/EventHandler.h:
* page/scrolling/ScrollingTree.cpp:
(WebCore::ScrollingTree::shouldHandleWheelEventSynchronously): Use new methods
on the PlatformWheelEvent class.
(WebCore::ScrollingTree::setOrClearLatchedNode): Ditto
* platform/PlatformWheelEvent.h:
(WebCore::PlatformWheelEvent::shouldConsiderLatching): Moved implementation from ScrollingTree.
(WebCore::PlatformWheelEvent::shouldClearLatchedNode): Ditto
* platform/ScrollableArea.cpp:
(WebCore::ScrollableArea::scrolledToTop): Added
(WebCore::ScrollableArea::scrolledToBottom):Added
(WebCore::ScrollableArea::scrolledToLeft): Added
(WebCore::ScrollableArea::scrolledToRight): Added
* platform/ScrollableArea.h:
* rendering/RenderListBox.cpp:
(WebCore::RenderListBox::scrolledToTop): Added
(WebCore::RenderListBox::scrolledToBottom): Added
(WebCore::RenderListBox::scrolledToLeft): Added
(WebCore::RenderListBox::scrolledToRight): Added
* rendering/RenderListBox.h: Changed to public inheritance of ScrollableArea to
allow generic use of this type in scroll wheel logic.

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

Source/WebCore/ChangeLog
Source/WebCore/WebCore.exp.in
Source/WebCore/page/EventHandler.cpp
Source/WebCore/page/EventHandler.h
Source/WebCore/page/mac/EventHandlerMac.mm
Source/WebCore/page/scrolling/ScrollingTree.cpp
Source/WebCore/platform/PlatformWheelEvent.h
Source/WebCore/platform/ScrollableArea.cpp
Source/WebCore/platform/ScrollableArea.h
Source/WebCore/rendering/RenderListBox.cpp
Source/WebCore/rendering/RenderListBox.h

index da8989d..26474f6 100644 (file)
@@ -1,3 +1,45 @@
+2014-02-12  Brent Fulgham  <bfulgham@apple.com>
+
+        Wheel events don't latch to inner scrollable elements 
+        https://bugs.webkit.org/show_bug.cgi?id=128225
+        <rdar://problem/12183688>
+
+        Reviewed by Simon Fraser
+
+        * WebCore.exp.in: Add declarations for new scrolledToTop, scrolledToBottom, scrolledToLeft, and scrolledToRight.
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::EventHandler):
+        (WebCore::EventHandler::clear):
+        (WebCore::findScrollableContainer): New helper function to locate first node
+        in enclosing region of document that is capable of handling mouse wheel events.
+        (WebCore::isAtMaxDominantScrollPosition): Predicate to check if the scrollable
+        area is at the limit we will hit based on scroll direction.
+        (WebCore::EventHandler::handleWheelEvent): Identify the case where we have hit
+        the end of a scroll, and treat that as a valid 'handled' case. If the scroll event
+        is just starting, treat end-of-scroll as unhandled so the parent element can
+        handle things.
+        * page/EventHandler.h:
+        * page/scrolling/ScrollingTree.cpp:
+        (WebCore::ScrollingTree::shouldHandleWheelEventSynchronously): Use new methods
+        on the PlatformWheelEvent class.
+        (WebCore::ScrollingTree::setOrClearLatchedNode): Ditto
+        * platform/PlatformWheelEvent.h:
+        (WebCore::PlatformWheelEvent::shouldConsiderLatching): Moved implementation from ScrollingTree.
+        (WebCore::PlatformWheelEvent::shouldClearLatchedNode): Ditto
+        * platform/ScrollableArea.cpp:
+        (WebCore::ScrollableArea::scrolledToTop): Added
+        (WebCore::ScrollableArea::scrolledToBottom):Added
+        (WebCore::ScrollableArea::scrolledToLeft): Added
+        (WebCore::ScrollableArea::scrolledToRight): Added
+        * platform/ScrollableArea.h:
+        * rendering/RenderListBox.cpp:
+        (WebCore::RenderListBox::scrolledToTop): Added
+        (WebCore::RenderListBox::scrolledToBottom): Added
+        (WebCore::RenderListBox::scrolledToLeft): Added
+        (WebCore::RenderListBox::scrolledToRight): Added
+        * rendering/RenderListBox.h: Changed to public inheritance of ScrollableArea to
+        allow generic use of this type in scroll wheel logic.
+
 2014-02-12  Brendan Long  <b.long@cablelabs.com>
 
         Implement DataCue for metadata cues
index d55aa5f..e05b938 100644 (file)
@@ -396,6 +396,10 @@ __ZN7WebCore14SchemeRegistry62registerURLSchemeAsAllowingLocalStorageAccessInPri
 __ZN7WebCore14ScrollableArea15contentsResizedEv
 __ZN7WebCore14ScrollableArea15didAddScrollbarEPNS_9ScrollbarENS_20ScrollbarOrientationE
 __ZN7WebCore14ScrollableArea16handleWheelEventERKNS_18PlatformWheelEventE
+__ZNK7WebCore14ScrollableArea16scrolledToBottomEv
+__ZNK7WebCore14ScrollableArea14scrolledToLeftEv
+__ZNK7WebCore14ScrollableArea13scrolledToTopEv
+__ZNK7WebCore14ScrollableArea15scrolledToRightEv
 __ZN7WebCore14ScrollableArea17willEndLiveResizeEv
 __ZN7WebCore14ScrollableArea19invalidateScrollbarEPNS_9ScrollbarERKNS_7IntRectE
 __ZN7WebCore14ScrollableArea19willRemoveScrollbarEPNS_9ScrollbarENS_20ScrollbarOrientationE
index 81f8d33..d4d1890 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2014 Apple Inc. All rights reserved.
  * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
  * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
  *
@@ -72,6 +72,7 @@
 #include "PluginDocument.h"
 #include "RenderFrameSet.h"
 #include "RenderLayer.h"
+#include "RenderListBox.h"
 #include "RenderTextControlSingleLine.h"
 #include "RenderView.h"
 #include "RenderWidget.h"
@@ -353,6 +354,7 @@ EventHandler::EventHandler(Frame& frame)
 #if PLATFORM(COCOA)
     , m_mouseDownView(nil)
     , m_sendingEventToSubview(false)
+    , m_startedGestureAtScrollLimit(false)
 #if !PLATFORM(IOS)
     , m_activationEventNumber(-1)
 #endif // !PLATFORM(IOS)
@@ -432,6 +434,9 @@ void EventHandler::clear()
     m_capturesDragging = false;
     m_capturingMouseEventsElement = nullptr;
     m_latchedWheelEventElement = nullptr;
+#if PLATFORM(COCOA)
+    m_latchedScrollableContainer = nullptr;
+#endif
     m_previousWheelScrolledElement = nullptr;
 #if ENABLE(TOUCH_EVENTS) && !ENABLE(IOS_TOUCH_EVENTS)
     m_originatingTouchPointTargets.clear();
@@ -2442,6 +2447,27 @@ bool EventHandler::shouldTurnVerticalTicksIntoHorizontal(const HitTestResult&, c
 }
 #endif
 
+#if !PLATFORM(MAC)
+void EventHandler::platformPrepareForWheelEvents(const PlatformWheelEvent&, const HitTestResult&, Element*&, ContainerNode*&, ScrollableArea*&, bool&)
+{
+}
+
+void EventHandler::platformRecordWheelEvent(const PlatformWheelEvent& event)
+{
+    m_recentWheelEventDeltaTracker->recordWheelEventDelta(event);
+}
+
+bool EventHandler::platformCompleteWheelEvent(const PlatformWheelEvent& event, ContainerNode*, ScrollableArea*)
+{
+    // We do another check on the frame view because the event handler can run JS which results in the frame getting destroyed.
+    FrameView* view = m_frame.view();
+    
+    bool didHandleEvent = view ? view->wheelEvent(event) : false;
+    m_isHandlingWheelEvent = false;
+    return didHandleEvent;
+}
+#endif
+
 bool EventHandler::handleWheelEvent(const PlatformWheelEvent& e)
 {
     Document* document = m_frame.document();
@@ -2463,27 +2489,13 @@ bool EventHandler::handleWheelEvent(const PlatformWheelEvent& e)
     HitTestResult result(vPoint);
     document->renderView()->hitTest(request, result);
 
-    bool useLatchedWheelEventElement = e.useLatchedEventElement();
-
     Element* element = result.innerElement();
 
-    bool isOverWidget;
-    if (useLatchedWheelEventElement) {
-        if (!m_latchedWheelEventElement) {
-            m_latchedWheelEventElement = element;
-            m_widgetIsLatched = result.isOverWidget();
-        } else
-            element = m_latchedWheelEventElement.get();
+    bool isOverWidget = result.isOverWidget();
 
-        isOverWidget = m_widgetIsLatched;
-    } else {
-        if (m_latchedWheelEventElement)
-            m_latchedWheelEventElement = nullptr;
-        if (m_previousWheelScrolledElement)
-            m_previousWheelScrolledElement = nullptr;
-
-        isOverWidget = result.isOverWidget();
-    }
+    ContainerNode* scrollableContainer = nullptr;
+    ScrollableArea* scrollableArea = nullptr;
+    platformPrepareForWheelEvents(e, result, element, scrollableContainer, scrollableArea, isOverWidget);
 
     // FIXME: It should not be necessary to do this mutation here.
     // Instead, the handlers should know convert vertical scrolls
@@ -2492,20 +2504,7 @@ bool EventHandler::handleWheelEvent(const PlatformWheelEvent& e)
     if (m_baseEventType == PlatformEvent::NoType && shouldTurnVerticalTicksIntoHorizontal(result, e))
         event = event.copyTurningVerticalTicksIntoHorizontalTicks();
 
-#if PLATFORM(COCOA)
-    switch (event.phase()) {
-    case PlatformWheelEventPhaseBegan:
-        m_recentWheelEventDeltaTracker->beginTrackingDeltas();
-        break;
-    case PlatformWheelEventPhaseEnded:
-        m_recentWheelEventDeltaTracker->endTrackingDeltas();
-        break;
-    default:
-        break;
-    }
-#endif
-
-    m_recentWheelEventDeltaTracker->recordWheelEventDelta(event);
+    platformRecordWheelEvent(event);
 
     if (element) {
         // Figure out which view to send the event to.
@@ -2525,12 +2524,7 @@ bool EventHandler::handleWheelEvent(const PlatformWheelEvent& e)
         }
     }
 
-
-    // We do another check on the frame view because the event handler can run JS which results in the frame getting destroyed.
-    view = m_frame.view();
-    bool didHandleEvent = view ? view->wheelEvent(event) : false;
-    m_isHandlingWheelEvent = false;
-    return didHandleEvent;
+    return platformCompleteWheelEvent(e, scrollableContainer, scrollableArea);
 }
 
 void EventHandler::defaultWheelEventHandler(Node* startNode, WheelEvent* wheelEvent)
index 17e47a2..0aa8cc0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2007, 2009, 2010, 2011, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -69,6 +69,7 @@ namespace WebCore {
 
 class AutoscrollController;
 class Clipboard;
+class ContainerNode;
 class Document;
 class Element;
 class Event;
@@ -90,6 +91,7 @@ class RenderBox;
 class RenderElement;
 class RenderLayer;
 class RenderWidget;
+class ScrollableArea;
 class SVGElementInstance;
 class Scrollbar;
 class TextEvent;
@@ -197,6 +199,10 @@ public:
     void defaultWheelEventHandler(Node*, WheelEvent*);
     bool handlePasteGlobalSelection(const PlatformMouseEvent&);
 
+    void platformPrepareForWheelEvents(const PlatformWheelEvent& wheelEvent, const HitTestResult& result, Element*& wheelEventTarget, ContainerNode*& scrollableContainer, ScrollableArea*& scrollableArea, bool& isOverWidget);
+    void platformRecordWheelEvent(const PlatformWheelEvent&);
+    bool platformCompleteWheelEvent(const PlatformWheelEvent&, ContainerNode* scrollableContainer, ScrollableArea* scrollableArea);
+
 #if ENABLE(IOS_TOUCH_EVENTS) || ENABLE(IOS_GESTURE_EVENTS)
     typedef Vector<RefPtr<Touch>> TouchArray;
     typedef HashMap<EventTarget*, TouchArray*> EventTargetTouchMap;
@@ -520,7 +526,9 @@ private:
 
 #if PLATFORM(COCOA)
     NSView *m_mouseDownView;
+    RefPtr<ContainerNode> m_latchedScrollableContainer;
     bool m_sendingEventToSubview;
+    bool m_startedGestureAtScrollLimit;
 #if !PLATFORM(IOS)
     int m_activationEventNumber;
 #endif
index c5b2af6..44f8c85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "FrameLoader.h"
 #include "FrameView.h"
 #include "KeyboardEvent.h"
+#include "MainFrame.h"
 #include "MouseEventWithHitTestResults.h"
 #include "NotImplemented.h"
 #include "Page.h"
 #include "Pasteboard.h"
 #include "PlatformEventFactoryMac.h"
+#include "RenderLayer.h"
+#include "RenderListBox.h"
 #include "RenderWidget.h"
 #include "RuntimeApplicationChecks.h"
+#include "ScrollableArea.h"
 #include "Scrollbar.h"
 #include "Settings.h"
+#include "ShadowRoot.h"
 #include "WebCoreSystemInterface.h"
 #include <wtf/MainThread.h>
 #include <wtf/NeverDestroyed.h>
@@ -734,4 +739,121 @@ unsigned EventHandler::accessKeyModifiers()
     return PlatformEvent::CtrlKey | PlatformEvent::AltKey;
 }
 
+static ContainerNode* findEnclosingScrollableContainer(ContainerNode& node)
+{
+    // Find the first node with a valid scrollable area starting with the current
+    // node and traversing its parents (or shadow hosts).
+    for (ContainerNode* candidate = &node; candidate; candidate = candidate->parentOrShadowHostNode()) {
+        RenderBox* box = candidate->renderBox();
+        if (box && box->canBeScrolledAndHasScrollableArea())
+            return candidate;
+    }
+    
+    return nullptr;
+}
+
+static bool scrolledToEdgeInDominantDirection(const ScrollableArea& area, DominantScrollGestureDirection direction, float deltaX, float deltaY)
+{
+    if (DominantScrollGestureDirection::Horizontal == direction && deltaX) {
+        if (deltaX < 0)
+            return area.scrolledToRight();
+        
+        return area.scrolledToLeft();
+    }
+    
+    if (deltaY < 0)
+        return area.scrolledToBottom();
+    
+    return area.scrolledToTop();
+}
+
+void EventHandler::platformPrepareForWheelEvents(const PlatformWheelEvent& wheelEvent, const HitTestResult& result, Element*& wheelEventTarget, ContainerNode*& scrollableContainer, ScrollableArea*& scrollableArea, bool& isOverWidget)
+{
+    FrameView* view = m_frame.view();
+
+    scrollableContainer = nullptr;
+    scrollableArea = nullptr;
+    if (!view || !view->frame().isMainFrame()) {
+        scrollableContainer = wheelEventTarget;
+        scrollableArea = view;
+    } else {
+        scrollableContainer = findEnclosingScrollableContainer(*wheelEventTarget);
+        if (scrollableContainer) {
+            if (RenderBox* box = scrollableContainer->renderBox()) {
+                if (box->isListBox())
+                    scrollableArea = toRenderListBox(box);
+                else
+                    scrollableArea = box->layer();
+            }
+        }
+    }
+    
+    if (wheelEvent.shouldConsiderLatching()) {
+        if (scrollableArea)
+            m_startedGestureAtScrollLimit = scrolledToEdgeInDominantDirection(*scrollableArea, m_recentWheelEventDeltaTracker->dominantScrollGestureDirection(), wheelEvent.deltaX(), wheelEvent.deltaY());
+        else
+            m_startedGestureAtScrollLimit = false;
+        m_latchedWheelEventElement = wheelEventTarget;
+        m_latchedScrollableContainer = scrollableContainer;
+        m_widgetIsLatched = result.isOverWidget();
+        isOverWidget = m_widgetIsLatched;
+        m_recentWheelEventDeltaTracker->beginTrackingDeltas();
+    } else if (wheelEvent.shouldResetLatching()) {
+        m_latchedWheelEventElement = nullptr;
+        m_latchedScrollableContainer = nullptr;
+        m_widgetIsLatched = false;
+        m_previousWheelScrolledElement = nullptr;
+        m_recentWheelEventDeltaTracker->endTrackingDeltas();
+    }
+    
+    if (!wheelEvent.shouldResetLatching() && m_latchedWheelEventElement) {
+        wheelEventTarget = m_latchedWheelEventElement.get();
+        isOverWidget = m_widgetIsLatched;
+    }
+}
+
+void EventHandler::platformRecordWheelEvent(const PlatformWheelEvent& wheelEvent)
+{
+    switch (wheelEvent.phase()) {
+        case PlatformWheelEventPhaseBegan:
+            m_recentWheelEventDeltaTracker->beginTrackingDeltas();
+            break;
+        case PlatformWheelEventPhaseEnded:
+            m_recentWheelEventDeltaTracker->endTrackingDeltas();
+            break;
+        default:
+            break;
+    }
+
+    m_recentWheelEventDeltaTracker->recordWheelEventDelta(wheelEvent);
+}
+
+bool EventHandler::platformCompleteWheelEvent(const PlatformWheelEvent& wheelEvent, ContainerNode* scrollableContainer, ScrollableArea* scrollableArea)
+{
+    // We do another check on the frame view because the event handler can run JS which results in the frame getting destroyed.
+    FrameView* view = m_frame.view();
+
+    if (wheelEvent.useLatchedEventElement() && m_latchedScrollableContainer) {
+        if (!view || !view->frame().isMainFrame()) {
+            bool didHandleWheelEvent = view && view->wheelEvent(wheelEvent);
+            if (!didHandleWheelEvent && scrollableContainer == m_latchedScrollableContainer) {
+                // If we are just starting a scroll event, and have nowhere left to scroll, allow
+                // the enclosing frame to handle the scroll.
+                didHandleWheelEvent = !m_startedGestureAtScrollLimit;
+            }
+            m_isHandlingWheelEvent = false;
+            return didHandleWheelEvent;
+        }
+        
+        if (scrollableArea && !m_startedGestureAtScrollLimit && scrollableContainer == m_latchedScrollableContainer) {
+            m_isHandlingWheelEvent = false;
+            return true;
+        }
+    }
+    
+    bool didHandleEvent = view ? view->wheelEvent(wheelEvent) : false;
+    m_isHandlingWheelEvent = false;
+    return didHandleEvent;
+}
+
 }
index 7c76bb2..bac904d 100644 (file)
@@ -58,23 +58,6 @@ ScrollingTree::~ScrollingTree()
 {
 }
 
-static bool shouldConsiderLatching(const PlatformWheelEvent& wheelEvent)
-{
-    return wheelEvent.phase() == PlatformWheelEventPhaseBegan
-        || wheelEvent.phase() == PlatformWheelEventPhaseMayBegin;
-}
-
-static bool eventShouldClearLatchedNode(const PlatformWheelEvent& wheelEvent)
-{
-    if (wheelEvent.phase() == PlatformWheelEventPhaseCancelled)
-        return true;
-    
-    if (wheelEvent.phase() == PlatformWheelEventPhaseNone && wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded)
-        return true;
-    
-    return false;
-}
-
 bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent& wheelEvent)
 {
     // This method is invoked by the event handling thread
@@ -83,7 +66,7 @@ bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent
     if (m_hasWheelEventHandlers)
         return true;
 
-    bool shouldSetLatch = shouldConsiderLatching(wheelEvent);
+    bool shouldSetLatch = wheelEvent.shouldConsiderLatching();
     
     if (hasLatchedNode() && !shouldSetLatch)
         return false;
@@ -103,9 +86,9 @@ bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent
 
 void ScrollingTree::setOrClearLatchedNode(const PlatformWheelEvent& wheelEvent, ScrollingNodeID nodeID)
 {
-    if (shouldConsiderLatching(wheelEvent))
+    if (wheelEvent.shouldConsiderLatching())
         setLatchedNode(nodeID);
-    else if (eventShouldClearLatchedNode(wheelEvent))
+    else if (wheelEvent.shouldResetLatching())
         clearLatchedNode();
 }
 
index e94bdf4..219a2ed 100644 (file)
@@ -167,6 +167,20 @@ namespace WebCore {
             return m_phase == PlatformWheelEventPhaseBegan || m_phase == PlatformWheelEventPhaseChanged
                 || m_momentumPhase == PlatformWheelEventPhaseBegan || m_momentumPhase == PlatformWheelEventPhaseChanged;
         }
+        bool shouldConsiderLatching() const
+        {
+            return m_phase == PlatformWheelEventPhaseBegan || m_phase == PlatformWheelEventPhaseMayBegin;
+        }
+        bool shouldResetLatching() const
+        {
+            if (m_phase == PlatformWheelEventPhaseCancelled || m_phase == PlatformWheelEventPhaseMayBegin)
+                return true;
+            
+            if (m_phase == PlatformWheelEventPhaseNone && m_momentumPhase == PlatformWheelEventPhaseEnded)
+                return true;
+            
+            return false;
+        }
 #else
         bool useLatchedEventElement() const { return false; }
 #endif
index 85f5852..0b8897b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2010, Google Inc. All rights reserved.
- * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008, 2011, 2014 Apple Inc. All Rights Reserved.
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -422,6 +422,26 @@ IntPoint ScrollableArea::maximumScrollPosition() const
     return IntPoint(totalContentsSize().width() - visibleWidth(), totalContentsSize().height() - visibleHeight());
 }
 
+bool ScrollableArea::scrolledToTop() const
+{
+    return scrollPosition().y() <= minimumScrollPosition().y();
+}
+
+bool ScrollableArea::scrolledToBottom() const
+{
+    return scrollPosition().y() >= maximumScrollPosition().y();
+}
+
+bool ScrollableArea::scrolledToLeft() const
+{
+    return scrollPosition().x() <= minimumScrollPosition().x();
+}
+
+bool ScrollableArea::scrolledToRight() const
+{
+    return scrollPosition().x() >= maximumScrollPosition().x();
+}
+
 IntSize ScrollableArea::totalContentsSize() const
 {
     IntSize totalContentsSize = contentsSize();
index 7b40770..9c11056 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008, 2011, 2014 Apple Inc. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -149,6 +149,10 @@ public:
     virtual IntPoint scrollPosition() const;
     virtual IntPoint minimumScrollPosition() const;
     virtual IntPoint maximumScrollPosition() const;
+    virtual bool scrolledToTop() const;
+    virtual bool scrolledToBottom() const;
+    virtual bool scrolledToLeft() const;
+    virtual bool scrolledToRight() const;
 
     enum VisibleContentRectIncludesScrollbars { ExcludeScrollbars, IncludeScrollbars };
     enum VisibleContentRectBehavior {
index 8969bf2..e3b6fe1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2006, 2007, 2008, 2011, 2014 Apple Inc. All rights reserved.
  *               2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
  *
  * Redistribution and use in source and binary forms, with or without
@@ -831,4 +831,36 @@ void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
 #endif
 }
 
+bool RenderListBox::scrolledToTop() const
+{
+    Scrollbar* vbar = verticalScrollbar();
+    if (!vbar)
+        return true;
+    
+    return vbar->value() <= 0;
+}
+
+bool RenderListBox::scrolledToBottom() const
+{
+    Scrollbar* vbar = verticalScrollbar();
+    if (!vbar)
+        return true;
+
+    return vbar->value() >= vbar->maximum();
+}
+
+bool RenderListBox::scrolledToLeft() const
+{
+    // We do not scroll horizontally in a select element, so always report
+    // that we are at the full extent of the scroll.
+    return true;
+}
+
+bool RenderListBox::scrolledToRight() const
+{
+    // We do not scroll horizontally in a select element, so always report
+    // that we are at the full extent of the scroll.
+    return true;
+}
+    
 } // namespace WebCore
index 2f318b1..ecff260 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the select element renderer in WebCore.
  *
- * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2006, 2007, 2009, 2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -38,7 +38,7 @@ namespace WebCore {
 
 class HTMLSelectElement;
 
-class RenderListBox final : public RenderBlockFlow, private ScrollableArea {
+class RenderListBox final : public RenderBlockFlow, public ScrollableArea {
 public:
     RenderListBox(HTMLSelectElement&, PassRef<RenderStyle>);
     virtual ~RenderListBox();
@@ -59,6 +59,11 @@ public:
 
     int size() const;
 
+    virtual bool scrolledToTop() const override;
+    virtual bool scrolledToBottom() const override;
+    virtual bool scrolledToLeft() const override;
+    virtual bool scrolledToRight() const override;
+
 private:
     void element() const = delete;