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
336void DrawingAreaCoordinatedGraphics::activityStateDidChange(OptionSet<ActivityState::Flag> changed, ActivityStateChangeID, const Vector<CallbackID>&)
337{
338 if (changed & ActivityState::IsVisible) {
339 if (m_webPage.isVisible())
340 resumePainting();
341 else
342 suspendPainting();
343 }
344}
345
346void DrawingAreaCoordinatedGraphics::attachViewOverlayGraphicsLayer(GraphicsLayer* viewOverlayRootLayer)
347{
348 if (m_layerTreeHost)
349 m_layerTreeHost->setViewOverlayRootLayer(viewOverlayRootLayer);
350 else if (m_previousLayerTreeHost)
351 m_previousLayerTreeHost->setViewOverlayRootLayer(viewOverlayRootLayer);
352}
353
354void DrawingAreaCoordinatedGraphics::updateBackingStoreState(uint64_t stateID, bool respondImmediately, float deviceScaleFactor, const IntSize& size, const IntSize& scrollOffset)
355{
356 if (stateID != m_backingStoreStateID && !m_layerTreeHost)
357 m_dirtyRegion = IntRect(IntPoint(), size);
358
359 ASSERT(!m_inUpdateBackingStoreState);
360 m_inUpdateBackingStoreState = true;
361
362 ASSERT_ARG(stateID, stateID >= m_backingStoreStateID);
363 if (stateID != m_backingStoreStateID) {
364 m_backingStoreStateID = stateID;
365 m_shouldSendDidUpdateBackingStoreState = true;
366
367 m_webPage.setDeviceScaleFactor(deviceScaleFactor);
368 m_webPage.setSize(size);
369 m_webPage.updateRendering();
370 m_webPage.flushPendingEditorStateUpdate();
371 m_webPage.scrollMainFrameIfNotAtMaxScrollPosition(scrollOffset);
372
373 if (m_layerTreeHost)
374 m_layerTreeHost->sizeDidChange(m_webPage.size());
375 else if (m_previousLayerTreeHost)
376 m_previousLayerTreeHost->sizeDidChange(m_webPage.size());
377 } else {
378 ASSERT(size == m_webPage.size());
379 if (!m_shouldSendDidUpdateBackingStoreState) {
380 // We've already sent a DidUpdateBackingStoreState message for this state. We have nothing more to do.
381 m_inUpdateBackingStoreState = false;
382 if (m_forceRepaintAfterBackingStoreStateUpdate)
383 forceRepaint();
384 return;
385 }
386 }
387
388 // The UI process has updated to a new backing store state. Any Update messages we sent before
389 // this point will be ignored. We wait to set this to false until after updating the page's
390 // size so that any displays triggered by the relayout will be ignored. If we're supposed to
391 // respond to the UpdateBackingStoreState message immediately, we'll do a display anyway in
392 // sendDidUpdateBackingStoreState; otherwise we shouldn't do one right now.
393 m_isWaitingForDidUpdate = false;
394
395 if (respondImmediately) {
396 // Make sure to resume painting if we're supposed to respond immediately, otherwise we'll just
397 // send back an empty UpdateInfo struct.
398 bool wasSuspended = m_isPaintingSuspended;
399 if (m_isPaintingSuspended)
400 resumePainting();
401
402 sendDidUpdateBackingStoreState();
403 if (wasSuspended)
404 suspendPainting();
405 }
406
407 m_inUpdateBackingStoreState = false;
408
409 if (m_forceRepaintAfterBackingStoreStateUpdate)
410 forceRepaint();
411}
412
413void DrawingAreaCoordinatedGraphics::didUpdate()
414{
415 // We might get didUpdate messages from the UI process even after we've
416 // entered accelerated compositing mode. Ignore them.
417 if (m_layerTreeHost)
418 return;
419
420 m_isWaitingForDidUpdate = false;
421
422 // Display if needed. We call displayTimerFired here since it will throttle updates to 60fps.
423 displayTimerFired();
424}
425
426void DrawingAreaCoordinatedGraphics::sendDidUpdateBackingStoreState()
427{
428 ASSERT(!m_isWaitingForDidUpdate);
429 ASSERT(m_shouldSendDidUpdateBackingStoreState);
430
431 if (!m_isPaintingSuspended && !m_layerTreeHost) {
432 UpdateInfo updateInfo;
433 display(updateInfo);
434 if (!m_layerTreeHost) {
435 m_shouldSendDidUpdateBackingStoreState = false;
436
437 LayerTreeContext layerTreeContext;
438 send(Messages::DrawingAreaProxy::DidUpdateBackingStoreState(m_backingStoreStateID, updateInfo, layerTreeContext));
439 m_compositingAccordingToProxyMessages = false;
440 return;
441 }
442 }
443
444 ASSERT(m_shouldSendDidUpdateBackingStoreState);
445 m_shouldSendDidUpdateBackingStoreState = false;
446
447 UpdateInfo updateInfo;
448 updateInfo.viewSize = m_webPage.size();
449 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
450
451 LayerTreeContext layerTreeContext;
452 if (m_layerTreeHost) {
453 layerTreeContext = m_layerTreeHost->layerTreeContext();
454
455 // We don't want the layer tree host to notify after the next scheduled
456 // layer flush because that might end up sending an EnterAcceleratedCompositingMode
457 // message back to the UI process, but the updated layer tree context
458 // will be sent back in the DidUpdateBackingStoreState message.
459 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(false);
460 m_layerTreeHost->forceRepaint();
461 }
462
463 send(Messages::DrawingAreaProxy::DidUpdateBackingStoreState(m_backingStoreStateID, updateInfo, layerTreeContext));
464 m_compositingAccordingToProxyMessages = !layerTreeContext.isEmpty();
465}
466
467void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingModeSoon()
468{
469 if (m_layerTreeStateIsFrozen) {
470 m_wantsToExitAcceleratedCompositingMode = true;
471 return;
472 }
473
474 if (exitAcceleratedCompositingModePending())
475 return;
476
477 m_exitCompositingTimer.startOneShot(0_s);
478}
479
480void DrawingAreaCoordinatedGraphics::discardPreviousLayerTreeHost()
481{
482 m_discardPreviousLayerTreeHostTimer.stop();
483 if (!m_previousLayerTreeHost)
484 return;
485
486 m_previousLayerTreeHost->invalidate();
487 m_previousLayerTreeHost = nullptr;
488}
489
490void DrawingAreaCoordinatedGraphics::suspendPainting()
491{
492 ASSERT(!m_isPaintingSuspended);
493
494 if (m_layerTreeHost)
495 m_layerTreeHost->pauseRendering();
496 else
497 m_displayTimer.stop();
498
499 m_isPaintingSuspended = true;
500
501 m_webPage.corePage()->suspendScriptedAnimations();
502}
503
504void DrawingAreaCoordinatedGraphics::resumePainting()
505{
506 if (!m_isPaintingSuspended) {
507 // FIXME: We can get a call to resumePainting when painting is not suspended.
508 // This happens when sending a synchronous message to create a new page. See <rdar://problem/8976531>.
509 return;
510 }
511
512 if (m_layerTreeHost)
513 m_layerTreeHost->resumeRendering();
514
515 m_isPaintingSuspended = false;
516
517 // FIXME: We shouldn't always repaint everything here.
518 setNeedsDisplay();
519
520 m_webPage.corePage()->resumeScriptedAnimations();
521}
522
523void DrawingAreaCoordinatedGraphics::enterAcceleratedCompositingMode(GraphicsLayer* graphicsLayer)
524{
525 m_discardPreviousLayerTreeHostTimer.stop();
526
527 m_exitCompositingTimer.stop();
528 m_wantsToExitAcceleratedCompositingMode = false;
529
530 // In order to ensure that we get a unique DisplayRefreshMonitor per-DrawingArea (necessary because ThreadedDisplayRefreshMonitor
531 // is driven by the ThreadedCompositor of the drawing area), give each page a unique DisplayID derived from WebPage's unique ID.
532 m_webPage.windowScreenDidChange(std::numeric_limits<uint32_t>::max() - m_webPage.pageID().toUInt64());
533
534 ASSERT(!m_layerTreeHost);
535 if (m_previousLayerTreeHost) {
536 m_layerTreeHost = WTFMove(m_previousLayerTreeHost);
537 m_layerTreeHost->setIsDiscardable(false);
538 if (!m_isPaintingSuspended)
539 m_layerTreeHost->resumeRendering();
540 if (!m_layerTreeStateIsFrozen)
541 m_layerTreeHost->setLayerFlushSchedulingEnabled(true);
542 } else {
543#if USE(COORDINATED_GRAPHICS)
544 m_layerTreeHost = std::make_unique<LayerTreeHost>(m_webPage);
545#else
546 m_layerTreeHost = nullptr;
547 return;
548#endif
549 if (m_isPaintingSuspended)
550 m_layerTreeHost->pauseRendering();
551 }
552
553 if (!m_inUpdateBackingStoreState)
554 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(true);
555
556 m_layerTreeHost->setRootCompositingLayer(graphicsLayer);
557
558 // Non-composited content will now be handled exclusively by the layer tree host.
559 m_dirtyRegion = Region();
560 m_scrollRect = IntRect();
561 m_scrollOffset = IntSize();
562 m_displayTimer.stop();
563 m_isWaitingForDidUpdate = false;
564}
565
566void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode()
567{
568 if (m_alwaysUseCompositing)
569 return;
570
571 ASSERT(!m_layerTreeStateIsFrozen);
572
573 m_exitCompositingTimer.stop();
574 m_wantsToExitAcceleratedCompositingMode = false;
575
576 ASSERT(m_layerTreeHost);
577 m_previousLayerTreeHost = WTFMove(m_layerTreeHost);
578 m_previousLayerTreeHost->setIsDiscardable(true);
579 m_previousLayerTreeHost->pauseRendering();
580 m_previousLayerTreeHost->setLayerFlushSchedulingEnabled(false);
581 m_discardPreviousLayerTreeHostTimer.startOneShot(5_s);
582
583 // Always use the primary display ID (0) when not in accelerated compositing mode.
584 m_webPage.windowScreenDidChange(0);
585
586 m_dirtyRegion = m_webPage.bounds();
587
588 if (m_inUpdateBackingStoreState)
589 return;
590
591 if (m_shouldSendDidUpdateBackingStoreState) {
592 sendDidUpdateBackingStoreState();
593 return;
594 }
595
596 UpdateInfo updateInfo;
597 if (m_isPaintingSuspended) {
598 updateInfo.viewSize = m_webPage.size();
599 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
600 } else
601 display(updateInfo);
602
603 // Send along a complete update of the page so we can paint the contents right after we exit the
604 // accelerated compositing mode, eliminiating flicker.
605 if (m_compositingAccordingToProxyMessages) {
606 send(Messages::DrawingAreaProxy::ExitAcceleratedCompositingMode(m_backingStoreStateID, updateInfo));
607 m_compositingAccordingToProxyMessages = false;
608 } else {
609 // If we left accelerated compositing mode before we sent an EnterAcceleratedCompositingMode message to the
610 // UI process, we still need to let it know about the new contents, so send an Update message.
611 send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo));
612 }
613}
614
615void DrawingAreaCoordinatedGraphics::scheduleDisplay()
616{
617 ASSERT(!m_layerTreeHost);
618
619 if (m_isWaitingForDidUpdate)
620 return;
621
622 if (m_isPaintingSuspended)
623 return;
624
625 if (m_dirtyRegion.isEmpty())
626 return;
627
628 if (m_displayTimer.isActive())
629 return;
630
631 m_displayTimer.startOneShot(0_s);
632}
633
634void DrawingAreaCoordinatedGraphics::displayTimerFired()
635{
636 display();
637}
638
639void DrawingAreaCoordinatedGraphics::display()
640{
641 ASSERT(!m_layerTreeHost);
642 ASSERT(!m_isWaitingForDidUpdate);
643 ASSERT(!m_inUpdateBackingStoreState);
644
645 if (m_isPaintingSuspended)
646 return;
647
648 if (m_dirtyRegion.isEmpty())
649 return;
650
651 if (m_shouldSendDidUpdateBackingStoreState) {
652 sendDidUpdateBackingStoreState();
653 return;
654 }
655
656 UpdateInfo updateInfo;
657 display(updateInfo);
658
659 if (m_layerTreeHost) {
660 // The call to update caused layout which turned on accelerated compositing.
661 // Don't send an Update message in this case.
662 return;
663 }
664
665 send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo));
666 m_isWaitingForDidUpdate = true;
667}
668
669static bool shouldPaintBoundsRect(const IntRect& bounds, const Vector<IntRect, 1>& rects)
670{
671 const size_t rectThreshold = 10;
672 const double wastedSpaceThreshold = 0.75;
673
674 if (rects.size() <= 1 || rects.size() > rectThreshold)
675 return true;
676
677 // Attempt to guess whether or not we should use the region bounds rect or the individual rects.
678 // We do this by computing the percentage of "wasted space" in the bounds. If that wasted space
679 // is too large, then we will do individual rect painting instead.
680 unsigned boundsArea = bounds.width() * bounds.height();
681 unsigned rectsArea = 0;
682 for (size_t i = 0; i < rects.size(); ++i)
683 rectsArea += rects[i].width() * rects[i].height();
684
685 double wastedSpace = 1 - (static_cast<double>(rectsArea) / boundsArea);
686
687 return wastedSpace <= wastedSpaceThreshold;
688}
689
690void DrawingAreaCoordinatedGraphics::display(UpdateInfo& updateInfo)
691{
692 ASSERT(!m_isPaintingSuspended);
693 ASSERT(!m_layerTreeHost);
694 ASSERT(!m_webPage.size().isEmpty());
695
696 m_webPage.updateRendering();
697 m_webPage.flushPendingEditorStateUpdate();
698
699 // The layout may have put the page into accelerated compositing mode. If the LayerTreeHost is
700 // in charge of displaying, we have nothing more to do.
701 if (m_layerTreeHost)
702 return;
703
704 updateInfo.viewSize = m_webPage.size();
705 updateInfo.deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
706
707 IntRect bounds = m_dirtyRegion.bounds();
708 ASSERT(m_webPage.bounds().contains(bounds));
709
710 IntSize bitmapSize = bounds.size();
711 float deviceScaleFactor = m_webPage.corePage()->deviceScaleFactor();
712 bitmapSize.scale(deviceScaleFactor);
713 auto bitmap = ShareableBitmap::createShareable(bitmapSize, { });
714 if (!bitmap)
715 return;
716
717 if (!bitmap->createHandle(updateInfo.bitmapHandle))
718 return;
719
720 auto rects = m_dirtyRegion.rects();
721 if (shouldPaintBoundsRect(bounds, rects)) {
722 rects.clear();
723 rects.append(bounds);
724 }
725
726 updateInfo.scrollRect = m_scrollRect;
727 updateInfo.scrollOffset = m_scrollOffset;
728
729 m_dirtyRegion = Region();
730 m_scrollRect = IntRect();
731 m_scrollOffset = IntSize();
732
733 auto graphicsContext = bitmap->createGraphicsContext();
734 graphicsContext->applyDeviceScaleFactor(deviceScaleFactor);
735
736 updateInfo.updateRectBounds = bounds;
737
738 graphicsContext->translate(-bounds.x(), -bounds.y());
739
740 for (const auto& rect : rects) {
741 m_webPage.drawRect(*graphicsContext, rect);
742 updateInfo.updateRects.append(rect);
743 }
744
745 // Layout can trigger more calls to setNeedsDisplay and we don't want to process them
746 // until the UI process has painted the update, so we stop the timer here.
747 m_displayTimer.stop();
748}
749
750} // namespace WebKit
751