Chromium Code Reviews| OLD | NEW |
|---|---|
| 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_hasLayoutAffectingPropertyChanged(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 Loading... | |
| 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()) { | |
|
ojan
2016/08/16 23:54:32
I think we'll still need to exclude out of flow po
skobes
2016/08/17 18:50:32
Acknowledged. Either way we shouldn't special-cas
| |
| 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 Loading... | |
| 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::computeHasLayoutAffectingPropertyChanged() | |
|
ojan
2016/08/16 23:54:32
This is n^2 in the depth of the scrollable areas.
skobes
2016/08/17 18:50:32
I agree this is probably fine.
| |
| 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->hasLayoutAffectingStyleChanged()) | |
| 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_hasLayoutAffectingPropertyChanged = computeHasLayoutAffectingPropertyChang 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_hasLayoutAffectingPropertyChanged) { | |
| 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; |
| 260 } | 257 } |
| 261 if (!m_current) | 258 |
| 262 return; | 259 adjust(adjustment); |
| 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 |
| OLD | NEW |