1 | /* |
2 | * Copyright (C) 2011 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
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. |
12 | * |
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. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | |
28 | #include "ScrollingCoordinator.h" |
29 | |
30 | #include "Document.h" |
31 | #include "EventNames.h" |
32 | #include "Frame.h" |
33 | #include "FrameView.h" |
34 | #include "GraphicsLayer.h" |
35 | #include "Page.h" |
36 | #include "PlatformWheelEvent.h" |
37 | #include "PluginViewBase.h" |
38 | #include "Region.h" |
39 | #include "RenderLayerCompositor.h" |
40 | #include "RenderView.h" |
41 | #include "RuntimeEnabledFeatures.h" |
42 | #include "ScrollAnimator.h" |
43 | #include "Settings.h" |
44 | #include <wtf/MainThread.h> |
45 | #include <wtf/text/StringBuilder.h> |
46 | #include <wtf/text/TextStream.h> |
47 | |
48 | namespace WebCore { |
49 | |
50 | #if !PLATFORM(MAC) && !USE(COORDINATED_GRAPHICS) |
51 | Ref<ScrollingCoordinator> ScrollingCoordinator::create(Page* page) |
52 | { |
53 | return adoptRef(*new ScrollingCoordinator(page)); |
54 | } |
55 | #endif |
56 | |
57 | ScrollingCoordinator::ScrollingCoordinator(Page* page) |
58 | : m_page(page) |
59 | { |
60 | } |
61 | |
62 | ScrollingCoordinator::~ScrollingCoordinator() |
63 | { |
64 | ASSERT(!m_page); |
65 | } |
66 | |
67 | void ScrollingCoordinator::pageDestroyed() |
68 | { |
69 | ASSERT(m_page); |
70 | m_page = nullptr; |
71 | } |
72 | |
73 | bool ScrollingCoordinator::coordinatesScrollingForFrameView(const FrameView& frameView) const |
74 | { |
75 | ASSERT(isMainThread()); |
76 | ASSERT(m_page); |
77 | |
78 | if (!frameView.frame().isMainFrame() && !m_page->settings().scrollingTreeIncludesFrames() |
79 | #if PLATFORM(MAC) |
80 | && !m_page->settings().asyncFrameScrollingEnabled() |
81 | #endif |
82 | ) |
83 | return false; |
84 | |
85 | auto* renderView = frameView.frame().contentRenderer(); |
86 | if (!renderView) |
87 | return false; |
88 | return renderView->usesCompositing(); |
89 | } |
90 | |
91 | bool ScrollingCoordinator::coordinatesScrollingForOverflowLayer(const RenderLayer& layer) const |
92 | { |
93 | ASSERT(isMainThread()); |
94 | ASSERT(m_page); |
95 | |
96 | return layer.hasCompositedScrollableOverflow(); |
97 | } |
98 | |
99 | EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegionsForFrame(const Frame& frame) const |
100 | { |
101 | auto* renderView = frame.contentRenderer(); |
102 | if (!renderView || renderView->renderTreeBeingDestroyed()) |
103 | return EventTrackingRegions(); |
104 | |
105 | #if ENABLE(IOS_TOUCH_EVENTS) |
106 | // On iOS, we use nonFastScrollableRegion to represent the region covered by elements with touch event handlers. |
107 | ASSERT(frame.isMainFrame()); |
108 | auto* document = frame.document(); |
109 | if (!document) |
110 | return EventTrackingRegions(); |
111 | auto eventTrackingRegions = document->eventTrackingRegions(); |
112 | |
113 | #if ENABLE(POINTER_EVENTS) |
114 | if (!document->quirks().shouldDisablePointerEventsQuirk() && RuntimeEnabledFeatures::sharedFeatures().pointerEventsEnabled()) { |
115 | if (auto* touchActionElements = frame.document()->touchActionElements()) { |
116 | auto& touchActionData = eventTrackingRegions.touchActionData; |
117 | for (const auto& element : *touchActionElements) { |
118 | ASSERT(element); |
119 | touchActionData.append({ |
120 | element->computedTouchActions(), |
121 | element->nearestScrollingNodeIDUsingTouchOverflowScrolling(), |
122 | element->document().absoluteEventRegionForNode(*element).first |
123 | }); |
124 | } |
125 | } |
126 | } |
127 | #endif |
128 | |
129 | return eventTrackingRegions; |
130 | #else |
131 | auto* frameView = frame.view(); |
132 | if (!frameView) |
133 | return EventTrackingRegions(); |
134 | |
135 | Region nonFastScrollableRegion; |
136 | |
137 | // FIXME: should ASSERT(!frameView->needsLayout()) here, but need to fix DebugPageOverlays |
138 | // to not ask for regions at bad times. |
139 | |
140 | if (auto* scrollableAreas = frameView->scrollableAreas()) { |
141 | for (auto& scrollableArea : *scrollableAreas) { |
142 | // Composited scrollable areas can be scrolled off the main thread. |
143 | if (scrollableArea->usesAsyncScrolling()) |
144 | continue; |
145 | |
146 | bool isInsideFixed; |
147 | IntRect box = scrollableArea->scrollableAreaBoundingBox(&isInsideFixed); |
148 | if (isInsideFixed) |
149 | box = IntRect(frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(box))); |
150 | |
151 | nonFastScrollableRegion.unite(box); |
152 | } |
153 | } |
154 | |
155 | for (auto& widget : frameView->widgetsInRenderTree()) { |
156 | if (!is<PluginViewBase>(*widget)) |
157 | continue; |
158 | if (!downcast<PluginViewBase>(*widget).wantsWheelEvents()) |
159 | continue; |
160 | auto* renderWidget = RenderWidget::find(*widget); |
161 | if (!renderWidget) |
162 | continue; |
163 | nonFastScrollableRegion.unite(renderWidget->absoluteBoundingBoxRect()); |
164 | } |
165 | |
166 | EventTrackingRegions eventTrackingRegions; |
167 | |
168 | // FIXME: if we've already accounted for this subframe as a scrollable area, we can avoid recursing into it here. |
169 | for (auto* subframe = frame.tree().firstChild(); subframe; subframe = subframe->tree().nextSibling()) { |
170 | auto* subframeView = subframe->view(); |
171 | if (!subframeView) |
172 | continue; |
173 | |
174 | EventTrackingRegions subframeRegion = absoluteEventTrackingRegionsForFrame(*subframe); |
175 | // Map from the frame document to our document. |
176 | IntPoint offset = subframeView->contentsToContainingViewContents(IntPoint()); |
177 | |
178 | // FIXME: this translation ignores non-trival transforms on the frame. |
179 | subframeRegion.translate(toIntSize(offset)); |
180 | eventTrackingRegions.unite(subframeRegion); |
181 | } |
182 | |
183 | auto wheelHandlerRegion = frame.document()->absoluteRegionForEventTargets(frame.document()->wheelEventTargets()); |
184 | bool wheelHandlerInFixedContent = wheelHandlerRegion.second; |
185 | if (wheelHandlerInFixedContent) { |
186 | // FIXME: need to handle position:sticky here too. |
187 | LayoutRect inflatedWheelHandlerBounds = frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(wheelHandlerRegion.first.bounds())); |
188 | wheelHandlerRegion.first.unite(enclosingIntRect(inflatedWheelHandlerBounds)); |
189 | } |
190 | |
191 | nonFastScrollableRegion.unite(wheelHandlerRegion.first); |
192 | |
193 | // FIXME: If this is not the main frame, we could clip the region to the frame's bounds. |
194 | eventTrackingRegions.uniteSynchronousRegion(eventNames().wheelEvent, nonFastScrollableRegion); |
195 | |
196 | return eventTrackingRegions; |
197 | #endif |
198 | } |
199 | |
200 | EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegions() const |
201 | { |
202 | return absoluteEventTrackingRegionsForFrame(m_page->mainFrame()); |
203 | } |
204 | |
205 | void ScrollingCoordinator::frameViewHasSlowRepaintObjectsDidChange(FrameView& frameView) |
206 | { |
207 | ASSERT(isMainThread()); |
208 | ASSERT(m_page); |
209 | |
210 | if (!coordinatesScrollingForFrameView(frameView)) |
211 | return; |
212 | |
213 | updateSynchronousScrollingReasons(frameView); |
214 | } |
215 | |
216 | void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView& frameView) |
217 | { |
218 | ASSERT(isMainThread()); |
219 | ASSERT(m_page); |
220 | |
221 | if (!coordinatesScrollingForFrameView(frameView)) |
222 | return; |
223 | |
224 | updateSynchronousScrollingReasons(frameView); |
225 | } |
226 | |
227 | GraphicsLayer* ScrollingCoordinator::scrollContainerLayerForFrameView(FrameView& frameView) |
228 | { |
229 | if (auto* renderView = frameView.frame().contentRenderer()) |
230 | return renderView->compositor().scrollContainerLayer(); |
231 | return nullptr; |
232 | } |
233 | |
234 | GraphicsLayer* ScrollingCoordinator::scrolledContentsLayerForFrameView(FrameView& frameView) |
235 | { |
236 | if (auto* renderView = frameView.frame().contentRenderer()) |
237 | return renderView->compositor().scrolledContentsLayer(); |
238 | return nullptr; |
239 | } |
240 | |
241 | GraphicsLayer* ScrollingCoordinator::(FrameView& frameView) |
242 | { |
243 | #if ENABLE(RUBBER_BANDING) |
244 | if (auto* renderView = frameView.frame().contentRenderer()) |
245 | return renderView->compositor().headerLayer(); |
246 | return nullptr; |
247 | #else |
248 | UNUSED_PARAM(frameView); |
249 | return nullptr; |
250 | #endif |
251 | } |
252 | |
253 | GraphicsLayer* ScrollingCoordinator::(FrameView& frameView) |
254 | { |
255 | #if ENABLE(RUBBER_BANDING) |
256 | if (auto* renderView = frameView.frame().contentRenderer()) |
257 | return renderView->compositor().footerLayer(); |
258 | return nullptr; |
259 | #else |
260 | UNUSED_PARAM(frameView); |
261 | return nullptr; |
262 | #endif |
263 | } |
264 | |
265 | GraphicsLayer* ScrollingCoordinator::counterScrollingLayerForFrameView(FrameView& frameView) |
266 | { |
267 | if (auto* renderView = frameView.frame().contentRenderer()) |
268 | return renderView->compositor().fixedRootBackgroundLayer(); |
269 | return nullptr; |
270 | } |
271 | |
272 | GraphicsLayer* ScrollingCoordinator::insetClipLayerForFrameView(FrameView& frameView) |
273 | { |
274 | if (auto* renderView = frameView.frame().contentRenderer()) |
275 | return renderView->compositor().clipLayer(); |
276 | return nullptr; |
277 | } |
278 | |
279 | GraphicsLayer* ScrollingCoordinator::contentShadowLayerForFrameView(FrameView& frameView) |
280 | { |
281 | #if ENABLE(RUBBER_BANDING) |
282 | if (auto* renderView = frameView.frame().contentRenderer()) |
283 | return renderView->compositor().layerForContentShadow(); |
284 | |
285 | return nullptr; |
286 | #else |
287 | UNUSED_PARAM(frameView); |
288 | return nullptr; |
289 | #endif |
290 | } |
291 | |
292 | GraphicsLayer* ScrollingCoordinator::rootContentsLayerForFrameView(FrameView& frameView) |
293 | { |
294 | if (auto* renderView = frameView.frame().contentRenderer()) |
295 | return renderView->compositor().rootContentsLayer(); |
296 | return nullptr; |
297 | } |
298 | |
299 | void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView& frameView) |
300 | { |
301 | ASSERT(isMainThread()); |
302 | ASSERT(m_page); |
303 | |
304 | if (!coordinatesScrollingForFrameView(frameView)) |
305 | return; |
306 | |
307 | frameViewLayoutUpdated(frameView); |
308 | updateSynchronousScrollingReasons(frameView); |
309 | } |
310 | |
311 | #if PLATFORM(COCOA) |
312 | void ScrollingCoordinator::handleWheelEventPhase(PlatformWheelEventPhase phase) |
313 | { |
314 | ASSERT(isMainThread()); |
315 | |
316 | if (!m_page) |
317 | return; |
318 | |
319 | auto* frameView = m_page->mainFrame().view(); |
320 | if (!frameView) |
321 | return; |
322 | |
323 | frameView->scrollAnimator().handleWheelEventPhase(phase); |
324 | } |
325 | #endif |
326 | |
327 | bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(const FrameView& frameView) const |
328 | { |
329 | const FrameView::ViewportConstrainedObjectSet* viewportConstrainedObjects = frameView.viewportConstrainedObjects(); |
330 | if (!viewportConstrainedObjects) |
331 | return false; |
332 | |
333 | for (auto& viewportConstrainedObject : *viewportConstrainedObjects) { |
334 | if (!is<RenderBoxModelObject>(*viewportConstrainedObject) || !viewportConstrainedObject->hasLayer()) |
335 | return true; |
336 | auto& layer = *downcast<RenderBoxModelObject>(*viewportConstrainedObject).layer(); |
337 | // Any explicit reason that a fixed position element is not composited shouldn't cause slow scrolling. |
338 | if (!layer.isComposited() && layer.viewportConstrainedNotCompositedReason() == RenderLayer::NoNotCompositedReason) |
339 | return true; |
340 | } |
341 | return false; |
342 | } |
343 | |
344 | SynchronousScrollingReasons ScrollingCoordinator::synchronousScrollingReasons(const FrameView& frameView) const |
345 | { |
346 | SynchronousScrollingReasons synchronousScrollingReasons = (SynchronousScrollingReasons)0; |
347 | |
348 | if (m_forceSynchronousScrollLayerPositionUpdates) |
349 | synchronousScrollingReasons |= ForcedOnMainThread; |
350 | if (frameView.hasSlowRepaintObjects()) |
351 | synchronousScrollingReasons |= HasSlowRepaintObjects; |
352 | if (hasVisibleSlowRepaintViewportConstrainedObjects(frameView)) |
353 | synchronousScrollingReasons |= HasNonLayerViewportConstrainedObjects; |
354 | if (frameView.frame().mainFrame().document() && frameView.frame().document()->isImageDocument()) |
355 | synchronousScrollingReasons |= IsImageDocument; |
356 | |
357 | return synchronousScrollingReasons; |
358 | } |
359 | |
360 | void ScrollingCoordinator::updateSynchronousScrollingReasons(FrameView& frameView) |
361 | { |
362 | ASSERT(coordinatesScrollingForFrameView(frameView)); |
363 | setSynchronousScrollingReasons(frameView, synchronousScrollingReasons(frameView)); |
364 | } |
365 | |
366 | void ScrollingCoordinator::updateSynchronousScrollingReasonsForAllFrames() |
367 | { |
368 | for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { |
369 | if (auto* frameView = frame->view()) { |
370 | if (coordinatesScrollingForFrameView(*frameView)) |
371 | updateSynchronousScrollingReasons(*frameView); |
372 | } |
373 | } |
374 | } |
375 | |
376 | void ScrollingCoordinator::setForceSynchronousScrollLayerPositionUpdates(bool forceSynchronousScrollLayerPositionUpdates) |
377 | { |
378 | if (m_forceSynchronousScrollLayerPositionUpdates == forceSynchronousScrollLayerPositionUpdates) |
379 | return; |
380 | |
381 | m_forceSynchronousScrollLayerPositionUpdates = forceSynchronousScrollLayerPositionUpdates; |
382 | updateSynchronousScrollingReasonsForAllFrames(); |
383 | } |
384 | |
385 | bool ScrollingCoordinator::shouldUpdateScrollLayerPositionSynchronously(const FrameView& frameView) const |
386 | { |
387 | if (&frameView == m_page->mainFrame().view()) |
388 | return synchronousScrollingReasons(frameView); |
389 | |
390 | return true; |
391 | } |
392 | |
393 | ScrollingNodeID ScrollingCoordinator::uniqueScrollingNodeID() |
394 | { |
395 | static ScrollingNodeID uniqueScrollingNodeID = 1; |
396 | return uniqueScrollingNodeID++; |
397 | } |
398 | |
399 | String ScrollingCoordinator::scrollingStateTreeAsText(ScrollingStateTreeAsTextBehavior) const |
400 | { |
401 | return String(); |
402 | } |
403 | |
404 | String ScrollingCoordinator::synchronousScrollingReasonsAsText(SynchronousScrollingReasons reasons) |
405 | { |
406 | StringBuilder stringBuilder; |
407 | |
408 | if (reasons & ScrollingCoordinator::ForcedOnMainThread) |
409 | stringBuilder.appendLiteral("Forced on main thread, " ); |
410 | if (reasons & ScrollingCoordinator::HasSlowRepaintObjects) |
411 | stringBuilder.appendLiteral("Has slow repaint objects, " ); |
412 | if (reasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers) |
413 | stringBuilder.appendLiteral("Has viewport constrained objects without supporting fixed layers, " ); |
414 | if (reasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects) |
415 | stringBuilder.appendLiteral("Has non-layer viewport-constrained objects, " ); |
416 | if (reasons & ScrollingCoordinator::IsImageDocument) |
417 | stringBuilder.appendLiteral("Is image document, " ); |
418 | |
419 | if (stringBuilder.length()) |
420 | stringBuilder.resize(stringBuilder.length() - 2); |
421 | return stringBuilder.toString(); |
422 | } |
423 | |
424 | String ScrollingCoordinator::synchronousScrollingReasonsAsText() const |
425 | { |
426 | if (auto* frameView = m_page->mainFrame().view()) |
427 | return synchronousScrollingReasonsAsText(synchronousScrollingReasons(*frameView)); |
428 | |
429 | return String(); |
430 | } |
431 | |
432 | TextStream& operator<<(TextStream& ts, ScrollableAreaParameters scrollableAreaParameters) |
433 | { |
434 | ts.dumpProperty("horizontal scroll elasticity" , scrollableAreaParameters.horizontalScrollElasticity); |
435 | ts.dumpProperty("vertical scroll elasticity" , scrollableAreaParameters.verticalScrollElasticity); |
436 | ts.dumpProperty("horizontal scrollbar mode" , scrollableAreaParameters.horizontalScrollbarMode); |
437 | ts.dumpProperty("vertical scrollbar mode" , scrollableAreaParameters.verticalScrollbarMode); |
438 | |
439 | if (scrollableAreaParameters.hasEnabledHorizontalScrollbar) |
440 | ts.dumpProperty("has enabled horizontal scrollbar" , scrollableAreaParameters.hasEnabledHorizontalScrollbar); |
441 | if (scrollableAreaParameters.hasEnabledVerticalScrollbar) |
442 | ts.dumpProperty("has enabled vertical scrollbar" , scrollableAreaParameters.hasEnabledVerticalScrollbar); |
443 | |
444 | if (scrollableAreaParameters.horizontalScrollbarHiddenByStyle) |
445 | ts.dumpProperty("horizontal scrollbar hidden by style" , scrollableAreaParameters.horizontalScrollbarHiddenByStyle); |
446 | if (scrollableAreaParameters.verticalScrollbarHiddenByStyle) |
447 | ts.dumpProperty("vertical scrollbar hidden by style" , scrollableAreaParameters.verticalScrollbarHiddenByStyle); |
448 | |
449 | return ts; |
450 | } |
451 | |
452 | TextStream& operator<<(TextStream& ts, ScrollingNodeType nodeType) |
453 | { |
454 | switch (nodeType) { |
455 | case ScrollingNodeType::MainFrame: |
456 | ts << "main-frame-scrolling" ; |
457 | break; |
458 | case ScrollingNodeType::Subframe: |
459 | ts << "subframe-scrolling" ; |
460 | break; |
461 | case ScrollingNodeType::FrameHosting: |
462 | ts << "frame-hosting" ; |
463 | break; |
464 | case ScrollingNodeType::Overflow: |
465 | ts << "overflow-scrolling" ; |
466 | break; |
467 | case ScrollingNodeType::Fixed: |
468 | ts << "fixed" ; |
469 | break; |
470 | case ScrollingNodeType::Sticky: |
471 | ts << "sticky" ; |
472 | break; |
473 | case ScrollingNodeType::Positioned: |
474 | ts << "positioned" ; |
475 | break; |
476 | } |
477 | return ts; |
478 | } |
479 | |
480 | TextStream& operator<<(TextStream& ts, ScrollingLayerPositionAction action) |
481 | { |
482 | switch (action) { |
483 | case ScrollingLayerPositionAction::Set: |
484 | ts << "set" ; |
485 | break; |
486 | case ScrollingLayerPositionAction::SetApproximate: |
487 | ts << "set approximate" ; |
488 | break; |
489 | case ScrollingLayerPositionAction::Sync: |
490 | ts << "sync" ; |
491 | break; |
492 | } |
493 | return ts; |
494 | } |
495 | |
496 | TextStream& operator<<(TextStream& ts, ViewportRectStability stability) |
497 | { |
498 | switch (stability) { |
499 | case ViewportRectStability::Stable: |
500 | ts << "stable" ; |
501 | break; |
502 | case ViewportRectStability::Unstable: |
503 | ts << "unstable" ; |
504 | break; |
505 | case ViewportRectStability::ChangingObscuredInsetsInteractively: |
506 | ts << "changing obscured insets interactively" ; |
507 | break; |
508 | } |
509 | return ts; |
510 | } |
511 | |
512 | TextStream& operator<<(TextStream& ts, ScrollType scrollType) |
513 | { |
514 | switch (scrollType) { |
515 | case ScrollType::User: ts << "user" ; break; |
516 | case ScrollType::Programmatic: ts << "programmatic" ; break; |
517 | } |
518 | return ts; |
519 | } |
520 | |
521 | } // namespace WebCore |
522 | |