1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
4 * Copyright (C) 2019 Igalia S.L.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 * THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "DrawingAreaCoordinatedGraphics.h"
30
31#include "DrawingAreaProxyMessages.h"
32#include "LayerTreeHost.h"
33#include "ShareableBitmap.h"
34#include "UpdateInfo.h"
35#include "WebPage.h"
36#include "WebPageCreationParameters.h"
37#include "WebPreferencesKeys.h"
38#include <WebCore/Frame.h>
39#include <WebCore/GraphicsContext.h>
40#include <WebCore/Page.h>
41#include <WebCore/PageOverlayController.h>
42#include <WebCore/Settings.h>
43
44#if USE(GLIB_EVENT_LOOP)
45#include <wtf/glib/RunLoopSourcePriority.h>
46#endif
47
48namespace WebKit {
49using namespace WebCore;
50
51DrawingAreaCoordinatedGraphics::DrawingAreaCoordinatedGraphics(WebPage& webPage, const WebPageCreationParameters& parameters)
52 : DrawingArea(DrawingAreaTypeCoordinatedGraphics, parameters.drawingAreaIdentifier, webPage)
53 , m_exitCompositingTimer(RunLoop::main(), this, &DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode)
54 , m_discardPreviousLayerTreeHostTimer(RunLoop::main(), this, &DrawingAreaCoordinatedGraphics::discardPreviousLayerTreeHost)
55 , m_displayTimer(RunLoop::main(), this, &DrawingAreaCoordinatedGraphics::displayTimerFired)
56{
57#if USE(GLIB_EVENT_LOOP)
58 m_discardPreviousLayerTreeHostTimer.setPriority(RunLoopSourcePriority::ReleaseUnusedResourcesTimer);
59#if !PLATFORM(WPE)
60 m_displayTimer.setPriority(RunLoopSourcePriority::NonAcceleratedDrawingTimer);
61#endif
62#endif
63}
64
65DrawingAreaCoordinatedGraphics::~DrawingAreaCoordinatedGraphics()
66{
67 discardPreviousLayerTreeHost();
68 if (m_layerTreeHost)
69 m_layerTreeHost->invalidate();
70}
71
72void DrawingAreaCoordinatedGraphics::setNeedsDisplay()
73{
74 if (m_layerTreeHost) {
75 ASSERT(m_dirtyRegion.isEmpty());
76 return;
77 }
78
79 setNeedsDisplayInRect(m_webPage.bounds());
80}
81
82void DrawingAreaCoordinatedGraphics::setNeedsDisplayInRect(const IntRect& rect)
83{
84 if (m_layerTreeHost) {
85 ASSERT(m_dirtyRegion.isEmpty());
86 return;
87 }
88
89 if (!m_isPaintingEnabled)
90 return;
91
92 IntRect dirtyRect = rect;
93 dirtyRect.intersect(m_webPage.bounds());
94 if (dirtyRect.isEmpty())
95 return;
96
97 m_dirtyRegion.unite(dirtyRect);
98 scheduleDisplay();
99}
100
101void DrawingAreaCoordinatedGraphics::scroll(const IntRect& scrollRect, const IntSize& scrollDelta)
102{
103 if (!m_isPaintingEnabled)
104 return;
105
106 if (m_layerTreeHost) {
107 ASSERT(m_scrollRect.isEmpty());
108 ASSERT(m_scrollOffset.isEmpty());
109 ASSERT(m_dirtyRegion.isEmpty());
110 m_layerTreeHost->scrollNonCompositedContents(scrollRect);
111 return;
112 }
113
114 if (scrollRect.isEmpty())
115 return;
116
117 if (m_previousLayerTreeHost)
118 m_previousLayerTreeHost->scrollNonCompositedContents(scrollRect);
119
120 if (!m_scrollRect.isEmpty() && scrollRect != m_scrollRect) {
121 unsigned scrollArea = scrollRect.width() * scrollRect.height();
122 unsigned currentScrollArea = m_scrollRect.width() * m_scrollRect.height();
123
124 if (currentScrollArea >= scrollArea) {
125 // The rect being scrolled is at least as large as the rect we'd like to scroll.
126 // Go ahead and just invalidate the scroll rect.
127 setNeedsDisplayInRect(scrollRect);
128 return;
129 }
130
131 // Just repaint the entire current scroll rect, we'll scroll the new rect instead.
132 setNeedsDisplayInRect(m_scrollRect);
133 m_scrollRect = IntRect();
134 m_scrollOffset = IntSize();
135 }
136
137 // Get the part of the dirty region that is in the scroll rect.
138 Region dirtyRegionInScrollRect = intersect(scrollRect, m_dirtyRegion);
139 if (!dirtyRegionInScrollRect.isEmpty()) {
140 // There are parts of the dirty region that are inside the scroll rect.
141 // We need to subtract them from the region, move them and re-add them.
142 m_dirtyRegion.subtract(scrollRect);
143
144 // Move the dirty parts.
145 Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, scrollDelta), scrollRect);
146
147 // And add them back.
148 m_dirtyRegion.unite(movedDirtyRegionInScrollRect);
149 }
150
151 // Compute the scroll repaint region.
152 Region scrollRepaintRegion = subtract(scrollRect, translate(scrollRect, scrollDelta));
153
154 m_dirtyRegion.unite(scrollRepaintRegion);
155 scheduleDisplay();
156
157 m_scrollRect = scrollRect;
158 m_scrollOffset += scrollDelta;
159}
160
161void DrawingAreaCoordinatedGraphics::forceRepaint()
162{
163 if (m_inUpdateBackingStoreState) {
164 m_forceRepaintAfterBackingStoreStateUpdate = true;
165 return;
166 }
167 m_forceRepaintAfterBackingStoreStateUpdate = false;
168
169 if (!m_layerTreeHost) {
170 m_isWaitingForDidUpdate = false;
171 if (m_isPaintingEnabled) {
172 m_dirtyRegion = m_webPage.bounds();
173 display();
174 }
175 return;
176 }
177
178 if (m_layerTreeStateIsFrozen)
179 return;
180
181 setNeedsDisplay();
182 m_webPage.layoutIfNeeded();
183 if (!m_layerTreeHost)
184 return;
185
186 // FIXME: We need to do the same work as the layerHostDidFlushLayers function here,
187 // but clearly it doesn't make sense to call the function with that name.
188 // Consider refactoring and renaming it.
189 if (m_compositingAccordingToProxyMessages)
190 m_layerTreeHost->forceRepaint();
191 else {
192 // Call setShouldNotifyAfterNextScheduledLayerFlush(false) here to
193 // prevent layerHostDidFlushLayers() from being called a second time.
194 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(false);
195 layerHostDidFlushLayers();
196 }
197}
198
199bool DrawingAreaCoordinatedGraphics::forceRepaintAsync(CallbackID callbackID)
200{
201 if (m_layerTreeStateIsFrozen)
202 return false;
203
204 return m_layerTreeHost && m_layerTreeHost->forceRepaintAsync(callbackID);
205}
206
207void DrawingAreaCoordinatedGraphics::setLayerTreeStateIsFrozen(bool isFrozen)
208{
209 if (m_layerTreeStateIsFrozen == isFrozen)
210 return;
211
212 m_layerTreeStateIsFrozen = isFrozen;
213
214 if (m_layerTreeHost)
215 m_layerTreeHost->setLayerFlushSchedulingEnabled(!isFrozen);
216
217 if (isFrozen)
218 m_exitCompositingTimer.stop();
219 else if (m_wantsToExitAcceleratedCompositingMode)
220 exitAcceleratedCompositingModeSoon();
221}
222
223void DrawingAreaCoordinatedGraphics::updatePreferences(const WebPreferencesStore& store)
224{
225 Settings& settings = m_webPage.corePage()->settings();
226 settings.setForceCompositingMode(store.getBoolValueForKey(WebPreferencesKey::forceCompositingModeKey()));
227 // Fixed position elements need to be composited and create stacking contexts
228 // in order to be scrolled by the ScrollingCoordinator.
229 settings.setAcceleratedCompositingForFixedPositionEnabled(settings.acceleratedCompositingEnabled());
230
231 m_alwaysUseCompositing = settings.acceleratedCompositingEnabled() && settings.forceCompositingMode();
232 if (m_alwaysUseCompositing && !m_layerTreeHost)
233 enterAcceleratedCompositingMode(nullptr);
234}
235
236void DrawingAreaCoordinatedGraphics::mainFrameContentSizeChanged(const IntSize& size)
237{
238 if (m_layerTreeHost)
239 m_layerTreeHost->contentsSizeChanged(size);
240 else if (m_previousLayerTreeHost)
241 m_previousLayerTreeHost->contentsSizeChanged(size);
242}
243
244void DrawingAreaCoordinatedGraphics::deviceOrPageScaleFactorChanged()
245{
246 if (m_layerTreeHost)
247 m_layerTreeHost->deviceOrPageScaleFactorChanged();
248 else if (m_previousLayerTreeHost)
249 m_previousLayerTreeHost->deviceOrPageScaleFactorChanged();
250}
251
252void DrawingAreaCoordinatedGraphics::didChangeViewportAttributes(ViewportAttributes&& attrs)
253{
254 if (m_layerTreeHost)
255 m_layerTreeHost->didChangeViewportAttributes(WTFMove(attrs));
256 else if (m_previousLayerTreeHost)
257 m_previousLayerTreeHost->didChangeViewportAttributes(WTFMove(attrs));
258}
259
260GraphicsLayerFactory* DrawingAreaCoordinatedGraphics::graphicsLayerFactory()
261{
262 if (!m_layerTreeHost)
263 enterAcceleratedCompositingMode(nullptr);
264 return m_layerTreeHost ? m_layerTreeHost->graphicsLayerFactory() : nullptr;
265}
266
267void DrawingAreaCoordinatedGraphics::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
268{
269 if (m_layerTreeHost) {
270 if (graphicsLayer) {
271 // We're already in accelerated compositing mode, but the root compositing layer changed.
272 m_exitCompositingTimer.stop();
273 m_wantsToExitAcceleratedCompositingMode = false;
274
275 // If we haven't sent the EnterAcceleratedCompositingMode message, make sure that the
276 // layer tree host calls us back after the next layer flush so we can send it then.
277 if (!m_compositingAccordingToProxyMessages)
278 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(true);
279 }
280 m_layerTreeHost->setRootCompositingLayer(graphicsLayer);
281
282 if (!graphicsLayer && !m_alwaysUseCompositing) {
283 // We'll exit accelerated compositing mode on a timer, to avoid re-entering
284 // compositing code via display() and layout.
285 // If we're leaving compositing mode because of a setSize, it is safe to
286 // exit accelerated compositing mode right away.
287 if (m_inUpdateBackingStoreState)
288 exitAcceleratedCompositingMode();
289 else
290 exitAcceleratedCompositingModeSoon();
291 }
292 return;
293 }
294
295 if (!graphicsLayer)
296 return;
297
298 // We're actually entering accelerated compositing mode.
299 enterAcceleratedCompositingMode(graphicsLayer);
300}
301
302void DrawingAreaCoordinatedGraphics::scheduleCompositingLayerFlush()
303{
304 if (m_layerTreeHost)
305 m_layerTreeHost->scheduleLayerFlush();
306 else
307 setNeedsDisplay();
308}
309
310void DrawingAreaCoordinatedGraphics::layerHostDidFlushLayers()
311{
312 ASSERT(m_layerTreeHost);
313 m_layerTreeHost->forceRepaint();
314
315 if (m_shouldSendDidUpdateBackingStoreState && !exitAcceleratedCompositingModePending()) {
316 sendDidUpdateBackingStoreState();
317 return;
318 }
319
320 ASSERT(!m_compositingAccordingToProxyMessages);
321 if (!exitAcceleratedCompositingModePending()) {
322 send(Messages::DrawingAreaProxy::EnterAcceleratedCompositingMode(m_backingStoreStateID, m_layerTreeHost->layerTreeContext()));
323 m_compositingAccordingToProxyMessages = true;
324 }
325}
326
327#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
328RefPtr<DisplayRefreshMonitor> DrawingAreaCoordinatedGraphics::createDisplayRefreshMonitor(PlatformDisplayID displayID)
329{
330 if (!m_layerTreeHost || m_wantsToExitAcceleratedCompositingMode || exitAcceleratedCompositingModePending())
331 return nullptr;
332 return m_layerTreeHost->createDisplayRefreshMonitor(displayID);
333}
334#endif
335
336#if USE(TEXTURE_MAPPER_GL) && PLATFORM(GTK) && PLATFORM(X11) && !USE(REDIRECTED_XCOMPOSITE_WINDOW)
337void DrawingAreaCoordinatedGraphics::setNativeSurfaceHandleForCompositing(uint64_t handle)
338{
339 m_nativeSurfaceHandleForCompositing = handle;
340 if (m_layerTreeHost) {
341 m_webPage.corePage()->settings().setAcceleratedCompositingEnabled(true);
342 m_layerTreeHost->setNativeSurfaceHandleForCompositing(handle);
343 }
344}
345
346void DrawingAreaCoordinatedGraphics::destroyNativeSurfaceHandleForCompositing(bool& handled)
347{
348 handled = true;
349 setNativeSurfaceHandleForCompositing(0);
350}
351#endif
352
353void DrawingAreaCoordinatedGraphics::activityStateDidChange(OptionSet<ActivityState::Flag> changed, ActivityStateChangeID, const Vector<CallbackID>&)
354{
355 if (changed & ActivityState::IsVisible) {
356 if (m_webPage.isVisible())
357 resumePainting();
358 else
359 suspendPainting();
360 }
361}
362
363void DrawingAreaCoordinatedGraphics::attachViewOverlayGraphicsLayer(GraphicsLayer* viewOverlayRootLayer)
364{
365 if (m_layerTreeHost)
366 m_layerTreeHost->setViewOverlayRootLayer(viewOverlayRootLayer);
367 else if (m_previousLayerTreeHost)
368 m_previousLayerTreeHost->setViewOverlayRootLayer(viewOverlayRootLayer);
369}
370
371void DrawingAreaCoordinatedGraphics::updateBackingStoreState(uint64_t stateID, bool respondImmediately, float deviceScaleFactor, const IntSize& size, const IntSize& scrollOffset)
372{
373 if (stateID != m_backingStoreStateID && !m_layerTreeHost)
374 m_dirtyRegion = IntRect(IntPoint(), size);
375
376 ASSERT(!m_inUpdateBackingStoreState);
377 m_inUpdateBackingStoreState = true;
378
379 ASSERT_ARG(stateID, stateID >= m_backingStoreStateID);
380 if (stateID != m_backingStoreStateID) {
381 m_backingStoreStateID = stateID;
382 m_shouldSendDidUpdateBackingStoreState = true;
383
384 m_webPage.setDeviceScaleFactor(deviceScaleFactor);
385 m_webPage.setSize(size);
386 m_webPage.updateRendering();
387 m_webPage.flushPendingEditorStateUpdate();
388 m_webPage.scrollMainFrameIfNotAtMaxScrollPosition(scrollOffset);
389
390 if (m_layerTreeHost)
391 m_layerTreeHost->sizeDidChange(m_webPage.size());
392 else if (m_previousLayerTreeHost)
393 m_previousLayerTreeHost->sizeDidChange(m_webPage.size());
394 } else {
395 ASSERT(size == m_webPage.size());
396 if (!m_shouldSendDidUpdateBackingStoreState) {
397 // We've already sent a DidUpdateBackingStoreState message for this state. We have nothing more to do.
398 m_inUpdateBackingStoreState = false;
399 if (m_forceRepaintAfterBackingStoreStateUpdate)
400 forceRepaint();
401 return;
402 }
403 }
404
405 // The UI process has updated to a new backing store state. Any Update messages we sent before
406 // this point will be ignored. We wait to set this to false until after updating the page's
407 // size so that any displays triggered by the relayout will be ignored. If we're supposed to
408 // respond to the UpdateBackingStoreState message immediately, we'll do a display anyway in
409 // sendDidUpdateBackingStoreState; otherwise we shouldn't do one right now.
410 m_isWaitingForDidUpdate = false;
411
412 if (respondImmediately) {
413 // Make sure to resume painting if we're supposed to respond immediately, otherwise we'll just
414 // send back an empty UpdateInfo struct.
415 bool wasSuspended = m_isPaintingSuspended;
416 if (m_isPaintingSuspended)
417 resumePainting();
418
419 sendDidUpdateBackingStoreState();
420 if (wasSuspended)
421 suspendPainting();
422 }
423
424 m_inUpdateBackingStoreState = false;
425
426 if (m_forceRepaintAfterBackingStoreStateUpdate)
427 forceRepaint();
428}
429
430void DrawingAreaCoordinatedGraphics::didUpdate()
431{
432 // We might get didUpdate messages from the UI process even after we've
433 // entered accelerated compositing mode. Ignore them.
434 if (m_layerTreeHost)
435 return;
436
437 m_isWaitingForDidUpdate = false;
438
439 // Display if needed. We call displayTimerFired here since it will throttle updates to 60fps.
440 displayTimerFired();
441}
442
443void DrawingAreaCoordinatedGraphics::sendDidUpdateBackingStoreState()
444{
445 ASSERT(!m_isWaitingForDidUpdate);
446 ASSERT(m_shouldSendDidUpdateBackingStoreState);
447
448 if (!m_isPaintingSuspended && !m_layerTreeHost) {
449 UpdateInfo updateInfo;
450 display(updateInfo);
451 if (!m_layerTreeHost) {
452 m_shouldSendDidUpdateBackingStoreState = false;
453
454 LayerTreeContext layerTreeContext;
455 send(Messages::DrawingAreaProxy::DidUpdateBackingStoreState(m_backingStoreStateID, updateInfo, layerTreeContext));
456 m_compositingAccordingToProxyMessages = false;
457 return;
458 }
459 }
460
461 ASSERT(m_shouldSendDidUpdateBackingStoreState);
462 m_shouldSendDidUpdateBackingStoreState = false;
463
464 UpdateInfo updateInfo;
465 updateInfo.viewSize = m_webPage.size();
466 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
467
468 LayerTreeContext layerTreeContext;
469 if (m_layerTreeHost) {
470 layerTreeContext = m_layerTreeHost->layerTreeContext();
471
472 // We don't want the layer tree host to notify after the next scheduled
473 // layer flush because that might end up sending an EnterAcceleratedCompositingMode
474 // message back to the UI process, but the updated layer tree context
475 // will be sent back in the DidUpdateBackingStoreState message.
476 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(false);
477 m_layerTreeHost->forceRepaint();
478 }
479
480 send(Messages::DrawingAreaProxy::DidUpdateBackingStoreState(m_backingStoreStateID, updateInfo, layerTreeContext));
481 m_compositingAccordingToProxyMessages = !layerTreeContext.isEmpty();
482}
483
484void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingModeSoon()
485{
486 if (m_layerTreeStateIsFrozen) {
487 m_wantsToExitAcceleratedCompositingMode = true;
488 return;
489 }
490
491 if (exitAcceleratedCompositingModePending())
492 return;
493
494 m_exitCompositingTimer.startOneShot(0_s);
495}
496
497void DrawingAreaCoordinatedGraphics::discardPreviousLayerTreeHost()
498{
499 m_discardPreviousLayerTreeHostTimer.stop();
500 if (!m_previousLayerTreeHost)
501 return;
502
503 m_previousLayerTreeHost->invalidate();
504 m_previousLayerTreeHost = nullptr;
505}
506
507void DrawingAreaCoordinatedGraphics::suspendPainting()
508{
509 ASSERT(!m_isPaintingSuspended);
510
511 if (m_layerTreeHost)
512 m_layerTreeHost->pauseRendering();
513 else
514 m_displayTimer.stop();
515
516 m_isPaintingSuspended = true;
517
518 m_webPage.corePage()->suspendScriptedAnimations();
519}
520
521void DrawingAreaCoordinatedGraphics::resumePainting()
522{
523 if (!m_isPaintingSuspended) {
524 // FIXME: We can get a call to resumePainting when painting is not suspended.
525 // This happens when sending a synchronous message to create a new page. See <rdar://problem/8976531>.
526 return;
527 }
528
529 if (m_layerTreeHost)
530 m_layerTreeHost->resumeRendering();
531
532 m_isPaintingSuspended = false;
533
534 // FIXME: We shouldn't always repaint everything here.
535 setNeedsDisplay();
536
537 m_webPage.corePage()->resumeScriptedAnimations();
538}
539
540void DrawingAreaCoordinatedGraphics::enterAcceleratedCompositingMode(GraphicsLayer* graphicsLayer)
541{
542 m_discardPreviousLayerTreeHostTimer.stop();
543
544 m_exitCompositingTimer.stop();
545 m_wantsToExitAcceleratedCompositingMode = false;
546
547 ASSERT(!m_layerTreeHost);
548 if (m_previousLayerTreeHost) {
549 m_layerTreeHost = WTFMove(m_previousLayerTreeHost);
550 m_layerTreeHost->setIsDiscardable(false);
551 if (!m_isPaintingSuspended)
552 m_layerTreeHost->resumeRendering();
553 if (!m_layerTreeStateIsFrozen)
554 m_layerTreeHost->setLayerFlushSchedulingEnabled(true);
555 } else {
556#if USE(COORDINATED_GRAPHICS)
557 m_layerTreeHost = std::make_unique<LayerTreeHost>(m_webPage);
558#else
559 m_layerTreeHost = nullptr;
560 return;
561#endif
562 if (m_isPaintingSuspended)
563 m_layerTreeHost->pauseRendering();
564 }
565
566#if USE(TEXTURE_MAPPER_GL) && PLATFORM(GTK) && PLATFORM(X11) && !USE(REDIRECTED_XCOMPOSITE_WINDOW)
567 if (m_nativeSurfaceHandleForCompositing)
568 m_layerTreeHost->setNativeSurfaceHandleForCompositing(m_nativeSurfaceHandleForCompositing);
569#endif
570 if (!m_inUpdateBackingStoreState)
571 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(true);
572
573 m_layerTreeHost->setRootCompositingLayer(graphicsLayer);
574
575 // Non-composited content will now be handled exclusively by the layer tree host.
576 m_dirtyRegion = Region();
577 m_scrollRect = IntRect();
578 m_scrollOffset = IntSize();
579 m_displayTimer.stop();
580 m_isWaitingForDidUpdate = false;
581}
582
583void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode()
584{
585 if (m_alwaysUseCompositing)
586 return;
587
588 ASSERT(!m_layerTreeStateIsFrozen);
589
590 m_exitCompositingTimer.stop();
591 m_wantsToExitAcceleratedCompositingMode = false;
592
593 ASSERT(m_layerTreeHost);
594 m_previousLayerTreeHost = WTFMove(m_layerTreeHost);
595 m_previousLayerTreeHost->setIsDiscardable(true);
596 m_previousLayerTreeHost->pauseRendering();
597 m_previousLayerTreeHost->setLayerFlushSchedulingEnabled(false);
598 m_discardPreviousLayerTreeHostTimer.startOneShot(5_s);
599
600 m_dirtyRegion = m_webPage.bounds();
601
602 if (m_inUpdateBackingStoreState)
603 return;
604
605 if (m_shouldSendDidUpdateBackingStoreState) {
606 sendDidUpdateBackingStoreState();
607 return;
608 }
609
610 UpdateInfo updateInfo;
611 if (m_isPaintingSuspended) {
612 updateInfo.viewSize = m_webPage.size();
613 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
614 } else
615 display(updateInfo);
616
617 // Send along a complete update of the page so we can paint the contents right after we exit the
618 // accelerated compositing mode, eliminiating flicker.
619 if (m_compositingAccordingToProxyMessages) {
620 send(Messages::DrawingAreaProxy::ExitAcceleratedCompositingMode(m_backingStoreStateID, updateInfo));
621 m_compositingAccordingToProxyMessages = false;
622 } else {
623 // If we left accelerated compositing mode before we sent an EnterAcceleratedCompositingMode message to the
624 // UI process, we still need to let it know about the new contents, so send an Update message.
625 send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo));
626 }
627}
628
629void DrawingAreaCoordinatedGraphics::scheduleDisplay()
630{
631 ASSERT(!m_layerTreeHost);
632
633 if (m_isWaitingForDidUpdate)
634 return;
635
636 if (m_isPaintingSuspended)
637 return;
638
639 if (m_dirtyRegion.isEmpty())
640 return;
641
642 if (m_displayTimer.isActive())
643 return;
644
645 m_displayTimer.startOneShot(0_s);
646}
647
648void DrawingAreaCoordinatedGraphics::displayTimerFired()
649{
650 display();
651}
652
653void DrawingAreaCoordinatedGraphics::display()
654{
655 ASSERT(!m_layerTreeHost);
656 ASSERT(!m_isWaitingForDidUpdate);
657 ASSERT(!m_inUpdateBackingStoreState);
658
659 if (m_isPaintingSuspended)
660 return;
661
662 if (m_dirtyRegion.isEmpty())
663 return;
664
665 if (m_shouldSendDidUpdateBackingStoreState) {
666 sendDidUpdateBackingStoreState();
667 return;
668 }
669
670 UpdateInfo updateInfo;
671 display(updateInfo);
672
673 if (m_layerTreeHost) {
674 // The call to update caused layout which turned on accelerated compositing.
675 // Don't send an Update message in this case.
676 return;
677 }
678
679 send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo));
680 m_isWaitingForDidUpdate = true;
681}
682
683static bool shouldPaintBoundsRect(const IntRect& bounds, const Vector<IntRect, 1>& rects)
684{
685 const size_t rectThreshold = 10;
686 const double wastedSpaceThreshold = 0.75;
687
688 if (rects.size() <= 1 || rects.size() > rectThreshold)
689 return true;
690
691 // Attempt to guess whether or not we should use the region bounds rect or the individual rects.
692 // We do this by computing the percentage of "wasted space" in the bounds. If that wasted space
693 // is too large, then we will do individual rect painting instead.
694 unsigned boundsArea = bounds.width() * bounds.height();
695 unsigned rectsArea = 0;
696 for (size_t i = 0; i < rects.size(); ++i)
697 rectsArea += rects[i].width() * rects[i].height();
698
699 double wastedSpace = 1 - (static_cast<double>(rectsArea) / boundsArea);
700
701 return wastedSpace <= wastedSpaceThreshold;
702}
703
704void DrawingAreaCoordinatedGraphics::display(UpdateInfo& updateInfo)
705{
706 ASSERT(!m_isPaintingSuspended);
707 ASSERT(!m_layerTreeHost);
708 ASSERT(!m_webPage.size().isEmpty());
709
710 m_webPage.updateRendering();
711 m_webPage.flushPendingEditorStateUpdate();
712
713 // The layout may have put the page into accelerated compositing mode. If the LayerTreeHost is
714 // in charge of displaying, we have nothing more to do.
715 if (m_layerTreeHost)
716 return;
717
718 updateInfo.viewSize = m_webPage.size();
719 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
720
721 IntRect bounds = m_dirtyRegion.bounds();
722 ASSERT(m_webPage.bounds().contains(bounds));
723
724 IntSize bitmapSize = bounds.size();
725 float deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
726 bitmapSize.scale(deviceScaleFactor);
727 auto bitmap = ShareableBitmap::createShareable(bitmapSize, { });
728 if (!bitmap)
729 return;
730
731 if (!bitmap->createHandle(updateInfo.bitmapHandle))
732 return;
733
734 auto rects = m_dirtyRegion.rects();
735 if (shouldPaintBoundsRect(bounds, rects)) {
736 rects.clear();
737 rects.append(bounds);
738 }
739
740 updateInfo.scrollRect = m_scrollRect;
741 updateInfo.scrollOffset = m_scrollOffset;
742
743 m_dirtyRegion = Region();
744 m_scrollRect = IntRect();
745 m_scrollOffset = IntSize();
746
747 auto graphicsContext = bitmap->createGraphicsContext();
748 graphicsContext->applyDeviceScaleFactor(deviceScaleFactor);
749
750 updateInfo.updateRectBounds = bounds;
751
752 graphicsContext->translate(-bounds.x(), -bounds.y());
753
754 for (const auto& rect : rects) {
755 m_webPage.drawRect(*graphicsContext, rect);
756 updateInfo.updateRects.append(rect);
757 }
758
759 // Layout can trigger more calls to setNeedsDisplay and we don't want to process them
760 // until the UI process has painted the update, so we stop the timer here.
761 m_displayTimer.stop();
762}
763
764} // namespace WebKit
765