blob: 621ea936b88734c9f2b1b1c903dca9438624653f [file] [log] [blame]
/*
* 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 "InputViewUpdateDeferrer.h"
#import "Logging.h"
#import "PageClientImplIOS.h"
#import "PrintInfo.h"
#import "RemoteLayerTreeDrawingAreaProxy.h"
#import "SmartMagnificationController.h"
#import "UIKitSPI.h"
#import "VersionChecks.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 "_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 <objc/message.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/TextStream.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;
#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);
#if ENABLE(FULLSCREEN_API)
_page->setFullscreenClient(makeUnique<WebKit::FullscreenClient>(_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(WebKit::SDKVersion::FirstWithLazyGestureRecognizerInstallation))
[self setupInteraction];
[self setUserInteractionEnabled:YES];
self.layer.hitTestsAsOpaque = YES;
#if PLATFORM(MACCATALYST)
[self _setFocusRingType:UIFocusRingTypeNone];
#endif
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
[self _setupVisibilityPropagationView];
#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]];
#if USE(UIKIT_KEYBOARD_ADDITIONS)
if (WebCore::IOSApplication::isEvernote() && !linkedOnOrAfter(WebKit::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 for WebContent process with PID %d", _visibilityPropagationView.get(), processIdentifier);
[self addSubview:_visibilityPropagationView.get()];
}
- (void)_removeVisibilityPropagationView
{
[_visibilityPropagationView removeFromSuperview];
_visibilityPropagationView = nullptr;
}
#endif
- (instancetype)initWithFrame:(CGRect)frame processPool:(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();
}
- (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::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;
[_textSelectionAssistant 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
inStableState:(BOOL)isStableState
isChangingObscuredInsetsInteractively:(BOOL)isChangingObscuredInsetsInteractively
enclosedInScrollableAncestorView:(BOOL)enclosedInScrollableAncestorView
{
auto drawingArea = _page->drawingArea();
if (!drawingArea)
return;
MonotonicTime timestamp = MonotonicTime::now();
WebCore::VelocityData velocityData;
if (!isStableState)
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->computeCustomFixedPositionRect(unobscuredContentRect, unobscuredContentRectRespectingInputViewBounds, _page->customFixedPositionRect(), zoomScale, WebCore::FrameView::LayoutViewportConstraint::ConstrainedToDocumentRect);
WebKit::VisibleContentRectUpdateInfo visibleContentRectUpdateInfo(
visibleContentRect,
unobscuredContentRect,
floatBoxExtent(contentInsets),
unobscuredRectInScrollViewCoordinates,
unobscuredContentRectRespectingInputViewBounds,
fixedPositionRectForLayout,
floatBoxExtent(obscuredInsets),
floatBoxExtent(unobscuredSafeAreaInsets),
zoomScale,
isStableState,
_sizeChangedSinceLastVisibleContentRectUpdate,
isChangingObscuredInsetsInteractively,
_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);
auto layoutViewport = _page->unconstrainedLayoutViewportRect();
_page->adjustLayersForLayoutViewport(layoutViewport);
_sizeChangedSinceLastVisibleContentRectUpdate = NO;
drawingArea->updateDebugIndicator();
[self updateFixedClippingView:layoutViewport];
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 (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
{
#if HAVE(UISCENE)
return self.window.windowScene.interfaceOrientation;
#else
return UIApp.interfaceOrientation;
#endif
}
#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)_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) {
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 cleanupInteraction];
[self setShowingInspectorIndication:NO];
[self _hideInspectorHighlight];
#if HAVE(VISIBILITY_PROPAGATION_VIEW)
[self _removeVisibilityPropagationView];
#endif
_isPrintingToPDF = NO;
}
- (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];
}
#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 = [[_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.
WebCore::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];
}
- (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();
}
@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 = WebCore::frameIdentifierFromID(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;
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)