Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(435)

Side by Side Diff: third_party/WebKit/Source/core/layout/ScrollAnchor.cpp

Issue 2250523003: Implement SANACLAP (http://bit.ly/sanaclap). (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address review comments Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "core/layout/ScrollAnchor.h" 5 #include "core/layout/ScrollAnchor.h"
6 6
7 #include "core/frame/FrameView.h" 7 #include "core/frame/FrameView.h"
8 #include "core/frame/UseCounter.h" 8 #include "core/frame/UseCounter.h"
9 #include "core/layout/line/InlineTextBox.h" 9 #include "core/layout/line/InlineTextBox.h"
10 #include "core/paint/PaintLayerScrollableArea.h" 10 #include "core/paint/PaintLayerScrollableArea.h"
11 #include "platform/Histogram.h" 11 #include "platform/Histogram.h"
12 12
13 namespace blink { 13 namespace blink {
14 14
15 using Corner = ScrollAnchor::Corner; 15 using Corner = ScrollAnchor::Corner;
16 16
17 static const int kMaxAdjustments = 20;
18
19 ScrollAnchor::ScrollAnchor() 17 ScrollAnchor::ScrollAnchor()
20 : m_hasBounced(false) 18 : m_anchorObject(nullptr)
21 , m_adjustmentCount(0) 19 , m_corner(Corner::TopLeft)
20 , m_scrollAnchorDisablingStyleChanged(false)
22 { 21 {
23 } 22 }
24 23
25 ScrollAnchor::ScrollAnchor(ScrollableArea* scroller) 24 ScrollAnchor::ScrollAnchor(ScrollableArea* scroller)
26 : ScrollAnchor() 25 : ScrollAnchor()
27 { 26 {
28 setScroller(scroller); 27 setScroller(scroller);
29 } 28 }
30 29
31 ScrollAnchor::~ScrollAnchor() 30 ScrollAnchor::~ScrollAnchor()
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
112 static LayoutPoint computeRelativeOffset(const LayoutObject* layoutObject, const ScrollableArea* scroller, Corner corner) 111 static LayoutPoint computeRelativeOffset(const LayoutObject* layoutObject, const ScrollableArea* scroller, Corner corner)
113 { 112 {
114 return cornerPointOfRect(relativeBounds(layoutObject, scroller), corner); 113 return cornerPointOfRect(relativeBounds(layoutObject, scroller), corner);
115 } 114 }
116 115
117 static bool candidateMayMoveWithScroller(const LayoutObject* candidate, const Sc rollableArea* scroller) 116 static bool candidateMayMoveWithScroller(const LayoutObject* candidate, const Sc rollableArea* scroller)
118 { 117 {
119 if (const ComputedStyle* style = candidate->style()) { 118 if (const ComputedStyle* style = candidate->style()) {
120 if (style->hasViewportConstrainedPosition()) 119 if (style->hasViewportConstrainedPosition())
121 return false; 120 return false;
122
123 if (style->hasOutOfFlowPosition()) {
124 // Absolute positioned elements with non-zero scrollTop/Left/Bottom/
125 // Right can stick to the viewport.
126 if (!style->top().isZero() || !style->left().isZero()
127 || !style->bottom().isZero() || !style->right().isZero())
128 return false;
129 }
130 } 121 }
131 122
132 bool skippedByContainerLookup = false; 123 bool skippedByContainerLookup = false;
133 candidate->container(scrollerLayoutBox(scroller), &skippedByContainerLookup) ; 124 candidate->container(scrollerLayoutBox(scroller), &skippedByContainerLookup) ;
134 return !skippedByContainerLookup; 125 return !skippedByContainerLookup;
135 } 126 }
136 127
137 ScrollAnchor::ExamineResult ScrollAnchor::examine(const LayoutObject* candidate) const 128 ScrollAnchor::ExamineResult ScrollAnchor::examine(const LayoutObject* candidate) const
138 { 129 {
139 if (candidate->isLayoutInline()) 130 if (candidate->isLayoutInline())
(...skipping 29 matching lines...) Expand all
169 void ScrollAnchor::findAnchor() 160 void ScrollAnchor::findAnchor()
170 { 161 {
171 TRACE_EVENT0("blink", "ScrollAnchor::findAnchor"); 162 TRACE_EVENT0("blink", "ScrollAnchor::findAnchor");
172 SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Layout.ScrollAnchor.TimeToFindAnchor"); 163 SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Layout.ScrollAnchor.TimeToFindAnchor");
173 164
174 LayoutObject* stayWithin = scrollerLayoutBox(m_scroller); 165 LayoutObject* stayWithin = scrollerLayoutBox(m_scroller);
175 LayoutObject* candidate = stayWithin->nextInPreOrder(stayWithin); 166 LayoutObject* candidate = stayWithin->nextInPreOrder(stayWithin);
176 while (candidate) { 167 while (candidate) {
177 ExamineResult result = examine(candidate); 168 ExamineResult result = examine(candidate);
178 if (result.viable) { 169 if (result.viable) {
179 m_current.m_anchorObject = candidate; 170 m_anchorObject = candidate;
180 m_current.m_corner = result.corner; 171 m_corner = result.corner;
181 } 172 }
182 switch (result.status) { 173 switch (result.status) {
183 case Skip: 174 case Skip:
184 candidate = candidate->nextInPreOrderAfterChildren(stayWithin); 175 candidate = candidate->nextInPreOrderAfterChildren(stayWithin);
185 break; 176 break;
186 case Constrain: 177 case Constrain:
187 stayWithin = candidate; 178 stayWithin = candidate;
188 // fall through 179 // fall through
189 case Continue: 180 case Continue:
190 candidate = candidate->nextInPreOrder(stayWithin); 181 candidate = candidate->nextInPreOrder(stayWithin);
191 break; 182 break;
192 case Return: 183 case Return:
193 return; 184 return;
194 } 185 }
195 } 186 }
196 } 187 }
197 188
189 bool ScrollAnchor::computeScrollAnchorDisablingStyleChanged()
190 {
191 LayoutObject* current = anchorObject();
192 if (!current)
193 return false;
194
195 LayoutObject* scrollerBox = scrollerLayoutBox(m_scroller);
196 while (true) {
197 DCHECK(current);
198 if (current->scrollAnchorDisablingStyleChanged())
199 return true;
200 if (current == scrollerBox)
201 return false;
202 current = current->parent();
203 }
204 }
205
198 void ScrollAnchor::save() 206 void ScrollAnchor::save()
199 { 207 {
200 DCHECK(m_scroller); 208 DCHECK(m_scroller);
201 if (m_scroller->scrollPosition() == IntPoint::zero()) { 209 if (m_scroller->scrollPosition() == IntPoint::zero()) {
202 clear(); 210 clear();
203 return; 211 return;
204 } 212 }
205 213
206 // TODO(ymalik): Just because we have a previously selected anchor doesn't 214 if (!m_anchorObject) {
207 // mean that it's guaranteed to be valid. For e.g. overflow-anchor: none 215 findAnchor();
208 // may have been added after anchor selection. The SANACLAP design poposal 216 if (!m_anchorObject)
209 // may fix this (see crbug.com/637626). 217 return;
210 if (m_current)
211 return;
212 218
213 findAnchor(); 219 m_anchorObject->setIsScrollAnchorObject();
214 if (!m_current) 220 m_savedRelativeOffset = computeRelativeOffset(
215 return; 221 m_anchorObject, m_scroller, m_corner);
222 }
216 223
217 m_current.m_anchorObject->setIsScrollAnchorObject(); 224 // Note that we must compute this during save() since the scroller's
218 m_current.m_savedRelativeOffset = computeRelativeOffset( 225 // descendants have finished layout (and had the bit cleared) by the
219 m_current.m_anchorObject, m_scroller, m_current.m_corner); 226 // time restore() is called.
220 227 m_scrollAnchorDisablingStyleChanged = computeScrollAnchorDisablingStyleChang ed();
221 if (m_lastAdjusted) {
222 // We need to update m_lastAdjusted.m_savedRelativeOffset, since it is
223 // relative to the visible rect and the user may have scrolled since the
224 // last adjustment.
225 if (!candidateMayMoveWithScroller(m_lastAdjusted.m_anchorObject, m_scrol ler)) {
226 m_lastAdjusted.clear();
227 } else if (m_lastAdjusted.m_anchorObject == m_current.m_anchorObject
228 && m_lastAdjusted.m_corner == m_current.m_corner) {
229 m_lastAdjusted.m_savedRelativeOffset = m_current.m_savedRelativeOffs et;
230 } else {
231 m_lastAdjusted.m_savedRelativeOffset = computeRelativeOffset(
232 m_lastAdjusted.m_anchorObject, m_scroller, m_lastAdjusted.m_corn er);
233 }
234 }
235 } 228 }
236 229
237 IntSize ScrollAnchor::computeAdjustment(const AnchorPoint& anchorPoint) const 230 IntSize ScrollAnchor::computeAdjustment() const
238 { 231 {
239 // The anchor node can report fractional positions, but it is DIP-snapped wh en 232 // The anchor node can report fractional positions, but it is DIP-snapped wh en
240 // painting (crbug.com/610805), so we must round the offsets to determine th e 233 // painting (crbug.com/610805), so we must round the offsets to determine th e
241 // visual delta. If we scroll by the delta in LayoutUnits, the snapping of t he 234 // visual delta. If we scroll by the delta in LayoutUnits, the snapping of t he
242 // anchor node may round differently from the snapping of the scroll positio n. 235 // anchor node may round differently from the snapping of the scroll positio n.
243 // (For example, anchor moving from 2.4px -> 2.6px is really 2px -> 3px, so we 236 // (For example, anchor moving from 2.4px -> 2.6px is really 2px -> 3px, so we
244 // should scroll by 1px instead of 0.2px.) This is true regardless of whethe r 237 // should scroll by 1px instead of 0.2px.) This is true regardless of whethe r
245 // the ScrollableArea actually uses fractional scroll positions. 238 // the ScrollableArea actually uses fractional scroll positions.
246 return roundedIntSize(computeRelativeOffset( 239 return roundedIntSize(computeRelativeOffset(m_anchorObject, m_scroller, m_co rner)) -
247 anchorPoint.m_anchorObject, m_scroller, anchorPoint.m_corner)) - 240 roundedIntSize(m_savedRelativeOffset);
248 roundedIntSize(anchorPoint.m_savedRelativeOffset);
249 } 241 }
250 242
251 void ScrollAnchor::restore() 243 void ScrollAnchor::restore()
252 { 244 {
253 DCHECK(m_scroller); 245 DCHECK(m_scroller);
254 if (m_lastAdjusted && m_lastAdjusted.m_anchorObject != m_current.m_anchorObj ect 246 if (!m_anchorObject)
255 && !m_hasBounced && computeAdjustment(m_lastAdjusted) == -m_lastAdjustme nt) { 247 return;
256 // If previous anchor point has bounced, follow the bounce. 248 IntSize adjustment = computeAdjustment();
249 if (adjustment.isZero())
250 return;
251
252 if (m_scrollAnchorDisablingStyleChanged) {
253 // Note that we only clear if the adjustment would have been non-zero.
254 // This minimizes redundant calls to findAnchor.
257 clear(); 255 clear();
258 adjust(-m_lastAdjustment);
259 return; 256 return;
ymalik 2016/08/18 07:44:20 Will it be useful to have an UMA metric for the nu
skobes 2016/08/18 17:45:56 Added TODO.
260 } 257 }
261 if (!m_current) 258
262 return; 259 adjust(adjustment);
ymalik 2016/08/18 07:44:20 nit: we don't seem to be using adjust elsewhere an
skobes 2016/08/18 17:45:56 Done.
263 IntSize adjustment = computeAdjustment(m_current);
264 if (adjustment.isZero())
265 return;
266 if (adjustment == -m_lastAdjustment && m_hasBounced) {
267 // Don't bounce more than once.
268 clear();
269 m_hasBounced = false;
270 m_lastAdjustment = IntSize();
271 m_lastAdjusted.clear();
272 return;
273 }
274 // We impose a limit on the number of adjustments between user scrolls, to
275 // mitigate the impact of pathological feedback loops with event handlers.
276 if (++m_adjustmentCount <= kMaxAdjustments)
277 adjust(adjustment);
278 } 260 }
279 261
280 void ScrollAnchor::adjust(IntSize adjustment) 262 void ScrollAnchor::adjust(IntSize adjustment)
281 { 263 {
282 m_scroller->setScrollPosition(m_scroller->scrollPositionDouble() + adjustmen t, AnchoringScroll); 264 m_scroller->setScrollPosition(m_scroller->scrollPositionDouble() + adjustmen t, AnchoringScroll);
283 265
284 if (m_current && m_lastAdjusted.m_anchorObject != m_current.m_anchorObject) {
285 m_lastAdjusted.clear();
286 m_lastAdjusted = m_current;
287 }
288 m_hasBounced = (m_lastAdjustment == -adjustment);
289 m_lastAdjustment = adjustment;
290
291 // Update UMA metric. 266 // Update UMA metric.
292 DEFINE_STATIC_LOCAL(EnumerationHistogram, adjustedOffsetHistogram, 267 DEFINE_STATIC_LOCAL(EnumerationHistogram, adjustedOffsetHistogram,
293 ("Layout.ScrollAnchor.AdjustedScrollOffset", 2)); 268 ("Layout.ScrollAnchor.AdjustedScrollOffset", 2));
294 adjustedOffsetHistogram.count(1); 269 adjustedOffsetHistogram.count(1);
295 UseCounter::count(scrollerLayoutBox(m_scroller)->document(), UseCounter::Scr ollAnchored); 270 UseCounter::count(scrollerLayoutBox(m_scroller)->document(), UseCounter::Scr ollAnchored);
296 } 271 }
297 272
298 void ScrollAnchor::clear() 273 void ScrollAnchor::clear()
299 { 274 {
300 m_adjustmentCount = 0;
301 m_current.clear();
302 }
303
304 void ScrollAnchor::AnchorPoint::clear()
305 {
306 LayoutObject* anchorObject = m_anchorObject; 275 LayoutObject* anchorObject = m_anchorObject;
307 m_anchorObject = nullptr; 276 m_anchorObject = nullptr;
308 277
309 if (anchorObject) 278 if (anchorObject)
310 anchorObject->maybeClearIsScrollAnchorObject(); 279 anchorObject->maybeClearIsScrollAnchorObject();
311 } 280 }
312 281
313 bool ScrollAnchor::refersTo(const LayoutObject* layoutObject) const 282 bool ScrollAnchor::refersTo(const LayoutObject* layoutObject) const
314 { 283 {
315 return m_current.m_anchorObject == layoutObject 284 return m_anchorObject == layoutObject;
316 || m_lastAdjusted.m_anchorObject == layoutObject;
317 } 285 }
318 286
319 void ScrollAnchor::notifyRemoved(LayoutObject* layoutObject) 287 void ScrollAnchor::notifyRemoved(LayoutObject* layoutObject)
320 { 288 {
321 if (m_current.m_anchorObject == layoutObject) 289 if (m_anchorObject == layoutObject)
322 m_current.clear(); 290 clear();
323 if (m_lastAdjusted.m_anchorObject == layoutObject)
324 m_lastAdjusted.clear();
325 } 291 }
326 292
327 } // namespace blink 293 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/layout/ScrollAnchor.h ('k') | third_party/WebKit/Source/core/layout/ScrollAnchorTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698