WKContentView.mm   [plain text]

/*
* 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_FAMILY)

#import "APIPageConfiguration.h"
#import "AccessibilityIOS.h"
#import "FullscreenClient.h"
#import "GPUProcessProxy.h"
#import "InputViewUpdateDeferrer.h"
#import "Logging.h"
#import "PageClientImplIOS.h"
#import "PrintInfo.h"
#import "RemoteLayerTreeDrawingAreaProxy.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 "WKWebViewIOS.h"
#import "WebFrameProxy.h"
#import "WebKit2Initialize.h"
#import "WebPageGroup.h"
#import "WebPageProxyMessages.h"
#import "WebProcessPool.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/Quirks.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebCore/VelocityData.h>
#import <WebCore/VersionChecks.h>
#import <objc/message.h>
#import <pal/spi/cocoa/NSAccessibilitySPI.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/TextStream.h>
#import "AppKitSoftLink.h"

@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

@interface WKQuirkyNSUndoManager : NSUndoManager
@property (readonly, weak) WKContentView *contentView;
@end

@implementation WKQuirkyNSUndoManager
- (instancetype)initWithContentView:(WKContentView *)contentView
{
if (!(self = [super init]))
return nil;
_contentView = contentView;
return self;
}

- (BOOL)canUndo
{
return YES;
}

- (BOOL)canRedo
{
return YES;
}

- (void)undo
{
[self.contentView generateSyntheticEditingCommand:WebKit::SyntheticEditingCommandType::Undo];
}

- (void)redo
{
[self.contentView generateSyntheticEditingCommand:WebKit::SyntheticEditingCommandType::Redo];
}

@end

@implementation WKContentView {
std::unique_ptr<WebKit::PageClientImpl> _pageClient;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
RetainPtr<WKBrowsingContextController> _browsingContextController;
ALLOW_DEPRECATED_DECLARATIONS_END

RetainPtr<UIView> _rootContentView;
RetainPtr<UIView> _fixedClippingView;
RetainPtr<WKInspectorIndicationView> _inspectorIndicationView;
RetainPtr<WKInspectorHighlightView> _inspectorHighlightView;

#if HAVE(VISIBILITY_PROPAGATION_VIEW)
RetainPtr<_UILayerHostView> _visibilityPropagationView;
RetainPtr<_UILayerHostView> _visibilityPropagationViewForGPUProcess;
#endif

WebCore::HistoricalVelocityData _historicalKinematicData;

RetainPtr<NSUndoManager> _undoManager;
RetainPtr<WKQuirkyNSUndoManager> _quirkyUndoManager;

BOOL _isPrintingToPDF;
RetainPtr<CGPDFDocumentRef> _printedDocument;
}

#if USE(UIKIT_KEYBOARD_ADDITIONS)

// Evernote expects to swizzle -keyCommands on WKContentView or they crash. Remove this hack
// as soon as reasonably possible. See <rdar://problem/51759247>.
static NSArray *keyCommandsPlaceholderHackForEvernote(id self, SEL _cmd)
{
struct objc_super super = { self, class_getSuperclass(object_getClass(self)) };
using SuperKeyCommandsFunction = NSArray *(*)(struct objc_super*, SEL);
return reinterpret_cast<SuperKeyCommandsFunction>(&objc_msgSendSuper)(&super, @selector(keyCommands));
}

#endif

- (instancetype)_commonInitializationWithProcessPool:(WebKit::WebProcessPool&)processPool configuration:(Ref<API::PageConfiguration>&&)configuration
{
ASSERT(_pageClient);

_page = processPool.createWebPage(*_pageClient, WTFMove(configuration));
_page->initializeWebPage();
_page->setIntrinsicDeviceScaleFactor(WebCore::screenScaleFactor([UIScreen mainScreen]));
_page->setUseFixedLayout(true);
_page->setDelegatesScrolling(true);
_page->setScreenIsBeingCaptured([[[self window] screen] isCaptured]);

#if ENABLE(FULLSCREEN_API)
_page->setFullscreenClient(makeUnique<WebKit::FullscreenClient>(self.webView));
#endif

WebKit::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()];

if (!linkedOnOrAfter(WebCore::SDKVersion::FirstWithLazyGestureRecognizerInstallation))
[self setUpInteraction];
[self setUserInteractionEnabled:YES];

self.layer.hitTestsAsOpaque = YES;

#if PLATFORM(MACCATALYST)
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
[self _setFocusRingType:UIFocusRingTypeNone];
ALLOW_DEPRECATED_DECLARATIONS_END
#endif

#if HAVE(VISIBILITY_PROPAGATION_VIEW)
[self _setupVisibilityPropagationView];
[self _setupVisibilityPropagationViewForGPUProcess];
#endif

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_screenCapturedDidChange:) name:UIScreenCapturedDidChangeNotification object:[UIScreen mainScreen]];

#if USE(UIKIT_KEYBOARD_ADDITIONS)
if (WebCore::IOSApplication::isEvernote() && !linkedOnOrAfter(WebCore::SDKVersion::FirstWhereWKContentViewDoesNotOverrideKeyCommands))
class_addMethod(self.class, @selector(keyCommands), reinterpret_cast<IMP>(&keyCommandsPlaceholderHackForEvernote), method_getTypeEncoding(class_getInstanceMethod(self.class, @selector(keyCommands))));
#endif

return self;
}

#if HAVE(VISIBILITY_PROPAGATION_VIEW)
- (void)_setupVisibilityPropagationView
{
auto processIdentifier = _page->process().processIdentifier();
auto contextID = _page->contextIDForVisibilityPropagation();
if (!processIdentifier || !contextID)
return;

ASSERT(!_visibilityPropagationView);
// Propagate the view's visibility state to the WebContent process so that it is marked as "Foreground Running" when necessary.
_visibilityPropagationView = adoptNS([[_UILayerHostView alloc] initWithFrame:CGRectZero pid:processIdentifier contextID:contextID]);
RELEASE_LOG(Process, "Created visibility propagation view %p (contextID: %u) for WebContent process with PID %d", _visibilityPropagationView.get(), contextID, processIdentifier);
[self addSubview:_visibilityPropagationView.get()];
}

- (void)_setupVisibilityPropagationViewForGPUProcess
{
auto* gpuProcess = _page->process().processPool().gpuProcess();
if (!gpuProcess)
return;
auto processIdentifier = gpuProcess->processIdentifier();
auto contextID = gpuProcess->contextIDForVisibilityPropagation();
if (!processIdentifier || !contextID)
return;

if (_visibilityPropagationViewForGPUProcess)
return;

// Propagate the view's visibility state to the GPU process so that it is marked as "Foreground Running" when necessary.
_visibilityPropagationViewForGPUProcess = adoptNS([[_UILayerHostView alloc] initWithFrame:CGRectZero pid:processIdentifier contextID:contextID]);
RELEASE_LOG(Process, "Created visibility propagation view %p (contextID: %u) for GPU process with PID %d", _visibilityPropagationViewForGPUProcess.get(), contextID, processIdentifier);
[self addSubview:_visibilityPropagationViewForGPUProcess.get()];
}

- (void)_removeVisibilityPropagationView
{
if (!_visibilityPropagationView)
return;

RELEASE_LOG(Process, "Removing visibility propagation view %p", _visibilityPropagationView.get());
[_visibilityPropagationView removeFromSuperview];
_visibilityPropagationView = nullptr;
}

- (void)_removeVisibilityPropagationViewForGPUProcess
{
if (!_visibilityPropagationViewForGPUProcess)
return;

RELEASE_LOG(Process, "Removing visibility propagation view %p", _visibilityPropagationViewForGPUProcess.get());
[_visibilityPropagationViewForGPUProcess removeFromSuperview];
_visibilityPropagationViewForGPUProcess = nullptr;
}
#endif

- (instancetype)initWithFrame:(CGRect)frame processPool:(NakedRef<WebKit::WebProcessPool>)processPool configuration:(Ref<API::PageConfiguration>&&)configuration webView:(WKWebView *)webView
{
if (!(self = [super initWithFrame:frame webView:webView]))
return nil;

WebKit::InitializeWebKit2();

_pageClient = makeUnique<WebKit::PageClientImpl>(self, webView);
_webView = webView;

return [self _commonInitializationWithProcessPool:processPool configuration:WTFMove(configuration)];
}

- (void)dealloc
{
[self cleanUpInteraction];

[[NSNotificationCenter defaultCenter] removeObserver:self];

_page->close();

WebKit::WebProcessPool::statistics().wkViewCount--;

[super dealloc];
}

- (WebKit::WebPageProxy*)page
{
return _page.get();
}

- (WKWebView *)webView
{
return _webView.getAutoreleased();
}

- (void)willMoveToWindow:(UIWindow *)newWindow
{
[super willMoveToWindow:newWindow];

NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
UIWindow *window = self.window;

if (window)
[defaultCenter removeObserver:self name:UIWindowDidMoveToScreenNotification object:window];

if (newWindow) {
[defaultCenter addObserver:self selector:@selector(_windowDidMoveToScreenNotification:) name:UIWindowDidMoveToScreenNotification object:newWindow];

[self _updateForScreen:newWindow.screen];
}
}

- (void)didMoveToWindow
{
[super didMoveToWindow];

if (self.window)
[self setUpInteraction];
}

ALLOW_DEPRECATED_DECLARATIONS_BEGIN
- (WKBrowsingContextController *)browsingContextController
{
if (!_browsingContextController)
_browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(_page.get())]);

return _browsingContextController.get();
}
ALLOW_DEPRECATED_DECLARATIONS_END

- (WKPageRef)_pageRef
{
return toAPI(_page.get());
}

- (BOOL)isFocusingElement
{
return [self isEditable];
}

- (void)_showInspectorHighlight:(const WebCore::InspectorOverlay::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:(WebCore::FloatRect)fixedPositionRectForUI
{
WebCore::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;

[_textInteractionAssistant deactivateSelection];
}

static WebCore::FloatBoxExtent floatBoxExtent(UIEdgeInsets insets)
{
return WebCore::FloatBoxExtent(insets.top, insets.right, insets.bottom, insets.left);
}

- (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
contentInsets:(UIEdgeInsets)contentInsets
unobscuredRectInScrollViewCoordinates:(CGRect)unobscuredRectInScrollViewCoordinates
obscuredInsets:(UIEdgeInsets)obscuredInsets
unobscuredSafeAreaInsets:(UIEdgeInsets)unobscuredSafeAreaInsets
inputViewBounds:(CGRect)inputViewBounds
scale:(CGFloat)zoomScale minimumScale:(CGFloat)minimumScale
viewStability:(OptionSet<WebKit::ViewStabilityFlag>)viewStability
enclosedInScrollableAncestorView:(BOOL)enclosedInScrollableAncestorView
sendEvenIfUnchanged:(BOOL)sendEvenIfUnchanged
{
auto drawingArea = _page->drawingArea();
if (!drawingArea)
return;

MonotonicTime timestamp = MonotonicTime::now();
WebCore::VelocityData velocityData;
bool inStableState = viewStability.isEmpty();
if (!inStableState)
velocityData = _historicalKinematicData.velocityForNewData(visibleContentRect.origin, zoomScale, timestamp);
else {
_historicalKinematicData.clear();
velocityData = { 0, 0, 0, timestamp };
}

CGRect unobscuredContentRectRespectingInputViewBounds = [self _computeUnobscuredContentRectRespectingInputViewBounds:unobscuredContentRect inputViewBounds:inputViewBounds];
WebCore::FloatRect fixedPositionRectForLayout = _page->computeLayoutViewportRect(unobscuredContentRect, unobscuredContentRectRespectingInputViewBounds, _page->layoutViewportRect(), zoomScale, WebCore::FrameView::LayoutViewportConstraint::ConstrainedToDocumentRect);

WebKit::VisibleContentRectUpdateInfo visibleContentRectUpdateInfo(
visibleContentRect,
unobscuredContentRect,
floatBoxExtent(contentInsets),
unobscuredRectInScrollViewCoordinates,
unobscuredContentRectRespectingInputViewBounds,
fixedPositionRectForLayout,
floatBoxExtent(obscuredInsets),
floatBoxExtent(unobscuredSafeAreaInsets),
zoomScale,
viewStability,
_sizeChangedSinceLastVisibleContentRectUpdate,
self.webView._allowsViewportShrinkToFit,
enclosedInScrollableAncestorView,
velocityData,
downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*drawingArea).lastCommittedLayerTreeTransactionID());

LOG_WITH_STREAM(VisibleRects, stream << "-[WKContentView didUpdateVisibleRect]" << visibleContentRectUpdateInfo.dump());

bool wasStableState = _page->inStableState();

_page->updateVisibleContentRects(visibleContentRectUpdateInfo, sendEvenIfUnchanged);

auto layoutViewport = _page->unconstrainedLayoutViewportRect();
_page->adjustLayersForLayoutViewport(layoutViewport);

_sizeChangedSinceLastVisibleContentRectUpdate = NO;

drawingArea->updateDebugIndicator();

[self updateFixedClippingView:layoutViewport];

if (wasStableState && !inStableState)
[self _didExitStableState];
}

- (void)didFinishScrolling
{
[self _didEndScrollingOrZooming];
}

- (void)didInterruptScrolling
{
_historicalKinematicData.clear();
}

- (void)willStartZoomOrScroll
{
[self _willStartScrollingOrZooming];
}

- (void)didZoomToScale:(CGFloat)scale
{
[self _didEndScrollingOrZooming];
}

- (NSUndoManager *)undoManager
{
if (self.focusedElementInformation.shouldSynthesizeKeyEventsForEditing && self.hasHiddenContentEditable) {
if (!_quirkyUndoManager)
_quirkyUndoManager = adoptNS([[WKQuirkyNSUndoManager alloc] initWithContentView:self]);
return _quirkyUndoManager.get();
}
if (!_undoManager)
_undoManager = adoptNS([[NSUndoManager alloc] init]);
return _undoManager.get();
}

- (UIInterfaceOrientation)interfaceOrientation
{
return self.window.windowScene.interfaceOrientation;
}

#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(WebCore::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)_updateRemoteAccessibilityRegistration:(BOOL)registerProcess
{
#if PLATFORM(MACCATALYST)
pid_t pid = 0;
if (registerProcess)
pid = _page->process().processIdentifier();
else
pid = [objc_getAssociatedObject(self, (void*)[@"ax-pid" hash]) intValue];
if (!pid)
return;

if (registerProcess)
[WebKit::getNSAccessibilityRemoteUIElementClass() registerRemoteUIProcessIdentifier:pid];
else
[WebKit::getNSAccessibilityRemoteUIElementClass() unregisterRemoteUIProcessIdentifier:pid];
#endif
}

- (void)_accessibilityRegisterUIProcessTokens
{
auto uuid = [NSUUID UUID];
NSData *remoteElementToken = WebKit::newAccessibilityRemoteToken(uuid);

// Store information about the WebProcess that can later be retrieved by the iOS Accessibility runtime.
if (_page->process().state() == WebKit::WebProcessProxy::State::Running) {
[self _updateRemoteAccessibilityRegistration:YES];
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<WebKit::DrawingAreaProxy>)_createDrawingAreaProxy:(WebKit::WebProcessProxy&)process
{
return makeUnique<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page, process);
}

- (void)_processDidExit
{
[self _updateRemoteAccessibilityRegistration:NO];
[self cleanUpInteraction];

[self setShowingInspectorIndication:NO];
[self _hideInspectorHighlight];

#if HAVE(VISIBILITY_PROPAGATION_VIEW)
[self _removeVisibilityPropagationView];
#endif

_isPrintingToPDF = NO;
}

#if ENABLE(GPU_PROCESS)
- (void)_gpuProcessCrashed
{
[self _removeVisibilityPropagationViewForGPUProcess];
}
#endif

- (void)_processWillSwap
{
// FIXME: Should we do something differently?
[self _processDidExit];
}

- (void)_didRelaunchProcess
{
[self _accessibilityRegisterUIProcessTokens];
[self setUpInteraction];
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
[self _setupVisibilityPropagationView];
#endif
}

#if HAVE(VISIBILITY_PROPAGATION_VIEW)
- (void)_processDidCreateContextForVisibilityPropagation
{
[self _setupVisibilityPropagationView];
}

- (void)_gpuProcessDidCreateContextForVisibilityPropagation
{
[self _setupVisibilityPropagationViewForGPUProcess];
}
#endif

- (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
{
CGSize contentsSize = layerTreeTransaction.contentsSize();
CGPoint scrollOrigin = -layerTreeTransaction.scrollOrigin();
CGRect contentBounds = { scrollOrigin, contentsSize };

LOG_WITH_STREAM(VisibleRects, stream << "-[WKContentView _didCommitLayerTree:] transactionID " << layerTreeTransaction.transactionID() << " contentBounds " << WebCore::FloatRect(contentBounds));

BOOL boundsChanged = !CGRectEqualToRect([self bounds], contentBounds);
if (boundsChanged)
[self setBounds:contentBounds];

[_webView _didCommitLayerTree:layerTreeTransaction];

if (_interactionViewsContainerView) {
WebCore::FloatPoint scaledOrigin = layerTreeTransaction.scrollOrigin();
float scale = self.webView.scrollView.zoomScale;
scaledOrigin.scale(scale);
[_interactionViewsContainerView setFrame:CGRectMake(scaledOrigin.x(), scaledOrigin.y(), 0, 0)];
}

if (boundsChanged) {
// FIXME: factor computeLayoutViewportRect() into something that gives us this rect.
WebCore::FloatRect fixedPositionRect = _page->computeLayoutViewportRect(_page->unobscuredContentRect(), _page->unobscuredContentRectRespectingInputViewBounds(), _page->layoutViewportRect(), self.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 fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
{
[_webView _zoomToFocusRect:rectToFocus
selectionRect:selectionRect
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];
}

- (double)_initialScaleFactor
{
return [_webView _initialScaleFactor];
}

- (double)_contentZoomScale
{
return [_webView _contentZoomScale];
}

- (double)_targetContentZoomScaleForRect:(const WebCore::FloatRect&)targetRect currentScale:(double)currentScale fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale
{
return [_webView _targetContentZoomScaleForRect:targetRect currentScale:currentScale fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale];
}

- (void)_applicationWillResignActive:(NSNotification*)notification
{
_page->applicationWillResignActive();
}

- (void)_applicationDidBecomeActive:(NSNotification*)notification
{
_page->applicationDidBecomeActive();
}

- (void)_applicationDidEnterBackground:(NSNotification*)notification
{
if (!self.window)
_page->applicationDidEnterBackgroundForMedia();
}

- (void)_applicationWillEnterForeground:(NSNotification*)notification
{
if (!self.window)
_page->applicationWillEnterForegroundForMedia();
}

- (void)_screenCapturedDidChange:(NSNotification *)notification
{
_page->setScreenIsBeingCaptured([[[self window] screen] isCaptured]);
}

@end

#pragma mark Printing

@interface WKContentView (_WKWebViewPrintFormatter) <_WKWebViewPrintProvider>
@end

@implementation WKContentView (_WKWebViewPrintFormatter)

- (NSUInteger)_wk_pageCountForPrintFormatter:(_WKWebViewPrintFormatter *)printFormatter
{
if (_isPrintingToPDF)
[self _waitForDrawToPDFCallback];

WebCore::FrameIdentifier frameID;
if (_WKFrameHandle *handle = printFormatter.frameToPrint)
frameID = handle->_frameHandle->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;

WebKit::PrintInfo printInfo;
printInfo.pageSetupScaleFactor = 1;
printInfo.snapshotFirstPage = printFormatter.snapshotFirstPage;

// FIXME: Paginate when exporting PDFs taller than 200"
if (printInfo.snapshotFirstPage) {
static const CGFloat maximumPDFHeight = 200 * 72; // maximum PDF height for a single page is 200 inches
CGSize contentSize = self.bounds.size;
printingRect = (CGRect) { CGPointZero, { contentSize.width, std::min(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, WebKit::CallbackBase::Error error) {
retainedSelf->_isPrintingToPDF = NO;
if (error != WebKit::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()));
});
}

- (BOOL)_waitForDrawToPDFCallback
{
if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::DrawToPDFCallback>(_page->webPageID(), Seconds::infinity()))
return false;
ASSERT(!_isPrintingToPDF);
return true;
}

- (CGPDFDocumentRef)_wk_printedDocument
{
if (_isPrintingToPDF) {
if (![self _waitForDrawToPDFCallback])
return nullptr;
}

return _printedDocument.get();
}

@end

#endif // PLATFORM(IOS_FAMILY)