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