| /* |
| * Copyright (C) 2013-2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "WKContentViewInteraction.h" |
| |
| #if PLATFORM(IOS) |
| |
| #import "APIPageConfiguration.h" |
| #import "AccessibilityIOS.h" |
| #import "ApplicationStateTracker.h" |
| #import "InputViewUpdateDeferrer.h" |
| #import "Logging.h" |
| #import "PageClientImplIOS.h" |
| #import "PrintInfo.h" |
| #import "RemoteLayerTreeDrawingAreaProxy.h" |
| #import "RemoteScrollingCoordinatorProxy.h" |
| #import "SmartMagnificationController.h" |
| #import "UIKitSPI.h" |
| #import "WKBrowsingContextControllerInternal.h" |
| #import "WKBrowsingContextGroupPrivate.h" |
| #import "WKInspectorHighlightView.h" |
| #import "WKPreferencesInternal.h" |
| #import "WKProcessGroupPrivate.h" |
| #import "WKWebViewConfiguration.h" |
| #import "WKWebViewInternal.h" |
| #import "WebFrameProxy.h" |
| #import "WebKit2Initialize.h" |
| #import "WebPageGroup.h" |
| #import "WebProcessPool.h" |
| #import "WebSystemInterface.h" |
| #import "_WKFrameHandleInternal.h" |
| #import "_WKWebViewPrintFormatterInternal.h" |
| #import <CoreGraphics/CoreGraphics.h> |
| #import <WebCore/FloatQuad.h> |
| #import <WebCore/FrameView.h> |
| #import <WebCore/InspectorOverlay.h> |
| #import <WebCore/NotImplemented.h> |
| #import <WebCore/PlatformScreen.h> |
| #import <WebCore/QuartzCoreSPI.h> |
| #import <WebCore/TextStream.h> |
| #import <wtf/CurrentTime.h> |
| #import <wtf/RetainPtr.h> |
| |
| using namespace WebCore; |
| using namespace WebKit; |
| |
| namespace WebKit { |
| class HistoricalVelocityData { |
| public: |
| struct VelocityData { |
| VelocityData() |
| : horizontalVelocity(0) |
| , verticalVelocity(0) |
| , scaleChangeRate(0) |
| { |
| } |
| |
| VelocityData(double horizontalVelocity, double verticalVelocity, double scaleChangeRate) |
| : horizontalVelocity(horizontalVelocity) |
| , verticalVelocity(verticalVelocity) |
| , scaleChangeRate(scaleChangeRate) |
| { |
| } |
| |
| double horizontalVelocity; |
| double verticalVelocity; |
| double scaleChangeRate; |
| }; |
| |
| HistoricalVelocityData() |
| : m_historySize(0) |
| , m_latestDataIndex(0) |
| { |
| } |
| |
| VelocityData velocityForNewData(CGPoint newPosition, double scale, MonotonicTime timestamp) |
| { |
| // Due to all the source of rect update, the input is very noisy. To smooth the output, we accumulate all changes |
| // within 1 frame as a single update. No speed computation is ever done on data within the same frame. |
| const Seconds filteringThreshold(1.0 / 60); |
| |
| VelocityData velocityData; |
| if (m_historySize > 0) { |
| unsigned oldestDataIndex; |
| unsigned distanceToLastHistoricalData = m_historySize - 1; |
| if (distanceToLastHistoricalData <= m_latestDataIndex) |
| oldestDataIndex = m_latestDataIndex - distanceToLastHistoricalData; |
| else |
| oldestDataIndex = m_historySize - (distanceToLastHistoricalData - m_latestDataIndex); |
| |
| Seconds timeDelta = timestamp - m_history[oldestDataIndex].timestamp; |
| if (timeDelta > filteringThreshold) { |
| Data& oldestData = m_history[oldestDataIndex]; |
| velocityData = VelocityData((newPosition.x - oldestData.position.x) / timeDelta.seconds(), (newPosition.y - oldestData.position.y) / timeDelta.seconds(), (scale - oldestData.scale) / timeDelta.seconds()); |
| } |
| } |
| |
| Seconds timeSinceLastAppend = timestamp - m_lastAppendTimestamp; |
| if (timeSinceLastAppend > filteringThreshold) |
| append(newPosition, scale, timestamp); |
| else |
| m_history[m_latestDataIndex] = { timestamp, newPosition, scale }; |
| return velocityData; |
| } |
| |
| void clear() { m_historySize = 0; } |
| |
| private: |
| void append(CGPoint newPosition, double scale, MonotonicTime timestamp) |
| { |
| m_latestDataIndex = (m_latestDataIndex + 1) % maxHistoryDepth; |
| m_history[m_latestDataIndex] = { timestamp, newPosition, scale }; |
| |
| unsigned size = m_historySize + 1; |
| if (size <= maxHistoryDepth) |
| m_historySize = size; |
| |
| m_lastAppendTimestamp = timestamp; |
| } |
| |
| |
| static const unsigned maxHistoryDepth = 3; |
| |
| unsigned m_historySize; |
| unsigned m_latestDataIndex; |
| MonotonicTime m_lastAppendTimestamp; |
| |
| struct Data { |
| MonotonicTime timestamp; |
| CGPoint position; |
| double scale; |
| } m_history[maxHistoryDepth]; |
| }; |
| } // namespace WebKit |
| |
| @interface WKInspectorIndicationView : UIView |
| @end |
| |
| @implementation WKInspectorIndicationView |
| |
| - (instancetype)initWithFrame:(CGRect)frame |
| { |
| if (!(self = [super initWithFrame:frame])) |
| return nil; |
| self.userInteractionEnabled = NO; |
| self.backgroundColor = [UIColor colorWithRed:(111.0 / 255.0) green:(168.0 / 255.0) blue:(220.0 / 255.0) alpha:0.66f]; |
| return self; |
| } |
| |
| @end |
| |
| @implementation WKContentView { |
| std::unique_ptr<PageClientImpl> _pageClient; |
| RetainPtr<WKBrowsingContextController> _browsingContextController; |
| |
| RetainPtr<UIView> _rootContentView; |
| RetainPtr<UIView> _fixedClippingView; |
| RetainPtr<WKInspectorIndicationView> _inspectorIndicationView; |
| RetainPtr<WKInspectorHighlightView> _inspectorHighlightView; |
| |
| HistoricalVelocityData _historicalKinematicData; |
| |
| RetainPtr<NSUndoManager> _undoManager; |
| |
| std::unique_ptr<ApplicationStateTracker> _applicationStateTracker; |
| |
| BOOL _isPrintingToPDF; |
| RetainPtr<CGPDFDocumentRef> _printedDocument; |
| } |
| |
| - (instancetype)_commonInitializationWithProcessPool:(WebKit::WebProcessPool&)processPool configuration:(Ref<API::PageConfiguration>&&)configuration |
| { |
| ASSERT(_pageClient); |
| |
| _page = processPool.createWebPage(*_pageClient, WTFMove(configuration)); |
| _page->initializeWebPage(); |
| _page->setIntrinsicDeviceScaleFactor(screenScaleFactor([UIScreen mainScreen])); |
| _page->setUseFixedLayout(true); |
| _page->setDelegatesScrolling(true); |
| |
| WebProcessPool::statistics().wkViewCount++; |
| |
| _rootContentView = adoptNS([[UIView alloc] init]); |
| [_rootContentView layer].name = @"RootContent"; |
| [_rootContentView layer].masksToBounds = NO; |
| [_rootContentView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; |
| |
| _fixedClippingView = adoptNS([[UIView alloc] init]); |
| [_fixedClippingView layer].name = @"FixedClipping"; |
| [_fixedClippingView layer].masksToBounds = YES; |
| [_fixedClippingView layer].anchorPoint = CGPointZero; |
| |
| [self addSubview:_fixedClippingView.get()]; |
| [_fixedClippingView addSubview:_rootContentView.get()]; |
| |
| [self setupInteraction]; |
| [self setUserInteractionEnabled:YES]; |
| |
| self.layer.hitTestsAsOpaque = YES; |
| |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]]; |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:[UIApplication sharedApplication]]; |
| |
| return self; |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame processPool:(WebKit::WebProcessPool&)processPool configuration:(Ref<API::PageConfiguration>&&)configuration webView:(WKWebView *)webView |
| { |
| if (!(self = [super initWithFrame:frame])) |
| return nil; |
| |
| InitializeWebKit2(); |
| |
| _pageClient = std::make_unique<PageClientImpl>(self, webView); |
| _webView = webView; |
| |
| return [self _commonInitializationWithProcessPool:processPool configuration:WTFMove(configuration)]; |
| } |
| |
| - (void)dealloc |
| { |
| [self cleanupInteraction]; |
| |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| _page->close(); |
| |
| WebProcessPool::statistics().wkViewCount--; |
| |
| [super dealloc]; |
| } |
| |
| - (WebPageProxy*)page |
| { |
| return _page.get(); |
| } |
| |
| - (void)willMoveToWindow:(UIWindow *)newWindow |
| { |
| NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; |
| UIWindow *window = self.window; |
| |
| if (window) { |
| [defaultCenter removeObserver:self name:UIWindowDidMoveToScreenNotification object:window]; |
| |
| if (!newWindow) { |
| ASSERT(_applicationStateTracker); |
| _applicationStateTracker = nullptr; |
| } |
| } |
| |
| if (newWindow) { |
| [defaultCenter addObserver:self selector:@selector(_windowDidMoveToScreenNotification:) name:UIWindowDidMoveToScreenNotification object:newWindow]; |
| |
| [self _updateForScreen:newWindow.screen]; |
| } |
| } |
| |
| - (void)didMoveToWindow |
| { |
| if (!self.window) |
| return; |
| |
| ASSERT(!_applicationStateTracker); |
| _applicationStateTracker = std::make_unique<ApplicationStateTracker>(self, @selector(_applicationDidEnterBackground), @selector(_applicationDidCreateWindowContext), @selector(_applicationDidFinishSnapshottingAfterEnteringBackground), @selector(_applicationWillEnterForeground)); |
| } |
| |
| - (WKBrowsingContextController *)browsingContextController |
| { |
| if (!_browsingContextController) |
| _browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(_page.get())]); |
| |
| return _browsingContextController.get(); |
| } |
| |
| - (WKPageRef)_pageRef |
| { |
| return toAPI(_page.get()); |
| } |
| |
| - (BOOL)isAssistingNode |
| { |
| return [self isEditable]; |
| } |
| |
| - (BOOL)isBackground |
| { |
| if (!_applicationStateTracker) |
| return YES; |
| |
| return _applicationStateTracker->isInBackground(); |
| } |
| |
| - (void)_showInspectorHighlight:(const WebCore::Highlight&)highlight |
| { |
| if (!_inspectorHighlightView) { |
| _inspectorHighlightView = adoptNS([[WKInspectorHighlightView alloc] initWithFrame:CGRectZero]); |
| [self insertSubview:_inspectorHighlightView.get() aboveSubview:_rootContentView.get()]; |
| } |
| |
| [_inspectorHighlightView update:highlight]; |
| } |
| |
| - (void)_hideInspectorHighlight |
| { |
| if (_inspectorHighlightView) { |
| [_inspectorHighlightView removeFromSuperview]; |
| _inspectorHighlightView = nil; |
| } |
| } |
| |
| - (BOOL)isShowingInspectorIndication |
| { |
| return !!_inspectorIndicationView; |
| } |
| |
| - (void)setShowingInspectorIndication:(BOOL)show |
| { |
| if (show) { |
| if (!_inspectorIndicationView) { |
| _inspectorIndicationView = adoptNS([[WKInspectorIndicationView alloc] initWithFrame:[self bounds]]); |
| [_inspectorIndicationView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; |
| [self insertSubview:_inspectorIndicationView.get() aboveSubview:_rootContentView.get()]; |
| } |
| } else { |
| if (_inspectorIndicationView) { |
| [_inspectorIndicationView removeFromSuperview]; |
| _inspectorIndicationView = nil; |
| } |
| } |
| } |
| |
| - (void)updateFixedClippingView:(FloatRect)fixedPositionRectForUI |
| { |
| FloatRect clippingBounds = [self bounds]; |
| clippingBounds.unite(fixedPositionRectForUI); |
| |
| [_fixedClippingView setCenter:clippingBounds.location()]; // Not really the center since we set an anchor point. |
| [_fixedClippingView setBounds:clippingBounds]; |
| } |
| |
| - (void)_didExitStableState |
| { |
| _needsDeferredEndScrollingSelectionUpdate = self.shouldHideSelectionWhenScrolling; |
| if (!_needsDeferredEndScrollingSelectionUpdate) |
| return; |
| |
| [_textSelectionAssistant deactivateSelection]; |
| [[_webSelectionAssistant selectionView] setHidden:YES]; |
| } |
| |
| - (CGRect)_computeUnobscuredContentRectRespectingInputViewBounds:(CGRect)unobscuredContentRect inputViewBounds:(CGRect)inputViewBounds |
| { |
| // The input view bounds are in window coordinates, but the unobscured rect is in content coordinates. Account for this by converting input view bounds to content coordinates. |
| CGRect inputViewBoundsInContentCoordinates = [self.window convertRect:inputViewBounds toView:self]; |
| if (CGRectGetHeight(inputViewBoundsInContentCoordinates)) |
| unobscuredContentRect.size.height = std::min<float>(CGRectGetHeight(unobscuredContentRect), CGRectGetMinY(inputViewBoundsInContentCoordinates) - CGRectGetMinY(unobscuredContentRect)); |
| return unobscuredContentRect; |
| } |
| |
| - (void)didUpdateVisibleRect:(CGRect)visibleContentRect |
| unobscuredRect:(CGRect)unobscuredContentRect |
| unobscuredRectInScrollViewCoordinates:(CGRect)unobscuredRectInScrollViewCoordinates |
| obscuredInsets:(UIEdgeInsets)obscuredInsets |
| unobscuredSafeAreaInsets:(UIEdgeInsets)unobscuredSafeAreaInsets |
| inputViewBounds:(CGRect)inputViewBounds |
| scale:(CGFloat)zoomScale minimumScale:(CGFloat)minimumScale |
| inStableState:(BOOL)isStableState |
| isChangingObscuredInsetsInteractively:(BOOL)isChangingObscuredInsetsInteractively |
| enclosedInScrollableAncestorView:(BOOL)enclosedInScrollableAncestorView |
| { |
| auto drawingArea = _page->drawingArea(); |
| if (!drawingArea) |
| return; |
| |
| MonotonicTime timestamp = MonotonicTime::now(); |
| HistoricalVelocityData::VelocityData velocityData; |
| if (!isStableState) |
| velocityData = _historicalKinematicData.velocityForNewData(visibleContentRect.origin, zoomScale, timestamp); |
| else |
| _historicalKinematicData.clear(); |
| |
| RemoteScrollingCoordinatorProxy* scrollingCoordinator = _page->scrollingCoordinatorProxy(); |
| |
| CGRect unobscuredContentRectRespectingInputViewBounds = [self _computeUnobscuredContentRectRespectingInputViewBounds:unobscuredContentRect inputViewBounds:inputViewBounds]; |
| FloatRect fixedPositionRectForLayout = _page->computeCustomFixedPositionRect(unobscuredContentRect, unobscuredContentRectRespectingInputViewBounds, _page->customFixedPositionRect(), zoomScale, WebPageProxy::UnobscuredRectConstraint::ConstrainedToDocumentRect, scrollingCoordinator->visualViewportEnabled()); |
| |
| VisibleContentRectUpdateInfo visibleContentRectUpdateInfo( |
| visibleContentRect, |
| unobscuredContentRect, |
| unobscuredRectInScrollViewCoordinates, |
| unobscuredContentRectRespectingInputViewBounds, |
| fixedPositionRectForLayout, |
| WebCore::FloatBoxExtent(obscuredInsets.top, obscuredInsets.right, obscuredInsets.bottom, obscuredInsets.left), |
| WebCore::FloatBoxExtent(unobscuredSafeAreaInsets.top, unobscuredSafeAreaInsets.right, unobscuredSafeAreaInsets.bottom, unobscuredSafeAreaInsets.left), |
| zoomScale, |
| isStableState, |
| _sizeChangedSinceLastVisibleContentRectUpdate, |
| isChangingObscuredInsetsInteractively, |
| _webView._allowsViewportShrinkToFit, |
| enclosedInScrollableAncestorView, |
| timestamp, |
| velocityData.horizontalVelocity, |
| velocityData.verticalVelocity, |
| velocityData.scaleChangeRate, |
| downcast<RemoteLayerTreeDrawingAreaProxy>(*drawingArea).lastCommittedLayerTreeTransactionID()); |
| |
| LOG_WITH_STREAM(VisibleRects, stream << "-[WKContentView didUpdateVisibleRect]" << visibleContentRectUpdateInfo.dump()); |
| |
| bool wasStableState = _page->inStableState(); |
| _page->updateVisibleContentRects(visibleContentRectUpdateInfo); |
| |
| _sizeChangedSinceLastVisibleContentRectUpdate = NO; |
| |
| FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), _page->unobscuredContentRectRespectingInputViewBounds(), _page->customFixedPositionRect(), zoomScale, WebPageProxy::UnobscuredRectConstraint::Unconstrained, scrollingCoordinator->visualViewportEnabled()); |
| scrollingCoordinator->viewportChangedViaDelegatedScrolling(scrollingCoordinator->rootScrollingNodeID(), fixedPositionRect, zoomScale); |
| |
| drawingArea->updateDebugIndicator(); |
| |
| [self updateFixedClippingView:fixedPositionRect]; |
| |
| if (wasStableState && !isStableState) |
| [self _didExitStableState]; |
| } |
| |
| - (void)didFinishScrolling |
| { |
| [self _didEndScrollingOrZooming]; |
| } |
| |
| - (void)didInterruptScrolling |
| { |
| _historicalKinematicData.clear(); |
| } |
| |
| - (void)willStartZoomOrScroll |
| { |
| [self _willStartScrollingOrZooming]; |
| } |
| |
| - (void)didZoomToScale:(CGFloat)scale |
| { |
| [self _didEndScrollingOrZooming]; |
| } |
| |
| - (NSUndoManager *)undoManager |
| { |
| if (!_undoManager) |
| _undoManager = adoptNS([[NSUndoManager alloc] init]); |
| |
| return _undoManager.get(); |
| } |
| |
| #pragma mark Internal |
| |
| - (void)_windowDidMoveToScreenNotification:(NSNotification *)notification |
| { |
| ASSERT(notification.object == self.window); |
| |
| UIScreen *screen = notification.userInfo[UIWindowNewScreenUserInfoKey]; |
| [self _updateForScreen:screen]; |
| } |
| |
| - (void)_updateForScreen:(UIScreen *)screen |
| { |
| ASSERT(screen); |
| _page->setIntrinsicDeviceScaleFactor(screenScaleFactor(screen)); |
| [self _accessibilityRegisterUIProcessTokens]; |
| } |
| |
| - (void)_setAccessibilityWebProcessToken:(NSData *)data |
| { |
| // This means the web process has checked in and we should send information back to that process. |
| [self _accessibilityRegisterUIProcessTokens]; |
| } |
| |
| static void storeAccessibilityRemoteConnectionInformation(id element, pid_t pid, mach_port_t sendPort, NSUUID *uuid) |
| { |
| // The accessibility bundle needs to know the uuid, pid and mach_port that this object will refer to. |
| objc_setAssociatedObject(element, (void*)[@"ax-uuid" hash], uuid, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| objc_setAssociatedObject(element, (void*)[@"ax-pid" hash], @(pid), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| objc_setAssociatedObject(element, (void*)[@"ax-machport" hash], @(sendPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| } |
| |
| - (void)_accessibilityRegisterUIProcessTokens |
| { |
| auto uuid = [NSUUID UUID]; |
| NSData *remoteElementToken = newAccessibilityRemoteToken(uuid); |
| |
| // Store information about the WebProcess that can later be retrieved by the iOS Accessibility runtime. |
| if (_page->process().state() == WebProcessProxy::State::Running) { |
| IPC::Connection* connection = _page->process().connection(); |
| storeAccessibilityRemoteConnectionInformation(self, _page->process().processIdentifier(), connection->identifier().port, uuid); |
| |
| IPC::DataReference elementToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteElementToken bytes]), [remoteElementToken length]); |
| _page->registerUIProcessAccessibilityTokens(elementToken, elementToken); |
| } |
| } |
| |
| - (void)_webViewDestroyed |
| { |
| _webView = nil; |
| } |
| |
| #pragma mark PageClientImpl methods |
| |
| - (std::unique_ptr<DrawingAreaProxy>)_createDrawingAreaProxy |
| { |
| return std::make_unique<RemoteLayerTreeDrawingAreaProxy>(*_page); |
| } |
| |
| - (void)_processDidExit |
| { |
| [self cleanupInteraction]; |
| |
| [self setShowingInspectorIndication:NO]; |
| [self _hideInspectorHighlight]; |
| } |
| |
| - (void)_didRelaunchProcess |
| { |
| [self _accessibilityRegisterUIProcessTokens]; |
| [self setupInteraction]; |
| } |
| |
| - (void)_didCommitLoadForMainFrame |
| { |
| [self _stopAssistingNode]; |
| [self _cancelLongPressGestureRecognizer]; |
| [_webView _didCommitLoadForMainFrame]; |
| } |
| |
| - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction |
| { |
| CGSize contentsSize = layerTreeTransaction.contentsSize(); |
| CGPoint scrollOrigin = -layerTreeTransaction.scrollOrigin(); |
| CGRect contentBounds = { scrollOrigin, contentsSize }; |
| |
| BOOL boundsChanged = !CGRectEqualToRect([self bounds], contentBounds); |
| if (boundsChanged) |
| [self setBounds:contentBounds]; |
| |
| [_webView _didCommitLayerTree:layerTreeTransaction]; |
| |
| if (_interactionViewsContainerView) { |
| FloatPoint scaledOrigin = layerTreeTransaction.scrollOrigin(); |
| float scale = [[_webView scrollView] zoomScale]; |
| scaledOrigin.scale(scale); |
| [_interactionViewsContainerView setFrame:CGRectMake(scaledOrigin.x(), scaledOrigin.y(), 0, 0)]; |
| } |
| |
| if (boundsChanged) { |
| // FIXME: factor computeCustomFixedPositionRect() into something that gives us this rect. |
| FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), _page->unobscuredContentRectRespectingInputViewBounds(), _page->customFixedPositionRect(), [[_webView scrollView] zoomScale]); |
| [self updateFixedClippingView:fixedPositionRect]; |
| |
| // We need to push the new content bounds to the webview to update fixed position rects. |
| [_webView _scheduleVisibleContentRectUpdate]; |
| } |
| |
| // Updating the selection requires a full editor state. If the editor state is missing post layout |
| // data then it means there is a layout pending and we're going to be called again after the layout |
| // so we delay the selection update. |
| if (!_page->editorState().isMissingPostLayoutData) |
| [self _updateChangedSelection]; |
| } |
| |
| - (void)_layerTreeCommitComplete |
| { |
| [_webView _layerTreeCommitComplete]; |
| } |
| |
| - (void)_setAcceleratedCompositingRootView:(UIView *)rootView |
| { |
| for (UIView* subview in [_rootContentView subviews]) |
| [subview removeFromSuperview]; |
| |
| [_rootContentView addSubview:rootView]; |
| } |
| |
| - (BOOL)_scrollToRect:(CGRect)targetRect withOrigin:(CGPoint)origin minimumScrollDistance:(CGFloat)minimumScrollDistance |
| { |
| return [_webView _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance]; |
| } |
| |
| - (void)_zoomToFocusRect:(CGRect)rectToFocus selectionRect:(CGRect)selectionRect insideFixed:(BOOL)insideFixed fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll |
| { |
| [_webView _zoomToFocusRect:rectToFocus |
| selectionRect:selectionRect |
| insideFixed:insideFixed |
| fontSize:fontSize |
| minimumScale:minimumScale |
| maximumScale:maximumScale |
| allowScaling:allowScaling |
| forceScroll:forceScroll]; |
| } |
| |
| - (BOOL)_zoomToRect:(CGRect)targetRect withOrigin:(CGPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(CGFloat)minimumScrollDistance |
| { |
| return [_webView _zoomToRect:targetRect withOrigin:origin fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale minimumScrollDistance:minimumScrollDistance]; |
| } |
| |
| - (void)_zoomOutWithOrigin:(CGPoint)origin |
| { |
| return [_webView _zoomOutWithOrigin:origin animated:YES]; |
| } |
| |
| - (void)_zoomToInitialScaleWithOrigin:(CGPoint)origin |
| { |
| return [_webView _zoomToInitialScaleWithOrigin:origin animated:YES]; |
| } |
| |
| - (void)_applicationWillResignActive:(NSNotification*)notification |
| { |
| _page->applicationWillResignActive(); |
| } |
| |
| - (void)_applicationDidEnterBackground |
| { |
| _page->applicationDidEnterBackground(); |
| _page->activityStateDidChange(ActivityState::AllFlags & ~ActivityState::IsInWindow); |
| } |
| |
| - (void)_applicationDidCreateWindowContext |
| { |
| if (auto drawingArea = _page->drawingArea()) |
| drawingArea->hideContentUntilAnyUpdate(); |
| } |
| |
| - (void)_applicationDidFinishSnapshottingAfterEnteringBackground |
| { |
| _page->applicationDidFinishSnapshottingAfterEnteringBackground(); |
| } |
| |
| - (void)_applicationWillEnterForeground |
| { |
| _page->applicationWillEnterForeground(); |
| _page->activityStateDidChange(ActivityState::AllFlags & ~ActivityState::IsInWindow, true, WebPageProxy::ActivityStateChangeDispatchMode::Immediate); |
| } |
| |
| - (void)_applicationDidBecomeActive:(NSNotification*)notification |
| { |
| _page->applicationDidBecomeActive(); |
| } |
| |
| @end |
| |
| #pragma mark Printing |
| |
| @interface WKContentView (_WKWebViewPrintFormatter) <_WKWebViewPrintProvider> |
| @end |
| |
| @implementation WKContentView (_WKWebViewPrintFormatter) |
| |
| - (NSUInteger)_wk_pageCountForPrintFormatter:(_WKWebViewPrintFormatter *)printFormatter |
| { |
| if (_isPrintingToPDF) |
| return 0; |
| |
| uint64_t frameID; |
| if (_WKFrameHandle *handle = printFormatter.frameToPrint) |
| frameID = handle._frameID; |
| else if (auto mainFrame = _page->mainFrame()) |
| frameID = mainFrame->frameID(); |
| else |
| return 0; |
| |
| // The first page can have a smaller content rect than subsequent pages if a top content inset |
| // is specified. Since WebKit requires a uniform content rect for each page during layout, use |
| // the intersection of the first and non-first page rects. |
| // FIXME: Teach WebCore::PrintContext to accept an initial content offset when paginating. |
| CGRect printingRect = CGRectIntersection([printFormatter _pageContentRect:YES], [printFormatter _pageContentRect:NO]); |
| if (CGRectIsEmpty(printingRect)) |
| return 0; |
| |
| PrintInfo printInfo; |
| printInfo.pageSetupScaleFactor = 1; |
| printInfo.snapshotFirstPage = printFormatter.snapshotFirstPage; |
| if (printInfo.snapshotFirstPage) { |
| static const CGFloat maximumPDFHeight = 200 * 72; // maximum PDF height for a single page is 200 inches |
| printingRect = (CGRect) { CGPointZero, { self.bounds.size.width, std::min(_webView.scrollView.contentSize.height, maximumPDFHeight) } }; |
| [printFormatter _setSnapshotPaperRect:printingRect]; |
| } |
| printInfo.availablePaperWidth = CGRectGetWidth(printingRect); |
| printInfo.availablePaperHeight = CGRectGetHeight(printingRect); |
| |
| _isPrintingToPDF = YES; |
| auto retainedSelf = retainPtr(self); |
| return _page->computePagesForPrintingAndDrawToPDF(frameID, printInfo, [retainedSelf](const IPC::DataReference& pdfData, CallbackBase::Error error) { |
| retainedSelf->_isPrintingToPDF = NO; |
| if (error != CallbackBase::Error::None) |
| return; |
| |
| auto data = adoptCF(CFDataCreate(kCFAllocatorDefault, pdfData.data(), pdfData.size())); |
| auto dataProvider = adoptCF(CGDataProviderCreateWithCFData(data.get())); |
| retainedSelf->_printedDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get())); |
| }); |
| } |
| |
| - (CGPDFDocumentRef)_wk_printedDocument |
| { |
| if (_isPrintingToPDF) { |
| if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DrawToPDFCallback>(_page->pageID(), Seconds::infinity())) { |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| ASSERT(!_isPrintingToPDF); |
| } |
| |
| return _printedDocument.autorelease(); |
| } |
| |
| @end |
| |
| #endif // PLATFORM(IOS) |