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 #include "DrawingAreaImpl.h" 28 29 #include "DrawingAreaProxyMessages.h" 30 #include "LayerTreeContext.h" 31 #include "ShareableBitmap.h" 32 #include "UpdateInfo.h" 33 #include "WebPage.h" 34 #include "WebPageCreationParameters.h" 35 #include "WebProcess.h" 36 #include <WebCore/GraphicsContext.h> 37 #include <WebCore/Page.h> 38 #include <WebCore/Settings.h> 39 40 #if !PLATFORM(MAC) && !PLATFORM(WIN) 41 #error "This drawing area is not ready for use by other ports yet." 42 #endif 43 44 using namespace WebCore; 45 using namespace std; 46 47 namespace WebKit { 48 49 PassOwnPtr<DrawingAreaImpl> DrawingAreaImpl::create(WebPage* webPage, const WebPageCreationParameters& parameters) 50 { 51 return adoptPtr(new DrawingAreaImpl(webPage, parameters)); 52 } 53 54 DrawingAreaImpl::~DrawingAreaImpl() 55 { 56 if (m_layerTreeHost) 57 m_layerTreeHost->invalidate(); 58 } 59 60 DrawingAreaImpl::DrawingAreaImpl(WebPage* webPage, const WebPageCreationParameters& parameters) 61 : DrawingArea(DrawingAreaTypeImpl, webPage) 62 , m_backingStoreStateID(0) 63 , m_inUpdateBackingStoreState(false) 64 , m_shouldSendDidUpdateBackingStoreState(false) 65 , m_isWaitingForDidUpdate(false) 66 , m_isPaintingSuspended(!parameters.isVisible) 67 , m_alwaysUseCompositing(false) 68 , m_lastDisplayTime(0) 69 , m_displayTimer(WebProcess::shared().runLoop(), this, &DrawingAreaImpl::displayTimerFired) 70 , m_exitCompositingTimer(WebProcess::shared().runLoop(), this, &DrawingAreaImpl::exitAcceleratedCompositingMode) 71 { 72 if (webPage->corePage()->settings()->acceleratedDrawingEnabled()) 73 m_alwaysUseCompositing = true; 74 75 if (m_alwaysUseCompositing) 76 enterAcceleratedCompositingMode(0); 77 } 78 79 void DrawingAreaImpl::setNeedsDisplay(const IntRect& rect) 80 { 81 IntRect dirtyRect = rect; 82 dirtyRect.intersect(m_webPage->bounds()); 83 84 if (dirtyRect.isEmpty()) 85 return; 86 87 if (m_layerTreeHost) { 88 ASSERT(m_dirtyRegion.isEmpty()); 89 90 m_layerTreeHost->setNonCompositedContentsNeedDisplay(dirtyRect); 91 return; 92 } 93 94 if (m_webPage->mainFrameHasCustomRepresentation()) 95 return; 96 97 m_dirtyRegion.unite(dirtyRect); 98 scheduleDisplay(); 99 } 100 101 void DrawingAreaImpl::scroll(const IntRect& scrollRect, const IntSize& scrollOffset) 102 { 103 if (m_layerTreeHost) { 104 ASSERT(m_scrollRect.isEmpty()); 105 ASSERT(m_scrollOffset.isEmpty()); 106 ASSERT(m_dirtyRegion.isEmpty()); 107 108 m_layerTreeHost->scrollNonCompositedContents(scrollRect, scrollOffset); 109 return; 110 } 111 112 if (m_webPage->mainFrameHasCustomRepresentation()) 113 return; 114 115 if (!m_scrollRect.isEmpty() && scrollRect != m_scrollRect) { 116 unsigned scrollArea = scrollRect.width() * scrollRect.height(); 117 unsigned currentScrollArea = m_scrollRect.width() * m_scrollRect.height(); 118 119 if (currentScrollArea >= scrollArea) { 120 // The rect being scrolled is at least as large as the rect we'd like to scroll. 121 // Go ahead and just invalidate the scroll rect. 122 setNeedsDisplay(scrollRect); 123 return; 124 } 125 126 // Just repaint the entire current scroll rect, we'll scroll the new rect instead. 127 setNeedsDisplay(m_scrollRect); 128 m_scrollRect = IntRect(); 129 m_scrollOffset = IntSize(); 130 } 131 132 // Get the part of the dirty region that is in the scroll rect. 133 Region dirtyRegionInScrollRect = intersect(scrollRect, m_dirtyRegion); 134 if (!dirtyRegionInScrollRect.isEmpty()) { 135 // There are parts of the dirty region that are inside the scroll rect. 136 // We need to subtract them from the region, move them and re-add them. 137 m_dirtyRegion.subtract(scrollRect); 138 139 // Move the dirty parts. 140 Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, scrollOffset), scrollRect); 141 142 // And add them back. 143 m_dirtyRegion.unite(movedDirtyRegionInScrollRect); 144 } 145 146 // Compute the scroll repaint region. 147 Region scrollRepaintRegion = subtract(scrollRect, translate(scrollRect, scrollOffset)); 148 149 m_dirtyRegion.unite(scrollRepaintRegion); 150 151 m_scrollRect = scrollRect; 152 m_scrollOffset += scrollOffset; 153 } 154 155 void DrawingAreaImpl::forceRepaint() 156 { 157 setNeedsDisplay(m_webPage->bounds()); 158 159 m_webPage->layoutIfNeeded(); 160 161 if (m_layerTreeHost) { 162 m_layerTreeHost->forceRepaint(); 163 if (!m_layerTreeHost->participatesInDisplay()) 164 return; 165 } 166 167 m_isWaitingForDidUpdate = false; 168 display(); 169 } 170 171 void DrawingAreaImpl::didInstallPageOverlay() 172 { 173 if (m_layerTreeHost) 174 m_layerTreeHost->didInstallPageOverlay(); 175 } 176 177 void DrawingAreaImpl::didUninstallPageOverlay() 178 { 179 if (m_layerTreeHost) 180 m_layerTreeHost->didUninstallPageOverlay(); 181 182 setNeedsDisplay(m_webPage->bounds()); 183 } 184 185 void DrawingAreaImpl::setPageOverlayNeedsDisplay(const IntRect& rect) 186 { 187 if (m_layerTreeHost) { 188 m_layerTreeHost->setPageOverlayNeedsDisplay(rect); 189 return; 190 } 191 192 setNeedsDisplay(rect); 193 } 194 195 void DrawingAreaImpl::setLayerHostNeedsDisplay() 196 { 197 ASSERT(m_layerTreeHost); 198 ASSERT(m_layerTreeHost->participatesInDisplay()); 199 scheduleDisplay(); 200 } 201 202 void DrawingAreaImpl::layerHostDidFlushLayers() 203 { 204 ASSERT(m_layerTreeHost); 205 206 m_layerTreeHost->forceRepaint(); 207 208 if (m_shouldSendDidUpdateBackingStoreState) { 209 sendDidUpdateBackingStoreState(); 210 return; 211 } 212 213 if (!m_layerTreeHost || m_layerTreeHost->participatesInDisplay()) { 214 // When the layer tree host participates in display, we never tell the UI process about 215 // accelerated compositing. From the UI process's point of view, we're still just sending 216 // it a series of bitmaps in Update messages. 217 return; 218 } 219 220 #if USE(ACCELERATED_COMPOSITING) 221 m_webPage->send(Messages::DrawingAreaProxy::EnterAcceleratedCompositingMode(m_backingStoreStateID, m_layerTreeHost->layerTreeContext())); 222 #endif 223 } 224 225 void DrawingAreaImpl::setRootCompositingLayer(GraphicsLayer* graphicsLayer) 226 { 227 if (graphicsLayer) { 228 if (!m_layerTreeHost) { 229 // We're actually entering accelerated compositing mode. 230 enterAcceleratedCompositingMode(graphicsLayer); 231 } else { 232 m_exitCompositingTimer.stop(); 233 // We're already in accelerated compositing mode, but the root compositing layer changed. 234 m_layerTreeHost->setRootCompositingLayer(graphicsLayer); 235 } 236 } else { 237 if (m_layerTreeHost) { 238 m_layerTreeHost->setRootCompositingLayer(0); 239 if (!m_alwaysUseCompositing) { 240 // We'll exit accelerated compositing mode on a timer, to avoid re-entering 241 // compositing code via display() and layout. 242 // If we're leaving compositing mode because of a setSize, it is safe to 243 // exit accelerated compositing mode right away. 244 if (m_inUpdateBackingStoreState) 245 exitAcceleratedCompositingMode(); 246 else 247 exitAcceleratedCompositingModeSoon(); 248 } 249 } 250 } 251 } 252 253 void DrawingAreaImpl::scheduleCompositingLayerSync() 254 { 255 if (!m_layerTreeHost) 256 return; 257 m_layerTreeHost->scheduleLayerFlush(); 258 } 259 260 void DrawingAreaImpl::syncCompositingLayers() 261 { 262 } 263 264 void DrawingAreaImpl::didReceiveMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::ArgumentDecoder*) 265 { 266 } 267 268 void DrawingAreaImpl::updateBackingStoreState(uint64_t stateID, bool respondImmediately, const WebCore::IntSize& size, const WebCore::IntSize& scrollOffset) 269 { 270 ASSERT(!m_inUpdateBackingStoreState); 271 m_inUpdateBackingStoreState = true; 272 273 ASSERT_ARG(stateID, stateID >= m_backingStoreStateID); 274 if (stateID != m_backingStoreStateID) { 275 m_backingStoreStateID = stateID; 276 m_shouldSendDidUpdateBackingStoreState = true; 277 278 m_webPage->setSize(size); 279 m_webPage->layoutIfNeeded(); 280 m_webPage->scrollMainFrameIfNotAtMaxScrollPosition(scrollOffset); 281 282 if (m_layerTreeHost) 283 m_layerTreeHost->sizeDidChange(size); 284 else 285 m_dirtyRegion = m_webPage->bounds(); 286 } else { 287 ASSERT(size == m_webPage->size()); 288 if (!m_shouldSendDidUpdateBackingStoreState) { 289 // We've already sent a DidUpdateBackingStoreState message for this state. We have nothing more to do. 290 m_inUpdateBackingStoreState = false; 291 return; 292 } 293 } 294 295 // The UI process has updated to a new backing store state. Any Update messages we sent before 296 // this point will be ignored. We wait to set this to false until after updating the page's 297 // size so that any displays triggered by the relayout will be ignored. If we're supposed to 298 // respond to the UpdateBackingStoreState message immediately, we'll do a display anyway in 299 // sendDidUpdateBackingStoreState; otherwise we shouldn't do one right now. 300 m_isWaitingForDidUpdate = false; 301 302 if (respondImmediately) 303 sendDidUpdateBackingStoreState(); 304 305 m_inUpdateBackingStoreState = false; 306 } 307 308 void DrawingAreaImpl::sendDidUpdateBackingStoreState() 309 { 310 ASSERT(!m_isWaitingForDidUpdate); 311 ASSERT(m_shouldSendDidUpdateBackingStoreState); 312 313 m_shouldSendDidUpdateBackingStoreState = false; 314 315 UpdateInfo updateInfo; 316 317 if (!m_isPaintingSuspended && (!m_layerTreeHost || m_layerTreeHost->participatesInDisplay())) 318 display(updateInfo); 319 320 #if USE(ACCELERATED_COMPOSITING) 321 LayerTreeContext layerTreeContext; 322 323 if (m_isPaintingSuspended || (m_layerTreeHost && !m_layerTreeHost->participatesInDisplay())) { 324 updateInfo.viewSize = m_webPage->size(); 325 326 if (m_layerTreeHost) { 327 layerTreeContext = m_layerTreeHost->layerTreeContext(); 328 329 // We don't want the layer tree host to notify after the next scheduled 330 // layer flush because that might end up sending an EnterAcceleratedCompositingMode 331 // message back to the UI process, but the updated layer tree context 332 // will be sent back in the DidUpdateBackingStoreState message. 333 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(false); 334 m_layerTreeHost->forceRepaint(); 335 } 336 } 337 338 m_webPage->send(Messages::DrawingAreaProxy::DidUpdateBackingStoreState(m_backingStoreStateID, updateInfo, layerTreeContext)); 339 #endif 340 } 341 342 void DrawingAreaImpl::didUpdate() 343 { 344 // We might get didUpdate messages from the UI process even after we've 345 // entered accelerated compositing mode. Ignore them. 346 if (m_layerTreeHost && !m_layerTreeHost->participatesInDisplay()) 347 return; 348 349 m_isWaitingForDidUpdate = false; 350 351 // Display if needed. We call displayTimerFired here since it will throttle updates to 60fps. 352 displayTimerFired(); 353 } 354 355 void DrawingAreaImpl::suspendPainting() 356 { 357 ASSERT(!m_isPaintingSuspended); 358 359 if (m_layerTreeHost) 360 m_layerTreeHost->pauseRendering(); 361 362 m_isPaintingSuspended = true; 363 m_displayTimer.stop(); 364 } 365 366 void DrawingAreaImpl::resumePainting() 367 { 368 if (!m_isPaintingSuspended) { 369 // FIXME: We can get a call to resumePainting when painting is not suspended. 370 // This happens when sending a synchronous message to create a new page. See <rdar://problem/8976531>. 371 return; 372 } 373 374 if (m_layerTreeHost) 375 m_layerTreeHost->resumeRendering(); 376 377 m_isPaintingSuspended = false; 378 379 // FIXME: We shouldn't always repaint everything here. 380 setNeedsDisplay(m_webPage->bounds()); 381 } 382 383 void DrawingAreaImpl::enterAcceleratedCompositingMode(GraphicsLayer* graphicsLayer) 384 { 385 m_exitCompositingTimer.stop(); 386 387 ASSERT(!m_layerTreeHost); 388 389 m_layerTreeHost = LayerTreeHost::create(m_webPage); 390 if (!m_inUpdateBackingStoreState) 391 m_layerTreeHost->setShouldNotifyAfterNextScheduledLayerFlush(true); 392 393 m_layerTreeHost->setRootCompositingLayer(graphicsLayer); 394 395 // Non-composited content will now be handled exclusively by the layer tree host. 396 m_dirtyRegion = Region(); 397 m_scrollRect = IntRect(); 398 m_scrollOffset = IntSize(); 399 400 if (!m_layerTreeHost->participatesInDisplay()) { 401 m_displayTimer.stop(); 402 m_isWaitingForDidUpdate = false; 403 } 404 } 405 406 void DrawingAreaImpl::exitAcceleratedCompositingMode() 407 { 408 if (m_alwaysUseCompositing) 409 return; 410 411 m_exitCompositingTimer.stop(); 412 413 ASSERT(m_layerTreeHost); 414 415 bool wasParticipatingInDisplay = m_layerTreeHost->participatesInDisplay(); 416 417 m_layerTreeHost->invalidate(); 418 m_layerTreeHost = nullptr; 419 m_dirtyRegion = m_webPage->bounds(); 420 421 if (m_inUpdateBackingStoreState) 422 return; 423 424 if (m_shouldSendDidUpdateBackingStoreState) { 425 sendDidUpdateBackingStoreState(); 426 return; 427 } 428 429 UpdateInfo updateInfo; 430 if (m_isPaintingSuspended) 431 updateInfo.viewSize = m_webPage->size(); 432 else 433 display(updateInfo); 434 435 #if USE(ACCELERATED_COMPOSITING) 436 if (wasParticipatingInDisplay) { 437 // When the layer tree host participates in display, we never tell the UI process about 438 // accelerated compositing. From the UI process's point of view, we're still just sending 439 // it a series of bitmaps in Update messages. 440 m_webPage->send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo)); 441 } else { 442 // Send along a complete update of the page so we can paint the contents right after we exit the 443 // accelerated compositing mode, eliminiating flicker. 444 m_webPage->send(Messages::DrawingAreaProxy::ExitAcceleratedCompositingMode(m_backingStoreStateID, updateInfo)); 445 } 446 #endif 447 } 448 449 void DrawingAreaImpl::exitAcceleratedCompositingModeSoon() 450 { 451 if (m_exitCompositingTimer.isActive()) 452 return; 453 454 m_exitCompositingTimer.startOneShot(0); 455 } 456 457 void DrawingAreaImpl::scheduleDisplay() 458 { 459 ASSERT(!m_layerTreeHost || m_layerTreeHost->participatesInDisplay()); 460 461 if (m_isWaitingForDidUpdate) 462 return; 463 464 if (m_isPaintingSuspended) 465 return; 466 467 if (m_layerTreeHost) { 468 if (!m_layerTreeHost->needsDisplay()) 469 return; 470 } else if (m_dirtyRegion.isEmpty()) 471 return; 472 473 if (m_displayTimer.isActive()) 474 return; 475 476 m_displayTimer.startOneShot(0); 477 } 478 479 void DrawingAreaImpl::displayTimerFired() 480 { 481 static const double minimumFrameInterval = 1.0 / 60.0; 482 483 double timeSinceLastDisplay = currentTime() - m_lastDisplayTime; 484 double timeUntilLayerTreeHostNeedsDisplay = m_layerTreeHost && m_layerTreeHost->participatesInDisplay() ? m_layerTreeHost->timeUntilNextDisplay() : 0; 485 double timeUntilNextDisplay = max(minimumFrameInterval - timeSinceLastDisplay, timeUntilLayerTreeHostNeedsDisplay); 486 487 if (timeUntilNextDisplay > 0) { 488 m_displayTimer.startOneShot(timeUntilNextDisplay); 489 return; 490 } 491 492 display(); 493 } 494 495 void DrawingAreaImpl::display() 496 { 497 ASSERT(!m_layerTreeHost || m_layerTreeHost->participatesInDisplay()); 498 ASSERT(!m_isWaitingForDidUpdate); 499 ASSERT(!m_inUpdateBackingStoreState); 500 501 if (m_isPaintingSuspended) 502 return; 503 504 if (m_layerTreeHost) { 505 if (!m_layerTreeHost->needsDisplay()) 506 return; 507 } else if (m_dirtyRegion.isEmpty()) 508 return; 509 510 if (m_shouldSendDidUpdateBackingStoreState) { 511 sendDidUpdateBackingStoreState(); 512 return; 513 } 514 515 UpdateInfo updateInfo; 516 display(updateInfo); 517 518 if (m_layerTreeHost && !m_layerTreeHost->participatesInDisplay()) { 519 // The call to update caused layout which turned on accelerated compositing. 520 // Don't send an Update message in this case. 521 return; 522 } 523 524 m_webPage->send(Messages::DrawingAreaProxy::Update(m_backingStoreStateID, updateInfo)); 525 m_isWaitingForDidUpdate = true; 526 } 527 528 static bool shouldPaintBoundsRect(const IntRect& bounds, const Vector<IntRect>& rects) 529 { 530 const size_t rectThreshold = 10; 531 const float wastedSpaceThreshold = 0.75f; 532 533 if (rects.size() <= 1 || rects.size() > rectThreshold) 534 return true; 535 536 // Attempt to guess whether or not we should use the region bounds rect or the individual rects. 537 // We do this by computing the percentage of "wasted space" in the bounds. If that wasted space 538 // is too large, then we will do individual rect painting instead. 539 unsigned boundsArea = bounds.width() * bounds.height(); 540 unsigned rectsArea = 0; 541 for (size_t i = 0; i < rects.size(); ++i) 542 rectsArea += rects[i].width() * rects[i].height(); 543 544 float wastedSpace = 1 - (rectsArea / boundsArea); 545 546 return wastedSpace <= wastedSpaceThreshold; 547 } 548 549 void DrawingAreaImpl::display(UpdateInfo& updateInfo) 550 { 551 ASSERT(!m_isPaintingSuspended); 552 ASSERT(!m_layerTreeHost || m_layerTreeHost->participatesInDisplay()); 553 ASSERT(!m_webPage->size().isEmpty()); 554 555 // FIXME: It would be better if we could avoid painting altogether when there is a custom representation. 556 if (m_webPage->mainFrameHasCustomRepresentation()) { 557 // ASSUMPTION: the custom representation will be painting the dirty region for us. 558 m_dirtyRegion = Region(); 559 return; 560 } 561 562 m_webPage->layoutIfNeeded(); 563 564 // The layout may have put the page into accelerated compositing mode. If the LayerTreeHost is 565 // in charge of displaying, we have nothing more to do. 566 if (m_layerTreeHost && !m_layerTreeHost->participatesInDisplay()) 567 return; 568 569 updateInfo.viewSize = m_webPage->size(); 570 571 if (m_layerTreeHost) 572 m_layerTreeHost->display(updateInfo); 573 else { 574 IntRect bounds = m_dirtyRegion.bounds(); 575 ASSERT(m_webPage->bounds().contains(bounds)); 576 577 RefPtr<ShareableBitmap> bitmap = ShareableBitmap::createShareable(bounds.size(), ShareableBitmap::SupportsAlpha); 578 if (!bitmap->createHandle(updateInfo.bitmapHandle)) 579 return; 580 581 Vector<IntRect> rects = m_dirtyRegion.rects(); 582 583 if (shouldPaintBoundsRect(bounds, rects)) { 584 rects.clear(); 585 rects.append(bounds); 586 } 587 588 updateInfo.scrollRect = m_scrollRect; 589 updateInfo.scrollOffset = m_scrollOffset; 590 591 m_dirtyRegion = Region(); 592 m_scrollRect = IntRect(); 593 m_scrollOffset = IntSize(); 594 595 OwnPtr<GraphicsContext> graphicsContext = bitmap->createGraphicsContext(); 596 597 updateInfo.updateRectBounds = bounds; 598 599 graphicsContext->translate(-bounds.x(), -bounds.y()); 600 601 for (size_t i = 0; i < rects.size(); ++i) { 602 m_webPage->drawRect(*graphicsContext, rects[i]); 603 if (m_webPage->hasPageOverlay()) 604 m_webPage->drawPageOverlay(*graphicsContext, rects[i]); 605 updateInfo.updateRects.append(rects[i]); 606 } 607 } 608 609 // Layout can trigger more calls to setNeedsDisplay and we don't want to process them 610 // until the UI process has painted the update, so we stop the timer here. 611 m_displayTimer.stop(); 612 613 m_lastDisplayTime = currentTime(); 614 } 615 616 617 } // namespace WebKit 618