| 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/LayoutView.h" | 9 #include "core/layout/LayoutView.h" |
| 10 #include "core/layout/line/InlineTextBox.h" | 10 #include "core/layout/line/InlineTextBox.h" |
| 11 #include "core/paint/PaintLayerScrollableArea.h" | 11 #include "core/paint/PaintLayerScrollableArea.h" |
| 12 #include "platform/Histogram.h" | 12 #include "platform/Histogram.h" |
| 13 | 13 |
| 14 namespace blink { | 14 namespace blink { |
| 15 | 15 |
| 16 using Corner = ScrollAnchor::Corner; | 16 using Corner = ScrollAnchor::Corner; |
| 17 | 17 |
| 18 ScrollAnchor::ScrollAnchor(ScrollableArea* scroller) | 18 ScrollAnchor::ScrollAnchor(ScrollableArea* scroller) |
| 19 : m_scroller(scroller) | 19 : m_scroller(scroller) |
| 20 , m_anchorObject(nullptr) | 20 , m_hasBounced(false) |
| 21 , m_corner(Corner::TopLeft) | |
| 22 { | 21 { |
| 23 ASSERT(m_scroller); | 22 ASSERT(m_scroller); |
| 24 ASSERT(m_scroller->isFrameView() || m_scroller->isPaintLayerScrollableArea()
); | 23 ASSERT(m_scroller->isFrameView() || m_scroller->isPaintLayerScrollableArea()
); |
| 25 } | 24 } |
| 26 | 25 |
| 27 ScrollAnchor::~ScrollAnchor() | 26 ScrollAnchor::~ScrollAnchor() |
| 28 { | 27 { |
| 29 } | 28 } |
| 30 | 29 |
| 31 // TODO(pilgrim) replace all instances of scrollerLayoutBox with scrollerLayoutB
oxItem | 30 // TODO(pilgrim) replace all instances of scrollerLayoutBox with scrollerLayoutB
oxItem |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 void ScrollAnchor::findAnchor() | 142 void ScrollAnchor::findAnchor() |
| 144 { | 143 { |
| 145 TRACE_EVENT0("blink", "ScrollAnchor::findAnchor"); | 144 TRACE_EVENT0("blink", "ScrollAnchor::findAnchor"); |
| 146 SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Layout.ScrollAnchor.TimeToFindAnchor"); | 145 SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Layout.ScrollAnchor.TimeToFindAnchor"); |
| 147 | 146 |
| 148 LayoutObject* stayWithin = scrollerLayoutBox(m_scroller); | 147 LayoutObject* stayWithin = scrollerLayoutBox(m_scroller); |
| 149 LayoutObject* candidate = stayWithin->nextInPreOrder(stayWithin); | 148 LayoutObject* candidate = stayWithin->nextInPreOrder(stayWithin); |
| 150 while (candidate) { | 149 while (candidate) { |
| 151 ExamineResult result = examine(candidate); | 150 ExamineResult result = examine(candidate); |
| 152 if (result.viable) { | 151 if (result.viable) { |
| 153 m_anchorObject = candidate; | 152 m_current.m_anchorObject = candidate; |
| 154 m_corner = result.corner; | 153 m_current.m_corner = result.corner; |
| 155 } | 154 } |
| 156 switch (result.status) { | 155 switch (result.status) { |
| 157 case Skip: | 156 case Skip: |
| 158 candidate = candidate->nextInPreOrderAfterChildren(stayWithin); | 157 candidate = candidate->nextInPreOrderAfterChildren(stayWithin); |
| 159 break; | 158 break; |
| 160 case Constrain: | 159 case Constrain: |
| 161 stayWithin = candidate; | 160 stayWithin = candidate; |
| 162 // fall through | 161 // fall through |
| 163 case Continue: | 162 case Continue: |
| 164 candidate = candidate->nextInPreOrder(stayWithin); | 163 candidate = candidate->nextInPreOrder(stayWithin); |
| 165 break; | 164 break; |
| 166 case Return: | 165 case Return: |
| 167 return; | 166 return; |
| 168 } | 167 } |
| 169 } | 168 } |
| 170 } | 169 } |
| 171 | 170 |
| 172 void ScrollAnchor::save() | 171 void ScrollAnchor::save() |
| 173 { | 172 { |
| 174 if (m_scroller->scrollPosition() == IntPoint::zero()) { | 173 if (m_scroller->scrollPosition() == IntPoint::zero()) { |
| 175 clear(); | 174 clear(); |
| 176 return; | 175 return; |
| 177 } | 176 } |
| 178 if (m_anchorObject) | 177 if (m_current) |
| 179 return; | 178 return; |
| 180 | 179 |
| 181 findAnchor(); | 180 findAnchor(); |
| 182 if (!m_anchorObject) | 181 if (!m_current) |
| 183 return; | 182 return; |
| 184 | 183 |
| 185 m_anchorObject->setIsScrollAnchorObject(); | 184 m_current.m_anchorObject->setIsScrollAnchorObject(); |
| 186 m_savedRelativeOffset = computeRelativeOffset(m_anchorObject, m_scroller, m_
corner); | 185 m_current.m_savedRelativeOffset = computeRelativeOffset( |
| 186 m_current.m_anchorObject, m_scroller, m_current.m_corner); |
| 187 |
| 188 if (m_lastAdjusted) { |
| 189 // We need to update m_lastAdjusted.m_savedRelativeOffset, since it is |
| 190 // relative to the visible rect and the user may have scrolled since the |
| 191 // last adjustment. |
| 192 if (!candidateMovesWithScroller(m_lastAdjusted.m_anchorObject, m_scrolle
r)) { |
| 193 m_lastAdjusted.clear(); |
| 194 } else if (m_lastAdjusted.m_anchorObject == m_current.m_anchorObject |
| 195 && m_lastAdjusted.m_corner == m_current.m_corner) { |
| 196 m_lastAdjusted.m_savedRelativeOffset = m_current.m_savedRelativeOffs
et; |
| 197 } else { |
| 198 m_lastAdjusted.m_savedRelativeOffset = computeRelativeOffset( |
| 199 m_lastAdjusted.m_anchorObject, m_scroller, m_lastAdjusted.m_corn
er); |
| 200 } |
| 201 } |
| 187 } | 202 } |
| 188 | 203 |
| 189 void ScrollAnchor::restore() | 204 IntSize ScrollAnchor::computeAdjustment(const AnchorPoint& anchorPoint) const |
| 190 { | 205 { |
| 191 if (!m_anchorObject) | |
| 192 return; | |
| 193 | |
| 194 // The anchor node can report fractional positions, but it is DIP-snapped wh
en | 206 // The anchor node can report fractional positions, but it is DIP-snapped wh
en |
| 195 // painting (crbug.com/610805), so we must round the offsets to determine th
e | 207 // painting (crbug.com/610805), so we must round the offsets to determine th
e |
| 196 // visual delta. If we scroll by the delta in LayoutUnits, the snapping of t
he | 208 // visual delta. If we scroll by the delta in LayoutUnits, the snapping of t
he |
| 197 // anchor node may round differently from the snapping of the scroll positio
n. | 209 // anchor node may round differently from the snapping of the scroll positio
n. |
| 198 // (For example, anchor moving from 2.4px -> 2.6px is really 2px -> 3px, so
we | 210 // (For example, anchor moving from 2.4px -> 2.6px is really 2px -> 3px, so
we |
| 199 // should scroll by 1px instead of 0.2px.) This is true regardless of whethe
r | 211 // should scroll by 1px instead of 0.2px.) This is true regardless of whethe
r |
| 200 // the ScrollableArea actually uses fractional scroll positions. | 212 // the ScrollableArea actually uses fractional scroll positions. |
| 201 IntSize adjustment = | 213 return roundedIntSize(computeRelativeOffset( |
| 202 roundedIntSize(computeRelativeOffset(m_anchorObject, m_scroller, m_corne
r)) - | 214 anchorPoint.m_anchorObject, m_scroller, anchorPoint.m_corner)) - |
| 203 roundedIntSize(m_savedRelativeOffset); | 215 roundedIntSize(anchorPoint.m_savedRelativeOffset); |
| 204 if (!adjustment.isZero()) { | 216 } |
| 205 DoublePoint desiredPos = m_scroller->scrollPositionDouble() + adjustment
; | 217 |
| 206 ScrollAnimatorBase* animator = m_scroller->existingScrollAnimator(); | 218 void ScrollAnchor::restore() |
| 207 if (!animator || !animator->hasRunningAnimation()) { | 219 { |
| 208 m_scroller->setScrollPosition(desiredPos, AnchoringScroll); | 220 if (m_lastAdjusted && m_lastAdjusted.m_anchorObject != m_current.m_anchorObj
ect |
| 209 } else { | 221 && !m_hasBounced && computeAdjustment(m_lastAdjusted) == -m_lastAdjustme
nt) { |
| 210 // If in the middle of a scroll animation, stop the animation, make | 222 // If previous anchor point has bounced, follow the bounce. |
| 211 // the adjustment, and continue the animation on the pending delta. | 223 clear(); |
| 212 FloatSize pendingDelta = animator->desiredTargetPosition() - FloatPo
int(m_scroller->scrollPositionDouble()); | 224 adjust(-m_lastAdjustment); |
| 213 animator->cancelAnimation(); | 225 return; |
| 214 m_scroller->setScrollPosition(desiredPos, AnchoringScroll); | |
| 215 animator->userScroll(ScrollByPixel, pendingDelta); | |
| 216 } | |
| 217 // Update UMA metric. | |
| 218 DEFINE_STATIC_LOCAL(EnumerationHistogram, adjustedOffsetHistogram, | |
| 219 ("Layout.ScrollAnchor.AdjustedScrollOffset", 2)); | |
| 220 adjustedOffsetHistogram.count(1); | |
| 221 UseCounter::count(scrollerLayoutBox(m_scroller)->document(), UseCounter:
:ScrollAnchored); | |
| 222 } | 226 } |
| 227 if (!m_current) |
| 228 return; |
| 229 IntSize adjustment = computeAdjustment(m_current); |
| 230 if (adjustment.isZero()) |
| 231 return; |
| 232 if (adjustment == -m_lastAdjustment && m_hasBounced) { |
| 233 // Don't bounce more than once. |
| 234 clear(); |
| 235 m_hasBounced = false; |
| 236 m_lastAdjustment = IntSize(); |
| 237 m_lastAdjusted.clear(); |
| 238 return; |
| 239 } |
| 240 adjust(adjustment); |
| 241 } |
| 242 |
| 243 void ScrollAnchor::adjust(IntSize adjustment) |
| 244 { |
| 245 DoublePoint desiredPos = m_scroller->scrollPositionDouble() + adjustment; |
| 246 ScrollAnimatorBase* animator = m_scroller->existingScrollAnimator(); |
| 247 if (!animator || !animator->hasRunningAnimation()) { |
| 248 m_scroller->setScrollPosition(desiredPos, AnchoringScroll); |
| 249 } else { |
| 250 // If in the middle of a scroll animation, stop the animation, make |
| 251 // the adjustment, and continue the animation on the pending delta. |
| 252 // TODO(skobes): This is not quite right, we are starting a new curve wi
thout |
| 253 // saving our progress on the existing curve. |
| 254 FloatSize pendingDelta = animator->desiredTargetPosition() - |
| 255 FloatPoint(m_scroller->scrollPositionDouble()); |
| 256 animator->cancelAnimation(); |
| 257 m_scroller->setScrollPosition(desiredPos, AnchoringScroll); |
| 258 animator->userScroll(ScrollByPixel, pendingDelta); |
| 259 } |
| 260 |
| 261 if (m_current && m_lastAdjusted.m_anchorObject != m_current.m_anchorObject)
{ |
| 262 m_lastAdjusted.clear(); |
| 263 m_lastAdjusted = m_current; |
| 264 } |
| 265 m_hasBounced = (m_lastAdjustment == -adjustment); |
| 266 m_lastAdjustment = adjustment; |
| 267 |
| 268 // Update UMA metric. |
| 269 DEFINE_STATIC_LOCAL(EnumerationHistogram, adjustedOffsetHistogram, |
| 270 ("Layout.ScrollAnchor.AdjustedScrollOffset", 2)); |
| 271 adjustedOffsetHistogram.count(1); |
| 272 UseCounter::count(scrollerLayoutBox(m_scroller)->document(), UseCounter::Scr
ollAnchored); |
| 223 } | 273 } |
| 224 | 274 |
| 225 void ScrollAnchor::clear() | 275 void ScrollAnchor::clear() |
| 226 { | 276 { |
| 277 m_current.clear(); |
| 278 } |
| 279 |
| 280 void ScrollAnchor::AnchorPoint::clear() |
| 281 { |
| 227 LayoutObject* anchorObject = m_anchorObject; | 282 LayoutObject* anchorObject = m_anchorObject; |
| 228 m_anchorObject = nullptr; | 283 m_anchorObject = nullptr; |
| 229 | 284 |
| 230 if (anchorObject) | 285 if (anchorObject) |
| 231 anchorObject->maybeClearIsScrollAnchorObject(); | 286 anchorObject->maybeClearIsScrollAnchorObject(); |
| 232 } | 287 } |
| 233 | 288 |
| 289 bool ScrollAnchor::refersTo(const LayoutObject* layoutObject) const |
| 290 { |
| 291 return m_current.m_anchorObject == layoutObject |
| 292 || m_lastAdjusted.m_anchorObject == layoutObject; |
| 293 } |
| 294 |
| 295 void ScrollAnchor::notifyRemoved(LayoutObject* layoutObject) |
| 296 { |
| 297 if (m_current.m_anchorObject == layoutObject) |
| 298 m_current.clear(); |
| 299 if (m_lastAdjusted.m_anchorObject == layoutObject) |
| 300 m_lastAdjusted.clear(); |
| 301 } |
| 302 |
| 234 } // namespace blink | 303 } // namespace blink |
| OLD | NEW |