75926306406e18559587a553ddf822ea424405c8
[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 "Length.h"
33 #include "RenderBox.h"
34 #include "ScrollableArea.h"
35 #include "StyleScrollSnapPoints.h"
36
37 #if ENABLE(CSS_SCROLL_SNAP)
38
39 namespace WebCore {
40
41 static void appendChildSnapOffsets(HTMLElement& parent, bool shouldAddHorizontalChildOffsets, Vector<LayoutUnit>& horizontalSnapOffsetSubsequence, bool shouldAddVerticalChildOffsets, Vector<LayoutUnit>& verticalSnapOffsetSubsequence)
42 {
43     // FIXME: Instead of traversing all children, register children with snap coordinates before appending to snapOffsetSubsequence.
44     for (auto& child : childrenOfType<Element>(parent)) {
45         if (RenderBox* box = child.renderBox()) {
46             const auto& scrollSnapCoordinates = box->style().scrollSnapCoordinates();
47             if (scrollSnapCoordinates.isEmpty())
48                 continue;
49
50             LayoutRect viewSize = box->contentBoxRect();
51             LayoutUnit viewWidth = viewSize.width();
52             LayoutUnit viewHeight = viewSize.height();
53             FloatPoint position = box->localToContainerPoint(FloatPoint(), parent.renderBox());
54             LayoutUnit left = position.x();
55             LayoutUnit top = position.y();
56             for (auto& coordinate : scrollSnapCoordinates) {
57                 LayoutUnit lastPotentialSnapPositionX = left + valueForLength(coordinate.width(), viewWidth);
58                 if (shouldAddHorizontalChildOffsets && lastPotentialSnapPositionX > 0)
59                     horizontalSnapOffsetSubsequence.append(lastPotentialSnapPositionX);
60
61                 LayoutUnit lastPotentialSnapPositionY = top + valueForLength(coordinate.height(), viewHeight);
62                 if (shouldAddVerticalChildOffsets && lastPotentialSnapPositionY > 0)
63                     verticalSnapOffsetSubsequence.append(lastPotentialSnapPositionY);
64             }
65         }
66     }
67 }
68
69 static LayoutUnit destinationOffsetForViewSize(ScrollEventAxis axis, const LengthSize& destination, LayoutUnit viewSize)
70 {
71     const Length& dimension = (axis == ScrollEventAxis::Horizontal) ? destination.width() : destination.height();
72     return valueForLength(dimension, viewSize);
73 }
74     
75 static void updateFromStyle(Vector<LayoutUnit>& snapOffsets, const RenderStyle& style, ScrollEventAxis axis, LayoutUnit viewSize, LayoutUnit scrollSize, Vector<LayoutUnit>& snapOffsetSubsequence)
76 {
77     std::sort(snapOffsetSubsequence.begin(), snapOffsetSubsequence.end());
78     if (snapOffsetSubsequence.isEmpty())
79         snapOffsetSubsequence.append(0);
80
81     // Always put a snap point on the zero offset.
82     snapOffsets.append(0);
83
84     auto* points = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
85     bool hasRepeat = points ? points->hasRepeat : false;
86     LayoutUnit repeatOffset = points ? valueForLength(points->repeatOffset, viewSize) : LayoutUnit();
87     LayoutUnit destinationOffset = destinationOffsetForViewSize(axis, style.scrollSnapDestination(), viewSize);
88     LayoutUnit curSnapPositionShift = 0;
89     LayoutUnit maxScrollOffset = scrollSize - viewSize;
90     LayoutUnit lastSnapPosition = curSnapPositionShift;
91     do {
92         for (auto& snapPosition : snapOffsetSubsequence) {
93             LayoutUnit potentialSnapPosition = curSnapPositionShift + snapPosition - destinationOffset;
94             if (potentialSnapPosition < 0)
95                 continue;
96
97             if (potentialSnapPosition >= maxScrollOffset)
98                 break;
99
100             // Don't add another zero offset value.
101             if (potentialSnapPosition)
102                 snapOffsets.append(potentialSnapPosition);
103
104             lastSnapPosition = potentialSnapPosition + destinationOffset;
105         }
106         curSnapPositionShift = lastSnapPosition + repeatOffset;
107     } while (hasRepeat && curSnapPositionShift < maxScrollOffset);
108
109     // Always put a snap point on the maximum scroll offset.
110     // Not a part of the spec, but necessary to prevent unreachable content when snapping.
111     if (snapOffsets.last() != maxScrollOffset)
112         snapOffsets.append(maxScrollOffset);
113 }
114
115 static bool styleUsesElements(ScrollEventAxis axis, const RenderStyle& style)
116 {
117     const ScrollSnapPoints* scrollSnapPoints = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
118     if (scrollSnapPoints)
119         return scrollSnapPoints->usesElements;
120
121     const Length& destination = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapDestination().width() : style.scrollSnapDestination().height();
122
123     return !destination.isUndefined();
124 }
125     
126 void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle)
127 {
128     if (scrollingElementStyle.scrollSnapType() == ScrollSnapType::None) {
129         scrollableArea.clearHorizontalSnapOffsets();
130         scrollableArea.clearVerticalSnapOffsets();
131         return;
132     }
133
134     LayoutRect viewSize = scrollingElementBox.contentBoxRect();
135     LayoutUnit viewWidth = viewSize.width();
136     LayoutUnit viewHeight = viewSize.height();
137     LayoutUnit scrollWidth = scrollingElementBox.scrollWidth();
138     LayoutUnit scrollHeight = scrollingElementBox.scrollHeight();
139     bool canComputeHorizontalOffsets = scrollWidth > 0 && viewWidth > 0 && viewWidth < scrollWidth;
140     bool canComputeVerticalOffsets = scrollHeight > 0 && viewHeight > 0 && viewHeight < scrollHeight;
141
142     if (!canComputeHorizontalOffsets)
143         scrollableArea.clearHorizontalSnapOffsets();
144     if (!canComputeVerticalOffsets)
145         scrollableArea.clearVerticalSnapOffsets();
146
147     if (!canComputeHorizontalOffsets && !canComputeVerticalOffsets)
148         return;
149
150     Vector<LayoutUnit> horizontalSnapOffsetSubsequence;
151     Vector<LayoutUnit> verticalSnapOffsetSubsequence;
152
153     bool scrollSnapPointsXUsesElements = styleUsesElements(ScrollEventAxis::Horizontal, scrollingElementStyle);
154     bool scrollSnapPointsYUsesElements = styleUsesElements(ScrollEventAxis::Vertical, scrollingElementStyle);
155
156     if (scrollSnapPointsXUsesElements || scrollSnapPointsYUsesElements) {
157         bool shouldAddHorizontalChildOffsets = scrollSnapPointsXUsesElements && canComputeHorizontalOffsets;
158         bool shouldAddVerticalChildOffsets = scrollSnapPointsYUsesElements && canComputeVerticalOffsets;
159         appendChildSnapOffsets(scrollingElement, shouldAddHorizontalChildOffsets, horizontalSnapOffsetSubsequence, shouldAddVerticalChildOffsets, verticalSnapOffsetSubsequence);
160     }
161
162     if (scrollingElementStyle.scrollSnapPointsX() && !scrollSnapPointsXUsesElements && canComputeHorizontalOffsets) {
163         for (auto& snapLength : scrollingElementStyle.scrollSnapPointsX()->offsets)
164             horizontalSnapOffsetSubsequence.append(valueForLength(snapLength, viewWidth));
165     }
166
167     if (scrollingElementStyle.scrollSnapPointsY() && !scrollSnapPointsYUsesElements && canComputeVerticalOffsets) {
168         for (auto& snapLength : scrollingElementStyle.scrollSnapPointsY()->offsets)
169             verticalSnapOffsetSubsequence.append(valueForLength(snapLength, viewHeight));
170     }
171
172     if (canComputeHorizontalOffsets) {
173         auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
174         updateFromStyle(*horizontalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Horizontal, viewWidth, scrollWidth, horizontalSnapOffsetSubsequence);
175         scrollableArea.setHorizontalSnapOffsets(WTF::move(horizontalSnapOffsets));
176     }
177     if (canComputeVerticalOffsets) {
178         auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
179         updateFromStyle(*verticalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Vertical, viewHeight, scrollHeight, verticalSnapOffsetSubsequence);
180         scrollableArea.setVerticalSnapOffsets(WTF::move(verticalSnapOffsets));
181     }
182 }
183
184 } // namespace WebCore
185
186 #endif // CSS_SCROLL_SNAP