Setting scroll-snap-desination to (100% 100%) locks up WebKit
[WebKit-https.git] / Source / WebCore / page / scrolling / AxisScrollSnapOffsets.cpp
1 /*
2  * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "AxisScrollSnapOffsets.h"
28
29 #include "ElementChildIterator.h"
30 #include "HTMLCollection.h"
31 #include "HTMLElement.h"
32 #include "RenderBox.h"
33 #include "ScrollableArea.h"
34 #include "StyleScrollSnapPoints.h"
35
36 #if ENABLE(CSS_SCROLL_SNAP)
37
38 namespace WebCore {
39
40 static void appendChildSnapOffsets(HTMLElement& parent, bool shouldAddHorizontalChildOffsets, Vector<LayoutUnit>& horizontalSnapOffsetSubsequence, bool shouldAddVerticalChildOffsets, Vector<LayoutUnit>& verticalSnapOffsetSubsequence)
41 {
42     // FIXME: Instead of traversing all children, register children with snap coordinates before appending to snapOffsetSubsequence.
43     for (auto& child : childrenOfType<Element>(parent)) {
44         if (RenderBox* box = child.renderBox()) {
45             LayoutUnit viewWidth = box->width();
46             LayoutUnit viewHeight = box->height();
47 #if PLATFORM(IOS)
48             // FIXME: Dangerous to call offsetLeft and offsetTop because they call updateLayoutIgnorePendingStylesheets, which can invalidate the RenderBox pointer we are holding.
49             // FIXME: Investigate why using localToContainerPoint gives the wrong offsets for iOS main frame. Also, these offsets won't take transforms into account (make sure to test this!).
50             float left = child.offsetLeft();
51             float top = child.offsetTop();
52 #else
53             // FIXME: Check that localToContainerPoint works with CSS rotations.
54             FloatPoint position = box->localToContainerPoint(FloatPoint(), parent.renderBox());
55             float left = position.x();
56             float top = position.y();
57 #endif
58             for (auto& coordinate : box->style().scrollSnapCoordinates()) {
59                 LayoutUnit lastPotentialSnapPositionX = LayoutUnit(left) + valueForLength(coordinate.width(), viewWidth);
60                 if (shouldAddHorizontalChildOffsets && lastPotentialSnapPositionX > 0)
61                     horizontalSnapOffsetSubsequence.append(lastPotentialSnapPositionX);
62
63                 LayoutUnit lastPotentialSnapPositionY = LayoutUnit(top) + valueForLength(coordinate.height(), viewHeight);
64                 if (shouldAddVerticalChildOffsets && lastPotentialSnapPositionY > 0)
65                     verticalSnapOffsetSubsequence.append(lastPotentialSnapPositionY);
66             }
67         }
68     }
69 }
70
71 static void updateFromStyle(Vector<LayoutUnit>& snapOffsets, const RenderStyle& style, ScrollEventAxis axis, LayoutUnit viewSize, LayoutUnit scrollSize, Vector<LayoutUnit>& snapOffsetSubsequence)
72 {
73     std::sort(snapOffsetSubsequence.begin(), snapOffsetSubsequence.end());
74     if (snapOffsetSubsequence.isEmpty())
75         snapOffsetSubsequence.append(0);
76
77     bool isHorizontalAxis = axis == ScrollEventAxis::Horizontal;
78     auto* points = isHorizontalAxis ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
79     auto& destination = style.scrollSnapDestination();
80     bool hasRepeat = points ? points->hasRepeat : false;
81     LayoutUnit repeatOffset = points ? valueForLength(points->repeatOffset, viewSize) : LayoutUnit();
82     LayoutUnit destinationOffset = valueForLength(isHorizontalAxis ? destination.width() : destination.height(), viewSize);
83     LayoutUnit curSnapPositionShift = 0;
84     LayoutUnit maxScrollOffset = scrollSize - viewSize;
85     LayoutUnit lastSnapPosition = curSnapPositionShift;
86     snapOffsets.append(0);
87     do {
88         for (auto& snapPosition : snapOffsetSubsequence) {
89             LayoutUnit potentialSnapPosition = curSnapPositionShift + snapPosition - destinationOffset;
90             if (potentialSnapPosition < 0)
91                 continue;
92
93             if (potentialSnapPosition >= maxScrollOffset)
94                 break;
95
96             snapOffsets.append(potentialSnapPosition);
97             lastSnapPosition = potentialSnapPosition + destinationOffset;
98         }
99         curSnapPositionShift = lastSnapPosition + repeatOffset;
100     } while (hasRepeat && curSnapPositionShift < maxScrollOffset);
101     // Always put a snap point on the maximum scroll offset.
102     // Not a part of the spec, but necessary to prevent unreachable content when snapping.
103     if (snapOffsets.last() != maxScrollOffset)
104         snapOffsets.append(maxScrollOffset);
105 }
106
107 void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle)
108 {
109     if (scrollingElementStyle.scrollSnapType() == ScrollSnapType::None) {
110         scrollableArea.clearHorizontalSnapOffsets();
111         scrollableArea.clearVerticalSnapOffsets();
112         return;
113     }
114
115     LayoutUnit viewWidth = scrollingElementBox.width();
116     LayoutUnit viewHeight = scrollingElementBox.height();
117     LayoutUnit scrollWidth = scrollingElementBox.scrollWidth();
118     LayoutUnit scrollHeight = scrollingElementBox.scrollHeight();
119     bool canComputeHorizontalOffsets = scrollWidth > 0 && viewWidth > 0 && viewWidth < scrollWidth;
120     bool canComputeVerticalOffsets = scrollHeight > 0 && viewHeight > 0 && viewHeight < scrollHeight;
121
122     if (!canComputeHorizontalOffsets)
123         scrollableArea.clearHorizontalSnapOffsets();
124     if (!canComputeVerticalOffsets)
125         scrollableArea.clearVerticalSnapOffsets();
126
127     if (!canComputeHorizontalOffsets && !canComputeVerticalOffsets)
128         return;
129
130     Vector<LayoutUnit> horizontalSnapOffsetSubsequence;
131     Vector<LayoutUnit> verticalSnapOffsetSubsequence;
132
133     bool scrollSnapPointsXUsesElements = scrollingElementStyle.scrollSnapPointsX() ? scrollingElementStyle.scrollSnapPointsX()->usesElements : false;
134     bool scrollSnapPointsYUsesElements = scrollingElementStyle.scrollSnapPointsY() ? scrollingElementStyle.scrollSnapPointsY()->usesElements : false;
135
136     if (scrollSnapPointsXUsesElements || scrollSnapPointsYUsesElements) {
137         bool shouldAddHorizontalChildOffsets = scrollSnapPointsXUsesElements && canComputeHorizontalOffsets;
138         bool shouldAddVerticalChildOffsets = scrollSnapPointsYUsesElements && canComputeVerticalOffsets;
139         appendChildSnapOffsets(scrollingElement, shouldAddHorizontalChildOffsets, horizontalSnapOffsetSubsequence, shouldAddVerticalChildOffsets, verticalSnapOffsetSubsequence);
140     }
141
142     if (scrollingElementStyle.scrollSnapPointsX() && !scrollSnapPointsXUsesElements && canComputeHorizontalOffsets) {
143         for (auto& snapLength : scrollingElementStyle.scrollSnapPointsX()->offsets)
144             horizontalSnapOffsetSubsequence.append(valueForLength(snapLength, viewWidth));
145     }
146
147     if (scrollingElementStyle.scrollSnapPointsY() && !scrollSnapPointsYUsesElements && canComputeVerticalOffsets) {
148         for (auto& snapLength : scrollingElementStyle.scrollSnapPointsY()->offsets)
149             verticalSnapOffsetSubsequence.append(valueForLength(snapLength, viewHeight));
150     }
151
152     if (canComputeHorizontalOffsets) {
153         auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
154         updateFromStyle(*horizontalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Horizontal, viewWidth, scrollWidth, horizontalSnapOffsetSubsequence);
155         scrollableArea.setHorizontalSnapOffsets(WTF::move(horizontalSnapOffsets));
156     }
157     if (canComputeVerticalOffsets) {
158         auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
159         updateFromStyle(*verticalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Vertical, viewHeight, scrollHeight, verticalSnapOffsetSubsequence);
160         scrollableArea.setVerticalSnapOffsets(WTF::move(verticalSnapOffsets));
161     }
162 }
163
164 } // namespace WebCore
165
166 #endif // CSS_SCROLL_SNAP