1/* 2 * Copyright (C) 2013, 2014 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#import "config.h" 27#import "ViewGestureController.h" 28 29#if PLATFORM(MAC) 30 31#import "FrameLoadState.h" 32#import "NativeWebWheelEvent.h" 33#import "WebPageGroup.h" 34#import "ViewGestureControllerMessages.h" 35#import "ViewGestureGeometryCollectorMessages.h" 36#import "ViewSnapshotStore.h" 37#import "WebBackForwardList.h" 38#import "WebPageMessages.h" 39#import "WebPageProxy.h" 40#import "WebPreferences.h" 41#import "WebProcessProxy.h" 42#import <Cocoa/Cocoa.h> 43#import <QuartzCore/QuartzCore.h> 44#import <WebCore/IOSurface.h> 45#import <WebCore/WebActionDisablingCALayerDelegate.h> 46 47#if defined(__has_include) && __has_include(<QuartzCore/QuartzCorePrivate.h>) 48#import <QuartzCore/QuartzCorePrivate.h> 49#else 50@interface CAFilter : NSObject <NSCopying, NSMutableCopying, NSCoding> 51@end 52#endif 53 54@interface CAFilter (Details) 55+ (CAFilter *)filterWithType:(NSString *)type; 56@end 57 58extern NSString * const kCAFilterColorInvert; 59 60using namespace WebCore; 61 62static const double minMagnification = 1; 63static const double maxMagnification = 3; 64 65static const double minElasticMagnification = 0.75; 66static const double maxElasticMagnification = 4; 67 68static const double zoomOutBoost = 1.6; 69static const double zoomOutResistance = 0.10; 70 71static const float smartMagnificationElementPadding = 0.05; 72static const float smartMagnificationPanScrollThreshold = 100; 73 74static const double swipeOverlayShadowOpacity = 0.66; 75static const double swipeOverlayShadowRadius = 3; 76 77static const CGFloat minimumHorizontalSwipeDistance = 15; 78static const float minimumScrollEventRatioForSwipe = 0.5; 79 80static const float swipeSnapshotRemovalRenderTreeSizeTargetFraction = 0.5; 81static const std::chrono::seconds swipeSnapshotRemovalWatchdogDuration = 5_s; 82static const std::chrono::seconds swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration = 3_s; 83static const std::chrono::milliseconds swipeSnapshotRemovalActiveLoadMonitoringInterval = 250_ms; 84 85@interface WKSwipeCancellationTracker : NSObject { 86@private 87 BOOL _isCancelled; 88} 89 90@property (nonatomic) BOOL isCancelled; 91 92@end 93 94@implementation WKSwipeCancellationTracker 95@synthesize isCancelled=_isCancelled; 96@end 97 98namespace WebKit { 99 100ViewGestureController::ViewGestureController(WebPageProxy& webPageProxy) 101 : m_webPageProxy(webPageProxy) 102 , m_activeGestureType(ViewGestureType::None) 103 , m_swipeWatchdogTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired) 104 , m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer(RunLoop::main(), this, &ViewGestureController::swipeSnapshotWatchdogTimerFired) 105 , m_swipeActiveLoadMonitoringTimer(RunLoop::main(), this, &ViewGestureController::activeLoadMonitoringTimerFired) 106 , m_lastMagnificationGestureWasSmartMagnification(false) 107 , m_visibleContentRectIsValid(false) 108 , m_frameHandlesMagnificationGesture(false) 109 , m_swipeTransitionStyle(SwipeTransitionStyle::Overlap) 110 , m_customSwipeViewsTopContentInset(0) 111 , m_pendingSwipeReason(PendingSwipeReason::None) 112 , m_didMoveSwipeSnapshotCallback(nullptr) 113 , m_shouldIgnorePinnedState(false) 114 , m_swipeWaitingForVisuallyNonEmptyLayout(false) 115 , m_swipeWaitingForRenderTreeSizeThreshold(false) 116 , m_swipeWaitingForRepaint(false) 117 , m_swipeInProgress(false) 118{ 119 m_webPageProxy.process().addMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID(), *this); 120} 121 122ViewGestureController::~ViewGestureController() 123{ 124 if (m_swipeCancellationTracker) 125 [m_swipeCancellationTracker setIsCancelled:YES]; 126 127 if (m_activeGestureType == ViewGestureType::Swipe) 128 removeSwipeSnapshot(); 129 130 if (m_didMoveSwipeSnapshotCallback) { 131 Block_release(m_didMoveSwipeSnapshotCallback); 132 m_didMoveSwipeSnapshotCallback = nullptr; 133 } 134 135 m_webPageProxy.process().removeMessageReceiver(Messages::ViewGestureController::messageReceiverName(), m_webPageProxy.pageID()); 136} 137 138static double resistanceForDelta(double deltaScale, double currentScale) 139{ 140 // Zoom out with slight acceleration, until we reach minimum scale. 141 if (deltaScale < 0 && currentScale > minMagnification) 142 return zoomOutBoost; 143 144 // Zoom in with no acceleration, until we reach maximum scale. 145 if (deltaScale > 0 && currentScale < maxMagnification) 146 return 1; 147 148 // Outside of the extremes, resist further scaling. 149 double limit = currentScale < minMagnification ? minMagnification : maxMagnification; 150 double scaleDistance = fabs(limit - currentScale); 151 double scalePercent = std::min(std::max(scaleDistance / limit, 0.), 1.); 152 double resistance = zoomOutResistance + scalePercent * (0.01 - zoomOutResistance); 153 154 return resistance; 155} 156 157FloatPoint ViewGestureController::scaledMagnificationOrigin(FloatPoint origin, double scale) 158{ 159 FloatPoint scaledMagnificationOrigin(origin); 160 scaledMagnificationOrigin.moveBy(m_visibleContentRect.location()); 161 float magnificationOriginScale = 1 - (scale / m_webPageProxy.pageScaleFactor()); 162 scaledMagnificationOrigin.scale(magnificationOriginScale, magnificationOriginScale); 163 return scaledMagnificationOrigin; 164} 165 166void ViewGestureController::didCollectGeometryForMagnificationGesture(FloatRect visibleContentRect, bool frameHandlesMagnificationGesture) 167{ 168 m_activeGestureType = ViewGestureType::Magnification; 169 m_visibleContentRect = visibleContentRect; 170 m_visibleContentRectIsValid = true; 171 m_frameHandlesMagnificationGesture = frameHandlesMagnificationGesture; 172} 173 174void ViewGestureController::handleMagnificationGesture(double scale, FloatPoint origin) 175{ 176 ASSERT(m_activeGestureType == ViewGestureType::None || m_activeGestureType == ViewGestureType::Magnification); 177 178 if (m_activeGestureType == ViewGestureType::None) { 179 // FIXME: We drop the first frame of the gesture on the floor, because we don't have the visible content bounds yet. 180 m_magnification = m_webPageProxy.pageScaleFactor(); 181 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForMagnificationGesture(), m_webPageProxy.pageID()); 182 m_lastMagnificationGestureWasSmartMagnification = false; 183 184 return; 185 } 186 187 // We're still waiting for the DidCollectGeometry callback. 188 if (!m_visibleContentRectIsValid) 189 return; 190 191 m_activeGestureType = ViewGestureType::Magnification; 192 193 double scaleWithResistance = resistanceForDelta(scale, m_magnification) * scale; 194 195 m_magnification += m_magnification * scaleWithResistance; 196 m_magnification = std::min(std::max(m_magnification, minElasticMagnification), maxElasticMagnification); 197 198 m_magnificationOrigin = origin; 199 200 if (m_frameHandlesMagnificationGesture) 201 m_webPageProxy.scalePage(m_magnification, roundedIntPoint(origin)); 202 else 203 m_webPageProxy.drawingArea()->adjustTransientZoom(m_magnification, scaledMagnificationOrigin(origin, m_magnification)); 204} 205 206void ViewGestureController::endMagnificationGesture() 207{ 208 ASSERT(m_activeGestureType == ViewGestureType::Magnification); 209 210 double newMagnification = std::min(std::max(m_magnification, minMagnification), maxMagnification); 211 212 if (m_frameHandlesMagnificationGesture) 213 m_webPageProxy.scalePage(newMagnification, roundedIntPoint(m_magnificationOrigin)); 214 else 215 m_webPageProxy.drawingArea()->commitTransientZoom(newMagnification, scaledMagnificationOrigin(m_magnificationOrigin, newMagnification)); 216 217 m_activeGestureType = ViewGestureType::None; 218} 219 220void ViewGestureController::handleSmartMagnificationGesture(FloatPoint origin) 221{ 222 if (m_activeGestureType != ViewGestureType::None) 223 return; 224 225 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::CollectGeometryForSmartMagnificationGesture(origin), m_webPageProxy.pageID()); 226} 227 228static float maximumRectangleComponentDelta(FloatRect a, FloatRect b) 229{ 230 return std::max(fabs(a.x() - b.x()), std::max(fabs(a.y() - b.y()), std::max(fabs(a.width() - b.width()), fabs(a.height() - b.height())))); 231} 232 233void ViewGestureController::didCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect renderRect, FloatRect visibleContentRect, bool isReplacedElement, double viewportMinimumScale, double viewportMaximumScale) 234{ 235 double currentScaleFactor = m_webPageProxy.pageScaleFactor(); 236 237 FloatRect unscaledTargetRect = renderRect; 238 239 // If there was no usable element under the cursor, we'll scale towards the cursor instead. 240 if (unscaledTargetRect.isEmpty()) 241 unscaledTargetRect.setLocation(origin); 242 243 unscaledTargetRect.scale(1 / currentScaleFactor); 244 unscaledTargetRect.inflateX(unscaledTargetRect.width() * smartMagnificationElementPadding); 245 unscaledTargetRect.inflateY(unscaledTargetRect.height() * smartMagnificationElementPadding); 246 247 double targetMagnification = visibleContentRect.width() / unscaledTargetRect.width(); 248 249 // For replaced elements like images, we want to fit the whole element 250 // in the view, so scale it down enough to make both dimensions fit if possible. 251 if (isReplacedElement) 252 targetMagnification = std::min(targetMagnification, static_cast<double>(visibleContentRect.height() / unscaledTargetRect.height())); 253 254 targetMagnification = std::min(std::max(targetMagnification, minMagnification), maxMagnification); 255 256 // Allow panning between elements via double-tap while magnified, unless the target rect is 257 // similar to the last one or the mouse has not moved, in which case we'll zoom all the way out. 258 if (currentScaleFactor > 1 && m_lastMagnificationGestureWasSmartMagnification) { 259 if (maximumRectangleComponentDelta(m_lastSmartMagnificationUnscaledTargetRect, unscaledTargetRect) < smartMagnificationPanScrollThreshold) 260 targetMagnification = 1; 261 262 if (m_lastSmartMagnificationOrigin == origin) 263 targetMagnification = 1; 264 } 265 266 FloatRect targetRect(unscaledTargetRect); 267 targetRect.scale(targetMagnification); 268 FloatPoint targetOrigin(visibleContentRect.center()); 269 targetOrigin.moveBy(-targetRect.center()); 270 271 m_webPageProxy.drawingArea()->adjustTransientZoom(m_webPageProxy.pageScaleFactor(), scaledMagnificationOrigin(FloatPoint(), m_webPageProxy.pageScaleFactor())); 272 m_webPageProxy.drawingArea()->commitTransientZoom(targetMagnification, targetOrigin); 273 274 m_lastSmartMagnificationUnscaledTargetRect = unscaledTargetRect; 275 m_lastSmartMagnificationOrigin = origin; 276 277 m_lastMagnificationGestureWasSmartMagnification = true; 278} 279 280bool ViewGestureController::scrollEventCanBecomeSwipe(NSEvent *event, ViewGestureController::SwipeDirection& potentialSwipeDirection) 281{ 282 if (event.phase != NSEventPhaseBegan) 283 return false; 284 285 if (!event.hasPreciseScrollingDeltas) 286 return false; 287 288 if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) 289 return false; 290 291 if (fabs(event.scrollingDeltaX) <= fabs(event.scrollingDeltaY)) 292 return false; 293 294 bool isPinnedToLeft = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToLeftSide(); 295 bool isPinnedToRight = m_shouldIgnorePinnedState || m_webPageProxy.isPinnedToRightSide(); 296 297 bool willSwipeLeft = event.scrollingDeltaX > 0 && isPinnedToLeft && m_webPageProxy.backForwardList().backItem(); 298 bool willSwipeRight = event.scrollingDeltaX < 0 && isPinnedToRight && m_webPageProxy.backForwardList().forwardItem(); 299 if (!willSwipeLeft && !willSwipeRight) 300 return false; 301 302 potentialSwipeDirection = willSwipeLeft ? ViewGestureController::SwipeDirection::Left : ViewGestureController::SwipeDirection::Right; 303 304 return true; 305} 306 307bool ViewGestureController::deltaIsSufficientToBeginSwipe(NSEvent *event) 308{ 309 if (m_pendingSwipeReason != PendingSwipeReason::InsufficientMagnitude) 310 return false; 311 312 m_cumulativeDeltaForPendingSwipe += FloatSize(event.scrollingDeltaX, event.scrollingDeltaY); 313 314 // If the cumulative delta is ever "too vertical", we will stop tracking this 315 // as a potential swipe until we get another "begin" event. 316 if (fabs(m_cumulativeDeltaForPendingSwipe.height()) >= fabs(m_cumulativeDeltaForPendingSwipe.width()) * minimumScrollEventRatioForSwipe) { 317 m_pendingSwipeReason = PendingSwipeReason::None; 318 return false; 319 } 320 321 if (fabs(m_cumulativeDeltaForPendingSwipe.width()) < minimumHorizontalSwipeDistance) 322 return false; 323 324 return true; 325} 326 327void ViewGestureController::setDidMoveSwipeSnapshotCallback(void(^callback)(CGRect)) 328{ 329 if (m_didMoveSwipeSnapshotCallback) 330 Block_release(m_didMoveSwipeSnapshotCallback); 331 m_didMoveSwipeSnapshotCallback = Block_copy(callback); 332} 333 334bool ViewGestureController::handleScrollWheelEvent(NSEvent *event) 335{ 336 if (event.phase == NSEventPhaseEnded) { 337 m_cumulativeDeltaForPendingSwipe = FloatSize(); 338 m_pendingSwipeReason = PendingSwipeReason::None; 339 } 340 341 if (m_pendingSwipeReason == PendingSwipeReason::InsufficientMagnitude) { 342 if (deltaIsSufficientToBeginSwipe(event)) { 343 trackSwipeGesture(event, m_pendingSwipeDirection); 344 return true; 345 } 346 } 347 348 if (m_activeGestureType != ViewGestureType::None) 349 return false; 350 351 SwipeDirection direction; 352 if (!scrollEventCanBecomeSwipe(event, direction)) 353 return false; 354 355 if (!m_shouldIgnorePinnedState && m_webPageProxy.willHandleHorizontalScrollEvents()) { 356 m_pendingSwipeReason = PendingSwipeReason::WebCoreMayScroll; 357 m_pendingSwipeDirection = direction; 358 return false; 359 } 360 361 if (!deltaIsSufficientToBeginSwipe(event)) { 362 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude; 363 m_pendingSwipeDirection = direction; 364 return true; 365 } 366 367 trackSwipeGesture(event, direction); 368 369 return true; 370} 371 372void ViewGestureController::wheelEventWasNotHandledByWebCore(NSEvent *event) 373{ 374 if (m_pendingSwipeReason != PendingSwipeReason::WebCoreMayScroll) 375 return; 376 377 m_pendingSwipeReason = PendingSwipeReason::None; 378 379 SwipeDirection direction; 380 if (!scrollEventCanBecomeSwipe(event, direction)) 381 return; 382 383 if (!deltaIsSufficientToBeginSwipe(event)) { 384 m_pendingSwipeReason = PendingSwipeReason::InsufficientMagnitude; 385 return; 386 } 387 388 trackSwipeGesture(event, m_pendingSwipeDirection); 389} 390 391void ViewGestureController::trackSwipeGesture(NSEvent *event, SwipeDirection direction) 392{ 393 ASSERT(m_activeGestureType == ViewGestureType::None); 394 m_pendingSwipeReason = PendingSwipeReason::None; 395 396 m_webPageProxy.recordNavigationSnapshot(); 397 398 CGFloat maxProgress = (direction == SwipeDirection::Left) ? 1 : 0; 399 CGFloat minProgress = (direction == SwipeDirection::Right) ? -1 : 0; 400 RefPtr<WebBackForwardListItem> targetItem = (direction == SwipeDirection::Left) ? m_webPageProxy.backForwardList().backItem() : m_webPageProxy.backForwardList().forwardItem(); 401 __block bool swipeCancelled = false; 402 403 ASSERT(!m_swipeCancellationTracker); 404 RetainPtr<WKSwipeCancellationTracker> swipeCancellationTracker = adoptNS([[WKSwipeCancellationTracker alloc] init]); 405 m_swipeCancellationTracker = swipeCancellationTracker; 406 407 [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:minProgress max:maxProgress usingHandler:^(CGFloat progress, NSEventPhase phase, BOOL isComplete, BOOL *stop) { 408 if ([swipeCancellationTracker isCancelled]) { 409 *stop = YES; 410 return; 411 } 412 if (phase == NSEventPhaseBegan) 413 this->beginSwipeGesture(targetItem.get(), direction); 414 CGFloat clampedProgress = std::min(std::max(progress, minProgress), maxProgress); 415 this->handleSwipeGesture(targetItem.get(), clampedProgress, direction); 416 if (phase == NSEventPhaseCancelled) 417 swipeCancelled = true; 418 if (isComplete) 419 this->endSwipeGesture(targetItem.get(), swipeCancelled); 420 }]; 421} 422 423FloatRect ViewGestureController::windowRelativeBoundsForCustomSwipeViews() const 424{ 425 FloatRect swipeArea; 426 for (const auto& view : m_customSwipeViews) 427 swipeArea.unite([view convertRect:[view bounds] toView:nil]); 428 swipeArea.setHeight(swipeArea.height() - m_customSwipeViewsTopContentInset); 429 return swipeArea; 430} 431 432static CALayer *leastCommonAncestorLayer(const Vector<RetainPtr<CALayer>>& layers) 433{ 434 Vector<Vector<CALayer *>> liveLayerPathsFromRoot(layers.size()); 435 436 size_t shortestPathLength = std::numeric_limits<size_t>::max(); 437 438 for (size_t layerIndex = 0; layerIndex < layers.size(); layerIndex++) { 439 CALayer *parent = [layers[layerIndex] superlayer]; 440 while (parent) { 441 liveLayerPathsFromRoot[layerIndex].insert(0, parent); 442 shortestPathLength = std::min(shortestPathLength, liveLayerPathsFromRoot[layerIndex].size()); 443 parent = parent.superlayer; 444 } 445 } 446 447 for (size_t pathIndex = 0; pathIndex < shortestPathLength; pathIndex++) { 448 CALayer *firstPathLayer = liveLayerPathsFromRoot[0][pathIndex]; 449 for (size_t layerIndex = 1; layerIndex < layers.size(); layerIndex++) { 450 if (liveLayerPathsFromRoot[layerIndex][pathIndex] != firstPathLayer) 451 return firstPathLayer; 452 } 453 } 454 455 return liveLayerPathsFromRoot[0][shortestPathLength]; 456} 457 458CALayer *ViewGestureController::determineSnapshotLayerParent() const 459{ 460 if (m_currentSwipeLiveLayers.size() == 1) 461 return [m_currentSwipeLiveLayers[0] superlayer]; 462 463 // We insert our snapshot into the first shared superlayer of the custom views' layer, above the frontmost or below the bottommost layer. 464 return leastCommonAncestorLayer(m_currentSwipeLiveLayers); 465} 466 467CALayer *ViewGestureController::determineLayerAdjacentToSnapshotForParent(SwipeDirection direction, CALayer *snapshotLayerParent) const 468{ 469 // If we have custom swiping views, we assume that the views were passed to us in back-to-front z-order. 470 CALayer *layerAdjacentToSnapshot = direction == SwipeDirection::Left ? m_currentSwipeLiveLayers.first().get() : m_currentSwipeLiveLayers.last().get(); 471 472 if (m_currentSwipeLiveLayers.size() == 1) 473 return layerAdjacentToSnapshot; 474 475 // If the layers are not all siblings, find the child of the layer we're going to insert the snapshot into which has the frontmost/bottommost layer as a child. 476 while (snapshotLayerParent != layerAdjacentToSnapshot.superlayer) 477 layerAdjacentToSnapshot = layerAdjacentToSnapshot.superlayer; 478 return layerAdjacentToSnapshot; 479} 480 481bool ViewGestureController::shouldUseSnapshotForSize(ViewSnapshot& snapshot, FloatSize swipeLayerSize, float topContentInset) 482{ 483 float deviceScaleFactor = m_webPageProxy.deviceScaleFactor(); 484 if (snapshot.deviceScaleFactor() != deviceScaleFactor) 485 return false; 486 487 FloatSize unobscuredSwipeLayerSizeInDeviceCoordinates = swipeLayerSize - FloatSize(0, topContentInset); 488 unobscuredSwipeLayerSizeInDeviceCoordinates.scale(deviceScaleFactor); 489 if (snapshot.size() != unobscuredSwipeLayerSizeInDeviceCoordinates) 490 return false; 491 492 return true; 493} 494 495static bool layerGeometryFlippedToRoot(CALayer *layer) 496{ 497 bool flipped = false; 498 CALayer *parent = layer; 499 while (parent) { 500 if (parent.isGeometryFlipped) 501 flipped = !flipped; 502 parent = parent.superlayer; 503 } 504 return flipped; 505} 506 507void ViewGestureController::applyDebuggingPropertiesToSwipeViews() 508{ 509#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 510 CAFilter* filter = [CAFilter filterWithType:kCAFilterColorInvert]; 511 [m_swipeLayer setFilters:@[ filter ]]; 512#endif 513 [m_swipeLayer setBackgroundColor:[NSColor blueColor].CGColor]; 514 [m_swipeLayer setBorderColor:[NSColor yellowColor].CGColor]; 515 [m_swipeLayer setBorderWidth:4]; 516 517 [m_swipeSnapshotLayer setBackgroundColor:[NSColor greenColor].CGColor]; 518 [m_swipeSnapshotLayer setBorderColor:[NSColor redColor].CGColor]; 519 [m_swipeSnapshotLayer setBorderWidth:2]; 520} 521 522void ViewGestureController::beginSwipeGesture(WebBackForwardListItem* targetItem, SwipeDirection direction) 523{ 524 ASSERT(m_currentSwipeLiveLayers.isEmpty()); 525 526 m_webPageProxy.navigationGestureDidBegin(); 527 528 m_activeGestureType = ViewGestureType::Swipe; 529 m_swipeInProgress = true; 530 531 CALayer *rootContentLayer = m_webPageProxy.acceleratedCompositingRootLayer(); 532 533 m_swipeLayer = adoptNS([[CALayer alloc] init]); 534 m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]); 535 m_currentSwipeCustomViewBounds = windowRelativeBoundsForCustomSwipeViews(); 536 537 FloatRect swipeArea; 538 float topContentInset = 0; 539 if (!m_customSwipeViews.isEmpty()) { 540 topContentInset = m_customSwipeViewsTopContentInset; 541 swipeArea = m_currentSwipeCustomViewBounds; 542 swipeArea.expand(0, topContentInset); 543 544 for (const auto& view : m_customSwipeViews) { 545 CALayer *layer = [view layer]; 546 ASSERT(layer); 547 m_currentSwipeLiveLayers.append(layer); 548 } 549 } else { 550 swipeArea = FloatRect(FloatPoint(), m_webPageProxy.viewSize()); 551 topContentInset = m_webPageProxy.topContentInset(); 552 m_currentSwipeLiveLayers.append(rootContentLayer); 553 } 554 555 CALayer *snapshotLayerParent = determineSnapshotLayerParent(); 556 bool geometryIsFlippedToRoot = layerGeometryFlippedToRoot(snapshotLayerParent); 557 558 RetainPtr<CGColorRef> backgroundColor = CGColorGetConstantColor(kCGColorWhite); 559 if (ViewSnapshot* snapshot = targetItem->snapshot()) { 560 if (shouldUseSnapshotForSize(*snapshot, swipeArea.size(), topContentInset)) 561 [m_swipeSnapshotLayer setContents:snapshot->asLayerContents()]; 562 563 Color coreColor = snapshot->backgroundColor(); 564 if (coreColor.isValid()) 565 backgroundColor = cachedCGColor(coreColor, ColorSpaceDeviceRGB); 566#if USE_IOSURFACE_VIEW_SNAPSHOTS 567 m_currentSwipeSnapshotSurface = snapshot->surface(); 568#endif 569 } 570 571 [m_swipeLayer setBackgroundColor:backgroundColor.get()]; 572 [m_swipeLayer setAnchorPoint:CGPointZero]; 573 [m_swipeLayer setFrame:swipeArea]; 574 [m_swipeLayer setName:@"Gesture Swipe Root Layer"]; 575 [m_swipeLayer setGeometryFlipped:geometryIsFlippedToRoot]; 576 [m_swipeLayer setDelegate:[WebActionDisablingCALayerDelegate shared]]; 577 578 [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft]; 579 [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()]; 580 [m_swipeSnapshotLayer setAnchorPoint:CGPointZero]; 581 [m_swipeSnapshotLayer setFrame:CGRectMake(0, 0, swipeArea.width(), swipeArea.height() - topContentInset)]; 582 [m_swipeSnapshotLayer setName:@"Gesture Swipe Snapshot Layer"]; 583 [m_swipeSnapshotLayer setDelegate:[WebActionDisablingCALayerDelegate shared]]; 584 585 [m_swipeLayer addSublayer:m_swipeSnapshotLayer.get()]; 586 587 if (m_webPageProxy.preferences().viewGestureDebuggingEnabled()) 588 applyDebuggingPropertiesToSwipeViews(); 589 590 // We don't know enough about the custom views' hierarchy to apply a shadow. 591 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap && m_customSwipeViews.isEmpty()) { 592 if (direction == SwipeDirection::Left) { 593 float topContentInset = m_webPageProxy.topContentInset(); 594 FloatRect shadowRect(FloatPoint(0, topContentInset), m_webPageProxy.viewSize() - FloatSize(0, topContentInset)); 595 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(shadowRect, 0)); 596 [rootContentLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)]; 597 [rootContentLayer setShadowOpacity:swipeOverlayShadowOpacity]; 598 [rootContentLayer setShadowRadius:swipeOverlayShadowRadius]; 599 [rootContentLayer setShadowPath:shadowPath.get()]; 600 } else { 601 RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect([m_swipeLayer bounds], 0)); 602 [m_swipeLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)]; 603 [m_swipeLayer setShadowOpacity:swipeOverlayShadowOpacity]; 604 [m_swipeLayer setShadowRadius:swipeOverlayShadowRadius]; 605 [m_swipeLayer setShadowPath:shadowPath.get()]; 606 } 607 } 608 609 CALayer *layerAdjacentToSnapshot = determineLayerAdjacentToSnapshotForParent(direction, snapshotLayerParent); 610 if (direction == SwipeDirection::Left) 611 [snapshotLayerParent insertSublayer:m_swipeLayer.get() below:layerAdjacentToSnapshot]; 612 else 613 [snapshotLayerParent insertSublayer:m_swipeLayer.get() above:layerAdjacentToSnapshot]; 614} 615 616void ViewGestureController::handleSwipeGesture(WebBackForwardListItem* targetItem, double progress, SwipeDirection direction) 617{ 618 ASSERT(m_activeGestureType == ViewGestureType::Swipe); 619 620 if (!m_webPageProxy.drawingArea()) 621 return; 622 623 double width; 624 if (!m_customSwipeViews.isEmpty()) 625 width = m_currentSwipeCustomViewBounds.width(); 626 else 627 width = m_webPageProxy.drawingArea()->size().width(); 628 629 double swipingLayerOffset = floor(width * progress); 630 631 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) { 632 if (direction == SwipeDirection::Right) { 633 [m_swipeLayer setTransform:CATransform3DMakeTranslation(width + swipingLayerOffset, 0, 0)]; 634 didMoveSwipeSnapshotLayer(); 635 } 636 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push) 637 [m_swipeLayer setTransform:CATransform3DMakeTranslation((direction == SwipeDirection::Left ? -width : width) + swipingLayerOffset, 0, 0)]; 638 639 for (const auto& layer : m_currentSwipeLiveLayers) { 640 if (m_swipeTransitionStyle == SwipeTransitionStyle::Overlap) { 641 if (direction == SwipeDirection::Left) 642 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)]; 643 } else if (m_swipeTransitionStyle == SwipeTransitionStyle::Push) 644 [layer setTransform:CATransform3DMakeTranslation(swipingLayerOffset, 0, 0)]; 645 } 646} 647 648void ViewGestureController::didMoveSwipeSnapshotLayer() 649{ 650 if (!m_didMoveSwipeSnapshotCallback) 651 return; 652 653 m_didMoveSwipeSnapshotCallback(m_webPageProxy.boundsOfLayerInLayerBackedWindowCoordinates(m_swipeLayer.get())); 654} 655 656void ViewGestureController::endSwipeGesture(WebBackForwardListItem* targetItem, bool cancelled) 657{ 658 ASSERT(m_activeGestureType == ViewGestureType::Swipe); 659 660 m_swipeCancellationTracker = nullptr; 661 662 m_swipeInProgress = false; 663 664 CALayer *rootLayer = m_webPageProxy.acceleratedCompositingRootLayer(); 665 666 [rootLayer setShadowOpacity:0]; 667 [rootLayer setShadowRadius:0]; 668 669 if (cancelled) { 670 removeSwipeSnapshot(); 671 m_webPageProxy.navigationGestureDidEnd(false, *targetItem); 672 return; 673 } 674 675 uint64_t renderTreeSize = 0; 676 if (ViewSnapshot* snapshot = targetItem->snapshot()) 677 renderTreeSize = snapshot->renderTreeSize(); 678 679 m_webPageProxy.process().send(Messages::ViewGestureGeometryCollector::SetRenderTreeSizeNotificationThreshold(renderTreeSize * swipeSnapshotRemovalRenderTreeSizeTargetFraction), m_webPageProxy.pageID()); 680 681 m_swipeWaitingForVisuallyNonEmptyLayout = true; 682 m_swipeWaitingForRenderTreeSizeThreshold = true; 683 684 m_webPageProxy.navigationGestureDidEnd(true, *targetItem); 685 m_webPageProxy.goToBackForwardItem(targetItem); 686 687 m_swipeWatchdogTimer.startOneShot(swipeSnapshotRemovalWatchdogDuration.count()); 688} 689 690void ViewGestureController::didHitRenderTreeSizeThreshold() 691{ 692 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress) 693 return; 694 695 m_swipeWaitingForRenderTreeSizeThreshold = false; 696 697 if (!m_swipeWaitingForVisuallyNonEmptyLayout) { 698 // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes 699 // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content. 700 } 701} 702 703void ViewGestureController::didFirstVisuallyNonEmptyLayoutForMainFrame() 704{ 705 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress) 706 return; 707 708 m_swipeWaitingForVisuallyNonEmptyLayout = false; 709 710 if (!m_swipeWaitingForRenderTreeSizeThreshold) { 711 // FIXME: Ideally we would call removeSwipeSnapshotAfterRepaint() here, but sometimes 712 // scroll position isn't done restoring until didFinishLoadForFrame, so we flash the wrong content. 713 } else { 714 m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.startOneShot(swipeSnapshotRemovalWatchdogAfterFirstVisuallyNonEmptyLayoutDuration.count()); 715 m_swipeWatchdogTimer.stop(); 716 } 717} 718 719void ViewGestureController::didFinishLoadForMainFrame() 720{ 721 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress) 722 return; 723 724 if (m_webPageProxy.pageLoadState().isLoading()) { 725 m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval); 726 return; 727 } 728 729 removeSwipeSnapshotAfterRepaint(); 730} 731 732void ViewGestureController::didSameDocumentNavigationForMainFrame(SameDocumentNavigationType type) 733{ 734 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress) 735 return; 736 737 if (type != SameDocumentNavigationSessionStateReplace && type != SameDocumentNavigationSessionStatePop) 738 return; 739 740 m_swipeActiveLoadMonitoringTimer.startRepeating(swipeSnapshotRemovalActiveLoadMonitoringInterval); 741} 742 743void ViewGestureController::activeLoadMonitoringTimerFired() 744{ 745 if (m_webPageProxy.pageLoadState().isLoading()) 746 return; 747 748 removeSwipeSnapshotAfterRepaint(); 749} 750 751void ViewGestureController::swipeSnapshotWatchdogTimerFired() 752{ 753 removeSwipeSnapshotAfterRepaint(); 754} 755 756void ViewGestureController::removeSwipeSnapshotAfterRepaint() 757{ 758 m_swipeActiveLoadMonitoringTimer.stop(); 759 760 if (m_activeGestureType != ViewGestureType::Swipe || m_swipeInProgress) 761 return; 762 763 if (m_swipeWaitingForRepaint) 764 return; 765 766 m_swipeWaitingForRepaint = true; 767 768 WebPageProxy* webPageProxy = &m_webPageProxy; 769 m_webPageProxy.forceRepaint(VoidCallback::create([webPageProxy] (CallbackBase::Error error) { 770 webPageProxy->removeNavigationGestureSnapshot(); 771 })); 772} 773 774void ViewGestureController::removeSwipeSnapshot() 775{ 776 m_swipeWaitingForRepaint = false; 777 778 m_swipeWatchdogTimer.stop(); 779 m_swipeWatchdogAfterFirstVisuallyNonEmptyLayoutTimer.stop(); 780 781 if (m_activeGestureType != ViewGestureType::Swipe) 782 return; 783 784#if USE_IOSURFACE_VIEW_SNAPSHOTS 785 if (m_currentSwipeSnapshotSurface) 786 m_currentSwipeSnapshotSurface->setIsVolatile(true); 787 m_currentSwipeSnapshotSurface = nullptr; 788#endif 789 790 for (const auto& layer : m_currentSwipeLiveLayers) 791 [layer setTransform:CATransform3DIdentity]; 792 793 [m_swipeSnapshotLayer removeFromSuperlayer]; 794 m_swipeSnapshotLayer = nullptr; 795 796 [m_swipeLayer removeFromSuperlayer]; 797 m_swipeLayer = nullptr; 798 799 m_currentSwipeLiveLayers.clear(); 800 801 m_activeGestureType = ViewGestureType::None; 802 803 m_webPageProxy.navigationGestureSnapshotWasRemoved(); 804} 805 806void ViewGestureController::endActiveGesture() 807{ 808 if (m_activeGestureType == ViewGestureType::Magnification) { 809 endMagnificationGesture(); 810 m_visibleContentRectIsValid = false; 811 } 812} 813 814double ViewGestureController::magnification() const 815{ 816 if (m_activeGestureType == ViewGestureType::Magnification) 817 return m_magnification; 818 819 return m_webPageProxy.pageScaleFactor(); 820} 821 822} // namespace WebKit 823 824#endif // !PLATFORM(IOS) 825