[ContentChangeObserver] Content observation should be limited to the current document.
[WebKit-https.git] / Source / WebCore / rendering / GridBaselineAlignment.cpp
1 /*
2  * Copyright (C) 2018 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "GridBaselineAlignment.h"
33
34 #include "RenderStyle.h"
35
36 namespace WebCore {
37
38 // This function gives the margin 'over' based on the baseline-axis, since in grid we can have 2-dimensional
39 // alignment by baseline. In horizontal writing-mode, the row-axis is the horizontal axis. When we use this
40 // axis to move the grid items so that they are baseline-aligned, we want their "horizontal" margin (right);
41 // the same will happen when using the column-axis under vertical writing mode, we also want in this case the
42 // 'right' margin.
43 LayoutUnit GridBaselineAlignment::marginOverForChild(const RenderBox& child, GridAxis axis) const
44 {
45     return isHorizontalBaselineAxis(axis) ? child.marginRight() : child.marginTop();
46 }
47
48 // This function gives the margin 'under' based on the baseline-axis, since in grid we can can 2-dimensional
49 // alignment by baseline. In horizontal writing-mode, the row-axis is the horizontal axis. When we use this
50 // axis to move the grid items so that they are baseline-aligned, we want their "horizontal" margin (left);
51 // the same will happen when using the column-axis under vertical writing mode, we also want in this case the
52 // 'left' margin.
53 LayoutUnit GridBaselineAlignment::marginUnderForChild(const RenderBox& child, GridAxis axis) const
54 {
55     return isHorizontalBaselineAxis(axis) ? child.marginLeft() : child.marginBottom();
56 }
57
58 LayoutUnit GridBaselineAlignment::logicalAscentForChild(const RenderBox& child, GridAxis baselineAxis) const
59 {
60     LayoutUnit ascent = ascentForChild(child, baselineAxis);
61     return isDescentBaselineForChild(child, baselineAxis) ? descentForChild(child, ascent, baselineAxis) : ascent;
62 }
63
64 LayoutUnit GridBaselineAlignment::ascentForChild(const RenderBox& child, GridAxis baselineAxis) const
65 {
66     LayoutUnit margin = isDescentBaselineForChild(child, baselineAxis) ? marginUnderForChild(child, baselineAxis) : marginOverForChild(child, baselineAxis);
67     LayoutUnit baseline(isParallelToBaselineAxisForChild(child, baselineAxis) ? child.firstLineBaseline().valueOr(-1) : -1);
68     // We take border-box's under edge if no valid baseline.
69     if (baseline == -1) {
70         if (isHorizontalBaselineAxis(baselineAxis))
71             return isFlippedWritingMode(m_blockFlow) ? child.size().width().toInt() + margin : margin;
72         return child.size().height() + margin;
73     }
74     return baseline + margin;
75 }
76
77 LayoutUnit GridBaselineAlignment::descentForChild(const RenderBox& child, LayoutUnit ascent, GridAxis baselineAxis) const
78 {
79     if (isParallelToBaselineAxisForChild(child, baselineAxis))
80         return child.marginLogicalHeight() + child.logicalHeight() - ascent;
81     return child.marginLogicalWidth() + child.logicalWidth() - ascent;
82 }
83
84 bool GridBaselineAlignment::isDescentBaselineForChild(const RenderBox& child, GridAxis baselineAxis) const
85 {
86     return isHorizontalBaselineAxis(baselineAxis)
87         && ((child.style().isFlippedBlocksWritingMode() && !isFlippedWritingMode(m_blockFlow))
88             || (child.style().isFlippedLinesWritingMode() && isFlippedWritingMode(m_blockFlow)));
89 }
90
91 bool GridBaselineAlignment::isHorizontalBaselineAxis(GridAxis axis) const
92 {
93     return axis == GridRowAxis ? isHorizontalWritingMode(m_blockFlow) : !isHorizontalWritingMode(m_blockFlow);
94 }
95
96 bool GridBaselineAlignment::isOrthogonalChildForBaseline(const RenderBox& child) const
97 {
98     return isHorizontalWritingMode(m_blockFlow) != child.isHorizontalWritingMode();
99 }
100
101 bool GridBaselineAlignment::isParallelToBaselineAxisForChild(const RenderBox& child, GridAxis axis) const
102 {
103     return axis == GridColumnAxis ? !isOrthogonalChildForBaseline(child) : isOrthogonalChildForBaseline(child);
104 }
105
106 const BaselineGroup& GridBaselineAlignment::baselineGroupForChild(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis) const
107 {
108     ASSERT(isBaselinePosition(preference));
109     bool isRowAxisContext = baselineAxis == GridColumnAxis;
110     auto& contextsMap = isRowAxisContext ? m_rowAxisAlignmentContext : m_colAxisAlignmentContext;
111     auto* context = contextsMap.get(sharedContext);
112     ASSERT(context);
113     return context->sharedGroup(child, preference);
114 }
115
116 void GridBaselineAlignment::updateBaselineAlignmentContext(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis)
117 {
118     ASSERT(isBaselinePosition(preference));
119     ASSERT(!child.needsLayout());
120
121     // Determine Ascent and Descent values of this child with respect to
122     // its grid container.
123     LayoutUnit ascent = ascentForChild(child, baselineAxis);
124     LayoutUnit descent = descentForChild(child, ascent, baselineAxis);
125     if (isDescentBaselineForChild(child, baselineAxis))
126         std::swap(ascent, descent);
127
128     // Looking up for a shared alignment context perpendicular to the
129     // baseline axis.
130     bool isRowAxisContext = baselineAxis == GridColumnAxis;
131     auto& contextsMap = isRowAxisContext ? m_rowAxisAlignmentContext : m_colAxisAlignmentContext;
132     auto addResult = contextsMap.add(sharedContext, nullptr);
133
134     // Looking for a compatible baseline-sharing group.
135     if (addResult.isNewEntry)
136         addResult.iterator->value = std::make_unique<BaselineContext>(child, preference, ascent, descent);
137     else {
138         auto* context = addResult.iterator->value.get();
139         context->updateSharedGroup(child, preference, ascent, descent);
140     }
141 }
142
143 LayoutUnit GridBaselineAlignment::baselineOffsetForChild(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis) const
144 {
145     ASSERT(isBaselinePosition(preference));
146     auto& group = baselineGroupForChild(preference, sharedContext, child, baselineAxis);
147     if (group.size() > 1)
148         return group.maxAscent() - logicalAscentForChild(child, baselineAxis);
149     return LayoutUnit();
150 }
151
152 void GridBaselineAlignment::clear(GridAxis baselineAxis)
153 {
154     if (baselineAxis == GridColumnAxis)
155         m_rowAxisAlignmentContext.clear();
156     else
157         m_colAxisAlignmentContext.clear();
158 }
159
160 BaselineGroup::BaselineGroup(WritingMode blockFlow, ItemPosition childPreference)
161     : m_maxAscent(0), m_maxDescent(0), m_items()
162 {
163     m_blockFlow = blockFlow;
164     m_preference = childPreference;
165 }
166
167 void BaselineGroup::update(const RenderBox& child, LayoutUnit ascent, LayoutUnit descent)
168 {
169     if (m_items.add(&child).isNewEntry) {
170         m_maxAscent = std::max(m_maxAscent, ascent);
171         m_maxDescent = std::max(m_maxDescent, descent);
172     }
173 }
174
175 bool BaselineGroup::isOppositeBlockFlow(WritingMode blockFlow) const
176 {
177     switch (blockFlow) {
178     case WritingMode::TopToBottomWritingMode:
179         return false;
180     case WritingMode::LeftToRightWritingMode:
181         return m_blockFlow == WritingMode::RightToLeftWritingMode;
182     case WritingMode::RightToLeftWritingMode:
183         return m_blockFlow == WritingMode::LeftToRightWritingMode;
184     default:
185         ASSERT_NOT_REACHED();
186         return false;
187     }
188 }
189
190 bool BaselineGroup::isOrthogonalBlockFlow(WritingMode blockFlow) const
191 {
192     switch (blockFlow) {
193     case WritingMode::TopToBottomWritingMode:
194         return m_blockFlow != WritingMode::TopToBottomWritingMode;
195     case WritingMode::LeftToRightWritingMode:
196     case WritingMode::RightToLeftWritingMode:
197         return m_blockFlow == WritingMode::TopToBottomWritingMode;
198     default:
199         ASSERT_NOT_REACHED();
200         return false;
201     }
202 }
203
204 bool BaselineGroup::isCompatible(WritingMode childBlockFlow, ItemPosition childPreference) const
205 {
206     ASSERT(isBaselinePosition(childPreference));
207     ASSERT(size() > 0);
208     return ((m_blockFlow == childBlockFlow || isOrthogonalBlockFlow(childBlockFlow)) && m_preference == childPreference) || (isOppositeBlockFlow(childBlockFlow) && m_preference != childPreference);
209 }
210
211 BaselineContext::BaselineContext(const RenderBox& child, ItemPosition preference, LayoutUnit ascent, LayoutUnit descent)
212 {
213     ASSERT(isBaselinePosition(preference));
214     updateSharedGroup(child, preference, ascent, descent);
215 }
216
217 const BaselineGroup& BaselineContext::sharedGroup(const RenderBox& child, ItemPosition preference) const
218 {
219     ASSERT(isBaselinePosition(preference));
220     return const_cast<BaselineContext*>(this)->findCompatibleSharedGroup(child, preference);
221 }
222
223 void BaselineContext::updateSharedGroup(const RenderBox& child, ItemPosition preference, LayoutUnit ascent, LayoutUnit descent)
224 {
225     ASSERT(isBaselinePosition(preference));
226     BaselineGroup& group = findCompatibleSharedGroup(child, preference);
227     group.update(child, ascent, descent);
228 }
229
230 // FIXME: Properly implement baseline-group compatibility.
231 // See https://github.com/w3c/csswg-drafts/issues/721
232 BaselineGroup& BaselineContext::findCompatibleSharedGroup(const RenderBox& child, ItemPosition preference)
233 {
234     WritingMode blockDirection = child.style().writingMode();
235     for (auto& group : m_sharedGroups) {
236         if (group.isCompatible(blockDirection, preference))
237             return group;
238     }
239     m_sharedGroups.insert(0, BaselineGroup(blockDirection, preference));
240     return m_sharedGroups[0];
241 }
242
243 } // namespace WebCore