2 * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "AxisScrollSnapOffsets.h"
29 #include "ElementChildIterator.h"
30 #include "HTMLCollection.h"
31 #include "HTMLElement.h"
33 #include "RenderBox.h"
34 #include "RenderView.h"
35 #include "ScrollableArea.h"
36 #include "StyleScrollSnapPoints.h"
38 #if ENABLE(CSS_SCROLL_SNAP)
42 static void appendChildSnapOffsets(HTMLElement& parent, bool shouldAddHorizontalChildOffsets, Vector<LayoutUnit>& horizontalSnapOffsetSubsequence, bool shouldAddVerticalChildOffsets, Vector<LayoutUnit>& verticalSnapOffsetSubsequence)
44 RenderElement* scrollContainer = parent.renderer();
45 ASSERT(scrollContainer);
47 RenderView& renderView = scrollContainer->view();
49 Vector<const RenderBox*> elements;
50 for (auto& element : renderView.boxesWithScrollSnapCoordinates()) {
51 if (element->findEnclosingScrollableContainer() != scrollContainer)
54 elements.append(element);
57 for (auto& box : elements) {
58 auto& scrollSnapCoordinates = box->style().scrollSnapCoordinates();
59 if (scrollSnapCoordinates.isEmpty())
62 LayoutRect viewSize = box->contentBoxRect();
63 FloatPoint position = box->localToContainerPoint(FloatPoint(), 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);
69 LayoutUnit lastPotentialSnapPositionY = position.y() + valueForLength(coordinate.height(), viewSize.height());
70 if (shouldAddVerticalChildOffsets && lastPotentialSnapPositionY > 0)
71 verticalSnapOffsetSubsequence.append(lastPotentialSnapPositionY);
76 static LayoutUnit destinationOffsetForViewSize(ScrollEventAxis axis, const LengthSize& destination, LayoutUnit viewSize)
78 const Length& dimension = (axis == ScrollEventAxis::Horizontal) ? destination.width() : destination.height();
79 return valueForLength(dimension, viewSize);
82 static void updateFromStyle(Vector<LayoutUnit>& snapOffsets, const RenderStyle& style, ScrollEventAxis axis, LayoutUnit viewSize, LayoutUnit scrollSize, Vector<LayoutUnit>& snapOffsetSubsequence)
84 std::sort(snapOffsetSubsequence.begin(), snapOffsetSubsequence.end());
85 if (snapOffsetSubsequence.isEmpty())
86 snapOffsetSubsequence.append(0);
88 // Always put a snap point on the zero offset.
89 snapOffsets.append(0);
91 auto* points = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
92 bool hasRepeat = points ? points->hasRepeat : false;
93 LayoutUnit repeatOffset = points ? valueForLength(points->repeatOffset, viewSize) : LayoutUnit();
94 LayoutUnit destinationOffset = destinationOffsetForViewSize(axis, style.scrollSnapDestination(), viewSize);
95 LayoutUnit curSnapPositionShift = 0;
96 LayoutUnit maxScrollOffset = scrollSize - viewSize;
97 LayoutUnit lastSnapPosition = curSnapPositionShift;
99 for (auto& snapPosition : snapOffsetSubsequence) {
100 LayoutUnit potentialSnapPosition = curSnapPositionShift + snapPosition - destinationOffset;
101 if (potentialSnapPosition < 0)
104 if (potentialSnapPosition >= maxScrollOffset)
107 // Don't add another zero offset value.
108 if (potentialSnapPosition)
109 snapOffsets.append(potentialSnapPosition);
111 lastSnapPosition = potentialSnapPosition + destinationOffset;
113 curSnapPositionShift = lastSnapPosition + repeatOffset;
114 } while (hasRepeat && curSnapPositionShift < maxScrollOffset);
116 // Always put a snap point on the maximum scroll offset.
117 // Not a part of the spec, but necessary to prevent unreachable content when snapping.
118 if (snapOffsets.last() != maxScrollOffset)
119 snapOffsets.append(maxScrollOffset);
122 static bool styleUsesElements(ScrollEventAxis axis, const RenderStyle& style)
124 const ScrollSnapPoints* scrollSnapPoints = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
125 if (scrollSnapPoints)
126 return scrollSnapPoints->usesElements;
128 const Length& destination = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapDestination().width() : style.scrollSnapDestination().height();
130 return !destination.isUndefined();
133 void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle)
135 if (scrollingElementStyle.scrollSnapType() == ScrollSnapType::None) {
136 scrollableArea.clearHorizontalSnapOffsets();
137 scrollableArea.clearVerticalSnapOffsets();
141 LayoutRect viewSize = scrollingElementBox.contentBoxRect();
142 LayoutUnit viewWidth = viewSize.width();
143 LayoutUnit viewHeight = viewSize.height();
144 LayoutUnit scrollWidth = scrollingElementBox.scrollWidth();
145 LayoutUnit scrollHeight = scrollingElementBox.scrollHeight();
146 bool canComputeHorizontalOffsets = scrollWidth > 0 && viewWidth > 0 && viewWidth < scrollWidth;
147 bool canComputeVerticalOffsets = scrollHeight > 0 && viewHeight > 0 && viewHeight < scrollHeight;
149 if (!canComputeHorizontalOffsets)
150 scrollableArea.clearHorizontalSnapOffsets();
151 if (!canComputeVerticalOffsets)
152 scrollableArea.clearVerticalSnapOffsets();
154 if (!canComputeHorizontalOffsets && !canComputeVerticalOffsets)
157 Vector<LayoutUnit> horizontalSnapOffsetSubsequence;
158 Vector<LayoutUnit> verticalSnapOffsetSubsequence;
160 bool scrollSnapPointsXUsesElements = styleUsesElements(ScrollEventAxis::Horizontal, scrollingElementStyle);
161 bool scrollSnapPointsYUsesElements = styleUsesElements(ScrollEventAxis::Vertical, scrollingElementStyle);
163 if (scrollSnapPointsXUsesElements || scrollSnapPointsYUsesElements) {
164 bool shouldAddHorizontalChildOffsets = scrollSnapPointsXUsesElements && canComputeHorizontalOffsets;
165 bool shouldAddVerticalChildOffsets = scrollSnapPointsYUsesElements && canComputeVerticalOffsets;
166 appendChildSnapOffsets(scrollingElement, shouldAddHorizontalChildOffsets, horizontalSnapOffsetSubsequence, shouldAddVerticalChildOffsets, verticalSnapOffsetSubsequence);
169 if (scrollingElementStyle.scrollSnapPointsX() && !scrollSnapPointsXUsesElements && canComputeHorizontalOffsets) {
170 for (auto& snapLength : scrollingElementStyle.scrollSnapPointsX()->offsets)
171 horizontalSnapOffsetSubsequence.append(valueForLength(snapLength, viewWidth));
174 if (scrollingElementStyle.scrollSnapPointsY() && !scrollSnapPointsYUsesElements && canComputeVerticalOffsets) {
175 for (auto& snapLength : scrollingElementStyle.scrollSnapPointsY()->offsets)
176 verticalSnapOffsetSubsequence.append(valueForLength(snapLength, viewHeight));
179 if (canComputeHorizontalOffsets) {
180 auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
181 updateFromStyle(*horizontalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Horizontal, viewWidth, scrollWidth, horizontalSnapOffsetSubsequence);
182 scrollableArea.setHorizontalSnapOffsets(WTF::move(horizontalSnapOffsets));
184 if (canComputeVerticalOffsets) {
185 auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
186 updateFromStyle(*verticalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Vertical, viewHeight, scrollHeight, verticalSnapOffsetSubsequence);
187 scrollableArea.setVerticalSnapOffsets(WTF::move(verticalSnapOffsets));
191 } // namespace WebCore
193 #endif // CSS_SCROLL_SNAP