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 "WKContentViewInteraction.h" 28 29#if PLATFORM(IOS) 30 31#import "PageClientImplIOS.h" 32#import "RemoteLayerTreeDrawingAreaProxy.h" 33#import "RemoteScrollingCoordinatorProxy.h" 34#import "SmartMagnificationController.h" 35#import "WKBrowsingContextControllerInternal.h" 36#import "WKBrowsingContextGroupPrivate.h" 37#import "WKGeolocationProviderIOS.h" 38#import "WKInspectorHighlightView.h" 39#import "WKPreferencesInternal.h" 40#import "WKProcessGroupPrivate.h" 41#import "WKProcessPoolInternal.h" 42#import "WKWebViewConfiguration.h" 43#import "WKWebViewInternal.h" 44#import "WebContext.h" 45#import "WebFrameProxy.h" 46#import "WebKit2Initialize.h" 47#import "WebKitSystemInterfaceIOS.h" 48#import "WebPageGroup.h" 49#import "WebSystemInterface.h" 50#import <CoreGraphics/CoreGraphics.h> 51#import <UIKit/UIWindow_Private.h> 52#import <WebCore/FloatQuad.h> 53#import <WebCore/FrameView.h> 54#import <WebCore/InspectorOverlay.h> 55#import <WebCore/NotImplemented.h> 56#import <wtf/CurrentTime.h> 57#import <wtf/RetainPtr.h> 58 59#if __has_include(<QuartzCore/QuartzCorePrivate.h>) 60#import <QuartzCore/QuartzCorePrivate.h> 61#endif 62 63@interface CALayer (Details) 64@property BOOL hitTestsAsOpaque; 65@end 66 67using namespace WebCore; 68using namespace WebKit; 69 70namespace WebKit { 71class HistoricalVelocityData { 72public: 73 struct VelocityData { 74 VelocityData() 75 : horizontalVelocity(0) 76 , verticalVelocity(0) 77 , scaleChangeRate(0) 78 { 79 } 80 81 VelocityData(double horizontalVelocity, double verticalVelocity, double scaleChangeRate) 82 : horizontalVelocity(horizontalVelocity) 83 , verticalVelocity(verticalVelocity) 84 , scaleChangeRate(scaleChangeRate) 85 { 86 } 87 88 double horizontalVelocity; 89 double verticalVelocity; 90 double scaleChangeRate; 91 }; 92 93 HistoricalVelocityData() 94 : m_historySize(0) 95 , m_latestDataIndex(0) 96 , m_lastAppendTimestamp(0) 97 { 98 } 99 100 VelocityData velocityForNewData(CGPoint newPosition, double scale, double timestamp) 101 { 102 // Due to all the source of rect update, the input is very noisy. To smooth the output, we accumulate all changes 103 // within 1 frame as a single update. No speed computation is ever done on data within the same frame. 104 const double filteringThreshold = 1 / 60.; 105 106 VelocityData velocityData; 107 if (m_historySize > 0) { 108 unsigned oldestDataIndex; 109 unsigned distanceToLastHistoricalData = m_historySize - 1; 110 if (distanceToLastHistoricalData <= m_latestDataIndex) 111 oldestDataIndex = m_latestDataIndex - distanceToLastHistoricalData; 112 else 113 oldestDataIndex = m_historySize - (distanceToLastHistoricalData - m_latestDataIndex); 114 115 double timeDelta = timestamp - m_history[oldestDataIndex].timestamp; 116 if (timeDelta > filteringThreshold) { 117 Data& oldestData = m_history[oldestDataIndex]; 118 velocityData = VelocityData((newPosition.x - oldestData.position.x) / timeDelta, (newPosition.y - oldestData.position.y) / timeDelta, (scale - oldestData.scale) / timeDelta); 119 } 120 } 121 122 double timeSinceLastAppend = timestamp - m_lastAppendTimestamp; 123 if (timeSinceLastAppend > filteringThreshold) 124 append(newPosition, scale, timestamp); 125 else 126 m_history[m_latestDataIndex] = { timestamp, newPosition, scale }; 127 return velocityData; 128 } 129 130 void clear() { m_historySize = 0; } 131 132private: 133 void append(CGPoint newPosition, double scale, double timestamp) 134 { 135 m_latestDataIndex = (m_latestDataIndex + 1) % maxHistoryDepth; 136 m_history[m_latestDataIndex] = { timestamp, newPosition, scale }; 137 138 unsigned size = m_historySize + 1; 139 if (size <= maxHistoryDepth) 140 m_historySize = size; 141 142 m_lastAppendTimestamp = timestamp; 143 } 144 145 146 static const unsigned maxHistoryDepth = 3; 147 148 unsigned m_historySize; 149 unsigned m_latestDataIndex; 150 double m_lastAppendTimestamp; 151 152 struct Data { 153 double timestamp; 154 CGPoint position; 155 double scale; 156 } m_history[maxHistoryDepth]; 157}; 158} // namespace WebKit 159 160@interface WKInspectorIndicationView : UIView 161@end 162 163@implementation WKInspectorIndicationView 164 165- (instancetype)initWithFrame:(CGRect)frame 166{ 167 if (!(self = [super initWithFrame:frame])) 168 return nil; 169 self.userInteractionEnabled = NO; 170 self.backgroundColor = [UIColor colorWithRed:(111.0 / 255.0) green:(168.0 / 255.0) blue:(220.0 / 255.0) alpha:0.66f]; 171 return self; 172} 173 174@end 175 176@implementation WKContentView { 177 std::unique_ptr<PageClientImpl> _pageClient; 178 RetainPtr<WKBrowsingContextController> _browsingContextController; 179 180 RetainPtr<UIView> _rootContentView; 181 RetainPtr<UIView> _fixedClippingView; 182 RetainPtr<WKInspectorIndicationView> _inspectorIndicationView; 183 RetainPtr<WKInspectorHighlightView> _inspectorHighlightView; 184 185 HistoricalVelocityData _historicalKinematicData; 186} 187 188- (instancetype)initWithFrame:(CGRect)frame context:(WebKit::WebContext&)context configuration:(WebKit::WebPageConfiguration)webPageConfiguration webView:(WKWebView *)webView 189{ 190 if (!(self = [super initWithFrame:frame])) 191 return nil; 192 193 InitializeWebKit2(); 194 195 _pageClient = std::make_unique<PageClientImpl>(self, webView); 196 197 _page = context.createWebPage(*_pageClient, WTF::move(webPageConfiguration)); 198 _page->initializeWebPage(); 199 _page->setIntrinsicDeviceScaleFactor(WKGetScaleFactorForScreen([UIScreen mainScreen])); 200 _page->setUseFixedLayout(true); 201 _page->setDelegatesScrolling(true); 202 203 _webView = webView; 204 205 _isBackground = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; 206 207 WebContext::statistics().wkViewCount++; 208 209 _rootContentView = adoptNS([[UIView alloc] init]); 210 [_rootContentView layer].masksToBounds = NO; 211 [_rootContentView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 212 213 _fixedClippingView = adoptNS([[UIView alloc] init]); 214 [_fixedClippingView layer].masksToBounds = YES; 215 [_fixedClippingView layer].anchorPoint = CGPointZero; 216#ifndef NDEBUG 217 [[_fixedClippingView layer] setName:@"Fixed clipping"]; 218#endif 219 220 [self addSubview:_fixedClippingView.get()]; 221 [_fixedClippingView addSubview:_rootContentView.get()]; 222 223 [self setupInteraction]; 224 [self setUserInteractionEnabled:YES]; 225 226 self.layer.hitTestsAsOpaque = YES; 227 228 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; 229 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; 230 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]]; 231 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:[UIApplication sharedApplication]]; 232 233 return self; 234} 235 236- (void)dealloc 237{ 238 [self cleanupInteraction]; 239 240 [[NSNotificationCenter defaultCenter] removeObserver:self]; 241 242 _page->close(); 243 244 WebContext::statistics().wkViewCount--; 245 246 [super dealloc]; 247} 248 249- (WebPageProxy*)page 250{ 251 return _page.get(); 252} 253 254- (void)willMoveToWindow:(UIWindow *)newWindow 255{ 256 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 257 UIWindow *window = self.window; 258 259 if (window) 260 [defaultCenter removeObserver:self name:UIWindowDidMoveToScreenNotification object:window]; 261 262 if (newWindow) 263 [defaultCenter addObserver:self selector:@selector(_windowDidMoveToScreenNotification:) name:UIWindowDidMoveToScreenNotification object:newWindow]; 264} 265 266- (void)didMoveToWindow 267{ 268 if (self.window) 269 [self _updateForScreen:self.window.screen]; 270 _page->viewStateDidChange(ViewState::AllFlags); 271} 272 273- (WKBrowsingContextController *)browsingContextController 274{ 275 if (!_browsingContextController) 276 _browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(_page.get())]); 277 278 return _browsingContextController.get(); 279} 280 281- (WKPageRef)_pageRef 282{ 283 return toAPI(_page.get()); 284} 285 286- (BOOL)isAssistingNode 287{ 288 return [self isEditable]; 289} 290 291- (BOOL)isBackground 292{ 293 return _isBackground; 294} 295 296- (void)_showInspectorHighlight:(const WebCore::Highlight&)highlight 297{ 298 if (!_inspectorHighlightView) { 299 _inspectorHighlightView = adoptNS([[WKInspectorHighlightView alloc] initWithFrame:CGRectZero]); 300 [self insertSubview:_inspectorHighlightView.get() aboveSubview:_rootContentView.get()]; 301 } 302 303 [_inspectorHighlightView update:highlight]; 304} 305 306- (void)_hideInspectorHighlight 307{ 308 if (_inspectorHighlightView) { 309 [_inspectorHighlightView removeFromSuperview]; 310 _inspectorHighlightView = nil; 311 } 312} 313 314- (BOOL)isShowingInspectorIndication 315{ 316 return !!_inspectorIndicationView; 317} 318 319- (void)setShowingInspectorIndication:(BOOL)show 320{ 321 if (show) { 322 if (!_inspectorIndicationView) { 323 _inspectorIndicationView = adoptNS([[WKInspectorIndicationView alloc] initWithFrame:[self bounds]]); 324 [_inspectorIndicationView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 325 [self insertSubview:_inspectorIndicationView.get() aboveSubview:_rootContentView.get()]; 326 } 327 } else { 328 if (_inspectorIndicationView) { 329 [_inspectorIndicationView removeFromSuperview]; 330 _inspectorIndicationView = nil; 331 } 332 } 333} 334 335- (void)updateFixedClippingView:(FloatRect)fixedPositionRectForUI 336{ 337 FloatRect clippingBounds = [self bounds]; 338 clippingBounds.unite(fixedPositionRectForUI); 339 340 [_fixedClippingView setCenter:clippingBounds.location()]; // Not really the center since we set an anchor point. 341 [_fixedClippingView setBounds:clippingBounds]; 342} 343 344- (void)didUpdateVisibleRect:(CGRect)visibleRect unobscuredRect:(CGRect)unobscuredRect unobscuredRectInScrollViewCoordinates:(CGRect)unobscuredRectInScrollViewCoordinates 345 scale:(CGFloat)zoomScale minimumScale:(CGFloat)minimumScale inStableState:(BOOL)isStableState isChangingObscuredInsetsInteractively:(BOOL)isChangingObscuredInsetsInteractively 346{ 347 double timestamp = monotonicallyIncreasingTime(); 348 HistoricalVelocityData::VelocityData velocityData; 349 if (!isStableState) 350 velocityData = _historicalKinematicData.velocityForNewData(visibleRect.origin, zoomScale, timestamp); 351 else 352 _historicalKinematicData.clear(); 353 354 FloatRect fixedPositionRectForLayout = _page->computeCustomFixedPositionRect(unobscuredRect, zoomScale, WebPageProxy::UnobscuredRectConstraint::ConstrainedToDocumentRect); 355 _page->updateVisibleContentRects(visibleRect, unobscuredRect, unobscuredRectInScrollViewCoordinates, fixedPositionRectForLayout, 356 zoomScale, isStableState, isChangingObscuredInsetsInteractively, timestamp, velocityData.horizontalVelocity, velocityData.verticalVelocity, velocityData.scaleChangeRate); 357 358 RemoteScrollingCoordinatorProxy* scrollingCoordinator = _page->scrollingCoordinatorProxy(); 359 FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), zoomScale); 360 scrollingCoordinator->viewportChangedViaDelegatedScrolling(scrollingCoordinator->rootScrollingNodeID(), fixedPositionRect, zoomScale); 361 362 if (auto drawingArea = _page->drawingArea()) 363 drawingArea->updateDebugIndicator(); 364 365 [self updateFixedClippingView:fixedPositionRect]; 366} 367 368- (void)didFinishScrolling 369{ 370 [self _didEndScrollingOrZooming]; 371} 372 373- (void)willStartZoomOrScroll 374{ 375 [self _willStartScrollingOrZooming]; 376} 377 378- (void)didZoomToScale:(CGFloat)scale 379{ 380 [self _didEndScrollingOrZooming]; 381} 382 383#pragma mark Internal 384 385- (void)_windowDidMoveToScreenNotification:(NSNotification *)notification 386{ 387 ASSERT(notification.object == self.window); 388 389 UIScreen *screen = notification.userInfo[UIWindowNewScreenUserInfoKey]; 390 [self _updateForScreen:screen]; 391} 392 393- (void)_updateForScreen:(UIScreen *)screen 394{ 395 ASSERT(screen); 396 _page->setIntrinsicDeviceScaleFactor(WKGetScaleFactorForScreen(screen)); 397 [self _accessibilityRegisterUIProcessTokens]; 398} 399 400- (void)_setAccessibilityWebProcessToken:(NSData *)data 401{ 402 // This means the web process has checked in and we should send information back to that process. 403 [self _accessibilityRegisterUIProcessTokens]; 404} 405 406- (void)_accessibilityRegisterUIProcessTokens 407{ 408 RetainPtr<CFUUIDRef> uuid = adoptCF(CFUUIDCreate(kCFAllocatorDefault)); 409 NSData *remoteElementToken = WKAXRemoteToken(uuid.get()); 410 411 // Store information about the WebProcess that can later be retrieved by the iOS Accessibility runtime. 412 if (_page->process().state() == WebProcessProxy::State::Running) { 413 IPC::Connection* connection = _page->process().connection(); 414 WKAXStoreRemoteConnectionInformation(self, _page->process().processIdentifier(), connection->identifier().port, uuid.get()); 415 416 IPC::DataReference elementToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteElementToken bytes]), [remoteElementToken length]); 417 _page->registerUIProcessAccessibilityTokens(elementToken, elementToken); 418 } 419} 420 421#pragma mark PageClientImpl methods 422 423- (std::unique_ptr<DrawingAreaProxy>)_createDrawingAreaProxy 424{ 425 return std::make_unique<RemoteLayerTreeDrawingAreaProxy>(_page.get()); 426} 427 428- (void)_processDidExit 429{ 430 [self cleanupInteraction]; 431 432 [self setShowingInspectorIndication:NO]; 433 [self _hideInspectorHighlight]; 434} 435 436- (void)_didRelaunchProcess 437{ 438 [self _accessibilityRegisterUIProcessTokens]; 439 [self setupInteraction]; 440} 441 442- (void)_didCommitLoadForMainFrame 443{ 444 [self _stopAssistingNode]; 445 [_webView _didCommitLoadForMainFrame]; 446} 447 448- (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction 449{ 450 CGSize contentsSize = layerTreeTransaction.contentsSize(); 451 CGRect contentBounds = { CGPointZero, contentsSize }; 452 CGRect oldBounds = [self bounds]; 453 454 BOOL boundsChanged = !CGRectEqualToRect(oldBounds, contentBounds); 455 if (boundsChanged) 456 [self setBounds:contentBounds]; 457 458 [_webView _didCommitLayerTree:layerTreeTransaction]; 459 460 if (boundsChanged) { 461 FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), [[_webView scrollView] zoomScale]); 462 [self updateFixedClippingView:fixedPositionRect]; 463 } 464 465 [self _updateChangedSelection]; 466} 467 468- (void)_setAcceleratedCompositingRootView:(UIView *)rootView 469{ 470 for (UIView* subview in [_rootContentView subviews]) 471 [subview removeFromSuperview]; 472 473 [_rootContentView addSubview:rootView]; 474} 475 476- (void)_decidePolicyForGeolocationRequestFromOrigin:(WebSecurityOrigin&)origin frame:(WebFrameProxy&)frame request:(GeolocationPermissionRequestProxy&)permissionRequest 477{ 478 [[wrapper(_page->process().context()) _geolocationProvider] decidePolicyForGeolocationRequestFromOrigin:toAPI(&origin) frame:toAPI(&frame) request:toAPI(&permissionRequest) window:[self window]]; 479} 480 481- (BOOL)_scrollToRect:(CGRect)targetRect withOrigin:(CGPoint)origin minimumScrollDistance:(CGFloat)minimumScrollDistance 482{ 483 return [_webView _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance]; 484} 485 486- (void)_zoomToFocusRect:(CGRect)rectToFocus selectionRect:(CGRect)selectionRect fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll 487{ 488 [_webView _zoomToFocusRect:rectToFocus 489 selectionRect:selectionRect 490 fontSize:fontSize 491 minimumScale:minimumScale 492 maximumScale:maximumScale 493 allowScaling:allowScaling 494 forceScroll:forceScroll]; 495} 496 497- (BOOL)_zoomToRect:(CGRect)targetRect withOrigin:(CGPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(CGFloat)minimumScrollDistance 498{ 499 return [_webView _zoomToRect:targetRect withOrigin:origin fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale minimumScrollDistance:minimumScrollDistance]; 500} 501 502- (void)_zoomOutWithOrigin:(CGPoint)origin 503{ 504 return [_webView _zoomOutWithOrigin:origin]; 505} 506 507- (void)_applicationWillResignActive:(NSNotification*)notification 508{ 509 _page->applicationWillResignActive(); 510} 511 512- (void)_applicationDidEnterBackground:(NSNotification*)notification 513{ 514 _isBackground = YES; 515 _page->viewStateDidChange(ViewState::AllFlags & ~ViewState::IsInWindow); 516} 517 518- (void)_applicationWillEnterForeground:(NSNotification*)notification 519{ 520 _isBackground = NO; 521 _page->applicationWillEnterForeground(); 522 if (auto drawingArea = _page->drawingArea()) 523 drawingArea->hideContentUntilNextUpdate(); 524 _page->viewStateDidChange(ViewState::AllFlags & ~ViewState::IsInWindow, true, WebPageProxy::ViewStateChangeDispatchMode::Immediate); 525} 526 527- (void)_applicationDidBecomeActive:(NSNotification*)notification 528{ 529 _page->applicationDidBecomeActive(); 530} 531 532@end 533 534#endif // PLATFORM(IOS) 535